diff options
Diffstat (limited to 'libcr')
-rw-r--r-- | libcr/CMakeLists.txt | 11 | ||||
-rw-r--r-- | libcr/coroutine.c | 299 | ||||
-rw-r--r-- | libcr/include/libcr/coroutine.h | 72 | ||||
-rw-r--r-- | libcr/tests/test_matrix.c | 6 | ||||
-rw-r--r-- | libcr/tests/test_matrix/config.h | 8 |
5 files changed, 263 insertions, 133 deletions
diff --git a/libcr/CMakeLists.txt b/libcr/CMakeLists.txt index 130b018..472820c 100644 --- a/libcr/CMakeLists.txt +++ b/libcr/CMakeLists.txt @@ -1,10 +1,13 @@ -# libcr/CMakeLists.txt - TODO +# libcr/CMakeLists.txt - Simple embeddable coroutine implementation # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later +add_library(libcr_headers INTERFACE) +target_include_directories(libcr_headers PUBLIC INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) + add_library(libcr INTERFACE) -target_include_directories(libcr SYSTEM INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) +target_link_libraries(libcr INTERFACE libcr_headers) target_sources(libcr INTERFACE coroutine.c ) @@ -30,7 +33,7 @@ function(add_libcr_matrix_test n defs) if ("CONFIG_COROUTINE_VALGRIND=1" IN_LIST defs) add_test( NAME "libcr/test_matrix${n}" - COMMAND valgrind --error-exitcode=2 "./test_matrix${n}" + COMMAND "${CMAKE_SOURCE_DIR}/build-aux/valgrind" "./test_matrix${n}" ) else() add_test( diff --git a/libcr/coroutine.c b/libcr/coroutine.c index aa23d58..182e85c 100644 --- a/libcr/coroutine.c +++ b/libcr/coroutine.c @@ -1,6 +1,6 @@ /* libcr/coroutine.c - Simple embeddable coroutine implementation * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -9,6 +9,7 @@ #include <string.h> /* for strncpy(), memset() */ #include <libmisc/assert.h> +#include <libmisc/macro.h> #define LOG_NAME COROUTINE #include <libmisc/log.h> @@ -20,9 +21,6 @@ #include "config.h" -#ifndef CONFIG_COROUTINE_DEFAULT_STACK_SIZE - #error config.h must define CONFIG_COROUTINE_DEFAULT_STACK_SIZE (non-negative integer) -#endif #ifndef CONFIG_COROUTINE_NAME_LEN #error config.h must define CONFIG_COROUTINE_NAME_LEN (non-negative integer) #endif @@ -45,6 +43,10 @@ #error config.h must define CONFIG_COROUTINE_GDB (bool) #endif +/* Enforce that CONFIG_COROUTINE_NUM is greater than 1, to work around + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118212 */ +static_assert(CONFIG_COROUTINE_NUM > 1); + /* Implementation *************************************************************/ #if CONFIG_COROUTINE_VALGRIND @@ -128,18 +130,6 @@ * no longer exists. */ -/* preprocessor macros ********************************************************/ - -/** Return `n` rounded up to the nearest multiple of `d` */ -#define ROUND_UP(n, d) ( ( ((n)+(d)-1) / (d) ) * (d) ) -#define ARRAY_LEN(arr) (sizeof(arr)/sizeof((arr)[0])) -#define NEXT_POWER_OF_2(x) ((1ULL)<<((sizeof(unsigned long long)*8)-__builtin_clzll(x))) - -#define UNUSED(name) - -#define ALWAYS_INLINE [[gnu::always_inline]] inline -#define NEVER_INLINE [[gnu::noinline]] - /* platform support ***********************************************************/ /* As part of sbc-harness, this only really needs to support ARM-32, but being @@ -165,9 +155,19 @@ #define _CR_SIG_GDB SIGWINCH #endif +#if CONFIG_COROUTINE_VALGRIND + /* Hack around a bug in Valgrind where it runs the + * sigsuspend(tmp_sigmask)-triggered handler function with the + * original mask, not the `tmp_mask`. */ + static bool _cr_plat_in_sigsuspend = false; +#endif + bool cr_plat_is_in_intrhandler(void) { +#if CONFIG_COROUTINE_VALGRIND + if (_cr_plat_in_sigsuspend) + return true; +#endif sigset_t cur_mask; - sigfillset(&cur_mask); sigprocmask(0, NULL, &cur_mask); if (sigismember(&cur_mask, _CR_SIG_SENTINEL)) /* Interrupts are disabled, so we cannot be in @@ -191,10 +191,13 @@ assert(!_cr_plat_are_interrupts_enabled()); sigset_t set; sigemptyset(&set); +#if CONFIG_COROUTINE_VALGRIND + _cr_plat_in_sigsuspend = true; +#endif sigsuspend(&set); - - sigfillset(&set); - sigprocmask(SIG_SETMASK, &set, NULL); +#if CONFIG_COROUTINE_VALGRIND + _cr_plat_in_sigsuspend = false; +#endif } bool _cr_plat_save_and_disable_interrupts(void) { assert(!cr_plat_is_in_intrhandler()); @@ -211,7 +214,7 @@ sigprocmask(SIG_SETMASK, &zero, NULL); } #if CONFIG_COROUTINE_GDB - static void _cr_gdb_intrhandler(int UNUSED(sig)) {} + static void _cr_gdb_intrhandler(int LM_UNUSED(sig)) {} #endif static void cr_plat_init(void) { #if CONFIG_COROUTINE_GDB @@ -231,7 +234,7 @@ ); return isr_number != 0; } - ALWAYS_INLINE static bool _cr_plat_are_interrupts_enabled(void) { + LM_ALWAYS_INLINE static bool _cr_plat_are_interrupts_enabled(void) { assert(!cr_plat_is_in_intrhandler()); uint32_t primask; asm volatile ("mrs %0, PRIMASK" @@ -240,7 +243,7 @@ return primask == 0; } - ALWAYS_INLINE static void cr_plat_wait_for_interrupt(void) { + LM_ALWAYS_INLINE static void cr_plat_wait_for_interrupt(void) { assert(!cr_plat_is_in_intrhandler()); assert(!_cr_plat_are_interrupts_enabled()); asm volatile ("wfi\n" @@ -271,7 +274,7 @@ #define CR_PLAT_STACK_GROWS_DOWNWARD 1 #if CONFIG_COROUTINE_MEASURE_STACK - ALWAYS_INLINE static uintptr_t cr_plat_get_sp(void) { + LM_ALWAYS_INLINE static uintptr_t cr_plat_get_sp(void) { uintptr_t sp; asm volatile ("mov %0, sp":"=r"(sp)); return sp; @@ -303,7 +306,7 @@ #define CR_PLAT_STACK_GROWS_DOWNWARD 1 #if CONFIG_COROUTINE_MEASURE_STACK - ALWAYS_INLINE static uintptr_t cr_plat_get_sp(void) { + LM_ALWAYS_INLINE static uintptr_t cr_plat_get_sp(void) { uintptr_t sp; asm volatile ("movq %%rsp, %0":"=r"(sp)); return sp; @@ -346,7 +349,7 @@ uintptr_t sp; #endif } cr_plat_jmp_buf; - static void _cr_plat_setjmp_pre(cr_plat_jmp_buf *env [[gnu::unused]]) { + static void _cr_plat_setjmp_pre(cr_plat_jmp_buf *env [[maybe_unused]]) { #if CONFIG_COROUTINE_MEASURE_STACK env->sp = cr_plat_get_sp(); #endif @@ -381,23 +384,25 @@ /* types **********************************************************************/ -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_RUNNABLE, /* not running, but runnable */ - CR_PAUSED, /* not running, and not runnable */ -}; - struct coroutine { + /* 1. state *************************************************/ volatile enum coroutine_state state; - cr_plat_jmp_buf env; + + /* 2. name **************************************************/ + [[gnu::nonstring]] char name[CONFIG_COROUTINE_NAME_LEN]; + + /* 3. stack *************************************************/ + /* stack_size *includes* CR_STACK_GUARD at each end. */ size_t stack_size; + /* stack is the bottom of the CR_STACK_GUARD at the bottom of the stack. */ void *stack; #if CONFIG_COROUTINE_VALGRIND unsigned stack_id; #endif - char name[CONFIG_COROUTINE_NAME_LEN]; + bool stack_free; + + /* 4. env ***************************************************/ + cr_plat_jmp_buf env; }; /* constants ******************************************************************/ @@ -419,10 +424,10 @@ static const uint8_t stack_pattern[] = { }; #endif #if CONFIG_COROUTINE_PROTECT_STACK - #define STACK_GUARD_SIZE \ - ROUND_UP(sizeof(stack_pattern), CR_PLAT_STACK_ALIGNMENT) + #define CR_STACK_GUARD_SIZE \ + ((size_t)LM_ROUND_UP(sizeof(stack_pattern), CR_PLAT_STACK_ALIGNMENT)) #else - #define STACK_GUARD_SIZE 0 + #define CR_STACK_GUARD_SIZE ((size_t)0) #endif /* global variables ***********************************************************/ @@ -453,7 +458,7 @@ static cr_plat_jmp_buf coroutine_gdb_env; * coroutine_ringbuf queue. */ -static struct coroutine coroutine_table[CONFIG_COROUTINE_NUM] = {0}; +static struct coroutine coroutine_table[CONFIG_COROUTINE_NUM] = {}; static struct { /* tail == head means empty */ /* buf[tail] is the next thing to run */ @@ -463,36 +468,31 @@ static struct { * compiler will optimize `%array_len` to &(array_len-1)`, (b) * we don't have to worry about funny wrap-around behavior * when head or tail overflow. */ - cid_t buf[NEXT_POWER_OF_2(CONFIG_COROUTINE_NUM)]; -} coroutine_ringbuf = {0}; + cid_t buf[LM_NEXT_POWER_OF_2(CONFIG_COROUTINE_NUM)]; +} coroutine_ringbuf = {}; static cid_t coroutine_running = 0; static size_t coroutine_cnt = 0; /* utility functions **********************************************************/ -static inline const char* coroutine_state_str(enum coroutine_state state) { - assert(state < ARRAY_LEN(coroutine_state_strs)); - return coroutine_state_strs[state]; -} - static inline void coroutine_ringbuf_push(cid_t val) { - coroutine_ringbuf.buf[coroutine_ringbuf.head++ % ARRAY_LEN(coroutine_ringbuf.buf)] = val; - assert((coroutine_ringbuf.head % ARRAY_LEN(coroutine_ringbuf.buf)) != - (coroutine_ringbuf.tail % ARRAY_LEN(coroutine_ringbuf.buf))); + coroutine_ringbuf.buf[coroutine_ringbuf.head++ % LM_ARRAY_LEN(coroutine_ringbuf.buf)] = val; + assert((coroutine_ringbuf.head % LM_ARRAY_LEN(coroutine_ringbuf.buf)) != + (coroutine_ringbuf.tail % LM_ARRAY_LEN(coroutine_ringbuf.buf))); } static inline cid_t coroutine_ringbuf_pop(void) { if (coroutine_ringbuf.tail == coroutine_ringbuf.head) return 0; - return coroutine_ringbuf.buf[coroutine_ringbuf.tail++ % ARRAY_LEN(coroutine_ringbuf.buf)]; + return coroutine_ringbuf.buf[coroutine_ringbuf.tail++ % LM_ARRAY_LEN(coroutine_ringbuf.buf)]; } #if CONFIG_COROUTINE_GDB -NEVER_INLINE void cr_gdb_breakpoint(void) { +LM_NEVER_INLINE void cr_gdb_breakpoint(void) { /* Prevent the call from being optimized away. */ asm (""); } -NEVER_INLINE void cr_gdb_readjmp(cr_plat_jmp_buf *env) { +LM_NEVER_INLINE void cr_gdb_readjmp(cr_plat_jmp_buf *env) { if (!cr_plat_setjmp(&coroutine_gdb_env)) cr_plat_longjmp(env, 2); } @@ -516,7 +516,7 @@ static inline void assert_cid(cid_t cid) { assert(coroutine_table[cid-1].stack_size); uint8_t *stack = coroutine_table[cid-1].stack; assert(stack); - for (size_t i = 0; i < STACK_GUARD_SIZE; i++) { + for (size_t i = 0; i < CR_STACK_GUARD_SIZE; i++) { size_t j = coroutine_table[cid-1].stack_size - (i+1); assert(stack[i] == stack_pattern[i%sizeof(stack_pattern)]); assert(stack[j] == stack_pattern[j%sizeof(stack_pattern)]); @@ -533,74 +533,94 @@ static inline void assert_cid(cid_t cid) { /* coroutine_add() ************************************************************/ -cid_t coroutine_add_with_stack_size(size_t stack_size, - const char *name, - cr_fn_t fn, void *args) { +LM_NEVER_INLINE +cid_t coroutine_allocate_cid(void) { static cid_t last_created = 0; + + size_t base = last_created; + for (size_t shift = 0; shift < CONFIG_COROUTINE_NUM; shift++) { + cid_t child = ((base + shift) % CONFIG_COROUTINE_NUM) + 1; + if (coroutine_table[child-1].state == CR_NONE) { + last_created = child; + return child; + } + } + return 0; +} + +static cid_t _coroutine_add(void *stack, size_t full_stack_size, + const char *name, + cr_fn_t fn, void *args) { cid_t parent = coroutine_running; if (parent) assert_cid_state(parent, state == CR_RUNNING); - assert(stack_size); + assert(full_stack_size > 2*CR_STACK_GUARD_SIZE); assert(fn); - debugf("coroutine_add_with_stack_size(%zu, \"%s\", %p, %p)...", - stack_size, name, fn, args); if (!coroutine_initialized) { cr_plat_init(); coroutine_initialized = true; } - cid_t child; - { - 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; - } + cid_t child = coroutine_allocate_cid(); + if (!child) return 0; - found: - } - debugf("...child=%zu", child); + log_debugln("...child=", child); - last_created = child; + /* 1. state *************************************************/ + coroutine_table[child-1].state = CR_INITIALIZING; + /* 2. name **************************************************/ if (name) strncpy(coroutine_table[child-1].name, name, sizeof(coroutine_table[child-1].name)); else memset(coroutine_table[child-1].name, 0, sizeof(coroutine_table[child-1].name)); - coroutine_table[child-1].stack_size = stack_size; - infof("allocing \"%s\" stack with size %zu", name, stack_size); - coroutine_table[child-1].stack = - aligned_alloc(CR_PLAT_STACK_ALIGNMENT, stack_size); - infof("...done"); + log_infoln("starting cid ", child, + " ", (qstrn, coroutine_table[child-1].name, sizeof(coroutine_table[child-1].name)), + ":"); + + /* 3. stack *************************************************/ + coroutine_table[child-1].stack_size = full_stack_size; + coroutine_table[child-1].stack_free = (stack == NULL); + if (!stack) { + log_infoln("allocing stack with size ", full_stack_size, "..."); + stack = aligned_alloc(CR_PLAT_STACK_ALIGNMENT, full_stack_size); + log_infoln("...done"); + } + coroutine_table[child-1].stack = stack; + log_infoln(" -> full stack is [", + (ptr, coroutine_table[child-1].stack), ",", + (ptr, coroutine_table[child-1].stack + full_stack_size), ")", + " ; size=", full_stack_size); + log_infoln(" -> usable stack is [", + (ptr, coroutine_table[child-1].stack + CR_STACK_GUARD_SIZE), ",", + (ptr, coroutine_table[child-1].stack + full_stack_size - CR_STACK_GUARD_SIZE), ")", + " ; size=", full_stack_size - 2*CR_STACK_GUARD_SIZE); #if CONFIG_COROUTINE_MEASURE_STACK || CONFIG_COROUTINE_PROTECT_STACK - for (size_t i = 0; i < stack_size; i++) + for (size_t i = 0; i < full_stack_size; i++) ((uint8_t *)coroutine_table[child-1].stack)[i] = stack_pattern[i%sizeof(stack_pattern)]; #endif #if CONFIG_COROUTINE_VALGRIND coroutine_table[child-1].stack_id = VALGRIND_STACK_REGISTER( - coroutine_table[child-1].stack + STACK_GUARD_SIZE, - coroutine_table[child-1].stack + stack_size - STACK_GUARD_SIZE); + coroutine_table[child-1].stack + CR_STACK_GUARD_SIZE, + coroutine_table[child-1].stack + full_stack_size - CR_STACK_GUARD_SIZE); #endif + /* 4. env ***************************************************/ coroutine_running = child; - coroutine_table[child-1].state = CR_INITIALIZING; coroutine_cnt++; if (!cr_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 + + full_stack_size - CR_STACK_GUARD_SIZE #else - + STACK_GUARD_SIZE + + CR_STACK_GUARD_SIZE #endif ; - debugf("...stack =%p", coroutine_table[child-1].stack); - debugf("...stack_base=%p", stack_base); + log_debugln("...stack_base=", (ptr, stack_base)); /* run until cr_begin() */ cr_plat_call_with_stack(stack_base, fn, args); assert_notreached("should cr_begin() instead of returning"); @@ -614,19 +634,37 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, * didn't actually check. */ cr_restore_interrupts(true); coroutine_running = parent; + log_infoln(" -> done"); return child; } -cid_t coroutine_add(const char *name, cr_fn_t fn, void *args) { - return coroutine_add_with_stack_size( - CONFIG_COROUTINE_DEFAULT_STACK_SIZE, name, fn, args); +cid_t coroutine_add_with_stack(void *stack, size_t full_stack_size, + const char *name, + cr_fn_t fn, void *args) { + if (name) + log_debugln("coroutine_add_with_stack(", (ptr, stack), full_stack_size, ", ", (qstr, name), ", ", (ptr, fn), ", ", (ptr, args), ")..."); + else + log_debugln("coroutine_add_with_stack(", (ptr, stack), full_stack_size, ", ", (ptr, name), ", ", (ptr, fn), ", ", (ptr, args), ")..."); + + return _coroutine_add(stack, full_stack_size, name, fn, args); +} + +cid_t coroutine_add_with_stack_size(size_t usable_stack_size, + const char *name, + cr_fn_t fn, void *args) { + if (name) + log_debugln("coroutine_add_with_stack_size(", usable_stack_size, ", ", (qstr, name), ", ", (ptr, fn), ", ", (ptr, args), ")..."); + else + log_debugln("coroutine_add_with_stack_size(", usable_stack_size, ", ", (ptr, name), ", ", (ptr, fn), ", ", (ptr, args), ")..."); + + return _coroutine_add(NULL, usable_stack_size + 2*CR_STACK_GUARD_SIZE, name, fn, args); } /* coroutine_main() ***********************************************************/ void coroutine_main(void) { - debugf("coroutine_main()"); + log_debugln("coroutine_main()"); if (!coroutine_initialized) { cr_plat_init(); coroutine_initialized = true; @@ -637,7 +675,7 @@ void coroutine_main(void) { coroutine_running = 0; #if CONFIG_COROUTINE_GDB /* Some pointless call to prevent cr_gdb_readjmp() from - * getting pruned out of the firmware image. */ + * getting pruned out by `ld --gc-sections`. */ if (coroutine_table[0].state != CR_NONE) cr_gdb_readjmp(&coroutine_table[0].env); #endif @@ -660,17 +698,19 @@ void coroutine_main(void) { #if CONFIG_COROUTINE_VALGRIND VALGRIND_STACK_DEREGISTER(coroutine_table[coroutine_running-1].stack_id); #endif - free(coroutine_table[coroutine_running-1].stack); - coroutine_table[coroutine_running-1] = (struct coroutine){0}; + if (coroutine_table[coroutine_running-1].stack_free) + free(coroutine_table[coroutine_running-1].stack); + coroutine_table[coroutine_running-1] = (struct coroutine){}; coroutine_cnt--; } + coroutine_running = 0; cr_restore_interrupts(saved); } /* cr_*() *********************************************************************/ void cr_begin(void) { - debugf("cid=%zu: cr_begin()", coroutine_running); + log_debugln("cid=", coroutine_running, ": cr_begin()"); assert_cid_state(coroutine_running, state == CR_INITIALIZING); bool saved = cr_save_and_disable_interrupts(); @@ -681,7 +721,7 @@ void cr_begin(void) { cr_restore_interrupts(saved); } -static inline void _cr_yield() { +static inline void _cr_yield(void) { cid_t next; while ( !((next = coroutine_ringbuf_pop())) ) { /* No coroutines are runnable, wait for an interrupt @@ -702,7 +742,7 @@ static inline void _cr_yield() { } void cr_yield(void) { - debugf("cid=%zu: cr_yield()", coroutine_running); + log_debugln("cid=", coroutine_running ,": cr_yield()"); assert(!cr_plat_is_in_intrhandler()); assert_cid_state(coroutine_running, state == CR_RUNNING); @@ -714,7 +754,7 @@ void cr_yield(void) { } void cr_pause_and_yield(void) { - debugf("cid=%zu: cr_pause_and_yield()", coroutine_running); + log_debugln("cid=", coroutine_running, ": cr_pause_and_yield()"); assert(!cr_plat_is_in_intrhandler()); assert_cid_state(coroutine_running, state == CR_RUNNING); @@ -725,7 +765,7 @@ void cr_pause_and_yield(void) { } [[noreturn]] void cr_exit(void) { - debugf("cid=%zu: cr_exit()", coroutine_running); + log_debugln("cid=", coroutine_running, ": cr_exit()"); assert(!cr_plat_is_in_intrhandler()); assert_cid_state(coroutine_running, state == CR_RUNNING); @@ -742,7 +782,7 @@ static void _cr_unpause(cid_t cid) { } void cr_unpause(cid_t cid) { - debugf("cr_unpause(%zu)", cid); + log_debugln("cr_unpause(", cid, ")"); assert(!cr_plat_is_in_intrhandler()); assert_cid_state(coroutine_running, state == CR_RUNNING); @@ -752,7 +792,7 @@ void cr_unpause(cid_t cid) { } void cr_unpause_from_intrhandler(cid_t cid) { - debugf("cr_unpause_from_intrhandler(%zu)", cid); + log_debugln("cr_unpause_from_intrhandler(", cid, ")"); assert(cr_plat_is_in_intrhandler()); _cr_unpause(cid); @@ -764,6 +804,7 @@ cid_t cr_getcid(void) { return coroutine_running; } +#ifndef NDEBUG void cr_assert_in_coroutine(void) { assert(!cr_plat_is_in_intrhandler()); assert_cid_state(coroutine_running, state == CR_RUNNING); @@ -772,31 +813,56 @@ void cr_assert_in_coroutine(void) { void cr_assert_in_intrhandler(void) { assert(cr_plat_is_in_intrhandler()); } +#endif -/* cr_cid_info() **************************************************************/ - -#if CONFIG_COROUTINE_MEASURE_STACK +/* answering questions about coroutines ***************************************/ void cr_cid_info(cid_t cid, struct cr_cid_info *ret) { - assert_cid(cid); + assert(cid > 0); + assert(cid <= CONFIG_COROUTINE_NUM); assert(ret); + memset(ret, 0, sizeof(*ret)); + if (coroutine_table[cid-1].state == CR_NONE) + return; + assert_cid(cid); + + /* 1. state *************************************************/ + ret->state = coroutine_table[cid-1].state; + + /* 2. name **************************************************/ + memcpy(ret->name, coroutine_table[cid-1].name, CONFIG_COROUTINE_NAME_LEN); + + /* 3. stack *************************************************/ +#if CONFIG_COROUTINE_MEASURE_STACK + uint8_t *stack = (uint8_t *)coroutine_table[cid-1].stack; + uint8_t *stack_lo = stack + CR_STACK_GUARD_SIZE; + uint8_t *stack_hi = stack + coroutine_table[cid-1].stack_size - CR_STACK_GUARD_SIZE; + /* stack_cap */ - ret->stack_cap = coroutine_table[cid-1].stack_size - 2*STACK_GUARD_SIZE; + ret->stack_cap = stack_hi - stack_lo; /* 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 + CR_STACK_GUARD_SIZE + ret->stack_cap - ret->stack_max #else - ret->stack_max - 1 - STACK_GUARD_SIZE + ret->stack_max - 1 - CR_STACK_GUARD_SIZE #endif ; - if (ret->stack_max == 0 || - stack[i] != stack_pattern[i%sizeof(stack_pattern)]) + if (ret->stack_max == 0) + break; + assert(stack_lo <= &stack[i] && &stack[i] < stack_hi); +#if CONFIG_COROUTINE_VALGRIND + VALGRIND_DISABLE_ERROR_REPORTING; +#endif + uint8_t v = stack[i]; +#if CONFIG_COROUTINE_VALGRIND + VALGRIND_ENABLE_ERROR_REPORTING; +#endif + if (v != stack_pattern[i%sizeof(stack_pattern)]) break; ret->stack_max--; } @@ -810,12 +876,17 @@ void cr_cid_info(cid_t cid, struct cr_cid_info *ret) { else sp = cr_plat_setjmp_get_sp(&coroutine_table[cid-1].env); 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; + uintptr_t sb = (uintptr_t)stack_hi; + ret->stack_cur = sb - sp; #else - ret->stack_cur = sp - (sb + STACK_GUARD_SIZE); + uintptr_t sb = (uintptr_t)stack_lo; + ret->stack_cur = sp - sb; #endif +#endif /* CONFIG_COROUTINE_MEASURE_STACK */ } -#endif /* CONFIG_COROUTINE_MEASURE_STACK */ +const char *coroutine_state_str(enum coroutine_state state) { + assert(state < LM_ARRAY_LEN(coroutine_state_strs)); + return coroutine_state_strs[state]; +} diff --git a/libcr/include/libcr/coroutine.h b/libcr/include/libcr/coroutine.h index eb5828b..f0e6e61 100644 --- a/libcr/include/libcr/coroutine.h +++ b/libcr/include/libcr/coroutine.h @@ -1,6 +1,6 @@ /* libcr/coroutine.h - Simple embeddable coroutine implementation * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -26,8 +26,18 @@ #ifndef _LIBCR_COROUTINE_H_ #define _LIBCR_COROUTINE_H_ -#include <stddef.h> /* for size_t */ -#include <stdbool.h> /* for bool */ +#include <stddef.h> /* for size_t */ + +/* Configuration **************************************************************/ + +#include "config.h" + +#ifndef CONFIG_COROUTINE_MEASURE_STACK + #error config.h must define CONFIG_COROUTINE_MEASURE_STACK (bool) +#endif +#ifndef CONFIG_COROUTINE_NAME_LEN + #error config.h must define CONFIG_COROUTINE_NAME_LEN (non-negative integer) +#endif /* typedefs *******************************************************************/ @@ -73,7 +83,15 @@ typedef void (*cr_fn_t)(void *args); /* managing coroutines ********************************************************/ /** - * Call `fn(args)` in a new coroutine with stack size `stack_size`. + * Call `fn(args)` in a new coroutine with the `full_stack_size`-sized + * block of memory `stack` as the coroutine stack. + * + * There may be CPU-specific requirements on the alignment of the + * `stack` pointer. + * + * If CONFIG_COROUTINE_PROTECT_STACK: The usable stack size will be + * slightly less than `full_stack_size`, in order to make room for a + * stack guard at each end. * * See the doc comment on c_fn_t for the requirements imposed on fn. * @@ -84,13 +102,39 @@ typedef void (*cr_fn_t)(void *args); * Returns the cid of the newly-created coroutine. May return 0 if * there are already COROUTINE_NUM active coroutines. */ -cid_t coroutine_add_with_stack_size(size_t stack_size, const char *name, cr_fn_t fn, void *args); +cid_t coroutine_add_with_stack(void *stack, size_t full_stack_size, const char *name, cr_fn_t fn, void *args); + +/** + * Like coroutine_add_with_stack(), but will use aligned_alloc() to + * allocate a block of memory to use as the stack. + * + * If CONFIG_COROUTINE_PROTECT_STACK: `usable_stack_size` does *not* + * include the size of the stack guard at each end; the amount of + * memory allocated for the stack will be slightly larger than + * `usable_stack_size`. + */ +cid_t coroutine_add_with_stack_size(size_t usable_stack_size, const char *name, cr_fn_t fn, void *args); /** * Like coroutine_add_with_stack_size(), but uses a default stack size so * you don't need to think about it. + * + * Either: + * - define CONFIG_COROUTINE_STACK_SIZE_DEFAULT to use for all + * coroutines; or + * - define/declare CONFIG_COROUTINE_STACK_SIZE_{fn} for each COROUTINE + * function; or + * - define CONFIG_COROUTINE_STACK_PREALLOCATE and then + * define/declare COROUTINE_STACK_{fn} and COROUTINE_STACK_{fn}_len + * for each COROUTINE function. */ -cid_t coroutine_add(const char *name, cr_fn_t fn, void *args); +#if defined(CONFIG_COROUTINE_STACK_PREALLOCATE) +#define coroutine_add(name, fn, args) coroutine_add_with_stack(COROUTINE_STACK_##fn, COROUTINE_STACK_##fn##_len, name, fn, args) +#elif defined(CONFIG_COROUTINE_STACK_SIZE_DEFAULT) +#define coroutine_add(name, fn, args) coroutine_add_with_stack_size(CONFIG_COROUTINE_STACK_SIZE_DEFAULT, name, fn, args) +#else +#define coroutine_add(name, fn, args) coroutine_add_with_stack_size(CONFIG_COROUTINE_STACK_SIZE_##fn, name, fn, args) +#endif /** * The main scheduler loop. Returns if all coroutines exit. @@ -187,13 +231,25 @@ void cr_assert_in_intrhandler(void); /* answering questions about coroutines ***************************************/ -/* While the following are defined here unconditionally, the - * implementations are #if'd on CONFIG_COROUTINE_MEASURE_STACK. */ +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_RUNNABLE, /* not running, but runnable */ + CR_PAUSED, /* not running, and not runnable */ +}; + +const char *coroutine_state_str(enum coroutine_state); struct cr_cid_info { + enum coroutine_state state; + [[gnu::nonstring]] char name[CONFIG_COROUTINE_NAME_LEN]; + +#if CONFIG_COROUTINE_MEASURE_STACK size_t stack_cap; size_t stack_max; size_t stack_cur; +#endif }; void cr_cid_info(cid_t cid, struct cr_cid_info *ret); diff --git a/libcr/tests/test_matrix.c b/libcr/tests/test_matrix.c index 1f23455..eaa4bdc 100644 --- a/libcr/tests/test_matrix.c +++ b/libcr/tests/test_matrix.c @@ -1,6 +1,6 @@ /* libcr/tests/test_matrix.c - Tests for libcr * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -8,14 +8,14 @@ int a = 1; -COROUTINE cr_init(void *) { +COROUTINE init_cr(void *) { cr_begin(); a = 2; cr_end(); } int main() { - coroutine_add("init", cr_init, NULL); + coroutine_add("init", init_cr, NULL); coroutine_main(); if (a != 2) return 1; diff --git a/libcr/tests/test_matrix/config.h b/libcr/tests/test_matrix/config.h index 9802f08..decd6de 100644 --- a/libcr/tests/test_matrix/config.h +++ b/libcr/tests/test_matrix/config.h @@ -1,14 +1,14 @@ -/* config.h - TODO +/* libcr/tests/test_matrix/config.h - Compile-time configuration for libcr test_matrix * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ #ifndef _CONFIG_H_ #define _CONFIG_H_ -#define CONFIG_COROUTINE_DEFAULT_STACK_SIZE (4*1024) +#define CONFIG_COROUTINE_STACK_SIZE_DEFAULT (4*1024) #define CONFIG_COROUTINE_NAME_LEN 16 -#define CONFIG_COROUTINE_NUM 1 +#define CONFIG_COROUTINE_NUM 2 #endif /* _CONFIG_H_ */ |