/* coroutine.h - Simple embeddable coroutine implementation * * Copyright (C) 2024 Luke T. Shumaker * SPDX-Licence-Identifier: AGPL-3.0-or-later */ /** * A coroutine is a cooperatively-multitasked form of threading. * * This coroutine.{h,c} form a lightweight coroutine implementation * for non-multithreaded environments (only one coroutine may be * running at a time (so no m:n scheduling), coroutine_{add,main} may * not be called concurrently with eachother). * * Unlike many other lightweight or embeddable coroutine * implementations, coroutine.{h,c} allow coroutines to use the stack * as normal C functions and do not forbid switch() blocks in * coroutines. * * See also: coroutine_chan.h is a request/response system built on * top of coroutine.{h,c}. */ #ifndef _COROUTINE_H_ #define _COROUTINE_H_ #include /* for size_t */ #include /* for bool */ /* configuration **************************************************************/ #define COROUTINE_DEFAULT_STACK_SIZE (8*1024) /* typedefs *******************************************************************/ /** * A cid_t is a "coroutine ID", used to refer to a coroutine. * * May be reused if a coroutine exits. * * Valid IDs are `1 <= cid <= COROUTINE_NUM`; `0` is used for * "none/invalid". */ typedef size_t cid_t; /** * The root function for a coroutine; a coroutine function should be * declared as * * COROUTINE myfunc(void *_args) { * myargtype *args = args; * cr_begin(); * * ... * * cr_end(); * } * * The function MUST NOT ever return (GCC enforces this); call * cr_exit() or cr_end() instead of returning. * * When creating a coroutine with coroutine_add(), the bit before * cr_begin() runs before coroutine_add() returns; if `_args` points * to a place on another coroutine's stack that may go away, then this * is an opportunity to copy it to this coroutine's stack. Otherwise * you shouldn't do anything else before calling cr_begin(). * Specifically, coroutine_add() and * cr_{yield,pause_and_yield,exit,end}() are explicitly forbidden to * call from within a coroutine before cr_begin() (note that the * cr_chan_*() macros call these functions). */ typedef void (*cr_fn_t)(void *args); #define COROUTINE __attribute__ ((noreturn, no_split_stack)) void /* managing coroutines ********************************************************/ /** * Call `fn(args)` in a new coroutine with stack size `stack_size`. * * See the doc comment on c_fn_t for the requirements imposed on fn. * * May be called from outside any coroutine (before calling * coroutine_main()) or from inside of a coroutine (after it cas * called cr_begin()). * * Returns the cid of the newly-created coroutine. May return 0 if * there are already COROUTINE_NUM active coroutines. */ __attribute__ ((no_split_stack)) cid_t coroutine_add_with_stack_size(size_t stack_size, 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. */ #define coroutine_add(fn, args) coroutine_add_with_stack_size(COROUTINE_DEFAULT_STACK_SIZE, fn, args) /** * The main scheduler loop. * * "Should" never return, but will print a message to stderr and * return if the program has deadlocked and there are no runnable * coroutines. So be sure to call coroutine_add() at least once * before calling this. */ void coroutine_main(void); /* inside of coroutines *******************************************************/ /** cr_begin() goes at the beginning of a coroutine, after it has initialized its stack. */ __attribute__ ((no_split_stack)) bool cr_begin( void); /** cr_exit() terminates the currently-running coroutine. */ __attribute__ ((no_split_stack, noreturn)) void cr_exit(void); /** cr_yield() switches to another coroutine (if there is another runnable coroutine to switch to). */ __attribute__ ((no_split_stack)) void cr_yield(void); /** cr_pause_and_yield() marks the current coroutine as not-runnable and switches to another coroutine. */ __attribute__ ((no_split_stack)) void cr_pause_and_yield(void); /** cr_unpause() marks a coroutine as runnable that has previously marked itself as non-runnable with cr_pause_and_yield(). */ __attribute__ ((no_split_stack)) void cr_unpause(cid_t); /** cr_end() is a counterpart to cr_begin(), but is really just cr_exit(). */ #define cr_end cr_exit /** cr_getcid() returns the cid of the currently-running coroutine. */ cid_t cr_getcid(void); /** * It is not possible for one coroutine to kill another or to mark * another as paused; a coroutine may only do those things to itself. */ #endif /* _COROUTINE_H_ */