Skip to content
Jesse Busman edited this page Jan 19, 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.

  • Coroutines can safely use return; statements

  • Can be used as non-preemptive multithreading, step-wise function execution, function 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.

Disadvantages

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

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 uint32 (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 uint32 yieldValue Coroutine* Finish execution of the current coroutine. Same as: return yieldValue; The yieldValue will be the final return value of CoRun. After calling CoReturn, the CoHasReturned function will return true.
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.
Clone this wiki locally