Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand All @@ -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
Expand All @@ -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*

Expand Down
137 changes: 136 additions & 1 deletion coroutine.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,26 @@
#include <stdbool.h>
#include <string.h>

#if _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <poll.h>
#include <unistd.h>
#include <sys/mman.h>
#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())
Expand Down Expand Up @@ -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;
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -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)
Expand All @@ -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");
Expand All @@ -191,6 +302,7 @@ void coroutine_switch_context(void *rsp, Sleep_Mode sm, int fd)
++i;
}
}
#endif
}

assert(active.count > 0);
Expand All @@ -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");
Expand All @@ -230,6 +345,7 @@ void coroutine__finish_current(void)
++i;
}
}
#endif
}

assert(active.count > 0);
Expand All @@ -245,21 +361,40 @@ 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
*(--rsp) = 0; // push r12
*(--rsp) = 0; // push r13
*(--rsp) = 0; // push r14
*(--rsp) = 0; // push r15
#endif
contexts.items[id].rsp = rsp;

da_append(&active, id);
Expand Down