coro.c (4695B)
1 /* 2 * lib/coro/coro.c -- asymmetric coroutine layer for <kit/coro.h>. 3 * 4 * Sits on top of the per-arch __kit_coro_switch / __kit_coro_ctx_init 5 * primitives (one of lib/coro/<arch>.c) and supplies the public 6 * coro_init / coro_resume / coro_yield / coro_self surface. 7 * 8 * Layout of coro_t.__kit_priv: 9 * 10 * offset 0: coro_ctx ctx 11 * offset 256: coro_t *resumer 12 * offset 256 + sizeof(void*): coro_fn user_fn 13 * 14 * Total = 256 + 2 * sizeof(void*) bytes (272 LP64 / 264 ILP32). The 15 * header reserves 288 -- comfortable headroom either way. The 16 * _Static_assert below pins the fit. 17 * 18 * Resume chain. coro_resume saves the previous "current coroutine" 19 * pointer (NULL means "no coroutine; main thread") into the resumed 20 * coroutine's resumer slot, switches in, and on return flips the 21 * pointer back. coro_yield reads its own resumer slot and switches 22 * there. The result is a stack of resumers; resumes nest like calls. 23 * 24 * Per-thread scheduler state. __kit_current and __kit_main_ctx 25 * are _Thread_local: each thread that drives coroutines gets its own 26 * resume chain and its own "main" save slot. kit's contract still 27 * defines __STDC_NO_THREADS__ (no <threads.h>), but _Thread_local is 28 * a C11 language feature independent of that library, so this is 29 * fine -- on hosted targets it Just Works, and bare-metal toolchains 30 * that don't link a TLS runtime fall through to per-image storage, 31 * which collapses to single-thread semantics. 32 * 33 * "Main" thread context. coro_resume needs a coro_ctx to save the 34 * caller's regs into; if the caller is itself a coroutine we use its 35 * ctx, otherwise the per-thread __kit_main_ctx. The "main" slot is 36 * only ever touched on the resume/yield boundary -- it lives outside 37 * any coroutine's lifecycle. 38 */ 39 40 #include <kit/coro.h> 41 #include <stddef.h> 42 #include <stdint.h> 43 44 typedef struct { 45 coro_ctx ctx; 46 coro_t* resumer; 47 coro_fn user_fn; 48 } __kit_coro_priv_t; 49 50 _Static_assert(sizeof(__kit_coro_priv_t) <= sizeof(((coro_t*)0)->__kit_priv), 51 "priv blob fits in coro_t reservation"); 52 _Static_assert(_Alignof(__kit_coro_priv_t) <= _Alignof(coro_t), 53 "priv blob alignment fits coro_t"); 54 55 /* Per-arch primitives (declared here, defined in lib/coro/<arch>.c). */ 56 extern uintptr_t __kit_coro_switch(coro_ctx* from, coro_ctx* to, 57 uintptr_t value); 58 extern void __kit_coro_ctx_init(coro_ctx* ctx, void* stack_base, 59 size_t stack_len, void (*entry)(uintptr_t)); 60 61 /* Per-thread scheduler state. */ 62 static _Thread_local coro_t* __kit_current = NULL; 63 static _Thread_local coro_ctx __kit_main_ctx; 64 65 static inline __kit_coro_priv_t* __priv(coro_t* c) { 66 return (__kit_coro_priv_t*)c->__kit_priv; 67 } 68 static inline coro_ctx* __ctx_of(coro_t* c) { return &__priv(c)->ctx; } 69 static inline coro_ctx* __resumer_ctx(coro_t* c) { 70 coro_t* r = __priv(c)->resumer; 71 return r ? __ctx_of(r) : &__kit_main_ctx; 72 } 73 74 /* Trampoline-side thunk. Each per-arch trampoline calls this with the 75 uintptr_t delivered by the first __kit_coro_switch into the fresh 76 context. The thunk dispatches to user_fn, then performs the 77 "DEAD + switch back to resumer" handoff so the symmetric primitive 78 doesn't need to know about coro_t lifecycle. */ 79 static void __kit_coro_thunk(uintptr_t value) { 80 coro_t* self = __kit_current; 81 uintptr_t retval = __priv(self)->user_fn(value); 82 83 self->status = CORO_DEAD; 84 __kit_coro_switch(__ctx_of(self), __resumer_ctx(self), retval); 85 __builtin_unreachable(); 86 } 87 88 void coro_init(coro_t* c, coro_fn fn, void* stack_base, size_t stack_len) { 89 __kit_coro_priv_t* p = __priv(c); 90 c->status = CORO_INIT; 91 p->resumer = NULL; 92 p->user_fn = fn; 93 __kit_coro_ctx_init(&p->ctx, stack_base, stack_len, __kit_coro_thunk); 94 } 95 96 coro_result_t coro_resume(coro_t* c, uintptr_t value) { 97 coro_t* prev = __kit_current; 98 coro_ctx* prev_ctx = prev ? __ctx_of(prev) : &__kit_main_ctx; 99 100 __priv(c)->resumer = prev; 101 __kit_current = c; 102 c->status = CORO_RUNNING; 103 104 uintptr_t v = __kit_coro_switch(prev_ctx, __ctx_of(c), value); 105 106 /* c either yielded (status set to CORO_SUSPENDED by coro_yield) 107 or finished (status set to CORO_DEAD by the thunk). */ 108 __kit_current = prev; 109 return (coro_result_t){.value = v, .status = c->status}; 110 } 111 112 uintptr_t coro_yield(uintptr_t value) { 113 coro_t* self = __kit_current; 114 self->status = CORO_SUSPENDED; 115 /* When the resumer next coro_resumes us, it sets status back to 116 CORO_RUNNING before the switch -- so our caller sees RUNNING. */ 117 return __kit_coro_switch(__ctx_of(self), __resumer_ctx(self), value); 118 } 119 120 coro_t* coro_self(void) { return __kit_current; }