├── .gitignore ├── examples ├── .gitignore ├── counter.c ├── counter.c3 ├── counter.cpp ├── counter.jai ├── lexer.c └── echo.c3 ├── thumbnail.png ├── coroutine.c3 ├── LICENSE ├── Makefile ├── README.md ├── coroutine.h └── coroutine.c /.gitignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | .build/ -------------------------------------------------------------------------------- /thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/coroutines/HEAD/thumbnail.png -------------------------------------------------------------------------------- /examples/counter.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "coroutine.h" 7 | 8 | void counter(void *arg) 9 | { 10 | long int n = (long int)arg; 11 | for (int i = 0; i < n; ++i) { 12 | printf("[%zu] %d\n", coroutine_id(), i); 13 | coroutine_yield(); 14 | } 15 | } 16 | 17 | int main() 18 | { 19 | coroutine_init(); 20 | coroutine_go(&counter, (void*)5); 21 | coroutine_go(&counter, (void*)10); 22 | while (coroutine_alive() > 1) coroutine_yield(); 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /examples/counter.c3: -------------------------------------------------------------------------------- 1 | // Almost the same as counter.c but in C3 2 | module counter_c3; 3 | import coroutine; 4 | import std::io; 5 | 6 | fn void counter(void* arg) { 7 | usz n = (usz)arg; 8 | for (usz i = 0; i < n; ++i) { 9 | io::printfn("[%d] %d", coroutine::id(), i); 10 | coroutine::yield(); 11 | } 12 | } 13 | 14 | fn void main() { 15 | coroutine::init(); 16 | coroutine::go(fn void(void *arg) { 17 | io::printfn("[%d] Hello from C3 Lambda", coroutine::id()); 18 | }); 19 | coroutine::go(&counter, (void*)5); 20 | coroutine::go(&counter, (void*)10); 21 | while (coroutine::alive() > 1) coroutine::yield(); 22 | } 23 | -------------------------------------------------------------------------------- /coroutine.c3: -------------------------------------------------------------------------------- 1 | // coroutine bindings for C3 (see coroutine.h for the API documentation) 2 | module coroutine; 3 | import std::net; 4 | 5 | def CoroutineFn = fn void(void*); 6 | 7 | extern fn void sleep_read(int fd) @extern("coroutine_sleep_read"); 8 | extern fn void sleep_write(int fd) @extern("coroutine_sleep_write"); 9 | extern fn void wake_up(usz id) @extern("coroutine_wake_up"); 10 | extern fn void init() @extern("coroutine_init"); 11 | extern fn void finish() @extern("coroutine_finish"); 12 | extern fn void yield() @extern("coroutine_yield"); 13 | extern fn void go(CoroutineFn f, void* arg = null) @extern("coroutine_go"); 14 | extern fn usz id() @extern("coroutine_id"); 15 | extern fn usz alive() @extern("coroutine_alive"); 16 | -------------------------------------------------------------------------------- /examples/counter.cpp: -------------------------------------------------------------------------------- 1 | // Almost the same as counter.c but in C++ 2 | #include 3 | #include "coroutine.h" 4 | 5 | void counter(void *arg) 6 | { 7 | size_t n = reinterpret_cast(arg); 8 | for (size_t i = 0; i < n; ++i) { 9 | std::cout << "[" << coroutine_id() << "] " << i << std::endl; 10 | coroutine_yield(); 11 | } 12 | } 13 | 14 | int main() 15 | { 16 | coroutine_init(); 17 | coroutine_go([](void*) { 18 | std::cout << "[" << coroutine_id() << "] Hello from C++ Lambda" << std::endl; 19 | }, nullptr); 20 | coroutine_go(counter, reinterpret_cast(5)); 21 | coroutine_go(counter, reinterpret_cast(10)); 22 | while (coroutine_alive() > 1) coroutine_yield(); 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2025 Alexey Kutepov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build/counter: examples/counter.c coroutine.h build/coroutine.a 2 | gcc -I. -Wall -Wextra -ggdb -o build/counter examples/counter.c build/coroutine.a 3 | 4 | .PHONY: examples 5 | examples: build/counter build/counter_cpp build/counter_c3 build/counter_jai build/echo build/lexer 6 | 7 | build/echo: examples/echo.c3 coroutine.c3 build/coroutine.a 8 | c3c compile -l build/coroutine.a -o build/echo examples/echo.c3 coroutine.c3 9 | 10 | build/counter_cpp: examples/counter.cpp coroutine.h build/coroutine.a 11 | g++ -I. -Wall -Wextra -ggdb -o build/counter_cpp examples/counter.cpp build/coroutine.a 12 | 13 | build/counter_c3: examples/counter.c3 coroutine.c3 build/coroutine.a 14 | c3c compile -l build/coroutine.a -o build/counter_c3 examples/counter.c3 coroutine.c3 15 | 16 | build/counter_jai: examples/counter.jai build/coroutine.a build/coroutine.so 17 | jai-linux examples/counter.jai 18 | 19 | build/lexer: examples/lexer.c coroutine.h build/coroutine.a 20 | gcc -I. -Wall -Wextra -ggdb -o build/lexer examples/lexer.c build/coroutine.a 21 | 22 | build/coroutine.so: coroutine.c 23 | mkdir -p build 24 | gcc -Wall -Wextra -ggdb -shared -fPIC -o build/coroutine.so coroutine.c 25 | 26 | build/coroutine.a: build/coroutine.o 27 | ar -rcs build/coroutine.a build/coroutine.o 28 | 29 | build/coroutine.o: coroutine.c coroutine.h 30 | mkdir -p build 31 | gcc -Wall -Wextra -ggdb -c -o build/coroutine.o coroutine.c 32 | -------------------------------------------------------------------------------- /examples/counter.jai: -------------------------------------------------------------------------------- 1 | // Almost the same as counter.c but in Jai 2 | #import "Basic"; 3 | 4 | coroutine :: #library "../build/coroutine"; 5 | coroutine_sleep_read :: (fd: int) #foreign coroutine; 6 | coroutine_sleep_write :: (fd: int) #foreign coroutine; 7 | coroutine_wake_up :: (id: int) #foreign coroutine; 8 | coroutine_init :: () #foreign coroutine; 9 | coroutine_yield :: () #foreign coroutine; 10 | coroutine_go :: (f: (*void) #c_call, arg: *void = null) #foreign coroutine; 11 | coroutine_id :: () -> int #foreign coroutine; 12 | coroutine_alive :: () -> int #foreign coroutine; 13 | 14 | counter :: (arg: *void) #c_call { 15 | push_context { 16 | n := cast(int) arg; 17 | for 0..n-1 { 18 | print("[%] %\n", coroutine_id(), it); 19 | coroutine_yield(); 20 | } 21 | } 22 | } 23 | 24 | main :: () { 25 | coroutine_init(); 26 | coroutine_go((arg: *void) #c_call { 27 | push_context { 28 | print("[%] Hello from Jai Lambda\n", coroutine_id()); 29 | } 30 | }); 31 | coroutine_go(counter, cast(*void) 5); 32 | coroutine_go(counter, cast(*void) 10); 33 | while coroutine_alive() > 1 { 34 | coroutine_yield(); 35 | } 36 | } 37 | 38 | #run { 39 | #import "Compiler"; 40 | set_build_options_dc(.{ 41 | output_path = "../build/", 42 | output_executable_name = "counter_jai", 43 | }); 44 | 45 | coroutine_init(); 46 | coroutine_go((arg: *void) #c_call { 47 | push_context { 48 | for 0..5 { 49 | print("[%] You can actually use\n", coroutine_id()); 50 | coroutine_yield(); 51 | } 52 | } 53 | }); 54 | coroutine_go((arg: *void) #c_call { 55 | push_context { 56 | for 0..5 { 57 | print("[%] coroutines at compile-time\n", coroutine_id()); 58 | coroutine_yield(); 59 | } 60 | } 61 | }); 62 | while coroutine_alive() > 1 { 63 | coroutine_yield(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Coroutines 2 | 3 | > [!WARNING] 4 | > The library is not in production ready state yet 5 | 6 | Custom coroutines implementation in GNU C. 7 | 8 | ## What is a Coroutine? 9 | 10 | 11 | 12 | Coroutine is a lightweight user space thread with its own stack that can suspend its execution and switch to another coroutine on demand. Coroutines do not run in parallel but rather cooperatively switch between each other whenever they feel like it. 13 | 14 | Coroutines are useful in cases when all your program does majority of the time is waiting on IO. So with coroutines you have an opportunity to switch the context and go do something else. It is not useful to split up heavy CPU computations because they all going to be executed on a single thread. Use proper threads for that (pthreads on POSIX). 15 | 16 | Good use cases for coroutines are usually Network Applications and UI. Anything with a slow Async IO. 17 | 18 | 19 | 20 | See [coroutine.h](./coroutine.h) for more info. See [./examples/counter.c](./examples/counter.c) for a simple usage example in C. 21 | 22 | To build the example: 23 | 24 | ```console 25 | $ make 26 | $ ./build/counter 27 | ``` 28 | 29 | There are actually much more examples in the [./examples/](./examples/) in a variety of languages. To build all of them do: 30 | 31 | ```console 32 | $ make examples 33 | ``` 34 | 35 | Make sure you have all the corresponding compilers for the languages. 36 | 37 | ## Supported platforms 38 | 39 | - Linux x86_64 40 | 41 | *More are planned in the future* 42 | 43 | ## Special Thanks 44 | 45 | This library is heavily inspired by [libmill](https://libmill.org/) and [libdill](https://libdill.org/). 46 | 47 | ## Screencast 48 | 49 | The initial implementation was done on a livestream 50 | 51 | [![thumbnail](./thumbnail.png)](https://www.youtube.com/watch?v=sYSP_elDdZw) 52 | -------------------------------------------------------------------------------- /examples/lexer.c: -------------------------------------------------------------------------------- 1 | // Lexer is a classical example of usecase for coroutines. 2 | // This is a *very* simple and basic lexer that 3 | // can lex single digit integers, + and -. 4 | // The example would be better if we could return values 5 | // when we yield (kind of like a generator). But it is what it is. 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | typedef enum { 14 | TK_INT, 15 | TK_OP, 16 | TK_EOF 17 | } TokenKind; 18 | 19 | typedef union { 20 | char tk_op; 21 | int tk_int; 22 | } TokenValue; 23 | 24 | TokenKind token_kind = TK_EOF; 25 | TokenValue token_value = {0}; 26 | 27 | void lex(void* input_void) { 28 | if (input_void == NULL) return; 29 | 30 | const char* input = input_void; 31 | 32 | while(true) { 33 | switch(*input) { 34 | // Numba 35 | case '0': case '1': case '2': case '3': case '4': 36 | case '5': case '6': case '7': case '8': case '9': { 37 | token_kind = TK_INT; 38 | token_value.tk_int = *input - '0'; 39 | } break; 40 | 41 | // Operators 42 | case '*': case '+': case '-': { 43 | token_kind = TK_OP; 44 | token_value.tk_op = *input; 45 | } break; 46 | 47 | default: { 48 | token_kind = TK_EOF; 49 | return; 50 | } 51 | } 52 | input++; 53 | 54 | // For every token we consume, we yield control back to the caller (a parser, I guess). 55 | coroutine_yield(); 56 | } 57 | } 58 | 59 | int main(int argc, char* argv[]){ 60 | if (argc != 2) { 61 | printf("Usage: %s \n", argv[0]); 62 | return 1; 63 | } 64 | 65 | coroutine_init(); 66 | coroutine_go(lex, argv[1]); 67 | 68 | // Consume those tokens 69 | bool quit = false; 70 | while(!quit && coroutine_alive() > 1){ 71 | // Yield control to the lexer. 72 | // It will lex and yield control back to here. 73 | coroutine_yield(); 74 | switch(token_kind){ 75 | case TK_INT: { printf("TK_INT: %d\n", token_value.tk_int); } break; 76 | case TK_OP: { printf("TK_OP: %c\n", token_value.tk_op); } break; 77 | default: { printf("Done!\n"); quit = true; } break; 78 | } 79 | } 80 | 81 | return 0; 82 | } 83 | -------------------------------------------------------------------------------- /examples/echo.c3: -------------------------------------------------------------------------------- 1 | module echo; 2 | 3 | import std::io; 4 | import std::net; 5 | import coroutine; 6 | 7 | bool quit = false; 8 | usz server_id = 0; 9 | 10 | fn void main() { 11 | coroutine::init(); 12 | 13 | server_id = coroutine::id(); 14 | 15 | const String HOST = "localhost"; 16 | const uint PORT = 6969; 17 | TcpServerSocket server = tcp::listen(HOST, PORT, 69, REUSEADDR)!!; 18 | server.sock.set_non_blocking(true)!!; 19 | 20 | io::printfn("[%d] Server listening to %s:%d", coroutine::id(), HOST, PORT); 21 | while SERVER: (true) { 22 | coroutine::sleep_read(server.sock); 23 | if (quit) break SERVER; 24 | TcpSocket client = tcp::accept(&server)!!; 25 | client.sock.set_non_blocking(true)!!; 26 | coroutine::go(fn void(void *arg) { 27 | io::printfn("[%d] Client connected!", coroutine::id()); 28 | 29 | TcpSocket* client = (TcpSocket*)arg; 30 | char[] buf = mem::new_array(char, 1024); 31 | defer { 32 | client.close()!!; 33 | free(client); 34 | free(buf.ptr); 35 | } 36 | 37 | while OUTER: (true) { 38 | coroutine::sleep_read(client.sock); 39 | usz n = client.read(buf)!!; 40 | if (n == 0) break OUTER; 41 | char[] chunk = buf[0:n]; 42 | 43 | switch (((String)chunk).trim()) { 44 | case "quit": 45 | io::printfn("[%d] Client requested to quit", coroutine::id(), chunk.len); 46 | return; 47 | case "shutdown": 48 | io::printfn("[%d] Client requested to shutdown the server", coroutine::id()); 49 | quit = true; 50 | coroutine::wake_up(server_id); 51 | return; 52 | } 53 | 54 | io::printfn("[%d] Client sent %d bytes", coroutine::id(), chunk.len); 55 | 56 | while (chunk.len > 0) { 57 | coroutine::sleep_write(client.sock); 58 | usz m = client.write(chunk)!!; 59 | if (m == 0) break OUTER; 60 | chunk = chunk[m..]; 61 | } 62 | } 63 | io::printfn("[%d] Client disconnected", coroutine::id()); 64 | }, @clone(client)); 65 | } 66 | io::printfn("[%d] Server has been shutdown", coroutine::id()); 67 | } 68 | -------------------------------------------------------------------------------- /coroutine.h: -------------------------------------------------------------------------------- 1 | #ifndef COROUTINE_H_ 2 | #define COROUTINE_H_ 3 | 4 | // # What is a Coroutine? 5 | // 6 | // Coroutine is a lightweight user space thread with its own stack that can 7 | // suspend its execution and switch to another coroutine (see coroutine_yield() 8 | // function). Coroutines do not run in parallel but rather cooperatively switch 9 | // between each other whenever they feel like it. 10 | // 11 | // Coroutines are useful in cases when all your program does majority of the 12 | // time is waiting on IO. So with coroutines you have an opportunity to do 13 | // coroutine_yield() and go do something else. It is not useful to split up 14 | // heavy CPU computations because they all going to be executed on a single 15 | // thread. Use proper threads for that (pthreads on POSIX). 16 | // 17 | // Good use cases for coroutines are usually Network Applications and UI. 18 | // Anything with a slow Async IO. 19 | // 20 | // # How does it work? 21 | // 22 | // Each coroutine has its own separate call stack. Every time a new coroutine is 23 | // created with coroutine_go() a new call stack is allocated in dynamic memory. 24 | // The library manages a global array of coroutine stacks and switches between 25 | // them (on x86_64 literally swaps out the value of the RSP register) on every 26 | // coroutine_yield(), coroutine_sleep_read(), or coroutine_sleep_write(). 27 | 28 | #ifdef __cplusplus 29 | extern "C" { 30 | #endif // __cplusplus 31 | 32 | // TODO: consider making coroutine.h an stb-style single header library 33 | 34 | // Initialize the coroutine runtime. Must be called before using any other 35 | // functions of this API. After the initialization the currently running code is 36 | // considered the main coroutine with the id = 0. Should not be called twice. 37 | // TODO: Allow calling it twice, 'cause why not?! 38 | void coroutine_init(void); 39 | 40 | // Switch to the next coroutine. The execution will continue starting from 41 | // coroutine_yield() (or any other flavor of it like coroutine_sleep_read() or 42 | // coroutine_sleep_write) call of another coroutine. 43 | void coroutine_yield(void); 44 | 45 | // Create a new coroutine. This function does not automatically switch to the 46 | // new coroutine, but continues executing in the current coroutine. The 47 | // execution of the new coroutine will start as soon as the scheduler gets to it 48 | // handling the chains of coroutine_yield()-s. 49 | void coroutine_go(void (*f)(void*), void *arg); 50 | 51 | // The id of the current coroutine. 52 | size_t coroutine_id(void); 53 | 54 | // How many coroutines are currently alive. Could be used by the main coroutine 55 | // to wait until all the "child" coroutines have died. It may also continue from 56 | // the call of coroutine_sleep_read() and coroutine_sleep_write() if the 57 | // corresponding coroutine was woken up. 58 | size_t coroutine_alive(void); 59 | 60 | // Put the current coroutine to sleep until the non-blocking socket `fd` has 61 | // avaliable data to read. Trying to read from fd after coroutine_sleep_read() 62 | // may still cause EAGAIN, if the coroutine was woken up by coroutine_wake_up 63 | // before the socket became available for reading. You may treat this function 64 | // as a flavor of coroutine_yield(). 65 | void coroutine_sleep_read(int fd); 66 | 67 | // Put the current coroutine to sleep until the non-blocking socket `fd` is 68 | // ready to accept data to write. Trying to write to fd after 69 | // coroutine_sleep_write() may still cause EAGAIN, if the coroutine was woken up 70 | // by coroutine_wake_up before the socket became available for writing. You may 71 | // treat this function as a flavor of coroutine_yield(). 72 | void coroutine_sleep_write(int fd); 73 | 74 | // Wake up coroutine by id if it is currently sleeping due to 75 | // coroutine_sleep_read() or coroutine_sleep_write() calls. 76 | void coroutine_wake_up(size_t id); 77 | 78 | // TODO: implement sleeping by timeout 79 | // TODO: add timeouts to coroutine_sleep_read() and coroutine_sleep_write() 80 | 81 | #ifdef __cplusplus 82 | } 83 | #endif // __cplusplus 84 | 85 | #endif // COROUTINE_H_ 86 | -------------------------------------------------------------------------------- /coroutine.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "coroutine.h" 12 | 13 | // TODO: make the STACK_CAPACITY customizable by the user 14 | //#define STACK_CAPACITY (4*1024) 15 | #define STACK_CAPACITY (1024*getpagesize()) 16 | 17 | // Initial capacity of a dynamic array 18 | #ifndef DA_INIT_CAP 19 | #define DA_INIT_CAP 256 20 | #endif 21 | 22 | // Append an item to a dynamic array 23 | #define da_append(da, item) \ 24 | do { \ 25 | if ((da)->count >= (da)->capacity) { \ 26 | (da)->capacity = (da)->capacity == 0 ? DA_INIT_CAP : (da)->capacity*2; \ 27 | (da)->items = realloc((da)->items, (da)->capacity*sizeof(*(da)->items)); \ 28 | assert((da)->items != NULL && "Buy more RAM lol"); \ 29 | } \ 30 | \ 31 | (da)->items[(da)->count++] = (item); \ 32 | } while (0) 33 | 34 | #define da_remove_unordered(da, i) \ 35 | do { \ 36 | size_t j = (i); \ 37 | assert(j < (da)->count); \ 38 | (da)->items[j] = (da)->items[--(da)->count]; \ 39 | } while(0) 40 | 41 | #define UNUSED(x) (void)(x) 42 | #define TODO(message) do { fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); abort(); } while(0) 43 | #define UNREACHABLE(message) do { fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); abort(); } while(0) 44 | 45 | typedef struct { 46 | void *rsp; 47 | void *stack_base; 48 | } Context; 49 | 50 | typedef struct { 51 | Context *items; 52 | size_t count; 53 | size_t capacity; 54 | } Contexts; 55 | 56 | typedef struct { 57 | size_t *items; 58 | size_t count; 59 | size_t capacity; 60 | } Indices; 61 | 62 | typedef struct { 63 | struct pollfd *items; 64 | size_t count; 65 | size_t capacity; 66 | } Polls; 67 | 68 | // TODO: coroutines library probably does not work well in multithreaded environment 69 | static size_t current = 0; 70 | static Indices active = {0}; 71 | static Indices dead = {0}; 72 | static Contexts contexts = {0}; 73 | static Indices asleep = {0}; 74 | static Polls polls = {0}; 75 | 76 | // TODO: ARM support 77 | // Requires modifications in all the @arch places 78 | 79 | typedef enum { 80 | SM_NONE = 0, 81 | SM_READ, 82 | SM_WRITE, 83 | } Sleep_Mode; 84 | 85 | // Linux x86_64 call convention 86 | // %rdi, %rsi, %rdx, %rcx, %r8, and %r9 87 | 88 | void __attribute__((naked)) coroutine_yield(void) 89 | { 90 | // @arch 91 | asm( 92 | " pushq %rdi\n" 93 | " pushq %rbp\n" 94 | " pushq %rbx\n" 95 | " pushq %r12\n" 96 | " pushq %r13\n" 97 | " pushq %r14\n" 98 | " pushq %r15\n" 99 | " movq %rsp, %rdi\n" // rsp 100 | " movq $0, %rsi\n" // sm = SM_NONE 101 | " jmp coroutine_switch_context\n"); 102 | } 103 | 104 | void __attribute__((naked)) coroutine_sleep_read(int fd) 105 | { 106 | (void) fd; 107 | // @arch 108 | asm( 109 | " pushq %rdi\n" 110 | " pushq %rbp\n" 111 | " pushq %rbx\n" 112 | " pushq %r12\n" 113 | " pushq %r13\n" 114 | " pushq %r14\n" 115 | " pushq %r15\n" 116 | " movq %rdi, %rdx\n" // fd 117 | " movq %rsp, %rdi\n" // rsp 118 | " movq $1, %rsi\n" // sm = SM_READ 119 | " jmp coroutine_switch_context\n"); 120 | } 121 | 122 | void __attribute__((naked)) coroutine_sleep_write(int fd) 123 | { 124 | (void) fd; 125 | // @arch 126 | asm( 127 | " pushq %rdi\n" 128 | " pushq %rbp\n" 129 | " pushq %rbx\n" 130 | " pushq %r12\n" 131 | " pushq %r13\n" 132 | " pushq %r14\n" 133 | " pushq %r15\n" 134 | " movq %rdi, %rdx\n" // fd 135 | " movq %rsp, %rdi\n" // rsp 136 | " movq $2, %rsi\n" // sm = SM_WRITE 137 | " jmp coroutine_switch_context\n"); 138 | } 139 | 140 | void __attribute__((naked)) coroutine_restore_context(void *rsp) 141 | { 142 | // @arch 143 | (void)rsp; 144 | asm( 145 | " movq %rdi, %rsp\n" 146 | " popq %r15\n" 147 | " popq %r14\n" 148 | " popq %r13\n" 149 | " popq %r12\n" 150 | " popq %rbx\n" 151 | " popq %rbp\n" 152 | " popq %rdi\n" 153 | " ret\n"); 154 | } 155 | 156 | void coroutine_switch_context(void *rsp, Sleep_Mode sm, int fd) 157 | { 158 | contexts.items[active.items[current]].rsp = rsp; 159 | 160 | switch (sm) { 161 | case SM_NONE: current += 1; break; 162 | case SM_READ: { 163 | da_append(&asleep, active.items[current]); 164 | struct pollfd pfd = {.fd = fd, .events = POLLRDNORM,}; 165 | da_append(&polls, pfd); 166 | da_remove_unordered(&active, current); 167 | } break; 168 | 169 | case SM_WRITE: { 170 | da_append(&asleep, active.items[current]); 171 | struct pollfd pfd = {.fd = fd, .events = POLLWRNORM,}; 172 | da_append(&polls, pfd); 173 | da_remove_unordered(&active, current); 174 | } break; 175 | 176 | default: UNREACHABLE("coroutine_switch_context"); 177 | } 178 | 179 | if (polls.count > 0) { 180 | int timeout = active.count == 0 ? -1 : 0; 181 | int result = poll(polls.items, polls.count, timeout); 182 | if (result < 0) TODO("poll"); 183 | 184 | for (size_t i = 0; i < polls.count;) { 185 | if (polls.items[i].revents) { 186 | size_t id = asleep.items[i]; 187 | da_remove_unordered(&polls, i); 188 | da_remove_unordered(&asleep, i); 189 | da_append(&active, id); 190 | } else { 191 | ++i; 192 | } 193 | } 194 | } 195 | 196 | assert(active.count > 0); 197 | current %= active.count; 198 | coroutine_restore_context(contexts.items[active.items[current]].rsp); 199 | } 200 | 201 | // TODO: think how to get rid of coroutine_init() call at all 202 | void coroutine_init(void) 203 | { 204 | if (contexts.count != 0) return; 205 | da_append(&contexts, (Context){0}); 206 | da_append(&active, 0); 207 | } 208 | 209 | void coroutine__finish_current(void) 210 | { 211 | if (active.items[current] == 0) { 212 | UNREACHABLE("Main Coroutine with id == 0 should never reach this place"); 213 | } 214 | 215 | da_append(&dead, active.items[current]); 216 | da_remove_unordered(&active, current); 217 | 218 | if (polls.count > 0) { 219 | int timeout = active.count == 0 ? -1 : 0; 220 | int result = poll(polls.items, polls.count, timeout); 221 | if (result < 0) TODO("poll"); 222 | 223 | for (size_t i = 0; i < polls.count;) { 224 | if (polls.items[i].revents) { 225 | size_t id = asleep.items[i]; 226 | da_remove_unordered(&polls, i); 227 | da_remove_unordered(&asleep, i); 228 | da_append(&active, id); 229 | } else { 230 | ++i; 231 | } 232 | } 233 | } 234 | 235 | assert(active.count > 0); 236 | current %= active.count; 237 | coroutine_restore_context(contexts.items[active.items[current]].rsp); 238 | } 239 | 240 | void coroutine_go(void (*f)(void*), void *arg) 241 | { 242 | size_t id; 243 | if (dead.count > 0) { 244 | id = dead.items[--dead.count]; 245 | } else { 246 | da_append(&contexts, ((Context){0})); 247 | id = contexts.count-1; 248 | contexts.items[id].stack_base = mmap(NULL, STACK_CAPACITY, PROT_WRITE|PROT_READ, MAP_PRIVATE|MAP_STACK|MAP_ANONYMOUS|MAP_GROWSDOWN, -1, 0); 249 | assert(contexts.items[id].stack_base != MAP_FAILED); 250 | } 251 | 252 | void **rsp = (void**)((char*)contexts.items[id].stack_base + STACK_CAPACITY); 253 | // @arch 254 | *(--rsp) = coroutine__finish_current; 255 | *(--rsp) = f; 256 | *(--rsp) = arg; // push rdi 257 | *(--rsp) = 0; // push rbx 258 | *(--rsp) = 0; // push rbp 259 | *(--rsp) = 0; // push r12 260 | *(--rsp) = 0; // push r13 261 | *(--rsp) = 0; // push r14 262 | *(--rsp) = 0; // push r15 263 | contexts.items[id].rsp = rsp; 264 | 265 | da_append(&active, id); 266 | } 267 | 268 | size_t coroutine_id(void) 269 | { 270 | return active.items[current]; 271 | } 272 | 273 | size_t coroutine_alive(void) 274 | { 275 | return active.count; 276 | } 277 | 278 | void coroutine_wake_up(size_t id) 279 | { 280 | // @speed coroutine_wake_up is linear 281 | for (size_t i = 0; i < asleep.count; ++i) { 282 | if (asleep.items[i] == id) { 283 | da_remove_unordered(&asleep, id); 284 | da_remove_unordered(&polls, id); 285 | da_append(&active, id); 286 | return; 287 | } 288 | } 289 | } 290 | --------------------------------------------------------------------------------