ambonvik
Joined 9 months ago
Joined 9 months ago
Compared to the coroutine implementations I do know, none of them quite met what I as looking for. The «trampoline» has been mentioned. I also needed a calling convention that fit the higher-level process model with a self pointer and a context, and a signal return value from each context switch. It also has to be thread safe to survive the pthreads. Not very difficult to do, but needs to be designed in from the beginning.
Same thing with random number generators. It will not work very well to keep state between calls in a static local variable or some global construction, needs to be kept thread local somewhere. Not difficult, but needs to be there from the start both for the basic generator and for the distributions on top of it.
Quite a bit more here: https://cimba.readthedocs.io/en/latest/background.html#
Not familiar with the details of Intel CET, but this is basically just implementing what others call fibers or "green threads", so any such special handling should certainly be possible if necessary.
https://github.com/ambonvik/cimba/blob/main/src/port/x86-64/...
https://github.com/ambonvik/cimba/blob/main/src/port/x86-64/...
Any ideas for how to speed up the context switches would be welcome, of course.
Others have done this elsewhere, of course. There are links/references to several other examples in the code. I mention two in particular in the NOTICE file, not because I copied their code, but because I read it very closely and followed the outline of their examples. It would probably taken me forever to figure out the Windows TIB on my own.
What I think is pretty cool (biased as I am) in my implementation is the «trampoline» that launches the coroutine function and waits silently in case it returns. If it does, it is intercepted and the proper coroutine exit() function gets called.
What did not quite fit for my purpose was having the asymmetric coroutines as the fundamental layer, meaning that the dispatcher also is tied to that layer. Building the next level dispatcher on top of that is probably possible, but seemed a bit complicated.
I instead started with symmetric coroutines as the fundamental building block and built the dispatcher at the next (process) layer where it can deal with the event queue to determine who goes next. The process level resume() call is always made from some event executed by the dispatcher on the main stack, making the processes asymmetric coroutines even if the lowest level coroutines are symmetric with transfer() calls.
Making asymmetric coroutines from symmetric ones is easy, just yield(void) { transfer(parent); } and resume(target) { transfer(target); } in slightly abbreviated code, while going the other way around requires an extra context switch via the dispatcher on the way there.