Lely core libraries 2.3.4
fiber.c
Go to the documentation of this file.
1
24#if !_WIN32
25// _FORTIFY_SOURCE=2 breaks longjmp() with a different stack frame.
26#ifdef _FORTIFY_SOURCE
27#undef _FORTIFY_SOURCE
28#endif
29#endif
30
31#include "util.h"
32
33#if !LELY_NO_MALLOC
34
35#include <lely/libc/stddef.h>
36#include <lely/util/errnum.h>
37#include <lely/util/fiber.h>
38#if !_WIN32
39#include <lely/util/mkjmp.h>
40#endif
41#include <lely/util/util.h>
42
43#include <assert.h>
44#if !_WIN32 && !defined(__NEWLIB__)
45#include <fenv.h>
46#endif
47#include <stdlib.h>
48
49#if _WIN32
50#include <windows.h>
51#elif _POSIX_MAPPED_FILES
52#include <sys/mman.h>
53#include <unistd.h>
54#endif
55
56#if !_WIN32 && LELY_HAVE_VALGRIND
57#include <valgrind/valgrind.h>
58#endif
59
60#ifndef LELY_FIBER_MINSTKSZ
62#ifdef MINSIGSTKSZ
63#define LELY_FIBER_MINSTKSZ MINSIGSTKSZ
64#elif __WORDSIZE == 64
65#define LELY_FIBER_MINSTKSZ 8192
66#else
67#define LELY_FIBER_MINSTKSZ 4096
68#endif
69#endif
70
71#ifndef LELY_FIBER_STKSZ
73#define LELY_FIBER_STKSZ 131072
74#endif
75
76#if !_WIN32 && _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
77
78/*
79 * The size (in bytes) of a memory page. The value is stored in a static
80 * variable to prevent multiple calls to sysconf().
81 */
82static long page_size;
83
99static void *guard_mmap(void *addr, size_t len, int prot);
100
107static void guard_munmap(void *addr, size_t len);
108
109#endif // !_WIN32 && _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
110
111#if !_WIN32
112#if _POSIX_C_SOURCE >= 200112L && (!defined(__NEWLIB__) || defined(__CYGWIN__))
117static inline void sigjmpto(sigjmp_buf from, sigjmp_buf to, int savemask);
118#else
123static inline void jmpto(jmp_buf from, jmp_buf to);
124#endif
125#endif // !_WIN32
126
127struct fiber_thrd;
128
130struct fiber {
134 void *arg;
136 int flags;
138 void *data;
143#if _WIN32
145 LPVOID lpFiber;
146#else
147#if _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
149 size_t stack_size;
154 void *stack_addr;
155#endif
156#if LELY_HAVE_VALGRIND
158 unsigned id;
159#endif
160#if _POSIX_C_SOURCE >= 200112L && (!defined(__NEWLIB__) || defined(__CYGWIN__))
162 sigjmp_buf env;
163#else
165 jmp_buf env;
166#endif
167#endif
168};
169
170#define FIBER_ALIGNOF _Alignof(max_align_t)
171#define FIBER_SIZEOF ALIGN(sizeof(fiber_t), FIBER_ALIGNOF)
172
177#if LELY_NO_THREADS
178static struct fiber_thrd {
179#else
197
203#if _WIN32
204static _Noreturn void CALLBACK fiber_start(void *arg);
205#else
206static _Noreturn void fiber_start(void *arg);
207#endif
208
209int
211{
212 struct fiber_thrd *thr = &fiber_thrd;
213 assert(!thr->curr || thr->curr == &thr->main);
214
215 if (flags & ~FIBER_SAVE_ALL) {
217 return -1;
218 }
219
220 if (thr->refcnt++)
221 return 1;
222
223#if _WIN32
224 DWORD dwFlags = 0;
225 if (flags & FIBER_SAVE_FENV)
226 dwFlags |= FIBER_FLAG_FLOAT_SWITCH;
227 assert(!thr->main.lpFiber);
228 thr->main.lpFiber = ConvertThreadToFiberEx(NULL, dwFlags);
229 if (!thr->main.lpFiber)
230 return -1;
231#endif
232 thr->main.thr = thr;
233 thr->main.flags = flags;
234
235 assert(!thr->curr);
236 thr->curr = &thr->main;
237
238 return 0;
239}
240
241void
243{
244 struct fiber_thrd *thr = &fiber_thrd;
245 assert(thr->refcnt);
246
247 if (!--thr->refcnt) {
248 assert(thr->curr == &thr->main);
249 thr->curr = NULL;
250#if _WIN32
251 assert(thr->main.lpFiber);
252 ConvertFiberToThread();
253 thr->main.lpFiber = NULL;
254#endif
255 thr->main.flags = 0;
256 }
257}
258
259fiber_t *
260fiber_create(fiber_func_t *func, void *arg, int flags, size_t data_size,
261 size_t stack_size)
262{
263 struct fiber_thrd *thr = &fiber_thrd;
264 assert(thr->curr);
265
266#if !_WIN32 && _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
267 if (flags & ~(FIBER_SAVE_ALL | FIBER_GUARD_STACK)) {
268#else
269 if (flags & ~FIBER_SAVE_ALL) {
270#endif
272 return NULL;
273 }
274
275 data_size = ALIGN(data_size, FIBER_ALIGNOF);
276
277 if (!stack_size)
278 stack_size = LELY_FIBER_STKSZ;
279 else if (stack_size < LELY_FIBER_MINSTKSZ)
280 stack_size = LELY_FIBER_MINSTKSZ;
281 stack_size = ALIGN(stack_size, FIBER_ALIGNOF);
282
283 size_t size = FIBER_SIZEOF + data_size;
284#if !_WIN32
285#if _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
286 if (!(flags & FIBER_GUARD_STACK))
287#endif
288 size += stack_size;
289#endif
290
291 int errc = 0;
292
293 fiber_t *fiber = malloc(size);
294 if (!fiber) {
295#if !LELY_NO_ERRNO
296 errc = errno2c(errno);
297#endif
298 goto error_malloc_fiber;
299 }
300
301 // The function pointer is stored by the call to fiber_resume_with()
302 // below.
303 fiber->func = NULL;
304 fiber->arg = NULL;
305
306 fiber->flags = flags;
307
308 // The data region immediately follows the fiber struct.
309 fiber->data = (char *)fiber + FIBER_SIZEOF;
310
311 fiber->thr = thr;
312 fiber->from = NULL;
313
314#if !_WIN32
315 void *sp;
316#if _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
317 fiber->stack_size = 0;
318 fiber->stack_addr = NULL;
319 if (flags & FIBER_GUARD_STACK) {
320 // Create an anonymous private mapping for the stack with guard
321 // pages at both ends. This is the most portable solution,
322 // although it does waste a page. If we know in which direction
323 // the stack grows, we could omit one of the pages.
324 fiber->stack_size = stack_size;
325 fiber->stack_addr = guard_mmap(NULL, fiber->stack_size,
326 PROT_READ | PROT_WRITE);
327 if (!fiber->stack_addr) {
328 errc = get_errc();
329 goto error_create_stack;
330 }
331 sp = fiber->stack_addr;
332 } else
333#endif
334 sp = (char *)fiber->data + data_size;
335#if LELY_HAVE_VALGRIND
336 // Register the stack with Valgrind to avoid false positives.
337 fiber->id = VALGRIND_STACK_REGISTER(sp, (char *)sp + stack_size);
338#endif
339#endif // !_WIN32
340
341 // Construct the fiber context.
342#if _WIN32
343 DWORD dwFlags = 0;
345 dwFlags |= FIBER_FLAG_FLOAT_SWITCH;
346 fiber->lpFiber = CreateFiberEx(
347 stack_size, 0, dwFlags, &fiber_start, fiber);
348 if (!fiber->lpFiber) {
349 errc = get_errc();
350 goto error_CreateFiberEx;
351 }
352#else
353#if _POSIX_C_SOURCE >= 200112L && (!defined(__NEWLIB__) || defined(__CYGWIN__))
354 // clang-format off
356 fiber, sp, stack_size) == -1) {
357 // clang-format on
358#else
359 if (mkjmp(fiber->env, &fiber_start, fiber, sp, stack_size) == -1) {
360#endif
361 errc = get_errc();
362 goto error_mkjmp;
363 }
364#endif
365
366 // Invoke fiber_start() and copy the user-specified function to the
367 // fiber stack. After setting up the stack it will return here.
368 fiber_resume_with(fiber, func, arg);
369
370 // Cppcheck gets confused by fiber_resume() and thinks we leak memory.
371 // cppcheck-suppress memleak
372 return fiber;
373
374#if _WIN32
375 // DeleteFiber(fiber->lpFiber);
376error_CreateFiberEx:
377#else
378error_mkjmp:
379#if LELY_HAVE_VALGRIND
380 VALGRIND_STACK_DEREGISTER(fiber->id);
381#endif
382#if _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
383error_create_stack:
384 if (fiber->stack_addr)
385 guard_munmap(fiber->stack_addr, fiber->stack_size);
386#endif
387#endif
388 free(fiber);
389error_malloc_fiber:
390 set_errc(errc);
391 return NULL;
392}
393
394void
396{
397 if (fiber) {
398 assert(fiber != &fiber_thrd.main);
399 assert(fiber != fiber_thrd.curr);
400#if _WIN32
401 DeleteFiber(fiber->lpFiber);
402#else
403#if LELY_HAVE_VALGRIND
404 VALGRIND_STACK_DEREGISTER(fiber->id);
405#endif
406#if _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
407 if (fiber->stack_addr)
408 guard_munmap(fiber->stack_addr, fiber->stack_size);
409#endif
410#endif
411 free(fiber);
412 }
413}
414
415void *
417{
418 assert(fiber_thrd.curr);
419
420 return fiber ? fiber->data : fiber_thrd.curr->data;
421}
422
423fiber_t *
425{
426 return fiber_resume_with(fiber, NULL, NULL);
427}
428
429fiber_t *
431{
432 struct fiber_thrd *thr = &fiber_thrd;
433 fiber_t *curr = thr->curr;
434 assert(curr);
435 fiber_t *to = fiber ? fiber : &thr->main;
436 assert(to);
437 assert(!to->func);
438 assert(!to->arg);
439
440 // If a fiber is resuming itself, execute the function and return.
441 if (to == curr) {
442 if (func)
443 fiber = func(fiber, arg);
444 return fiber;
445 }
446
447 // Save the function to be executed.
448 if (func) {
449 to->func = func;
450 to->arg = arg;
451 }
452
453 // Save the error code(s). On Windows, the thread's last-error code
454 // reported by GetLastError() is distinct from errno, but it is equal to
455 // the error status reported by WSAGetLastError().
456#if _WIN32
457 DWORD dwErrCode = 0;
458 int errsv = 0;
459 if (curr->flags & FIBER_SAVE_ERROR) {
460 dwErrCode = GetLastError();
461 errsv = errno;
462 }
463#else
464 int errc = 0;
466 errc = get_errc();
467#endif
468
469 // Save the floating-point environment. Windows fibers can do this
470 // automatically, depending on the flags provided to CreateFiberEx() or
471 // ConvertThreadToFiberEx().
472#if !_WIN32 && !defined(__NEWLIB__)
473 fenv_t fenv;
475 fegetenv(&fenv);
476#endif
477
478 to->thr = thr;
479 to->from = curr;
480 thr->curr = to;
481 // Perform the actual context switch.
482#if _WIN32
483 assert(to->lpFiber);
484 SwitchToFiber(to->lpFiber);
485#elif _POSIX_C_SOURCE >= 200112L \
486 && (!defined(__NEWLIB__) || defined(__CYGWIN__))
488#else
489 jmpto(curr->env, to->env);
490#endif
491 thr = curr->thr;
492 assert(thr);
493 assert(curr->from);
494 fiber = curr->from != &thr->main ? curr->from : NULL;
495
496 // Restore the floating-point environment.
497#if !_WIN32 && !defined(__NEWLIB__)
499 fesetenv(&fenv);
500#endif
501
502 // Restore the error code(s).
503 if (curr->flags & FIBER_SAVE_ERROR) {
504#if _WIN32
505 errno = errsv;
506 SetLastError(dwErrCode);
507#else
508 set_errc(errc);
509#endif
510 }
511
512 // Execute the saved function, if any, in this fiber before resuming the
513 // suspended function.
514 if (curr->func) {
515 func = curr->func;
516 curr->func = NULL;
517 arg = curr->arg;
518 curr->arg = NULL;
519 fiber = func(fiber, arg);
520 }
521
522 return fiber;
523}
524
525#if !_WIN32 && _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
526
527static void *
528guard_mmap(void *addr, size_t len, int prot)
529{
530 if (!page_size)
531 page_size = sysconf(_SC_PAGE_SIZE);
532 assert(page_size > 0);
533 assert(powerof2(page_size));
534
535 // Adjust the hint, if any, to accomodate the guard page.
536 if (addr)
537 addr = (char *)addr - page_size;
538
539 // Round the length up to the nearest multiple of the page size.
540 len = ALIGN(len, page_size);
541
542 int errc = 0;
543
544 // Create a single anonymous private mapping that includes the guard
545 // pages.
546 addr = mmap(addr, len + 2 * page_size, prot,
547 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
548 if (addr == MAP_FAILED) {
549 errc = get_errc();
550 goto error_mmap;
551 }
552 addr = (char *)addr + page_size;
553
554 // Guard the page at the beginning.
555 if (mprotect((char *)addr - page_size, page_size, PROT_NONE) == -1) {
556 errc = get_errc();
557 goto error_mprotect;
558 }
559 // Guard the page at the end.
560 if (mprotect((char *)addr + len, page_size, PROT_NONE) == -1) {
561 errc = get_errc();
562 goto error_mprotect;
563 }
564
565 return addr;
566
567error_mprotect:
568 munmap((char *)addr - page_size, len + 2 * page_size);
569error_mmap:
570 set_errc(errc);
571 return NULL;
572}
573
574static void
575guard_munmap(void *addr, size_t len)
576{
577 if (addr) {
578 assert(page_size > 0);
579 assert(powerof2(page_size));
580
581 len = ALIGN(len, page_size);
582
583 munmap((char *)addr - page_size, len + 2 * page_size);
584 }
585}
586
587#endif // _POSIX_MAPPED_FILES && MAP_ANONYMOUS
588
589#if !_WIN32
590#if _POSIX_C_SOURCE >= 200112L && (!defined(__NEWLIB__) || defined(__CYGWIN__))
591static inline void
592sigjmpto(sigjmp_buf from, sigjmp_buf to, int savemask)
593{
594 if (!sigsetjmp(from, savemask))
595 siglongjmp(to, 1);
596}
597#else
598static inline void
599jmpto(jmp_buf from, jmp_buf to)
600{
601 if (!setjmp(from))
602 longjmp(to, 1);
603}
604#endif
605#endif // !_WIN32
606
607#if _WIN32
608static _Noreturn void CALLBACK
609#else
610static _Noreturn void
611#endif
612fiber_start(void *arg)
613{
614 fiber_t *curr = arg;
615 assert(curr);
617 assert(fiber);
618
619 // Copy the function to be executed to the stack for later reference.
620 fiber_func_t *func = curr->func;
621 curr->func = NULL;
622 arg = curr->arg;
623 curr->arg = NULL;
624
625 // Resume fiber_create().
627
628 // Execute the original function, if specified.
629 if (func)
630 fiber = func(fiber, arg);
631
632 // The function has terminated, so resume the caller fiber immediately.
633 for (;;)
635}
636
637#endif // !LELY_NO_MALLOC
This header file is part of the utilities library; it contains the native and platform-independent er...
@ ERRNUM_INVAL
Invalid argument.
Definition errnum.h:132
int get_errc(void)
Returns the last (thread-specific) native error code set by a system call or library function.
Definition errnum.c:932
void set_errc(int errc)
Sets the current (thread-specific) native error code to errc.
Definition errnum.c:944
int errno2c(int errnum)
Transforms a standard C error number to a native error code.
Definition errnum.c:46
void set_errnum(errnum_t errnum)
Sets the current (thread-specific) platform-independent error number to errnum.
Definition errnum.h:424
#define _Thread_local
An object whose identifier is declared with the storage-class specifier _Thread_local has thread stor...
Definition features.h:249
#define _Noreturn
A function declared with a _Noreturn function specifier SHALL not return to its caller.
Definition features.h:224
fiber_t * fiber_resume_with(fiber_t *fiber, fiber_func_t *func, void *arg)
Suspends the calling fiber and resumes the specified fiber, optionally executing a function before re...
Definition fiber.c:430
#define LELY_FIBER_MINSTKSZ
The minimum size (in bytes) of a fiber stack frame.
Definition fiber.c:65
void * fiber_data(const fiber_t *fiber)
Returns a pointer to the data region of the specified fiber, or of the calling fiber if fiber is NULL...
Definition fiber.c:416
int fiber_thrd_init(int flags)
Initializes the fiber associated with the calling thread.
Definition fiber.c:210
fiber_t * fiber_create(fiber_func_t *func, void *arg, int flags, size_t data_size, size_t stack_size)
Creates a new fiber, allocates a stack and sets up a calling environment to begin executing the speci...
Definition fiber.c:260
static void sigjmpto(sigjmp_buf from, sigjmp_buf to, int savemask)
Saves the from calling environment with sigsetjmp(from, savemask) and restores the to calling environ...
Definition fiber.c:592
void fiber_thrd_fini(void)
Finalizes the fiber associated with the calling thread.
Definition fiber.c:242
static _Noreturn void fiber_start(void *arg)
The function running in a fiber.
Definition fiber.c:612
fiber_t * fiber_resume(fiber_t *fiber)
Equivalent to fiber_resume_with(fiber, NULL, NULL).
Definition fiber.c:424
#define LELY_FIBER_STKSZ
The default size (in bytes) of a fiber stack frame.
Definition fiber.c:73
void fiber_destroy(fiber_t *fiber)
Destroys the specified fiber.
Definition fiber.c:395
This header file is part of the utilities library; it contains the fiber declarations.
#define FIBER_SAVE_MASK
A flag specifying a fiber to save and restore the signal mask (only supported on POSIX platforms).
Definition fiber.h:47
#define FIBER_GUARD_STACK
A flag specifying a fiber to add a guard page when allocating the stack frame so that the kernel gene...
Definition fiber.h:85
#define FIBER_SAVE_ERROR
A flag specifying a fiber to save and restore the error values (i.e., errno and GetLastError() on Win...
Definition fiber.h:58
fiber_t * fiber_func_t(fiber_t *fiber, void *arg)
The type of the function executed by a fiber.
Definition fiber.h:106
#define FIBER_SAVE_FENV
A flag specifying a fiber to save and restore the floating-point environment.
Definition fiber.h:52
#define FIBER_SAVE_ALL
A combination of those flags in FIBER_SAVE_MASK, FIBER_SAVE_FENV and FIBER_SAVE_ERROR that are suppor...
Definition fiber.h:68
This is the public header file of the utilities library.
#define powerof2(x)
Returns 1 if x is a power of two, and 0 otherwise.
Definition util.h:78
#define ALIGN(x, a)
Rounds x up to the nearest multiple of a.
Definition util.h:41
This header file is part of the utilities library; it contains the mkjmp() and sigmkjmp() function de...
int mkjmp(jmp_buf env, void(*func)(void *), void *arg, void *sp, size_t size)
Creates and stores a calling environment with a user-provided stack suitable for use by longjmp().
Definition mkjmp.c:98
int sigmkjmp(sigjmp_buf env, int savemask, void(*func)(void *), void *arg, void *sp, size_t size)
Creates and stores a calling environment with a user-provided stack suitable for use by siglongjmp().
Definition mkjmp.c:113
This is the internal header file of the utilities library.
This header file is part of the C11 and POSIX compatibility library; it includes <stddef....
This header file is part of the C11 and POSIX compatibility library; it includes <stdlib....
A thread-local struct containing the fiber associated with the thread and a pointer to the fiber curr...
Definition fiber.c:180
size_t refcnt
The reference counter tracking the number of calls to fiber_thrd_init() minus those to fiber_thrd_fin...
Definition fiber.c:186
fiber_t * curr
A pointer to the fiber currently running on this thread.
Definition fiber.c:195
fiber_t main
The fiber representing this thread.
Definition fiber.c:193
A fiber.
Definition fiber.c:130
int flags
The flags provided to fiber_create().
Definition fiber.c:136
void * arg
The second argument supplied to func.
Definition fiber.c:134
void * data
A pointer to the data region.
Definition fiber.c:138
struct fiber_thrd * thr
A pointer to the thread that resumed this fiber.
Definition fiber.c:140
fiber_func_t * func
A pointer to the function to be executed in the fiber.
Definition fiber.c:132
fiber_t * from
A pointer to the now suspended fiber that resumed this fiber.
Definition fiber.c:142
sigjmp_buf env
The saved registers and signal mask.
Definition fiber.c:162
This header file is part of the C11 and POSIX compatibility library; it includes <unistd....