Skip to content

Commit 3955486

Browse files
committed
Add basic support for windows
I wanted to keep it as simple as possible, so didn't even add guard pages. I'll do it in a separate PR I tried but just could not get it to work with gcc from mingw64, though gcc from clang64 works, and clang from mingw64 and clang64 works. Probably a compiler bug or something. I was getting a segfault when calling coroutine_restore_context in coroutine_switch_context ..
1 parent 142d958 commit 3955486

File tree

3 files changed

+152
-7
lines changed

3 files changed

+152
-7
lines changed

Makefile

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
cc ?= gcc
2+
13
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
4+
$(cc) -I. -Wall -Wextra -ggdb -o build/counter examples/counter.c build/coroutine.a
35

46
.PHONY: examples
57
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
1719
jai-linux examples/counter.jai
1820

1921
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
22+
$(cc) -I. -Wall -Wextra -ggdb -o build/lexer examples/lexer.c build/coroutine.a
2123

2224
build/coroutine.so: coroutine.c
2325
mkdir -p build
24-
gcc -Wall -Wextra -ggdb -shared -fPIC -o build/coroutine.so coroutine.c
26+
$(cc) -Wall -Wextra -ggdb -shared -fPIC -o build/coroutine.so coroutine.c
2527

2628
build/coroutine.a: build/coroutine.o
2729
ar -rcs build/coroutine.a build/coroutine.o
2830

2931
build/coroutine.o: coroutine.c coroutine.h
3032
mkdir -p build
31-
gcc -Wall -Wextra -ggdb -c -o build/coroutine.o coroutine.c
33+
$(cc) -Wall -Wextra -ggdb -c -o build/coroutine.o coroutine.c

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
> [!WARNING]
44
> The library is not in production ready state yet
55
6-
Custom coroutines implementation in GNU C.
6+
Custom coroutines implementation in C.
77

88
## What is a Coroutine?
99

@@ -26,6 +26,13 @@ $ make
2626
$ ./build/counter
2727
```
2828

29+
For Windows you need to use clang
30+
31+
```console
32+
$ make cc=clang
33+
$ ./build/counter.exe
34+
```
35+
2936
There are actually much more examples in the [./examples/](./examples/) in a variety of languages. To build all of them do:
3037

3138
```console
@@ -36,7 +43,8 @@ Make sure you have all the corresponding compilers for the languages.
3643

3744
## Supported platforms
3845

39-
- Linux x86_64
46+
- Linux x86_64
47+
- Windows x86_64 (tested for Msys clang)
4048

4149
*More are planned in the future*
4250

coroutine.c

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,26 @@
44
#include <stdbool.h>
55
#include <string.h>
66

7+
#if _WIN32
8+
#define WIN32_LEAN_AND_MEAN
9+
#include <windows.h>
10+
#else
711
#include <poll.h>
812
#include <unistd.h>
913
#include <sys/mman.h>
14+
#endif
1015

1116
#include "coroutine.h"
1217

18+
19+
#if _WIN32
20+
int getpagesize() {
21+
SYSTEM_INFO si;
22+
GetSystemInfo(&si);
23+
return si.dwPageSize;
24+
}
25+
#endif
26+
1327
// TODO: make the STACK_CAPACITY customizable by the user
1428
//#define STACK_CAPACITY (4*1024)
1529
#define STACK_CAPACITY (1024*getpagesize())
@@ -60,7 +74,11 @@ typedef struct {
6074
} Indices;
6175

6276
typedef struct {
77+
#if _WIN32
78+
char *items; // not supported yet
79+
#else
6380
struct pollfd *items;
81+
#endif
6482
size_t count;
6583
size_t capacity;
6684
} Polls;
@@ -82,12 +100,33 @@ typedef enum {
82100
SM_WRITE,
83101
} Sleep_Mode;
84102

85-
// Linux x86_64 call convention
103+
// Linux x86_64 calling convention
86104
// %rdi, %rsi, %rdx, %rcx, %r8, and %r9
87105

106+
// https://learn.microsoft.com/en-us/cpp/build/x64-software-conventions?view=msvc-170#x64-register-usage
107+
// Windows x64 calling convention: RCX, RDX, R8, R9
108+
// Windows x64 ABI considers registers RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-XMM15 nonvolatile.
109+
// They must be saved and restored by a function that uses them.
110+
88111
void __attribute__((naked)) coroutine_yield(void)
89112
{
90113
// @arch
114+
#if _WIN32
115+
asm(
116+
" pushq %rcx\n"
117+
" pushq %rbx\n"
118+
" pushq %rbp\n"
119+
" pushq %rdi\n"
120+
" pushq %rsi\n"
121+
" pushq %r12\n"
122+
" pushq %r13\n"
123+
" pushq %r14\n"
124+
" pushq %r15\n"
125+
// TODO: push XMM6-XMM15
126+
" movq %rsp, %rcx\n" // rsp
127+
" movq $0, %rdx\n" // sm = SM_READ
128+
" jmp coroutine_switch_context\n");
129+
#else
91130
asm(
92131
" pushq %rdi\n"
93132
" pushq %rbp\n"
@@ -99,12 +138,32 @@ void __attribute__((naked)) coroutine_yield(void)
99138
" movq %rsp, %rdi\n" // rsp
100139
" movq $0, %rsi\n" // sm = SM_NONE
101140
" jmp coroutine_switch_context\n");
141+
#endif
102142
}
103143

104144
void __attribute__((naked)) coroutine_sleep_read(int fd)
105145
{
146+
#if !defined(__clang__)
106147
(void) fd;
148+
#endif
107149
// @arch
150+
#if _WIN32
151+
asm(
152+
" pushq %rcx\n"
153+
" pushq %rbx\n"
154+
" pushq %rbp\n"
155+
" pushq %rdi\n"
156+
" pushq %rsi\n"
157+
" pushq %r12\n"
158+
" pushq %r13\n"
159+
" pushq %r14\n"
160+
" pushq %r15\n"
161+
// TODO: push XMM6-XMM15
162+
" movq %rcx, %r8\n" // fd
163+
" movq %rsp, %rcx\n" // rsp
164+
" movq $1, %rdx\n" // sm = SM_READ
165+
" jmp coroutine_switch_context\n");
166+
#else
108167
asm(
109168
" pushq %rdi\n"
110169
" pushq %rbp\n"
@@ -117,12 +176,32 @@ void __attribute__((naked)) coroutine_sleep_read(int fd)
117176
" movq %rsp, %rdi\n" // rsp
118177
" movq $1, %rsi\n" // sm = SM_READ
119178
" jmp coroutine_switch_context\n");
179+
#endif
120180
}
121181

122182
void __attribute__((naked)) coroutine_sleep_write(int fd)
123183
{
184+
#if !defined(__clang__)
124185
(void) fd;
186+
#endif
125187
// @arch
188+
#if _WIN32
189+
asm(
190+
" pushq %rcx\n"
191+
" pushq %rbx\n"
192+
" pushq %rbp\n"
193+
" pushq %rdi\n"
194+
" pushq %rsi\n"
195+
" pushq %r12\n"
196+
" pushq %r13\n"
197+
" pushq %r14\n"
198+
" pushq %r15\n"
199+
// TODO: push XMM6-XMM15
200+
" movq %rcx, %r8\n" // fd
201+
" movq %rsp, %rcx\n" // rsp
202+
" movq $2, %rdx\n" // sm = SM_READ
203+
" jmp coroutine_switch_context\n");
204+
#else
126205
asm(
127206
" pushq %rdi\n"
128207
" pushq %rbp\n"
@@ -135,12 +214,30 @@ void __attribute__((naked)) coroutine_sleep_write(int fd)
135214
" movq %rsp, %rdi\n" // rsp
136215
" movq $2, %rsi\n" // sm = SM_WRITE
137216
" jmp coroutine_switch_context\n");
217+
#endif
138218
}
139219

140220
void __attribute__((naked)) coroutine_restore_context(void *rsp)
141221
{
142222
// @arch
223+
#if !defined(__clang__)
143224
(void)rsp;
225+
#endif
226+
#if _WIN32
227+
asm(
228+
" movq %rcx, %rsp\n"
229+
// TODO: pop XMM15-XMM6
230+
" popq %r15\n"
231+
" popq %r14\n"
232+
" popq %r13\n"
233+
" popq %r12\n"
234+
" popq %rsi\n"
235+
" popq %rdi\n"
236+
" popq %rbp\n"
237+
" popq %rbx\n"
238+
" popq %rcx\n"
239+
" ret\n");
240+
#else
144241
asm(
145242
" movq %rdi, %rsp\n"
146243
" popq %r15\n"
@@ -151,6 +248,7 @@ void __attribute__((naked)) coroutine_restore_context(void *rsp)
151248
" popq %rbp\n"
152249
" popq %rdi\n"
153250
" ret\n");
251+
#endif
154252
}
155253

156254
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)
160258
switch (sm) {
161259
case SM_NONE: current += 1; break;
162260
case SM_READ: {
261+
#if _WIN32
262+
(void)fd;
263+
TODO("polling is not implemented for windows");
264+
#else
163265
da_append(&asleep, active.items[current]);
164266
struct pollfd pfd = {.fd = fd, .events = POLLRDNORM,};
165267
da_append(&polls, pfd);
166268
da_remove_unordered(&active, current);
269+
#endif
167270
} break;
168271

169272
case SM_WRITE: {
273+
#if _WIN32
274+
(void)fd;
275+
TODO("polling is not implemented for windows");
276+
#else
170277
da_append(&asleep, active.items[current]);
171278
struct pollfd pfd = {.fd = fd, .events = POLLWRNORM,};
172279
da_append(&polls, pfd);
173280
da_remove_unordered(&active, current);
281+
#endif
174282
} break;
175283

176284
default: UNREACHABLE("coroutine_switch_context");
177285
}
178286

179287
if (polls.count > 0) {
288+
#if _WIN32
289+
TODO("polling is not implemented for windows");
290+
#else
180291
int timeout = active.count == 0 ? -1 : 0;
181292
int result = poll(polls.items, polls.count, timeout);
182293
if (result < 0) TODO("poll");
@@ -191,6 +302,7 @@ void coroutine_switch_context(void *rsp, Sleep_Mode sm, int fd)
191302
++i;
192303
}
193304
}
305+
#endif
194306
}
195307

196308
assert(active.count > 0);
@@ -216,6 +328,9 @@ void coroutine__finish_current(void)
216328
da_remove_unordered(&active, current);
217329

218330
if (polls.count > 0) {
331+
#if _WIN32
332+
TODO("polling is not implemented for windows");
333+
#else
219334
int timeout = active.count == 0 ? -1 : 0;
220335
int result = poll(polls.items, polls.count, timeout);
221336
if (result < 0) TODO("poll");
@@ -230,6 +345,7 @@ void coroutine__finish_current(void)
230345
++i;
231346
}
232347
}
348+
#endif
233349
}
234350

235351
assert(active.count > 0);
@@ -245,21 +361,40 @@ void coroutine_go(void (*f)(void*), void *arg)
245361
} else {
246362
da_append(&contexts, ((Context){0}));
247363
id = contexts.count-1;
364+
#if _WIN32
365+
void *base = contexts.items[id].stack_base = VirtualAlloc(NULL, STACK_CAPACITY, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
366+
assert(base != NULL);
367+
// TODO: add VirtualProtect with PAGE_NOACCESS for overflow and underflow
368+
#else
248369
contexts.items[id].stack_base = mmap(NULL, STACK_CAPACITY, PROT_WRITE|PROT_READ, MAP_PRIVATE|MAP_STACK|MAP_ANONYMOUS|MAP_GROWSDOWN, -1, 0);
249370
assert(contexts.items[id].stack_base != MAP_FAILED);
371+
#endif
250372
}
251373

252374
void **rsp = (void**)((char*)contexts.items[id].stack_base + STACK_CAPACITY);
253375
// @arch
254376
*(--rsp) = coroutine__finish_current;
255377
*(--rsp) = f;
378+
#if _WIN32
379+
*(--rsp) = arg; // push rcx
380+
*(--rsp) = 0; // push rbx
381+
*(--rsp) = 0; // push rbp
382+
*(--rsp) = 0; // push rdi
383+
*(--rsp) = 0; // push rsi
384+
*(--rsp) = 0; // push r12
385+
*(--rsp) = 0; // push r13
386+
*(--rsp) = 0; // push r14
387+
*(--rsp) = 0; // push r15
388+
// TODO: push XMM6-XMM15
389+
#else
256390
*(--rsp) = arg; // push rdi
257391
*(--rsp) = 0; // push rbx
258392
*(--rsp) = 0; // push rbp
259393
*(--rsp) = 0; // push r12
260394
*(--rsp) = 0; // push r13
261395
*(--rsp) = 0; // push r14
262396
*(--rsp) = 0; // push r15
397+
#endif
263398
contexts.items[id].rsp = rsp;
264399

265400
da_append(&active, id);

0 commit comments

Comments
 (0)