summaryrefslogtreecommitdiff
path: root/libcr
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2024-09-30 08:00:05 -0600
committerLuke T. Shumaker <lukeshu@lukeshu.com>2024-09-30 08:00:05 -0600
commit2ec9929e7bab7c87d3851e932baf0c1146981657 (patch)
treea5e33c22074ec66f1a4a4c164ee5cc2e09750966 /libcr
parentcd0b080d638005bff762f1f8cae9a6c76648265b (diff)
wip
Diffstat (limited to 'libcr')
-rw-r--r--libcr/coroutine.c262
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);
}
}