diff --git a/Makefile b/Makefile index bac3cff..9ccf5f3 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ +cc ?= gcc + build/counter: examples/counter.c coroutine.h build/coroutine.a - gcc -I. -Wall -Wextra -ggdb -o build/counter examples/counter.c build/coroutine.a + $(cc) -I. -Wall -Wextra -ggdb -o build/counter examples/counter.c build/coroutine.a .PHONY: examples examples: build/counter build/counter_cpp build/counter_c3 build/counter_jai build/echo build/lexer @@ -17,15 +19,15 @@ build/counter_jai: examples/counter.jai build/coroutine.a build/coroutine.so jai-linux examples/counter.jai build/lexer: examples/lexer.c coroutine.h build/coroutine.a - gcc -I. -Wall -Wextra -ggdb -o build/lexer examples/lexer.c build/coroutine.a + $(cc) -I. -Wall -Wextra -ggdb -o build/lexer examples/lexer.c build/coroutine.a build/coroutine.so: coroutine.c mkdir -p build - gcc -Wall -Wextra -ggdb -shared -fPIC -o build/coroutine.so coroutine.c + $(cc) -Wall -Wextra -ggdb -shared -fPIC -o build/coroutine.so coroutine.c build/coroutine.a: build/coroutine.o ar -rcs build/coroutine.a build/coroutine.o build/coroutine.o: coroutine.c coroutine.h mkdir -p build - gcc -Wall -Wextra -ggdb -c -o build/coroutine.o coroutine.c + $(cc) -Wall -Wextra -ggdb -c -o build/coroutine.o coroutine.c diff --git a/README.md b/README.md index 882c8b4..728955f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ > [!WARNING] > The library is not in production ready state yet -Custom coroutines implementation in GNU C. +Custom coroutines implementation in C. ## What is a Coroutine? @@ -26,6 +26,13 @@ $ make $ ./build/counter ``` +For Windows you need to use clang + +```console +$ make cc=clang +$ ./build/counter.exe +``` + There are actually much more examples in the [./examples/](./examples/) in a variety of languages. To build all of them do: ```console @@ -36,7 +43,8 @@ Make sure you have all the corresponding compilers for the languages. ## Supported platforms -- Linux x86_64 +- Linux x86_64 +- Windows x86_64 (tested for Msys clang) *More are planned in the future* diff --git a/coroutine.c b/coroutine.c index f28d249..c449ac3 100644 --- a/coroutine.c +++ b/coroutine.c @@ -4,12 +4,26 @@ #include #include +#if _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else #include #include #include +#endif #include "coroutine.h" + +#if _WIN32 +int getpagesize() { + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwPageSize; +} +#endif + // TODO: make the STACK_CAPACITY customizable by the user //#define STACK_CAPACITY (4*1024) #define STACK_CAPACITY (1024*getpagesize()) @@ -60,7 +74,11 @@ typedef struct { } Indices; typedef struct { +#if _WIN32 + char *items; // not supported yet +#else struct pollfd *items; +#endif size_t count; size_t capacity; } Polls; @@ -82,12 +100,33 @@ typedef enum { SM_WRITE, } Sleep_Mode; -// Linux x86_64 call convention +// Linux x86_64 calling convention // %rdi, %rsi, %rdx, %rcx, %r8, and %r9 +// https://learn.microsoft.com/en-us/cpp/build/x64-software-conventions?view=msvc-170#x64-register-usage +// Windows x64 calling convention: RCX, RDX, R8, R9 +// Windows x64 ABI considers registers RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-XMM15 nonvolatile. +// They must be saved and restored by a function that uses them. + void __attribute__((naked)) coroutine_yield(void) { // @arch +#if _WIN32 + asm( + " pushq %rcx\n" + " pushq %rbx\n" + " pushq %rbp\n" + " pushq %rdi\n" + " pushq %rsi\n" + " pushq %r12\n" + " pushq %r13\n" + " pushq %r14\n" + " pushq %r15\n" + // TODO: push XMM6-XMM15 + " movq %rsp, %rcx\n" // rsp + " movq $0, %rdx\n" // sm = SM_READ + " jmp coroutine_switch_context\n"); +#else asm( " pushq %rdi\n" " pushq %rbp\n" @@ -99,12 +138,32 @@ void __attribute__((naked)) coroutine_yield(void) " movq %rsp, %rdi\n" // rsp " movq $0, %rsi\n" // sm = SM_NONE " jmp coroutine_switch_context\n"); +#endif } void __attribute__((naked)) coroutine_sleep_read(int fd) { +#if !defined(__clang__) (void) fd; +#endif // @arch +#if _WIN32 + asm( + " pushq %rcx\n" + " pushq %rbx\n" + " pushq %rbp\n" + " pushq %rdi\n" + " pushq %rsi\n" + " pushq %r12\n" + " pushq %r13\n" + " pushq %r14\n" + " pushq %r15\n" + // TODO: push XMM6-XMM15 + " movq %rcx, %r8\n" // fd + " movq %rsp, %rcx\n" // rsp + " movq $1, %rdx\n" // sm = SM_READ + " jmp coroutine_switch_context\n"); +#else asm( " pushq %rdi\n" " pushq %rbp\n" @@ -117,12 +176,32 @@ void __attribute__((naked)) coroutine_sleep_read(int fd) " movq %rsp, %rdi\n" // rsp " movq $1, %rsi\n" // sm = SM_READ " jmp coroutine_switch_context\n"); +#endif } void __attribute__((naked)) coroutine_sleep_write(int fd) { +#if !defined(__clang__) (void) fd; +#endif // @arch +#if _WIN32 + asm( + " pushq %rcx\n" + " pushq %rbx\n" + " pushq %rbp\n" + " pushq %rdi\n" + " pushq %rsi\n" + " pushq %r12\n" + " pushq %r13\n" + " pushq %r14\n" + " pushq %r15\n" + // TODO: push XMM6-XMM15 + " movq %rcx, %r8\n" // fd + " movq %rsp, %rcx\n" // rsp + " movq $2, %rdx\n" // sm = SM_READ + " jmp coroutine_switch_context\n"); +#else asm( " pushq %rdi\n" " pushq %rbp\n" @@ -135,12 +214,30 @@ void __attribute__((naked)) coroutine_sleep_write(int fd) " movq %rsp, %rdi\n" // rsp " movq $2, %rsi\n" // sm = SM_WRITE " jmp coroutine_switch_context\n"); +#endif } void __attribute__((naked)) coroutine_restore_context(void *rsp) { // @arch +#if !defined(__clang__) (void)rsp; +#endif +#if _WIN32 + asm( + " movq %rcx, %rsp\n" + // TODO: pop XMM15-XMM6 + " popq %r15\n" + " popq %r14\n" + " popq %r13\n" + " popq %r12\n" + " popq %rsi\n" + " popq %rdi\n" + " popq %rbp\n" + " popq %rbx\n" + " popq %rcx\n" + " ret\n"); +#else asm( " movq %rdi, %rsp\n" " popq %r15\n" @@ -151,6 +248,7 @@ void __attribute__((naked)) coroutine_restore_context(void *rsp) " popq %rbp\n" " popq %rdi\n" " ret\n"); +#endif } void coroutine_switch_context(void *rsp, Sleep_Mode sm, int fd) @@ -160,23 +258,36 @@ void coroutine_switch_context(void *rsp, Sleep_Mode sm, int fd) switch (sm) { case SM_NONE: current += 1; break; case SM_READ: { +#if _WIN32 + (void)fd; + TODO("polling is not implemented for windows"); +#else da_append(&asleep, active.items[current]); struct pollfd pfd = {.fd = fd, .events = POLLRDNORM,}; da_append(&polls, pfd); da_remove_unordered(&active, current); +#endif } break; case SM_WRITE: { +#if _WIN32 + (void)fd; + TODO("polling is not implemented for windows"); +#else da_append(&asleep, active.items[current]); struct pollfd pfd = {.fd = fd, .events = POLLWRNORM,}; da_append(&polls, pfd); da_remove_unordered(&active, current); +#endif } break; default: UNREACHABLE("coroutine_switch_context"); } if (polls.count > 0) { +#if _WIN32 + TODO("polling is not implemented for windows"); +#else int timeout = active.count == 0 ? -1 : 0; int result = poll(polls.items, polls.count, timeout); if (result < 0) TODO("poll"); @@ -191,6 +302,7 @@ void coroutine_switch_context(void *rsp, Sleep_Mode sm, int fd) ++i; } } +#endif } assert(active.count > 0); @@ -216,6 +328,9 @@ void coroutine__finish_current(void) da_remove_unordered(&active, current); if (polls.count > 0) { +#if _WIN32 + TODO("polling is not implemented for windows"); +#else int timeout = active.count == 0 ? -1 : 0; int result = poll(polls.items, polls.count, timeout); if (result < 0) TODO("poll"); @@ -230,6 +345,7 @@ void coroutine__finish_current(void) ++i; } } +#endif } assert(active.count > 0); @@ -245,14 +361,32 @@ void coroutine_go(void (*f)(void*), void *arg) } else { da_append(&contexts, ((Context){0})); id = contexts.count-1; +#if _WIN32 + void *base = contexts.items[id].stack_base = VirtualAlloc(NULL, STACK_CAPACITY, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + assert(base != NULL); + // TODO: add VirtualProtect with PAGE_NOACCESS for overflow and underflow +#else contexts.items[id].stack_base = mmap(NULL, STACK_CAPACITY, PROT_WRITE|PROT_READ, MAP_PRIVATE|MAP_STACK|MAP_ANONYMOUS|MAP_GROWSDOWN, -1, 0); assert(contexts.items[id].stack_base != MAP_FAILED); +#endif } void **rsp = (void**)((char*)contexts.items[id].stack_base + STACK_CAPACITY); // @arch *(--rsp) = coroutine__finish_current; *(--rsp) = f; +#if _WIN32 + *(--rsp) = arg; // push rcx + *(--rsp) = 0; // push rbx + *(--rsp) = 0; // push rbp + *(--rsp) = 0; // push rdi + *(--rsp) = 0; // push rsi + *(--rsp) = 0; // push r12 + *(--rsp) = 0; // push r13 + *(--rsp) = 0; // push r14 + *(--rsp) = 0; // push r15 + // TODO: push XMM6-XMM15 +#else *(--rsp) = arg; // push rdi *(--rsp) = 0; // push rbx *(--rsp) = 0; // push rbp @@ -260,6 +394,7 @@ void coroutine_go(void (*f)(void*), void *arg) *(--rsp) = 0; // push r13 *(--rsp) = 0; // push r14 *(--rsp) = 0; // push r15 +#endif contexts.items[id].rsp = rsp; da_append(&active, id);