diff options
author | Luke T. Shumaker <lukeshu@lukeshu.com> | 2024-09-30 08:00:05 -0600 |
---|---|---|
committer | Luke T. Shumaker <lukeshu@lukeshu.com> | 2024-09-30 08:00:05 -0600 |
commit | 2ec9929e7bab7c87d3851e932baf0c1146981657 (patch) | |
tree | a5e33c22074ec66f1a4a4c164ee5cc2e09750966 /libcr | |
parent | cd0b080d638005bff762f1f8cae9a6c76648265b (diff) |
wip
Diffstat (limited to 'libcr')
-rw-r--r-- | libcr/coroutine.c | 262 |
1 files changed, 156 insertions, 106 deletions
diff --git a/libcr/coroutine.c b/libcr/coroutine.c index 74c74d8..b3dccff 100644 --- a/libcr/coroutine.c +++ b/libcr/coroutine.c @@ -10,13 +10,6 @@ #include <stdio.h> /* for printf(), fprintf(), stderr */ #include <stdlib.h> /* for aligned_alloc(), free() */ -#if __x86_64__ -# include <unistd.h> /* for pause() */ -#elif __arm__ -# include "hardware/sync.h" /* for __wfi(); */ -# define pause() __wfi() -#endif - #include <libcr/coroutine.h> /* Configuration **************************************************************/ @@ -45,15 +38,14 @@ /* * Portability notes: * - * - It uses GCC `__attribute__`s, though these can likely be easily - * swapped for other compilers. + * - It uses GCC `__attribute__`s, and the GNUC ({ ... }) statement exprs + etension. * - * - It has a small bit of CPU-specific code (assembly, and definition - * of a STACK_GROWS_DOWNWARD={0,1} macro) in - * coroutine.c:call_with_stack(). Other than this, it should be - * portable to other CPUs. It currently contains implementations - * for __x86_64__ and __arm__, and should be fairly easy to add - * implementations for other CPUs. + * - 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 uses setjmp()/longjmp() in "unsafe" ways. POSIX-2017 * longjmp(3) says @@ -119,19 +111,27 @@ * 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() +#endif + /* 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_PRE_RUNNABLE, /* running, between cr_unpause_from_intrhandler() and cr_pause() */ CR_RUNNABLE, /* not running, but runnable */ CR_PAUSED, /* not running, and not runnable */ }; struct coroutine { volatile enum coroutine_state state; - volatile bool intr_unpause; jmp_buf env; size_t stack_size; void *stack; @@ -143,16 +143,18 @@ const char *coroutine_state_strs[] = { [CR_NONE] = "CR_NONE", [CR_INITIALIZING] = "CR_INITIALIZING", [CR_RUNNING] = "CR_RUNNING", + [CR_PRE_RUNNABLE] = "CR_PRE_RUNNABLE", [CR_RUNNABLE] = "CR_RUNNABLE", [CR_PAUSED] = "CR_PAUSED", }; -#define STACK_ALIGNMENT ({ __attribute__((aligned)) void fn(void) {}; __alignof__(fn); }) - #if CONFIG_COROUTINE_MEASURE_STACK || CONFIG_COROUTINE_PROTECT_STACK /* We just need a pattern that is unlikely to occur naturaly; this is * just a few bytes that I read from /dev/random. */ -static const uint8_t stack_pattern[] = {0x1e, 0x15, 0x16, 0x0a, 0xcc, 0x52, 0x7e, 0xb7}; +static const uint8_t stack_pattern[] = { + 0xa1, 0x31, 0xe6, 0x07, 0x1f, 0x61, 0x20, 0x32, + 0x4b, 0x14, 0xc4, 0xe0, 0xea, 0x62, 0x25, 0x63, +}; #endif /* global variables ***********************************************************/ @@ -226,56 +228,113 @@ static inline void assert_cid(cid_t cid) { #endif } -#define assert_cid_state(cid, opstate) do { \ - assert_cid(cid); \ - assert(coroutine_table[(cid)-1].state opstate); \ +#define assert_cid_state(cid, expr) do { \ + assert_cid(cid); \ + cid_t state = coroutine_table[(cid)-1].state; \ + assert(expr); \ } while (0) -/* call_with_stack() **********************************************************/ +/* platform support ***********************************************************/ -static void call_with_stack(void *stack, cr_fn_t fn, void *args) { - static void *saved_sp = NULL; +/* 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" + ); - /* As part of sbc-harness, this only really needs to support - * ARM-32, but being able to run it on x86-64 is useful for - * debugging. */ -#if __x86_64__ -#define STACK_GROWS_DOWNWARD 1 - 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" - ); -#elif __arm__ -#define STACK_GROWS_DOWNWARD 1 - /* 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" - ); #else -# error unsupported architecture + #error unsupported platform + #endif +#if 0 } +#endif + /* coroutine_add() ************************************************************/ cid_t coroutine_add_with_stack_size(size_t stack_size, cr_fn_t fn, void *args) { @@ -283,7 +342,7 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, cr_fn_t fn, void *args) { cid_t parent = coroutine_running; if (parent) - assert_cid_state(parent, == CR_RUNNING); + 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); @@ -304,7 +363,7 @@ 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(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)]; @@ -313,23 +372,23 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, cr_fn_t fn, void *args) { 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 + (STACK_GROWS_DOWNWARD ? stack_size : 0); + void *stack_base = coroutine_table[child-1].stack + (CR_PLAT_STACK_GROWS_DOWNWARD ? stack_size : 0); #if CONFIG_COROUTINE_PROTECT_STACK -# if STACK_GROWS_DOWNWARD - stack_base -= round_up(sizeof(stack_pattern), STACK_ALIGNMENT); +# 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), STACK_ALIGNMENT); + stack_base += round_up(sizeof(stack_pattern), CR_PLAT_STACK_ALIGNMENT); # endif #endif debugf("...stack =%p\n", coroutine_table[child-1].stack); debugf("...stack_base=%p\n", stack_base); /* run until cr_begin() */ - call_with_stack(stack_base, fn, args); + cr_plat_call_with_stack(stack_base, fn, args); assert(false); /* should cr_begin() instead of returning */ } - assert_cid_state(child, == CR_RUNNABLE); + assert_cid_state(child, state == CR_RUNNABLE); if (parent) - assert_cid_state(parent, == CR_RUNNING); + assert_cid_state(parent, state == CR_RUNNING); coroutine_running = parent; return child; @@ -349,13 +408,15 @@ struct stack_stats { }; 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), STACK_ALIGNMENT) : 0); + 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 (;;) { - size_t i = STACK_GROWS_DOWNWARD - ? (CONFIG_COROUTINE_PROTECT_STACK ? round_up(sizeof(stack_pattern), STACK_ALIGNMENT) : 0) + ret->cap - ret->max - : ret->max - 1 - (CONFIG_COROUTINE_PROTECT_STACK ? round_up(sizeof(stack_pattern), STACK_ALIGNMENT) : 0); +#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--; @@ -387,7 +448,7 @@ void coroutine_main(void) { coroutine_table[coroutine_running-1].state = CR_RUNNING; longjmp(coroutine_table[coroutine_running-1].env, 1); /* jump to point=c */ } - assert_cid_state(coroutine_running, == CR_NONE); + assert_cid_state(coroutine_running, state == CR_NONE); #if CONFIG_COROUTINE_MEASURE_STACK struct stack_stats sizes; measure_stack(coroutine_running, &sizes); @@ -401,21 +462,14 @@ void coroutine_main(void) { /* cr_*() *********************************************************************/ void cr_begin(void) { - assert_cid_state(coroutine_running, == CR_INITIALIZING); + 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 */ } -static inline void _cr_transition(enum coroutine_state state) { - debugf("cid=%zu: transition %s->%s\n", - coroutine_running, - coroutine_state_str(coroutine_table[coroutine_running-1].state), - coroutine_state_str(state)); - - coroutine_table[coroutine_running-1].state = state; - +static inline void _yield() { cid_t next; for (;;) { next = next_coroutine(); @@ -425,9 +479,9 @@ static inline void _cr_transition(enum coroutine_state state) { debugf("cid=%zu: no other runnable coroutines, not yielding\n", coroutine_running); coroutine_table[coroutine_running-1].state = CR_RUNNING; return; - } else { - debugf("cid=%zu: no runnable coroutines, sleeping\n", coroutine_running); - pause(); + } else { // XXX + debugf("cid=%zu: no runnable coroutines, sleeping\n", coroutine_running); // XXX + wait_for_interrupt(); // XXX } } if (!setjmp(coroutine_table[coroutine_running-1].env)) { /* point=c2 */ @@ -438,21 +492,19 @@ static inline void _cr_transition(enum coroutine_state state) { } void cr_yield(void) { - assert_cid_state(coroutine_running, == CR_RUNNING); - _cr_transition(CR_RUNNABLE); + assert_cid_state(coroutine_running, state == CR_RUNNING); + coroutine_table[coroutine_running-1].state = CR_RUNNABLE; + _yield(); } void cr_pause_and_yield(void) { - assert_cid_state(coroutine_running, == CR_RUNNING); - if (coroutine_table[coroutine_running-1].intr_unpause) - _cr_transition(CR_RUNNABLE); - else - _cr_transition(CR_PAUSED); - coroutine_table[coroutine_running-1].intr_unpause = false; + assert_cid_state(coroutine_running, state == CR_RUNNING || state == CR_PRE_RUNNABLE); + coroutine_table[coroutine_running-1].state = CR_PAUSED; + _yield(); } void cr_exit(void) { - assert_cid_state(coroutine_running, == CR_RUNNING); + assert_cid_state(coroutine_running, state == CR_RUNNING); debugf("cid=%zu: exit\n", coroutine_running); coroutine_table[coroutine_running-1].state = CR_NONE; @@ -460,30 +512,28 @@ void cr_exit(void) { } void cr_unpause(cid_t cid) { - assert_cid_state(cid, == CR_PAUSED); + assert_cid_state(cid, state == CR_PAUSED); debugf("cr_unpause(%zu)\n", cid); - coroutine_table[cid-1].intr_unpause = false; - coroutine_table[cid-1].state = CR_RUNNABLE; + coroutine_table[cid-1].state = CR_RUNNABLE; } void cr_unpause_from_intrhandler(cid_t cid) { - assert_cid(cid); + assert_cid_state(cid, state == CR_RUNNING || state == CR_PAUSED); debugf("cr_unpause_from_intrhandler(%zu)\n", cid); switch (coroutine_table[cid-1].state) { case CR_RUNNING: + assert(cid == coroutine_running); debugf("... raced, deferring unpause\n"); - coroutine_table[cid-1].intr_unpause = true; + coroutine_table[cid-1].state = CR_PRE_RUNNABLE; break; case CR_PAUSED: debugf("... actual unpause\n"); - coroutine_table[cid-1].intr_unpause = false; - coroutine_table[cid-1].state = CR_RUNNABLE; + coroutine_table[cid-1].state = CR_RUNNABLE; break; default: - assertf(false, "cid=%zu state=%s\n", - cid, coroutine_state_str(coroutine_table[cid-1].state)); + assert(false); } } |