1 contributor
367 lines9.8 KB
Newer
Older
-
+
commited
{line.log.rev}
on
8 months ago
4
1
#include "coroutine.h"
2
#include <assert.h>
3
#include <setjmp.h>
4
#include <stdbool.h>
5
#include <stddef.h>
6
#include <stdio.h>
7
#include <semaphore.h>
8
#include <fcntl.h>
9
#include <unistd.h>
10
11
#define COROUTINE_STACK_SIZE 16384
12
#define COROUTINE_STARTUP_STACK_SIZE 1024
13
14
15
///////////////////////////////////////////////////////////////////////////////
16
// 2-way linked lists...
17
//
18
// Broght inline here to avoid namespace polution
19
///////////////////////////////////////////////////////////////////////////////
20
21
typedef struct List_Link List_Link;
22
struct List_Link {
23
List_Link *next;
24
List_Link *prev;
25
};
26
27
typedef struct List_Head List_Head;
28
struct List_Head {
29
union {
30
struct {
31
List_Link link;
32
List_Link *filler;
33
} fwd;
34
struct {
35
List_Link *filler;
36
List_Link link;
37
} back;
38
};
39
};
40
41
static inline bool List_IsEmpty(const List_Head *list) {
42
return list->fwd.link.next == &list->back.link;
43
}
44
45
static inline List_Link *List_GetHead(const List_Head *list) {
46
return List_IsEmpty(list) ? NULL : list->fwd.link.next;
47
}
48
static inline List_Link *List_GetTail(const List_Head *list) {
49
return List_IsEmpty(list) ? NULL : list->back.link.prev;
50
}
51
#define OFFSETOF(Container, Field) ((char *)&((Container *)4)->Field - (char *)(Container *)4)
52
#define List_Link_Container(Container, Link, link) ((Container *)((char *)(link) - OFFSETOF(Container, Link)))
53
54
static inline void List_Init(List_Head *list)
55
{
56
list->fwd.link.next = &list->back.link;
57
list->fwd.link.prev = NULL;
58
list->back.link.prev = &list->fwd.link;
59
}
60
61
static inline void List_AddHead(List_Head *list, List_Link *link)
62
{
63
List_Link *first = list->fwd.link.next;
64
link->next = first;
65
link->prev = &list->fwd.link;
66
first->prev = link;
67
list->fwd.link.next = link;
68
}
69
70
static inline void List_AddTail(List_Head *list, List_Link *link)
71
{
72
List_Link *last = list->back.link.prev;
73
link->prev = last;
74
link->next = &list->back.link;
75
last->next = link;
76
list->back.link.prev = link;
77
}
78
79
static inline void List_Remove(List_Link *link)
80
{
81
link->prev->next = link->next;
82
link->next->prev = link->prev;
83
}
84
85
///////////////////////////////////////////////////////////////////////////////
86
// ...2-way linked lists
87
///////////////////////////////////////////////////////////////////////////////
88
89
90
enum {
91
Coroutines_Idle,
92
Coroutines_Starting,
93
Coroutines_Started,
94
Coroutines_Active,
95
Coroutines_Stopping
96
};
97
98
enum {
99
Chunk_Initial,
100
Chunk_Create,
101
Chunk_Enter
102
};
103
104
enum {
105
Coroutine_Constructing,
106
Coroutine_Free,
107
Coroutine_Idle,
108
Coroutine_Running,
109
Coroutine_Waiting,
110
Coroutine_Complete
111
};
112
113
enum {
114
Coroutines_Init,
115
Coroutines_AllocatedChunk,
116
Coroutines_CoroutineComplete,
117
};
118
119
struct Coroutine {
120
List_Link link;
121
jmp_buf buf;
122
void *this;
123
Coroutine_YieldCallback on_yield;
124
Coroutine_Start start;
125
void *entry_param;
126
void *value;
127
char state;
128
char action;
129
};
130
131
typedef struct Coroutines Coroutines;
132
133
struct Coroutines {
134
jmp_buf controller;
135
jmp_buf chunk_allocated;
136
137
// singletons
138
Coroutine *tip; // top of stack chunk
139
Coroutine *active; // currently running coroutine
140
Coroutine *primary; // Coroutine_Run coroutine
141
142
// lists
143
List_Head free;
144
List_Head inactive; // idle or complete
145
List_Head runable; // running or waiting to run
146
List_Head waiting; // yielded / waiting to run
147
sem_t *waiting_sem;
148
149
// state
150
char state;
151
};
152
153
Coroutines g_c;
154
155
static void stack_chunk_chunk(Coroutine *parent);
156
static void stack_chunk_base(Coroutine *parent);
157
158
static void Coroutine_PrimeStackChunks()
159
{
160
unsigned char chunk_of_stack[COROUTINE_STACK_SIZE];
161
chunk_of_stack[0] = 0xde;
162
chunk_of_stack[1] = 0xad;
163
chunk_of_stack[2] = 0xbe;
164
chunk_of_stack[3] = 0xef;
165
chunk_of_stack[COROUTINE_STACK_SIZE - 4] = 0xde;
166
chunk_of_stack[COROUTINE_STACK_SIZE - 3] = 0xad;
167
chunk_of_stack[COROUTINE_STACK_SIZE - 2] = 0xbe;
168
chunk_of_stack[COROUTINE_STACK_SIZE - 1] = 0xef;
169
stack_chunk_base(NULL);
170
}
171
172
static void stack_chunk_chunk(Coroutine *parent){
173
unsigned char chunk_of_stack[COROUTINE_STACK_SIZE];
174
chunk_of_stack[0] = 0xde;
175
chunk_of_stack[1] = 0xad;
176
chunk_of_stack[2] = 0xbe;
177
chunk_of_stack[3] = 0xef;
178
chunk_of_stack[COROUTINE_STACK_SIZE - 4] = 0xde;
179
chunk_of_stack[COROUTINE_STACK_SIZE - 3] = 0xad;
180
chunk_of_stack[COROUTINE_STACK_SIZE - 2] = 0xbe;
181
chunk_of_stack[COROUTINE_STACK_SIZE - 1] = 0xef;
182
stack_chunk_base(parent);
183
}
184
185
static void Coroutine_RunNext()
186
{
187
sem_wait(g_c.waiting_sem);
188
assert(!List_IsEmpty(&g_c.runable));
189
Coroutine *next = List_Link_Container(Coroutine, link, List_GetHead(&g_c.runable));
190
assert(next->state == Coroutine_Running);
191
longjmp(next->buf, Chunk_Enter);
192
assert(false);
193
}
194
195
static void stack_chunk_base(Coroutine *parent){
196
Coroutine here;
197
here.state = Coroutine_Constructing;
198
switch (setjmp(here.buf)) {
199
case Chunk_Initial:
200
// got here for the first time
201
// parent now has a chunk_of_stack - add it to the free list
202
if (parent) {
203
assert(parent->state == Coroutine_Constructing);
204
parent->state = Coroutine_Free;
205
List_AddHead(&g_c.free, &parent->link);
206
}
207
// note that here is the tip of the chunk-claim stack
208
g_c.tip = &here;
209
210
// return to the coroutine allocator
211
longjmp(g_c.chunk_allocated, 1);
212
case Chunk_Create:
213
// request to create a new chunk on the stack
214
assert(here.state == Coroutine_Constructing);
215
stack_chunk_chunk(&here);
216
assert(false);
217
case Chunk_Enter:
218
// request to start a coroutine (ie use the chunk for a coroutine)
219
assert(here.state == Coroutine_Running);
220
g_c.active = &here;
221
here.value = here.start(here.entry_param);
222
g_c.active = NULL;
223
assert(here.state == Coroutine_Running);
224
List_Remove(&here.link);
225
here.state = Coroutine_Complete;
226
List_AddTail(&g_c.inactive, &here.link);
227
// coroutine has completed
228
if (g_c.primary == &here) {
229
// if primary coroutine - return to Coroutine_Run
230
longjmp(g_c.controller, Coroutines_CoroutineComplete);
231
}
232
Coroutine_RunNext();
233
assert(false);
234
}
235
}
236
237
void Coroutine_StartSystem()
238
{
239
assert(g_c.state == Coroutines_Idle);
240
g_c.state = Coroutines_Starting;
241
242
g_c.tip = NULL;
243
g_c.active = NULL;
244
245
List_Init(&g_c.free);
246
List_Init(&g_c.inactive);
247
List_Init(&g_c.runable);
248
List_Init(&g_c.waiting);
249
char tbuf[256];
250
snprintf(tbuf, sizeof(tbuf), "/coroutine_waiting_sem_%d", getpid());
251
g_c.waiting_sem = sem_open(tbuf, O_CREAT, 0644, 0);
252
sem_unlink(tbuf);
253
assert(g_c.waiting_sem != SEM_FAILED);
254
255
// prime the chunk system
256
if (!setjmp(g_c.chunk_allocated)){
257
Coroutine_PrimeStackChunks();
258
assert(false);
259
}
260
assert(g_c.state == Coroutines_Starting);
261
g_c.state = Coroutines_Started;
262
}
263
264
void Coroutine_StopSystem()
265
{
266
assert(g_c.state == Coroutines_Started);
267
g_c.state = Coroutines_Stopping;
268
269
assert(List_IsEmpty(&g_c.inactive));
270
sem_close(g_c.waiting_sem);
271
g_c.waiting_sem = NULL;
272
273
assert(g_c.state == Coroutines_Stopping);
274
g_c.state = Coroutines_Idle;
275
}
276
277
void *Coroutine_Run(Coroutine *cor, void *value){
278
assert(g_c.state == Coroutines_Started);
279
g_c.state = Coroutines_Active;
280
g_c.primary = cor;
281
Coroutine_Continue(g_c.primary, value, true);
282
283
if (!setjmp(g_c.controller)){
284
// start the first coroutine
285
Coroutine_RunNext();
286
}
287
assert(List_IsEmpty(&g_c.runable));
288
assert(List_IsEmpty(&g_c.waiting));
289
assert(g_c.state == Coroutines_Active);
290
g_c.state = Coroutines_Started;
291
return Coroutine_GetValue(cor);
292
}
293
294
Coroutine *Coroutine_New(void *this, Coroutine_YieldCallback on_yield, Coroutine_Start start){
295
assert(g_c.state == Coroutines_Started || g_c.state == Coroutines_Active);
296
297
// if none free - add one
298
if (List_IsEmpty(&g_c.free)){
299
if (!setjmp(g_c.chunk_allocated)){
300
longjmp(g_c.tip->buf, Chunk_Create);
301
}
302
}
303
304
Coroutine *cor = List_Link_Container(Coroutine, link, List_GetHead(&g_c.free));
305
assert(cor->state == Coroutine_Free);
306
cor->state = Coroutine_Idle;
307
cor->this = this;
308
cor->start = start;
309
cor->on_yield = on_yield;
310
cor->value = NULL;
311
List_Remove(&cor->link);
312
List_AddHead(&g_c.inactive, &cor->link);
313
314
return cor;
315
}
316
317
void Coroutine_Delete(Coroutine *cor){
318
assert(cor->state == Coroutine_Idle || cor->state == Coroutine_Complete);
319
cor->state = Coroutine_Free;
320
List_Remove(&cor->link);
321
List_AddTail(&g_c.free, &cor->link);
322
}
323
324
void Coroutine_Continue(Coroutine *cor, void *value, bool early){
325
assert(cor->state == Coroutine_Idle || cor->state == Coroutine_Waiting);
326
cor->entry_param = value;
327
cor->state = Coroutine_Running;
328
List_Remove(&cor->link);
329
if ( early ) {
330
List_AddHead(&g_c.runable, &cor->link);
331
} else {
332
List_AddTail(&g_c.runable, &cor->link);
333
}
334
sem_post(g_c.waiting_sem);
335
}
336
337
void *Coroutine_Yield(void *value){
338
Coroutine *me = g_c.active;
339
assert(me && me->state == Coroutine_Running);
340
me->value = value;
341
me->state = Coroutine_Waiting;
342
List_Remove(&me->link);
343
List_AddTail(&g_c.waiting, &me->link);
344
if (!setjmp(me->buf)){
345
me->on_yield(me->this);
346
Coroutine_RunNext();
347
}
348
g_c.active = me;
349
// when we return here - we are running again
350
assert(me->state == Coroutine_Running);
351
return me->entry_param;
352
}
353
354
void *Coroutine_GetValue(Coroutine *cor){
355
return cor->value;
356
}
357
358
Coroutine *Coroutine_GetActive()
359
{
360
return g_c.active;
361
}
362
363
bool Coroutine_IsRunning(Coroutine *cor)
364
{
365
return cor->state == Coroutine_Running || cor->state == Coroutine_Waiting;
366
}
367