Skip to content
Jesse Busman edited this page May 15, 2019 · 7 revisions

Advantages

  • Fast, only does what it really needs to do

  • Coroutines can start other coroutines

  • Interrupt routines can start coroutines

  • Caller can pass data to coroutine, and coroutine can pass data back to caller.

  • Can be used as non-preemptive multithreading, step-by-step function execution, functional programming, or however you want to use it.

  • Sub-coroutine can yield value towards its parent coroutine's caller, instead of to its parent coroutine. This is not recommended because it's pretty much the definition of spaghetti code. Almost no other coroutine implementations have this option available.

Disadvantages

  • Requires your system to have a malloc function that takes an int and returns a pointer.

  • Only tested on ARMv7 Thumb-2

  • You must manually configure stack size when starting a new coroutine

  • Coroutines cannot safely use return; statements, they must use return co; (where co is the Coroutine*) or call the CoReturn function.

Speed

CoInit runs in 16 instructions + 1 call to malloc

CoRun and CoYield run in 9 instructions.

CoReturn runs in 6 instructions.

Usage

  1. Place a link to your system's malloc function in coroutines_asm.s. Detailed instructions are in that file's comments.

  2. Compile coroutines.c and coroutines_asm.s together with your project.

  3. #include "coroutines.h" where you want to use coroutines.

The 4 functions implemented in ASM (CoInit, CoYield, CoRun and CoReturn) will preserve the caller's r4-r14. The caller must preserve his own r0-r3. (this is standard ARM calling convention, most likely your compiler already does this)

Functions

Name Argument 0 Argument 1 Return value Description
CoInit Coroutine* (funcPtr)(uint32 runValue, Coroutine co)); uint32 stackSizeBytes Coroutine* Allocates memory for and initializes a new coroutine. Returns a pointer to the allocated Coroutine. It is your responsibility to make sure you allocate enough stack space for your coroutine function and its function calls.
CoRun uint32 runValue Coroutine* uint32 yieldValue Start or resume execution of a coroutine. If this is the first call to CoRun, the runValue will be the first argument to the coroutine function. If this is not the first call to CoRun, the runValue will be the return value of CoYield. You may only call CoRun more than once if CoHasReturned returns false.
CoYield uint32 yieldValue Coroutine* uint32 runValue Pause execution of the current coroutine and resume execution of the CoRun caller. The yieldValue will be the return value of CoRun.
CoReturn Coroutine* Finish execution of the current coroutine. Same as: return co; After calling CoReturn, the CoHasReturned function will return true, and the return value of CoRun should not be used.
CoHasReturned Coroutine* bool Returns true if the given Coroutine* has called CoReturn(..) Returns true if the given Coroutine* has executed a return; statement Returns false otherwise If CoHasReturned(..) returns true, you may call CoRun(..) on that coroutine. If CoHasReturned(..) returns false, you may not call CoRun(..) on that coroutine and you should call CoEnd(..)
CoEnd Coroutine* Finalizes and de-allocates a coroutine that has returned.
CoKill Coroutine* Same as CoEnd, but also works if the Coroutine* has not returned yet. This is useful if: - You don't need any more output from the coroutine, but the coroutine has not returned. - Your coroutine is in an infinite loop. CoKill should not be used if your coroutine still owns allocated memory, other coroutines or other resources.

Memory layout

Main stack:
____________________
|  saved           | <----\
|  r4-r12, lr, pc  |       |
|------------------|       |
| coroutine caller |       |         Coroutine object:
| local variables  |       |       ____________________
| Coroutine* gen;  | ------|--->   |  coroutine SP    | -----\
|                  |       |       |------------------|      |
|                  |        \ -----|   caller SP      |      |
                                   |------------------|      |
                                   |     unused       |      |
                                   |    coroutine     |      |
                                   |   stack space    |      |
                                   |------------------|      |
                                   |  saved           |  <---/
                                   |  r4-r12, lr pc   |
                                   |------------------|
                                   | coroutine local  |
                                   |    variables     |
                                   |__________________|
Clone this wiki locally