Lely core libraries  2.2.5
fiber-sjlj.c
Go to the documentation of this file.
1 
24 // _FORTIFY_SOURCE=2 breaks longjmp() with a different stack frame.
25 #ifdef _FORTIFY_SOURCE
26 #undef _FORTIFY_SOURCE
27 #endif
28 
29 #include "fiber.h"
30 
31 #if !_WIN32
32 
33 #include <lely/libc/stddef.h>
34 #include <lely/util/errnum.h>
35 #include <lely/util/fiber.h>
36 #include <lely/util/mkjmp.h>
37 
38 #include <assert.h>
39 #include <errno.h>
40 #ifndef __NEWLIB__
41 #include <fenv.h>
42 #endif
43 #include <stdlib.h>
44 
45 #if _POSIX_MAPPED_FILES
46 #include <sys/mman.h>
47 #include <unistd.h>
48 #endif
49 
50 #if LELY_HAVE_VALGRIND
51 #include <valgrind/valgrind.h>
52 #endif
53 
54 #if _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
55 #ifndef LELY_FIBER_MMAPSZ
56 
60 #define LELY_FIBER_MMAPSZ 131072
61 #endif
62 #endif
63 
64 struct fiber_thrd;
65 
66 struct fiber {
67  fiber_func_t *func;
68  void *arg;
69  int flags;
70  void *data;
71  struct fiber_thrd *thr;
72  fiber_t *from;
73 #if _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
74  size_t size;
75  void *sp;
76 #endif
77 #if LELY_HAVE_VALGRIND
78  unsigned id;
79 #endif
80 #if _POSIX_C_SOURCE >= 200112L && (!defined(__NEWLIB__) || defined(__CYGWIN__))
81  sigjmp_buf env;
82 #else
83  jmp_buf env;
84 #endif
85 #ifndef __NEWLIB__
86  fenv_t fenv;
87 #endif
88  int errsv;
89 };
90 
91 #define FIBER_SIZE ALIGN(sizeof(fiber_t), _Alignof(max_align_t))
92 
93 #if LELY_NO_THREADS
94 static struct fiber_thrd {
95 #else
96 static _Thread_local struct fiber_thrd {
97 #endif
98  size_t refcnt;
99  fiber_t main;
100  fiber_t *curr;
101 } fiber_thrd;
102 
103 static _Noreturn void fiber_start(void *arg);
104 
105 static fiber_t *fiber_switch(fiber_t *fiber);
106 
107 int
108 fiber_thrd_init(int flags)
109 {
110  struct fiber_thrd *thr = &fiber_thrd;
111 
112  if (flags & ~FIBER_SAVE_ALL) {
113 #ifdef EINVAL
114  errno = EINVAL;
115 #endif
116  return -1;
117  }
118 
119  if (thr->refcnt++)
120  return 1;
121 
122  assert(!thr->main.thr);
123  assert(!thr->main.from);
124  assert(!thr->curr);
125 
126  thr->main.flags = flags;
127  thr->main.thr = thr;
128 
129  thr->curr = &thr->main;
130 
131  return 0;
132 }
133 
134 void
136 {
137  struct fiber_thrd *thr = &fiber_thrd;
138  assert(thr->refcnt);
139 
140  if (!--thr->refcnt) {
141  assert(thr->curr == &thr->main);
142 
143  thr->curr = NULL;
144 
145  thr->main.from = NULL;
146  thr->main.thr = NULL;
147  thr->main.flags = 0;
148  }
149 }
150 
151 fiber_t *
152 fiber_create(fiber_func_t *func, void *arg, int flags, size_t data_size,
153  size_t stack_size)
154 {
155 #if _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
156  if (flags & ~(FIBER_SAVE_ALL | FIBER_GUARD_STACK)) {
157 #else
158  if (flags & ~FIBER_SAVE_ALL) {
159 #endif
160 #ifdef EINVAL
161  errno = EINVAL;
162 #endif
163  return NULL;
164  }
165 
166  if (!stack_size)
167  stack_size = LELY_FIBER_STKSZ;
168  else if (stack_size < LELY_FIBER_MINSTKSZ)
169  stack_size = LELY_FIBER_MINSTKSZ;
170 
171  size_t size = FIBER_SIZE + data_size;
172 #if _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
173  int use_mmap = (flags & FIBER_GUARD_STACK)
174  || ALIGN(size, _Alignof(max_align_t)) + stack_size
175  >= LELY_FIBER_MMAPSZ;
176  if (!use_mmap)
177 #endif
178  size = ALIGN(size, _Alignof(max_align_t));
179 
180  int errsv = errno;
181 
182  fiber_t *fiber;
183 #if _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
184  if (use_mmap)
185  fiber = malloc(size);
186  else
187 #endif
188  fiber = malloc(size + stack_size);
189 
190  if (!fiber) {
191  errsv = errno;
192  goto error_malloc_fiber;
193  }
194 
195  fiber->func = func;
196  fiber->arg = arg;
197  fiber->flags = flags;
198 
199  fiber->data = (char *)fiber + FIBER_SIZE;
200 
201  fiber->thr = &fiber_thrd;
202  fiber->from = NULL;
203 
204  void *sp;
205 #if _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
206  fiber->size = 0;
207  fiber->sp = NULL;
208  if (use_mmap) {
209  long page_size = sysconf(_SC_PAGE_SIZE);
210  assert(page_size > 0);
211  assert(powerof2(page_size));
212  stack_size = ALIGN(stack_size, page_size);
213 
214  fiber->size = stack_size;
215  if (fiber->flags & FIBER_GUARD_STACK)
216  // Allocate two guard pages on both ends of the stack.
217  // This is the most portable solution, although it does
218  // waste a page. If we know in which direction the stack
219  // grows, we can omit one of the pages.
220  fiber->size += 2 * page_size;
221 
222  fiber->sp = mmap(NULL, fiber->size,
223  PROT_READ | PROT_WRITE | PROT_EXEC,
224  MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
225  if (fiber->sp == MAP_FAILED)
226  fiber->sp = mmap(NULL, fiber->size,
227  PROT_READ | PROT_WRITE,
228  MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
229  if (fiber->sp == MAP_FAILED) {
230  errsv = errno;
231  goto error_mmap;
232  }
233  errno = errsv;
234 
235  sp = fiber->sp;
236  if (fiber->flags & FIBER_GUARD_STACK) {
237  sp = (char *)sp + page_size;
238  // clang-format off
239  if (mprotect((char *)sp - page_size, page_size,
240  PROT_NONE) == -1) {
241  // clang-format on
242  errsv = errno;
243  goto error_mprotect;
244  }
245  // clang-format off
246  if (mprotect((char *)sp + stack_size, page_size,
247  PROT_NONE) == -1) {
248  // clang-format on
249  errsv = errno;
250  goto error_mprotect;
251  }
252  }
253  } else
254 #endif // _POSIX_MAPPED_FILES && MAP_ANONYMOUS
255  sp = (char *)fiber + size;
256 
257 #if LELY_HAVE_VALGRIND
258  fiber->id = VALGRIND_STACK_REGISTER(sp, (char *)sp + stack_size);
259 #endif
260 
261 #if _POSIX_C_SOURCE >= 200112L && (!defined(__NEWLIB__) || defined(__CYGWIN__))
262  // clang-format off
263  if (sigmkjmp(fiber->env, fiber->flags & FIBER_SAVE_MASK, &fiber_start,
264  fiber, sp, stack_size) == -1) {
265  // clang-format on
266 #else
267  if (mkjmp(fiber->env, &fiber_start, fiber, sp, stack_size) == -1) {
268 #endif
269  errsv = errno;
270  goto error_mkjmp;
271  }
272 
273  // Invoke fiber_start(). After setting up the environment it will return
274  // here.
275  fiber_resume(fiber);
276 
277  // Cppcheck gets confused by the lonjmp() performed in fiber_resume()
278  // and assumes we never return and therefore leak memory.
279  // cppcheck-suppress memleak
280  return fiber;
281 
282 error_mkjmp:
283 #if LELY_HAVE_VALGRIND
284  VALGRIND_STACK_DEREGISTER(fiber->id);
285 #endif
286 #if _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
287 error_mprotect:
288  if (use_mmap)
289  munmap(fiber->sp, fiber->size);
290 error_mmap:
291 #endif
292  free(fiber);
293 error_malloc_fiber:
294  errno = errsv;
295  return NULL;
296 }
297 
298 void
300 {
301  if (fiber && fiber->data) {
302 #if LELY_HAVE_VALGRIND
303  VALGRIND_STACK_DEREGISTER(fiber->id);
304 #endif
305 #if _POSIX_MAPPED_FILES && defined(MAP_ANONYMOUS)
306  if (fiber->sp)
307  munmap(fiber->sp, fiber->size);
308 #endif
309  free(fiber);
310  }
311 }
312 
313 void *
314 fiber_data(const fiber_t *fiber)
315 {
316  assert(fiber_thrd.curr);
317 
318  return fiber ? fiber->data : fiber_thrd.curr->data;
319 }
320 
321 fiber_t *
323 {
324  if (!fiber)
325  fiber = &fiber_thrd.main;
326 
327  if (fiber == fiber->thr->curr)
328  return fiber->from = fiber;
329 
330  return fiber_switch(fiber);
331 }
332 
333 fiber_t *
334 fiber_resume_with(fiber_t *fiber, fiber_func_t *func, void *arg)
335 {
336  if (!fiber)
337  fiber = &fiber_thrd.main;
338 
339  if (fiber == fiber->thr->curr)
340  return fiber->from = func ? func(fiber, arg) : fiber;
341 
342  fiber->func = func;
343  fiber->arg = arg;
344 
345  return fiber_switch(fiber);
346 }
347 
348 static _Noreturn void
349 fiber_start(void *arg)
350 {
351  fiber_t *fiber = arg;
352  assert(fiber);
353 
354  // Copy the function to be executed to the stack for later reference.
355  fiber_func_t *func = fiber->func;
356  fiber->func = NULL;
357  arg = fiber->arg;
358  fiber->arg = NULL;
359 
360  // Resume fiber_create().
361  fiber = fiber_resume(fiber->from);
362 
363  // If the fiber is started with fiber_resume_with(), execute that
364  // function before starting the original function.
365  if (fiber->func) {
366  fiber_func_t *func = fiber->func;
367  fiber->func = NULL;
368  void *arg = fiber->arg;
369  fiber->arg = NULL;
370  fiber = func(fiber, arg);
371  }
372 
373  if (func)
374  fiber = func(fiber, arg);
375 
376  // If no valid fiber is returned, return to the main context.
377  if (!fiber)
378  fiber = &fiber_thrd.main;
379 
380  // The function has terminated, so return to the caller immediately.
381  for (;;)
382  fiber = fiber_resume(fiber);
383 }
384 
385 static fiber_t *
386 fiber_switch(fiber_t *fiber)
387 {
388  assert(fiber);
389  struct fiber_thrd *thr = fiber->thr;
390  assert(thr == &fiber_thrd);
391  fiber_t *curr = thr->curr;
392  assert(curr);
393  assert(fiber != curr);
394 
395  if (curr->flags & FIBER_SAVE_ERROR)
396  curr->errsv = errno;
397 
398 #ifndef __NEWLIB__
399  if (curr->flags & FIBER_SAVE_FENV)
400  fegetenv(&curr->fenv);
401 #endif
402 
403 #if _POSIX_C_SOURCE >= 200112L && (!defined(__NEWLIB__) || defined(__CYGWIN__))
404  if (!sigsetjmp(curr->env, curr->flags & FIBER_SAVE_MASK)) {
405 #else
406  if (!setjmp(curr->env)) {
407 #endif
408  fiber->from = thr->curr;
409  thr->curr = fiber;
410 #if _POSIX_C_SOURCE >= 200112L && (!defined(__NEWLIB__) || defined(__CYGWIN__))
411  siglongjmp(fiber->env, 1);
412 #else
413  longjmp(fiber->env, 1);
414 #endif
415  }
416  curr = thr->curr;
417 
418 #ifndef __NEWLIB__
419  if (curr->flags & FIBER_SAVE_FENV)
420  fesetenv(&curr->fenv);
421 #endif
422 
423  if (curr->flags & FIBER_SAVE_ERROR)
424  errno = curr->errsv;
425 
426  if (curr->func) {
427  fiber_func_t *func = curr->func;
428  curr->func = NULL;
429  void *arg = curr->arg;
430  curr->arg = NULL;
431  curr->from = func(curr->from, arg);
432  }
433 
434  return curr->from;
435 }
436 
437 #endif // !_WIN32
#define LELY_FIBER_STKSZ
The default size (in bytes) of a fiber stack frame.
Definition: fiber.h:41
This header file is part of the utilities library; it contains the fiber declarations.
#define powerof2(x)
Returns 1 if x is a power of two, and 0 otherwise.
Definition: util.h:78
This header file is part of the utilities library; it contains the mkjmp() and sigmkjmp() function de...
#define _Alignof(x)
Specifies the alignment requirement of the declared object or member.
Definition: features.h:196
#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:65
void fiber_destroy(fiber_t *fiber)
Destroys the specified fiber.
Definition: fiber-sjlj.c:299
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
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-sjlj.c:334
This header file is part of the utilities library; it contains the native and platform-independent er...
#define FIBER_SAVE_FENV
A flag specifying a fiber to save and restore the floating-point environment.
Definition: fiber.h:52
#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:77
An object type whose alignment is as great as is supported by the implementation in all contexts...
Definition: stddef.h:34
This header file is part of the C11 and POSIX compatibility library; it includes <unistd.h>, if it exists, and defines any missing functionality.
#define LELY_FIBER_MINSTKSZ
The minimum size (in bytes) of a fiber stack frame.
Definition: fiber.h:33
This header file is part of the C11 and POSIX compatibility library; it includes <stddef.h> and defines any missing functionality.
#define ALIGN(x, a)
Rounds x up to the nearest multiple of a.
Definition: util.h:41
fiber_t * fiber_resume(fiber_t *fiber)
Equivalent to fiber_resume_with(fiber, NULL, NULL).
Definition: fiber-sjlj.c:322
fiber_t * fiber_func_t(fiber_t *fiber, void *arg)
The type of the function executed by a fiber.
Definition: fiber.h:96
int fiber_thrd_init(int flags)
Initializes the fiber associated with the calling thread.
Definition: fiber-sjlj.c:108
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-sjlj.c:314
#define _Thread_local
An object whose identifier is declared with the storage-class specifier _Thread_local has thread stor...
Definition: features.h:239
void fiber_thrd_fini(void)
Finalizes the fiber associated with the calling thread.
Definition: fiber-sjlj.c:135
#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
#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
This header file is part of the C11 and POSIX compatibility library; it includes <stdlib.h> and defines any missing functionality.
#define _Noreturn
A function declared with a _Noreturn function specifier SHALL not return to its caller.
Definition: features.h:214
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-sjlj.c:152