I love Go language because of it's simplicity AND the goroutine. I always thinking of having similar thing in C or C3 language.
D language provides a very lightweight simple-minded coroutine, in the name of Fiber, very clean API. Recently I also get interested in C3 language. A modern successor of C language, cleaner, safer, stronger. So I set out to implement Fiber for C3 language.
There are 4 different way of implementing Fiber coroutine. (1) Calling assembly code for context switching, (2) Using ucontext library on POSIX systems, (3) Using sigsetjmp/siglongjmp on POSIX, (4) Using native Fiber on windows.
Fiber coroutine using Assembly
Calling assembly code is extremely lightweight, but it is not very easy to maintain assembly codes for all architectures. For my case, I decided to cover only X86_64 and AARCH64, because they are most frequent platforms.
Another issue with C3/inline assembly is that C3's assembly coverage is limited and it's not easy to express required inline assembly code. Luckily enough I found a workaround. Converting the assembly code into a hex array of binary data, and then call that memory area as like a normal function call. Following is an example switching function of X86_64 architecture.
static const uint8_t FIBER_ASM_AMD64[] = {
/* --- save prev (rsi) --- */
0x48, 0x89, 0x26, /* mov [rsi], rsp */
0x48, 0x8b, 0x27, /* mov rsp, [rdi] */
0x58, /* pop rax */ /* entrypoint → rax */
0x48, 0x89, 0x6e, 0x08, /* mov [rsi+ 8], rbp */
0x48, 0x89, 0x5e, 0x10, /* mov [rsi+16], rbx */
0x4c, 0x89, 0x66, 0x18, /* mov [rsi+24], r12 */
0x4c, 0x89, 0x6e, 0x20, /* mov [rsi+32], r13 */
0x4c, 0x89, 0x76, 0x28, /* mov [rsi+40], r14 */
0x4c, 0x89, 0x7e, 0x30, /* mov [rsi+48], r15 */
/* --- restore new (rdi) --- */
0x48, 0x8b, 0x6f, 0x08, /* mov rbp, [rdi+ 8] */
0x48, 0x8b, 0x5f, 0x10, /* mov rbx, [rdi+16] */
0x4c, 0x8b, 0x67, 0x18, /* mov r12, [rdi+24] */
0x4c, 0x8b, 0x6f, 0x20, /* mov r13, [rdi+32] */
0x4c, 0x8b, 0x77, 0x28, /* mov r14, [rdi+40] */
0x4c, 0x8b, 0x7f, 0x30, /* mov r15, [rdi+48] */
0xff, 0xe0, /* jmp rax */
};
For some security reason, you have to align that assembly memory area properly, and protect that area.
mprotect((void*)base, (size_t)span, PROT_READ | PROT_EXEC);
Fiber coroutine using ucontext
ucontext was a POSIX standard of context switching, very easy to handle. (getcontext(), setcontext(), makecontext(), swapcontext()) For some reason POSIX removed ucontext from standard but it is still available there, because it is widely used. glibc and other libc supports it. Other than X86_64 and AARCH64, I used this for posix systems.
Fiber coroutine using sigsetjmp/siglongjmp
This is also a viable way of implementing Fiber coroutine.
Windows Fiber
Windows has a native Fiber library.
How to determine stack size?
Fiber* fiber_create(stack_size, coroutin_fn);
You have to pre-determine the size of the stack. It must be at least 64 KB, and Windows Fiber defaults to be 1MB.
Canary and stack_used()
In the debugging stage, we can prepend and postpend CANARY string to the heap allocated stack, and if stack overflows or underflows, the CANARY string gets broken and we can check it from time to time.
For the stack buffer, we can fill 'Z' for the whole buffer and see how much of them are overwritten.
According to that, we can see stack_used(fiber) and determine appropriate stack size.
API functions
import ext::thread::fiber;
alias Coroutine = fn void();
Fiber* fib = fiber::create(usz stack_size, Coroutine coro); // stack_size must be larger than 64KB, this cannot be called in other coroutine
Fiber* fib = fiber::active(); // get current fiber
void fiber::switch_to(fib); // context switch
void fiber::yield(); // within a coroutine, suspend and goto main coroutine
void fiber::done(); // you need to call this at the end of your coroutine function
void fiber::delete(fib);
void fiber::set_debug(false); // default is true
void fiber::set_allocator(Allocator allocx); // default is mem
fn usz fiber::stack_used(); // in bytes, you can call this to determine proper stack size, not available on Windows
For more information, visit Fiber in C3
Top comments (0)