https://github.com/tidwall/llco
A low-level coroutine library for C
https://github.com/tidwall/llco
Last synced: 9 months ago
JSON representation
A low-level coroutine library for C
- Host: GitHub
- URL: https://github.com/tidwall/llco
- Owner: tidwall
- License: other
- Created: 2024-01-09T14:20:45.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2024-07-25T14:55:37.000Z (over 1 year ago)
- Last Synced: 2025-04-05T03:11:48.648Z (9 months ago)
- Language: C
- Size: 141 KB
- Stars: 64
- Watchers: 3
- Forks: 3
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# llco
A low-level coroutine library for C.
The main purpose of this project is to power
[sco](https://github.com/tidwall/sco) and
[neco](https://github.com/tidwall/neco), which are more general purpose
coroutine libraries.
## Features
- Stackful coroutines.
- No allocations (bring your own stack)
- Cross-platform. Linux, Mac, Webassembly, iOS, Android, FreeBSD, Windows, RaspPi, RISC-V
- Fast context switching. Uses assembly in most cases
- No built-in scheduler. You are in charge of the coroutine priority
- Single file amalgamation. No dependencies.
## Example
```C
void cleanup(void *stack, size_t stack_size, void *udata) {
// Free the coroutine stack
free(stack);
}
void entry(void *udata) {
printf("Coroutine started\n");
// Switch back to the main thread and cleanup this coroutine
llco_switch(0, true);
}
int main(void) {
// Start a coroutine from the main function using an newly allocated stack.
struct llco_desc desc = {
.stack = malloc(LLCO_MINSTACKSIZE),
.stack_size = LLCO_MINSTACKSIZE,
.entry = entry,
.cleanup = cleanup,
};
llco_start(&desc, false);
printf("Back to main\n");
}
```
## API
```C
// Switch to another coroutine. Set the `co` param to NULL to switch to the
// main function. Use the final param to tell the program that you are done
// with the current coroutine, at which point it's respective `cleanup`
// callback will be called.
void llco_switch(struct llco *co, bool final);
// Start a new coroutine. This can be called from the main function or a
// nested coroutine.
void llco_start(struct llco_desc *desc, bool final);
// Return the current coroutine or NULL if not currently running in a
// coroutine.
struct llco *llco_current(void);
// Returns a string that indicates which coroutine method is being used by
// the program. Such as "asm" or "ucontext", etc.
const char *llco_method(void *caps);
```
## Caveats
- Windows: Only x86_64 is supported at this time. The Windows Fibers API is not
currently suitable as a fallback due to the `CreateFiber` call needing to
allocate memory dynamically.
- Webassembly: Must be compiled with Emscripten using the `-sASYNCIFY` flag.
- All other platforms may fallback to using ucontext when the assembly method
is not available. The `uco_method(0)` function can be used to see if assembly
or ucontext is being used.
- The ucontext fallback method only uses the ucontext API when starting a
coroutine. Once the coroutine has been started, `setjmp` and `longjmp` take
over the switching duties.
## Compiler Options
- `-DLLCO_NOASM`: Disable assembly. Use ucontext fallback instead.
- `-DLLCO_STACKJMP`: Use `setjmp` and `longjmp` for jumping between stacks,
even with the assembly method.
- `-DLLCO_VALGRIND`: Enable support for Valgrind
- `-DLLCO_NOUNWIND`: Disable support for stack unwinding
## Running test
```sh
# Basic test
cc llco.c test.c && ./a.out
# Using Valgrind
cc -DLLCO_VALGRIND llco.c test.c && valgrind ./a.out
# Using Emscripten (Web Assembly)
emcc -sASYNCIFY llco.c test.c && node ./a.out.js
```
## Acknowledgements
Much of the assembly code was adapted from the [minicoro](https://github.com/edubart/minicoro)
project by [Eduardo Bart](https://github.com/edubart), which was originally
adapted from the [Lua Coco](https://coco.luajit.org) project by
[Mike Pall](https://github.com/MikePall).
## License
Public Domain or MIT No Attribution, your choice.