- Fast by using custom assembly per platform: switching coroutines is just a few memory/register swaps (e.g. on amd64: 9 registers)
- Minimal: about 300 lines of C and under 100 lines of assembly per platform
- replace your state machine with something more readable
- replace your event loop callbacks with a more natural thread-like control flow
- lazy streams and generators
- agent systems (e.g. AI in games)
- cooperative threading in general (see
test1.c
for an example)
- ELF Targets, 32 or 64-bit, running on x86, ARM (e.g. Linux, BSD's), RISC-V 32/64 and PPC64
- Windows 32 or 64-bit x86
- MacOS 64bit x86 and Apple Silicon (special thanks to @rsmmr for Apple Silicon support!)
Tested on the following machines:
- ArchLinux: clang/gcc: 32 and 64-bit builds
- FreeBSD 11: clang: 32 and 64-bit builds
- Windows x86: MSVC: 32 and 64-bit builds
- Windows x86: MinGW: 64-bit builds
- RaspberryPI (armv6): gcc builds
- RaspberryPI 3 (aarch64): gcc/clang builds
- MacOS 10.14: clang 64-bit builds
- Its possible to use very small stacks, but it is not recommended, since signal handlers or linkers might run code on your coroutine stack at unexpected times, better use a more conservative stack size of e.g 32kb.
- Moving fibers from one OS thread to another is problematic: (caching of thread locals and platform specific use of registers)
- If you use C++ exceptions: The top stack frame of each coroutine should catch and rethrow all exceptions in a toplevel fiber (this also applies to Windows SEH)
- Interleaving setjmp/longjmp with fiber switching is not allowed
- Debuggers are confused by the switching of stacks, in particular backtraces only show the frames of the currently active fiber.
git clone https://github.com/simonfxr/fiber
git submodule update --init
mkdir build
cd build
cmake ..
make
Or integrate it in your cmake build
add_subdirectory(path/to/fiber)
target_link_libraries(your-target fiber::fiber)
#include <fiber/fiber.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
Fiber *caller;
Fiber *self;
const char *str;
} Args;
/* invoked if coroutine is called after it finished */
void
guard(Fiber *self, void *null)
{
(void) self;
(void) null;
abort();
}
/* entry point */
void
coroutine(void *args0)
{
Args *args = (Args *) args0;
printf("From coroutine: %s\n", args->str);
fiber_switch(args->self, args->caller);
}
int
main()
{
Fiber main_fiber;
fiber_init_toplevel(&main_fiber);
Fiber crt;
/* allocate a stack of 16kb, additionally add an unmapped page to detect overflows */
fiber_alloc(&crt, 1024 * 16, guard, NULL, FIBER_FLAG_GUARD_LO);
Args args;
args.caller = &main_fiber;
args.self = &crt;
args.str = "Hello";
/* push a new return stack frame, defines the entry point of our coroutine */
fiber_push_return(&crt, coroutine, &args, sizeof args);
/* arguments are copied into the coroutine, its safe to destroy them here */
memset(&args, 0, sizeof args);
/* run our coroutine */
fiber_switch(&main_fiber, &crt);
fiber_destroy(&crt);
/* main_fiber does not have to be destroyed */
return 0;
}