summaryrefslogtreecommitdiff
path: root/libcr/coroutine.c
diff options
context:
space:
mode:
Diffstat (limited to 'libcr/coroutine.c')
-rw-r--r--libcr/coroutine.c520
1 files changed, 296 insertions, 224 deletions
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 */