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
61 #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
72 #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  */
82 static long page_size;
83 
99 static void *guard_mmap(void *addr, size_t len, int prot);
100 
107 static 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__))
113 
117 static inline void sigjmpto(sigjmp_buf from, sigjmp_buf to, int savemask);
118 #else
119 
123 static inline void jmpto(jmp_buf from, jmp_buf to);
124 #endif
125 #endif // !_WIN32
126 
127 struct fiber_thrd;
128 
130 struct fiber {
134  void *arg;
136  int flags;
138  void *data;
140  struct fiber_thrd *thr;
143 #if _WIN32
144  LPVOID lpFiber;
146 #else
147 #if _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
148  size_t stack_size;
154  void *stack_addr;
155 #endif
156 #if LELY_HAVE_VALGRIND
157  unsigned id;
159 #endif
160 #if _POSIX_C_SOURCE >= 200112L && (!defined(__NEWLIB__) || defined(__CYGWIN__))
161  sigjmp_buf env;
163 #else
164  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
178 static struct fiber_thrd {
179 #else
180 static _Thread_local struct fiber_thrd {
181 #endif
182 
186  size_t refcnt;
196 } fiber_thrd;
197 
203 #if _WIN32
204 static _Noreturn void CALLBACK fiber_start(void *arg);
205 #else
206 static _Noreturn void fiber_start(void *arg);
207 #endif
208 
209 int
210 fiber_thrd_init(int flags)
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 
241 void
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 
259 fiber_t *
260 fiber_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;
344  if (fiber->flags & FIBER_SAVE_FENV)
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);
376 error_CreateFiberEx:
377 #else
378 error_mkjmp:
379 #if LELY_HAVE_VALGRIND
380  VALGRIND_STACK_DEREGISTER(fiber->id);
381 #endif
382 #if _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
383 error_create_stack:
384  if (fiber->stack_addr)
385  guard_munmap(fiber->stack_addr, fiber->stack_size);
386 #endif
387 #endif
388  free(fiber);
389 error_malloc_fiber:
390  set_errc(errc);
391  return NULL;
392 }
393 
394 void
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 
415 void *
417 {
418  assert(fiber_thrd.curr);
419 
420  return fiber ? fiber->data : fiber_thrd.curr->data;
421 }
422 
423 fiber_t *
425 {
426  return fiber_resume_with(fiber, NULL, NULL);
427 }
428 
429 fiber_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;
465  if (curr->flags & FIBER_SAVE_ERROR)
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;
474  if (curr->flags & FIBER_SAVE_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__)
498  if (curr->flags & FIBER_SAVE_FENV)
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 
527 static void *
528 guard_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 
567 error_mprotect:
568  munmap((char *)addr - page_size, len + 2 * page_size);
569 error_mmap:
570  set_errc(errc);
571  return NULL;
572 }
573 
574 static void
575 guard_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__))
591 static inline void
592 sigjmpto(sigjmp_buf from, sigjmp_buf to, int savemask)
593 {
594  if (!sigsetjmp(from, savemask))
595  siglongjmp(to, 1);
596 }
597 #else
598 static inline void
599 jmpto(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
608 static _Noreturn void CALLBACK
609 #else
610 static _Noreturn void
611 #endif
612 fiber_start(void *arg)
613 {
614  fiber_t *curr = arg;
615  assert(curr);
616  fiber_t *fiber = curr->from;
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
FIBER_SAVE_ALL
#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
fiber.h
mkjmp.h
fiber
A fiber.
Definition: fiber.c:130
LELY_FIBER_STKSZ
#define LELY_FIBER_STKSZ
The default size (in bytes) of a fiber stack frame.
Definition: fiber.c:73
util.h
fiber_thrd::curr
fiber_t * curr
A pointer to the fiber currently running on this thread.
Definition: fiber.c:195
fiber_thrd_fini
void fiber_thrd_fini(void)
Finalizes the fiber associated with the calling thread.
Definition: fiber.c:242
get_errc
int get_errc(void)
Returns the last (thread-specific) native error code set by a system call or library function.
Definition: errnum.c:932
errno2c
int errno2c(int errnum)
Transforms a standard C error number to a native error code.
Definition: errnum.c:46
fiber::flags
int flags
The flags provided to fiber_create().
Definition: fiber.c:136
fiber_thrd
A thread-local struct containing the fiber associated with the thread and a pointer to the fiber curr...
Definition: fiber.c:180
fiber_thrd_init
int fiber_thrd_init(int flags)
Initializes the fiber associated with the calling thread.
Definition: fiber.c:210
FIBER_SAVE_MASK
#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
fiber::thr
struct fiber_thrd * thr
A pointer to the thread that resumed this fiber.
Definition: fiber.c:140
fiber_thrd::refcnt
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
_Noreturn
#define _Noreturn
A function declared with a _Noreturn function specifier SHALL not return to its caller.
Definition: features.h:224
fiber_resume_with
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
set_errnum
void set_errnum(errnum_t errnum)
Sets the current (thread-specific) platform-independent error number to errnum.
Definition: errnum.h:424
set_errc
void set_errc(int errc)
Sets the current (thread-specific) native error code to errc.
Definition: errnum.c:944
fiber::env
sigjmp_buf env
The saved registers and signal mask.
Definition: fiber.c:162
errnum.h
ALIGN
#define ALIGN(x, a)
Rounds x up to the nearest multiple of a.
Definition: util.h:41
FIBER_GUARD_STACK
#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
fiber_func_t
fiber_t * fiber_func_t(fiber_t *fiber, void *arg)
The type of the function executed by a fiber.
Definition: fiber.h:106
ERRNUM_INVAL
@ ERRNUM_INVAL
Invalid argument.
Definition: errnum.h:132
sigjmpto
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
mkjmp
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
fiber_data
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
FIBER_SAVE_FENV
#define FIBER_SAVE_FENV
A flag specifying a fiber to save and restore the floating-point environment.
Definition: fiber.h:52
unistd.h
fiber::from
fiber_t * from
A pointer to the now suspended fiber that resumed this fiber.
Definition: fiber.c:142
fiber_destroy
void fiber_destroy(fiber_t *fiber)
Destroys the specified fiber.
Definition: fiber.c:395
fiber_start
static _Noreturn void fiber_start(void *arg)
The function running in a fiber.
Definition: fiber.c:612
fiber::func
fiber_func_t * func
A pointer to the function to be executed in the fiber.
Definition: fiber.c:132
fiber::arg
void * arg
The second argument supplied to func.
Definition: fiber.c:134
sigmkjmp
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
FIBER_SAVE_ERROR
#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
_Thread_local
#define _Thread_local
An object whose identifier is declared with the storage-class specifier _Thread_local has thread stor...
Definition: features.h:249
fiber_thrd::main
fiber_t main
The fiber representing this thread.
Definition: fiber.c:193
stdlib.h
powerof2
#define powerof2(x)
Returns 1 if x is a power of two, and 0 otherwise.
Definition: util.h:78
stddef.h
fiber_create
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
LELY_FIBER_MINSTKSZ
#define LELY_FIBER_MINSTKSZ
The minimum size (in bytes) of a fiber stack frame.
Definition: fiber.c:65
fiber_resume
fiber_t * fiber_resume(fiber_t *fiber)
Equivalent to fiber_resume_with(fiber, NULL, NULL).
Definition: fiber.c:424
fiber::data
void * data
A pointer to the data region.
Definition: fiber.c:138