diff options
author | Luke T. Shumaker <lukeshu@lukeshu.com> | 2024-09-30 14:55:35 -0600 |
---|---|---|
committer | Luke T. Shumaker <lukeshu@lukeshu.com> | 2024-09-30 14:55:35 -0600 |
commit | d16d8bfeb1766c339da797eb2de3126e63a5a44c (patch) | |
tree | c1bc0c050ab27465a31ee0278fde031df07802af /libcr | |
parent | d82d7f59f9227a0ea33d13af3c7ed95e16fdd2e3 (diff) |
make coroutine.c signal-safe, add cr_cid_info()
Diffstat (limited to 'libcr')
-rw-r--r-- | libcr/CMakeLists.txt | 3 | ||||
-rw-r--r-- | libcr/coroutine.c | 520 | ||||
-rw-r--r-- | libcr/include/libcr/coroutine.h | 12 |
3 files changed, 311 insertions, 224 deletions
diff --git a/libcr/CMakeLists.txt b/libcr/CMakeLists.txt index 14acba6..92a290a 100644 --- a/libcr/CMakeLists.txt +++ b/libcr/CMakeLists.txt @@ -8,3 +8,6 @@ target_include_directories(libcr SYSTEM INTERFACE ${CMAKE_CURRENT_LIST_DIR}/incl target_sources(libcr INTERFACE coroutine.c ) +target_compile_options(libcr INTERFACE + -fno-split-stack +) diff --git a/libcr/coroutine.c b/libcr/coroutine.c index b3dccff..8bfdb86 100644 --- a/libcr/coroutine.c +++ b/libcr/coroutine.c @@ -18,19 +18,19 @@ #include "config.h" #ifndef CONFIG_COROUTINE_DEFAULT_STACK_SIZE -# error config.h must define CONFIG_COROUTINE_DEFAULT_STACK_SIZE +# error config.h must define CONFIG_COROUTINE_DEFAULT_STACK_SIZE #endif #ifndef CONFIG_COROUTINE_NUM -# error config.h must define CONFIG_COROUTINE_NUM +# error config.h must define CONFIG_COROUTINE_NUM #endif #ifndef CONFIG_COROUTINE_MEASURE_STACK -# error config.h must define CONFIG_COROUTINE_MEASURE_STACK +# error config.h must define CONFIG_COROUTINE_MEASURE_STACK #endif #ifndef CONFIG_COROUTINE_PROTECT_STACK -# error config.h must define CONFIG_COROUTINE_PROTECT_STACK +# error config.h must define CONFIG_COROUTINE_PROTECT_STACK #endif #ifndef CONFIG_COROUTINE_DEBUG -# error config.h must define CONFIG_COROUTINE_DEBUG +# error config.h must define CONFIG_COROUTINE_DEBUG #endif /* Implementation *************************************************************/ @@ -38,17 +38,17 @@ /* * Portability notes: * - * - It uses GCC `__attribute__`s, and the GNUC ({ ... }) statement exprs - etension. + * - It uses GCC `__attribute__`s, and the GNUC ({ ... }) statement + * exprs extension. * - * - It has a small bit of platform-specific code in the "platform support" - * section. Other than this, it should be portable to other platforms CPUs. - * It currently contains implementations for __x86_64__ (assumes POSIX) and - * __arm__ (assumes ARM CMSIS), and should be fairly easy to add - * implementations for other platforms. + * - It has a small bit of platform-specific code in the "platform + * support" section. Other than this, it should be portable to + * other platforms CPUs. It currently contains implementations for + * __x86_64__ (assumes POSIX) and __arm__ (assumes bare-metal), and + * should be fairly easy to add implementations for other platforms. * * - It uses setjmp()/longjmp() in "unsafe" ways. POSIX-2017 - * longjmp(3) says + * longjmp(3p) says * * > If the most recent invocation of setjmp() with the * > corresponding jmp_buf ... or if the function containing the @@ -65,29 +65,29 @@ * choose to do something else, but I'm calling it out here because * you never know. * - * Note that setjmp()/longjmp() are defined in 3 places: in the libc - * (glibc/newlib), as GCC intrinsics, and the lower-level GCC - * __builtin_{setjmp,newlib} which the libc and intrinsic versions - * likely use. Our assumptions seem to be valid for + * Our assumptions seem to be valid for * x86_64-pc-linux-gnu/gcc-14.2.1/glibc-2.40 and * arm-none-eabi/gcc-14.1.0/newlib-4.4.0. * - * Why not use <ucontext.h>, the now-deprecated (was in POSIX.1-2001, - * is gone in POSIX-2008) predecesor to <setjmp.h>? It would let us - * do this without any assembly or unsafe assumptions. Simply: - * because newlib does not provide it. + * Why not use <ucontext.h>, the now-deprecated (was in + * POSIX.1-2001, is gone in POSIX-2008) predecesor to <setjmp.h>? + * It would let us do this without any assembly or unsafe + * assumptions. Simply: because newlib does not provide it. But it + * would let us avoid having an `sp` member in `struct coroutine`... + * Maybe https://github.com/kaniini/libucontext ? Or building a + * ucontext-lib abstraction on top of setjmp/longjmp? */ /* * Design decisions and notes: * - * - Coroutines are launched with a stack stack that is filled with - * known values (zero or a pre-determined pattern, depending on the - * config above). Because all stack variables should be initialized - * before they are read, this "shouldn't" make a difference, but: - * (1) Initializing it to a known value allows us to measure how - * much of the stack was written to, which is helpful to tune stack - * sizes. (2) Leaving it uninitialized just gives me the willies. + * - Coroutines are launched with a stack that is filled with known + * arbitrary values. Because all stack variables should be + * initialized before they are read, this "shouldn't" make a + * difference, but: (1) Initializing it to a known value allows us + * to measure how much of the stack was written to, which is helpful + * to tune stack sizes. (2) Leaving it uninitialized just gives me + * the willies. * * - Because embedded programs should be adverse to using the heap, * CONFIG_COROUTINE_NUM is fixed, instead of having coroutine_add() @@ -111,12 +111,133 @@ * no longer exists. */ -#if __x86_64__ -# include <unistd.h> /* for pause() */ -# define wait_for_interrupt() pause() -#elif __arm__ -# include "hardware/sync.h" /* for __wfi(); */ -# define wait_for_interrupt() __wfi() +#define ALWAYS_INLINE inline __attribute__((always_inline)) + +/* platform support ***********************************************************/ + +/* As part of sbc-harness, this only really needs to support ARM-32, but being + * able to run it on my x86-64 GNU/Linux laptop is useful for debugging. */ + +#define CR_PLAT_STACK_ALIGNMENT \ + ({ __attribute__((aligned)) void fn(void) {}; __alignof__(fn); }) + +#if 0 +{ /* to get Emacs indentation to work how I want */ +#endif +#if __arm__ /*======================================================*/ + /* Assume bare metal ARMv6-M. */ + + #define CR_PLAT_STACK_GROWS_DOWNWARD 1 + + /* Wrappers for setjmp()/longjmp() that do *not* save the + * interrupt mask. + * + * newlib setjmp()/longjmp() do not save the interrupt mask, * + * so we can use them directly. */ + #define cr_plat_setjmp(env) setjmp(env) + #define cr_plat_longjmp(env, val) longjmp(env, val) + + static ALWAYS_INLINE void cr_plat_wait_for_interrupt(void) { + asm volatile ("wfi":::"memory"); + } + static ALWAYS_INLINE void cr_plat_disable_interrupts(void) { + asm volatile ("cpsid i":::"memory"); + } + static ALWAYS_INLINE void cr_plat_enable_interrupts(void) { + asm volatile ("cpsie i":::"memory"); + } + + #if CONFIG_COROUTINE_MEASURE_STACK + static ALWAYS_INLINE uintptr_t cr_plat_get_sp(void) { + uintptr_t sp; + asm volatile ("mov %0, sp":"=r"(sp)); + return sp; + } + #endif + + static void cr_plat_call_with_stack(void *stack, + cr_fn_t fn, void *args) { + static void *saved_sp = NULL; + /* str/ldr can only work with a "lo" register, which + * sp is not, so we use r0 as an intermediate because + * we're going to clobber it with args anyway. */ + asm volatile ("mov r0, sp\n\t" /* [saved_sp = sp */ + "str r0, %0\n\t" /* ] */ + "mov sp, %1\n\t" /* [sp = stack] */ + "mov r0, %3\n\t" /* [arg0 = args] */ + "blx %2\n\t" /* [fn()] */ + "ldr r0, %0\n\t" /* [sp = staved_sp */ + "mov sp, r0" /* ] */ + : + : /* %0 */"m"(saved_sp), + /* %1 */"r"(stack), + /* %2 */"r"(fn), + /* %3 */"r"(args) + : "r0" + ); + } + +#elif __x86_64__ /*=================================================*/ + /* Assume POSIX. */ + #include <signal.h> /* for sig*, SIG_* */ + #include <unistd.h> /* for pause() */ + + #define CR_PLAT_STACK_GROWS_DOWNWARD 1 + + /* Wrappers for setjmp()/longjmp() that do *not* save the + * interrupt mask. + * + * POSIX leaves it implementation-defined whether they save + * the signal mask; while glibc does not save it, let's not + * rely on that. */ + #define cr_plat_setjmp(env) sigsetjmp(env, 0) + #define cr_plat_longjmp(env, val) siglongjmp(env, val) + + static inline void cr_plat_wait_for_interrupt(void) { + pause(); + } + static inline void cr_plat_disable_interrupts(void) { + sigset_t all; + sigfillset(&all); + sigprocmask(SIG_BLOCK, &all, NULL); + } + static inline void cr_plat_enable_interrupts(void) { + sigset_t all; + sigfillset(&all); + sigprocmask(SIG_UNBLOCK, &all, NULL); + } + + #if CONFIG_COROUTINE_MEASURE_STACK + static ALWAYS_INLINE uintptr_t cr_plat_get_sp(void) { + uintptr_t sp; + asm volatile ("movq %%rsp, %0":"=r"(sp)); + return sp; + } + #endif + + static void cr_plat_call_with_stack(void *stack, + cr_fn_t fn, void *args) { + static void *saved_sp = NULL; + asm volatile ("movq %%rsp , %0\n\t" /* saved_sp = sp */ + "movq %1 , %%rsp\n\t" /* sp = stack */ + "movq %3 , %%rdi\n\t" /* arg0 = args */ + "call *%2\n\t" /* fn() */ + "movq %0 , %%rsp" /* sp = saved_sp */ + : + : /* %0 */"m"(saved_sp), + /* %1 */"r"(stack), + /* %2 */"r"(fn), + /* %3 */"r"(args) + : "rdi" + ); + } + +#else + #error unsupported platform + +#endif +#if 0 +} #endif /* types **********************************************************************/ @@ -125,7 +246,8 @@ enum coroutine_state { CR_NONE = 0, /* this slot in the table is empty */ CR_INITIALIZING, /* running, before cr_begin() */ CR_RUNNING, /* running, after cr_begin() */ - CR_PRE_RUNNABLE, /* running, between cr_unpause_from_intrhandler() and cr_pause() */ + CR_PRE_RUNNABLE, /* running, after cr_unpause_from_intrhandler() + * but before cr_pause() */ CR_RUNNABLE, /* not running, but runnable */ CR_PAUSED, /* not running, and not runnable */ }; @@ -133,6 +255,12 @@ enum coroutine_state { struct coroutine { volatile enum coroutine_state state; jmp_buf env; +#if CONFIG_COROUTINE_MEASURE_STACK + /* We aught to be able to get this out of .env, but libc + * authors insist on jmp_buf being opaque, glibc going as far + * as to xor it with a secret ot obfuscate it! */ + uintptr_t sp; +#endif size_t stack_size; void *stack; }; @@ -156,6 +284,12 @@ static const uint8_t stack_pattern[] = { 0x4b, 0x14, 0xc4, 0xe0, 0xea, 0x62, 0x25, 0x63, }; #endif +#if CONFIG_COROUTINE_PROTECT_STACK +# define STACK_GUARD_SIZE \ + round_up(sizeof(stack_pattern), CR_PLAT_STACK_ALIGNMENT) +#else +# define STACK_GUARD_SIZE 0 +#endif /* global variables ***********************************************************/ @@ -193,24 +327,23 @@ static cid_t coroutine_running = 0; #endif #ifdef __GLIBC__ -# define assertf(expr, ...) \ - ((void) sizeof ((expr) ? 1 : 0), __extension__ ({ \ - if (expr) \ - ; /* empty */ \ - else { \ - errorf("assertion: " __VA_ARGS__); \ - __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION); \ - } \ - })) +# define assertf(expr, ...) ({ \ + if (!(expr)) { \ + errorf("assertion: " __VA_ARGS__); \ + __assert_fail(#expr, __FILE__, __LINE__, __func__); \ + } \ + }) #else -# define assertf(expr, ...) assert(expr) +# define assertf(expr, ...) assert(expr) #endif /** Return `n` rounded up to the nearest multiple of `d` */ -#define round_up(n, d) ( ( ((n)+(d)-1) / (d) ) * (d) ) +#define round_up(n, d) ( ( ((n)+(d)-1) / (d) ) * (d) ) + +#define array_len(arr) (sizeof(arr)/sizeof(arr[0])) static inline const char* coroutine_state_str(enum coroutine_state state) { - assert(state < (sizeof(coroutine_state_strs)/sizeof(coroutine_state_strs[0]))); + assert(state < array_len(coroutine_state_strs)); return coroutine_state_strs[state]; } @@ -219,121 +352,22 @@ static inline void assert_cid(cid_t cid) { assert(cid <= CONFIG_COROUTINE_NUM); #if CONFIG_COROUTINE_PROTECT_STACK assert(coroutine_table[cid-1].stack_size); - assert(coroutine_table[cid-1].stack); - for (size_t i = 0; i < sizeof(stack_pattern); i++) { + uint8_t *stack = coroutine_table[cid-1].stack; + assert(stack); + for (size_t i = 0; i < STACK_GUARD_SIZE; i++) { size_t j = coroutine_table[cid-1].stack_size - (i+1); - assert(((uint8_t*)coroutine_table[cid-1].stack)[i] == stack_pattern[i]); - assert(((uint8_t*)coroutine_table[cid-1].stack)[j] == stack_pattern[j%sizeof(stack_pattern)]); + assert(stack[i] == stack_pattern[i%sizeof(stack_pattern)]); + assert(stack[j] == stack_pattern[j%sizeof(stack_pattern)]); } #endif } -#define assert_cid_state(cid, expr) do { \ - assert_cid(cid); \ +#define assert_cid_state(cid, expr) do { \ + assert_cid(cid); \ cid_t state = coroutine_table[(cid)-1].state; \ - assert(expr); \ + assert(expr); \ } while (0) -/* platform support ***********************************************************/ - -/* As part of sbc-harness, this only really needs to support ARM-32, but being - * able to run it on my x86-64 GNU/Linux laptop is useful for debugging. */ - -#define CR_PLAT_STACK_ALIGNMENT ({ __attribute__((aligned)) void fn(void) {}; __alignof__(fn); }) - -#if 0 -{ /* to get Emacs indentation to work how I want */ -#endif -#if __arm__ - /* Assume bare metal ARMv6-M. */ - #include "cmsis_compiler.h" /* for __WFI(), __disable_irq(), __enable_irq() */ - - #define CR_PLAT_STACK_GROWS_DOWNWARD 1 - - static void cr_plat_wait_for_interrupt(void) { asm volatile ("wfi":::"memory"); } - static void cr_plat_disable_interrupt(void) { asm volatile ("cpsid i":::"memory"); } - static void cr_plat_enable_interrupts(void) { asm volatile ("cpsie i":::"memory"); } - -#if CONFIG_COROUTINE_MEASURE_STACK - static size_t cr_plat_get_sp(void) { - size_t sp; - asm volatile ("mov %0, sp":"=r"(sp)); - return sp; - } -#endif - - static void cr_plat_call_with_stack(void *stack, cr_fn_t fn, void *args) { - static void *saved_sp = NULL; - /* str/ldr can only work with a "lo" register, which sp is - * not, so we use r0 as an intermediate because we're going to - * clobber it with args anyway. */ - asm volatile ("mov r0, sp\n\t" /* [saved_sp = sp */ - "str r0, %0\n\t" /* ] */ - "mov sp, %1\n\t" /* [sp = stack] */ - "mov r0, %3\n\t" /* [arg0 = args] */ - "blx %2\n\t" /* [fn()] */ - "ldr r0, %0\n\t" /* [sp = staved_sp */ - "mov sp, r0" /* ] */ - : - : /* %0 */"m"(saved_sp), - /* %1 */"r"(stack), - /* %2 */"r"(fn), - /* %3 */"r"(args) - : "r0" - ); - } - -#elif __x86_64__ - /* Assume POSIX. */ - #include <signal.h> /* for sigset_t, sigprocmask(), SIG_*, sigfillset() */ - #include <unistd.h> /* for pause() */ - - #define CR_PLAT_STACK_GROWS_DOWNWARD 1 - - static void cr_plat_wait_for_interrupt(void) { - pause(); - } - static void cr_plat_disable_interrupts(void) { - sigset_t all; - sigfillset(&all); - sigprocmask(SIG_BLOCK, &all, NULL); - } - static void cr_plat_enable_interrupts(void) { - sigset_t all; - sigfillset(&all); - sigprocmask(SIG_UNBLOCK, &all, NULL); - } - -#if CONFIG_COROUTINE_MEASURE_STACK - static size_t cr_plat_get_sp(void) { - size_t sp; - asm volatile ("movq %%rsp, %0":"=r"(sp)); - return sp; - } -#endif - - static void cr_plat_call_with_stack(void *stack, cr_fn_t fn, void *args) { - static void *saved_sp = NULL; - asm volatile ("movq %%rsp , %0\n\t" /* saved_sp = sp */ - "movq %1 , %%rsp\n\t" /* sp = stack */ - "movq %3 , %%rdi\n\t" /* arg0 = args */ - "call *%2\n\t" /* fn() */ - "movq %0 , %%rsp" /* sp = saved_sp */ - : - : /* %0 */"m"(saved_sp), - /* %1 */"r"(stack), - /* %2 */"r"(fn), - /* %3 */"r"(args) - : "rdi" - ); - -#else - #error unsupported platform - -#endif -#if 0 -} -#endif /* coroutine_add() ************************************************************/ @@ -345,13 +379,14 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, cr_fn_t fn, void *args) { assert_cid_state(parent, state == CR_RUNNING); assert(stack_size); assert(fn); - debugf("coroutine_add_with_stack_size(%zu, %p, %p)...\n", stack_size, fn, args); + debugf("coroutine_add_with_stack_size(%zu, %p, %p)...\n", + stack_size, fn, args); cid_t child; { - size_t idx_base = last_created; - for (size_t idx_shift = 0; idx_shift < CONFIG_COROUTINE_NUM; idx_shift++) { - child = ((idx_base + idx_shift) % CONFIG_COROUTINE_NUM) + 1; + size_t base = last_created; + for (size_t shift = 0; shift < CONFIG_COROUTINE_NUM; shift++) { + child = ((base + shift) % CONFIG_COROUTINE_NUM) + 1; if (coroutine_table[child-1].state == CR_NONE) goto found; } @@ -363,23 +398,25 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, cr_fn_t fn, void *args) { last_created = child; coroutine_table[child-1].stack_size = stack_size; - coroutine_table[child-1].stack = aligned_alloc(CR_PLAT_STACK_ALIGNMENT, stack_size); + coroutine_table[child-1].stack = + aligned_alloc(CR_PLAT_STACK_ALIGNMENT, stack_size); #if CONFIG_COROUTINE_MEASURE_STACK || CONFIG_COROUTINE_PROTECT_STACK for (size_t i = 0; i < stack_size; i++) - ((uint8_t*)coroutine_table[child-1].stack)[i] = stack_pattern[i%sizeof(stack_pattern)]; + ((uint8_t *)coroutine_table[child-1].stack)[i] = + stack_pattern[i%sizeof(stack_pattern)]; #endif coroutine_running = child; coroutine_table[child-1].state = CR_INITIALIZING; - if (!setjmp(coroutine_add_env)) { /* point=a */ - void *stack_base = coroutine_table[child-1].stack + (CR_PLAT_STACK_GROWS_DOWNWARD ? stack_size : 0); -#if CONFIG_COROUTINE_PROTECT_STACK -# if CR_PLAT_STACK_GROWS_DOWNWARD - stack_base -= round_up(sizeof(stack_pattern), CR_PLAT_STACK_ALIGNMENT); -# else - stack_base += round_up(sizeof(stack_pattern), CR_PLAT_STACK_ALIGNMENT); -# endif + if (!cr_plat_setjmp(coroutine_add_env)) { /* point=a */ + void *stack_base = coroutine_table[child-1].stack +#if CR_PLAT_STACK_GROWS_DOWNWARD + + stack_size + - STACK_GUARD_SIZE +#else + + STACK_GUARD_SIZE #endif + ; debugf("...stack =%p\n", coroutine_table[child-1].stack); debugf("...stack_base=%p\n", stack_base); /* run until cr_begin() */ @@ -395,35 +432,12 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, cr_fn_t fn, void *args) { } cid_t coroutine_add(cr_fn_t fn, void *args) { - return coroutine_add_with_stack_size(CONFIG_COROUTINE_DEFAULT_STACK_SIZE, fn, args); + return coroutine_add_with_stack_size( + CONFIG_COROUTINE_DEFAULT_STACK_SIZE, fn, args); } /* coroutine_main() ***********************************************************/ -#if CONFIG_COROUTINE_MEASURE_STACK -struct stack_stats { - size_t cap; - size_t max; - //size_t cur; -}; - -static void measure_stack(cid_t cid, struct stack_stats *ret) { - ret->cap = coroutine_table[cid-1].stack_size - (CONFIG_COROUTINE_PROTECT_STACK ? 2*round_up(sizeof(stack_pattern), CR_PLAT_STACK_ALIGNMENT) : 0); - - ret->max = ret->cap; - for (;;) { -#if CR_PLAT_STACK_GROWS_DOWNWARD - size_t i = (CONFIG_COROUTINE_PROTECT_STACK ? round_up(sizeof(stack_pattern), CR_PLAT_STACK_ALIGNMENT) : 0) + ret->cap - ret->max; -#else - size_t i = ret->max - 1 - (CONFIG_COROUTINE_PROTECT_STACK ? round_up(sizeof(stack_pattern), CR_PLAT_STACK_ALIGNMENT) : 0); -#endif - if (ret->max == 0 || ((uint8_t*)coroutine_table[cid-1].stack)[i] != stack_pattern[i%sizeof(stack_pattern)]) - break; - ret->max--; - } -} -#endif - static inline cid_t next_coroutine() { for (cid_t next = (coroutine_running%CONFIG_COROUTINE_NUM)+1; next != coroutine_running; @@ -436,24 +450,23 @@ static inline cid_t next_coroutine() { void coroutine_main(void) { debugf("coroutine_main()\n"); + cr_plat_disable_interrupts(); coroutine_running = 0; for (;;) { cid_t next = next_coroutine(); if (!next) { + cr_plat_enable_interrupts(); errorf("no coroutines\n"); return; } - if (!setjmp(coroutine_main_env)) { /* point=b */ + if (!cr_plat_setjmp(coroutine_main_env)) { /* point=b */ coroutine_running = next; coroutine_table[coroutine_running-1].state = CR_RUNNING; - longjmp(coroutine_table[coroutine_running-1].env, 1); /* jump to point=c */ + cr_plat_longjmp(coroutine_table[coroutine_running-1].env, 1); /* jump to point=c */ } + /* This is where we jump to from cr_exit(), and from + * nowhere else. */ assert_cid_state(coroutine_running, state == CR_NONE); -#if CONFIG_COROUTINE_MEASURE_STACK - struct stack_stats sizes; - measure_stack(coroutine_running, &sizes); - infof("cid=%zu: exited having used %zu B stack space\n", coroutine_running, sizes.max); -#endif free(coroutine_table[coroutine_running-1].stack); coroutine_table[coroutine_running-1] = (struct coroutine){0}; } @@ -462,81 +475,140 @@ void coroutine_main(void) { /* cr_*() *********************************************************************/ void cr_begin(void) { + debugf("cid=%zu: cr_begin()\n", coroutine_running); assert_cid_state(coroutine_running, state == CR_INITIALIZING); coroutine_table[coroutine_running-1].state = CR_RUNNABLE; - if (!setjmp(coroutine_table[coroutine_running-1].env)) /* point=c1 */ - longjmp(coroutine_add_env, 1); /* jump to point=a */ + coroutine_table[coroutine_running-1].sp = cr_plat_get_sp(); + if (!cr_plat_setjmp(coroutine_table[coroutine_running-1].env)) /* point=c1 */ + cr_plat_longjmp(coroutine_add_env, 1); /* jump to point=a */ + cr_plat_enable_interrupts(); } -static inline void _yield() { +static inline void _cr_transition(enum coroutine_state state) { + coroutine_table[coroutine_running-1].state = state; + cid_t next; for (;;) { next = next_coroutine(); if (next) + /* Switch to `next`. */ break; if (coroutine_table[coroutine_running-1].state == CR_RUNNABLE) { - debugf("cid=%zu: no other runnable coroutines, not yielding\n", coroutine_running); + /* No other coroutine is runnable, don't + * transition after all. */ coroutine_table[coroutine_running-1].state = CR_RUNNING; return; - } else { // XXX - debugf("cid=%zu: no runnable coroutines, sleeping\n", coroutine_running); // XXX - wait_for_interrupt(); // XXX } + /* No coroutines are runnable, wait for an interrupt + * to change that. */ + cr_plat_wait_for_interrupt(); } - if (!setjmp(coroutine_table[coroutine_running-1].env)) { /* point=c2 */ + + coroutine_table[coroutine_running-1].sp = cr_plat_get_sp(); + if (!cr_plat_setjmp(coroutine_table[coroutine_running-1].env)) { /* point=c2 */ coroutine_running = next; coroutine_table[coroutine_running-1].state = CR_RUNNING; - longjmp(coroutine_table[coroutine_running-1].env, 1); /* jump to point=b */ + cr_plat_longjmp(coroutine_table[coroutine_running-1].env, 1); /* jump to point=b */ } } void cr_yield(void) { + debugf("cid=%zu: cr_yield()\n", coroutine_running); assert_cid_state(coroutine_running, state == CR_RUNNING); - coroutine_table[coroutine_running-1].state = CR_RUNNABLE; - _yield(); + + cr_plat_disable_interrupts(); + _cr_transition(CR_RUNNABLE); + cr_plat_enable_interrupts(); } void cr_pause_and_yield(void) { + debugf("cid=%zu: cr_pause_and_yield()\n", coroutine_running); assert_cid_state(coroutine_running, state == CR_RUNNING || state == CR_PRE_RUNNABLE); - coroutine_table[coroutine_running-1].state = CR_PAUSED; - _yield(); + + cr_plat_disable_interrupts(); + if (coroutine_table[coroutine_running-1].state == CR_PRE_RUNNABLE) + _cr_transition(CR_RUNNABLE); + else + _cr_transition(CR_PAUSED); + cr_plat_enable_interrupts(); } void cr_exit(void) { + debugf("cid=%zu: cr_exit()\n", coroutine_running); assert_cid_state(coroutine_running, state == CR_RUNNING); - debugf("cid=%zu: exit\n", coroutine_running); + cr_plat_disable_interrupts(); coroutine_table[coroutine_running-1].state = CR_NONE; - longjmp(coroutine_main_env, 1); /* jump to point=b */ + cr_plat_longjmp(coroutine_main_env, 1); /* jump to point=b */ } void cr_unpause(cid_t cid) { - assert_cid_state(cid, state == CR_PAUSED); debugf("cr_unpause(%zu)\n", cid); + assert_cid_state(cid, state == CR_PAUSED); coroutine_table[cid-1].state = CR_RUNNABLE; } void cr_unpause_from_intrhandler(cid_t cid) { - assert_cid_state(cid, state == CR_RUNNING || state == CR_PAUSED); debugf("cr_unpause_from_intrhandler(%zu)\n", cid); + assert_cid_state(cid, state == CR_RUNNING || state == CR_PAUSED); - switch (coroutine_table[cid-1].state) { - case CR_RUNNING: + if (coroutine_table[cid-1].state == CR_RUNNING) { assert(cid == coroutine_running); debugf("... raced, deferring unpause\n"); coroutine_table[cid-1].state = CR_PRE_RUNNABLE; - break; - case CR_PAUSED: + } else { debugf("... actual unpause\n"); coroutine_table[cid-1].state = CR_RUNNABLE; - break; - default: - assert(false); } } cid_t cr_getcid(void) { return coroutine_running; } + +/* cr_cid_info() **************************************************************/ + +#if CONFIG_COROUTINE_MEASURE_STACK + +void cr_cid_info(cid_t cid, struct cr_cid_info *ret) { + assert_cid(cid); + assert(ret); + + /* stack_cap */ + ret->stack_cap = coroutine_table[cid-1].stack_size - 2*STACK_GUARD_SIZE; + + /* stack_max */ + ret->stack_max = ret->stack_cap; + uint8_t *stack = (uint8_t *)coroutine_table[cid-1].stack; + for (;;) { + size_t i = +#if CR_PLAT_STACK_GROWS_DOWNWARD + STACK_GUARD_SIZE + ret->stack_cap - ret->stack_max +#else + ret->stack_max - 1 - STACK_GUARD_SIZE +#endif + ; + if (ret->stack_max == 0 || + stack[i] != stack_pattern[i%sizeof(stack_pattern)]) + break; + ret->stack_max--; + } + + /* stack_cur */ + uintptr_t sp; + if (cid == coroutine_running) + sp = cr_plat_get_sp(); + else + sp = coroutine_table[cid-1].sp; + assert(sp); + uintptr_t sb = (uintptr_t)coroutine_table[cid-1].stack; +#if CR_PLAT_STACK_GROWS_DOWNWARD + ret->stack_cur = (sb - STACK_GUARD_SIZE) - sp; +#else + ret->stack_cur = sp - (sb + STACK_GUARD_SIZE); +#endif +} + +#endif /* CONFIG_COROUTINE_MEASURE_STACK */ diff --git a/libcr/include/libcr/coroutine.h b/libcr/include/libcr/coroutine.h index f580b09..0c5eac3 100644 --- a/libcr/include/libcr/coroutine.h +++ b/libcr/include/libcr/coroutine.h @@ -126,4 +126,16 @@ cid_t cr_getcid(void); * another as paused; a coroutine may only do those things to itself. */ +/* While the following are defined here unconditionally, the + * implementations are #if'd on CONFIG_COROUTINE_MEASURE_STACK. + */ + +struct cr_cid_info { + size_t stack_cap; + size_t stack_max; + size_t stack_cur; +}; + +void cr_cid_info(cid_t cid, struct cr_cid_info *ret); + #endif /* _COROUTINE_H_ */ |