- a switchable task
- need manually switch
A pico 32bit RISC-V core
- init/create a coroutine
- for main function, we init as 1st exist coroutine
- for other coroutine, we need to create
- switch context: yield
- exit coroutine
- save context
- find next coroutine to execute
- restore context
- register: all callee saved register in ABI need to save, besides return address
ra
- stack: not save, just use different stack for different coroutine
name | ABI Mnemonic | Meaning | Preserved across calls? |
---|---|---|---|
x0 | zero | zero | - (immutable) |
x1 | ra | return address | no |
x2 | sp | stack pointer | yes |
x3 | gp | global pointer | - (unallocatable) |
x4 | tp | thread pointer | - (unallocatable) |
x5-x7 | t0-t2 | temporary registers | no |
x8-x9 | s0-s1 | callee-saved registers | yes |
x10-x17 | a0-a7 | argument registers | no |
x18-x27 | s2-s11 | callee-saved registers | yes |
x28-x31 | t3-t6 | temporary registers | no |
global variable
typedef struct _nc_ctx_t {
uintptr_t ra;
uintptr_t sp;
uintptr_t s[12];
} nc_ctx_t;
- as naive implementation, chain all coroutine with linked list, form a ring
- merged into
struct _nc_ctx_t
- define a global variable, point to current context:
_curr
typedef struct _nc_ctx_t {
struct _nc_ctx_t *next;
uintptr_t ra;
uintptr_t sp;
uintptr_t s[12];
} nc_ctx_t;
nc_ctx_t *_curr;
- restore
ra
,sp
&s[12]
from new_curr
- jump to address
ra
, with instructionret
- alternative: restore
_curr->ra
to other reg, then jump
- alternative: restore
nc_yield
- naked function
- need some special handling, see next sections
- define a static context:
_main_ctx
- init
_main_ctx
, point_main_ctx.next
to self - set init value of
_curr
static nc_ctx_t _main_ctx = {
.next = &_main_ctx;
};
nc_ctx_t *_curr = &_main_ctx;
func
: function pointer as main body of new coroutinectx
: context variable, a pointer global/static variablestack_top
: pointer to stack top
- caller: view of
nc_yield
,- which called from other coroutine to switch to
ctx
- as if a previous
nc_yield
is called just before the beginning offunc
- which called from other coroutine to switch to
- callee: view of
func
- which jump from
nc_yield
called by previous coroutine - as regular call to
func
by caller function
- which jump from
review the last step of nc_yield
- set
sp
of context tostack_top
- set
ra
of context tofunc
- add this context as next element of coroutine list
ra
need special handle, see next section
ctx->sp = (uintptr_t)stack_top;
ctx->ra = (uintptr_t)func;
ctx->next = _curr->next;
_curr->next = ctx;
- jump to initial value of
ra
at beginning of coroutine function - which point to beginning of coroutine function
- old
ra
for target address when exit fromnc_yield
- new
ra
for actual value thatra
register should restore - setup new
ra
when initial context, to specify where to jump when coroutine exit - set old
ra
to the beginning of coroutine function
typedef struct _nc_ctx_t {
struct _nc_ctx_t *next;
uintptr_t ra_old;
uintptr_t sp_old;
uintptr_t s_old[12];
uintptr_t ra_new;
} nc_ctx_t;
- set finalize function to
ra_new
when create coroutine - remove current coroutine context from linked list
- file:./src/nc.h
- file:./src/nc.c
file:./run_c_snippet_on_picorv32
file:./test/t1.c
file:./test/t2.c