8 months ago |
4 |
1 |
#include "coroutine.h" | ||
2 |
#include <assert.h> | ||||
3 |
#include <setjmp.h> | ||||
4 |
#include <stdbool.h> | ||||
5 |
#include <stddef.h> | ||||
7 months ago |
6 |
#include "cor_platform.h" | |||
8 months ago |
4 |
7 |
|||
6 months ago |
8 |
// see CPython again, this time from ctypes.h | |||
9 |
#if (defined (__SVR4) && defined (__sun)) || defined(COROUTINE_HAVE_ALLOCA_H) | ||||
10 |
# include <alloca.h> | ||||
11 |
#elif defined(MS_WIN32) | ||||
12 |
# include <malloc.h> | ||||
13 |
#endif | ||||
8 months ago |
4 |
14 |
|||
6 months ago |
15 |
/* If the system does not define alloca(), we have to hope for a compiler builtin. */ | |||
16 |
#ifndef alloca | ||||
17 |
# if defined __GNUC__ || (__clang_major__ >= 4) | ||||
18 |
# define alloca __builtin_alloca | ||||
19 |
# else | ||||
20 |
# error "Could not define alloca() on your platform." | ||||
21 |
# endif | ||||
22 |
#endif | ||||
23 |
|||||
7 months ago |
24 |
static void Coroutine_RunNext(void); | |||
7 months ago |
25 |
static void _Coroutine_Continue(Coroutine *cor, void *value, bool early); | |||
6 months ago |
26 |
static unsigned char *StackTopNow(void); | |||
8 months ago |
27 |
||||
8 months ago |
4 |
28 |
/////////////////////////////////////////////////////////////////////////////// | ||
29 |
// 2-way linked lists... | ||||
30 |
// | ||||
8 months ago |
31 |
// Brought inline here to avoid namespace polution | |||
8 months ago |
4 |
32 |
/////////////////////////////////////////////////////////////////////////////// | ||
33 |
|||||
34 |
typedef struct List_Link List_Link; | ||||
35 |
struct List_Link { | ||||
36 |
List_Link *next; | ||||
37 |
List_Link *prev; | ||||
38 |
}; | ||||
39 |
|||||
40 |
typedef struct List_Head List_Head; | ||||
41 |
struct List_Head { | ||||
42 |
union { | ||||
43 |
struct { | ||||
44 |
List_Link link; | ||||
45 |
List_Link *filler; | ||||
46 |
} fwd; | ||||
47 |
struct { | ||||
48 |
List_Link *filler; | ||||
49 |
List_Link link; | ||||
50 |
} back; | ||||
51 |
}; | ||||
52 |
}; | ||||
53 |
|||||
8 months ago |
54 |
||||
55 |
static inline bool List_IsEmpty( | ||||
56 |
const List_Head *list | ||||
57 |
){ | ||||
8 months ago |
4 |
58 |
return list->fwd.link.next == &list->back.link; | ||
59 |
} | ||||
60 |
|||||
8 months ago |
61 |
||||
62 |
static inline List_Link *List_GetHead( | ||||
63 |
const List_Head *list | ||||
64 |
){ | ||||
8 months ago |
4 |
65 |
return List_IsEmpty(list) ? NULL : list->fwd.link.next; | ||
66 |
} | ||||
8 months ago |
67 |
||||
68 |
|||||
69 |
// static inline List_Link *List_GetTail( | ||||
70 |
// const List_Head *list | ||||
71 |
// ){ | ||||
8 months ago |
72 |
// return List_IsEmpty(list) ? NULL : list->back.link.prev; | |||
73 |
// } | ||||
8 months ago |
74 |
||||
75 |
|||||
8 months ago |
4 |
76 |
#define OFFSETOF(Container, Field) ((char *)&((Container *)4)->Field - (char *)(Container *)4) | ||
77 |
#define List_Link_Container(Container, Link, link) ((Container *)((char *)(link) - OFFSETOF(Container, Link))) | ||||
78 |
|||||
8 months ago |
79 |
||||
80 |
static inline void List_Init( | ||||
81 |
List_Head *list | ||||
82 |
){ | ||||
8 months ago |
4 |
83 |
list->fwd.link.next = &list->back.link; | ||
84 |
list->fwd.link.prev = NULL; | ||||
85 |
list->back.link.prev = &list->fwd.link; | ||||
86 |
} | ||||
87 |
|||||
8 months ago |
88 |
||||
89 |
static inline void List_AddHead( | ||||
90 |
List_Head *list, | ||||
91 |
List_Link *link | ||||
92 |
){ | ||||
8 months ago |
4 |
93 |
List_Link *first = list->fwd.link.next; | ||
94 |
link->next = first; | ||||
95 |
link->prev = &list->fwd.link; | ||||
96 |
first->prev = link; | ||||
97 |
list->fwd.link.next = link; | ||||
98 |
} | ||||
99 |
|||||
8 months ago |
100 |
||||
101 |
static inline void List_AddTail( | ||||
102 |
List_Head *list, | ||||
103 |
List_Link *link | ||||
104 |
){ | ||||
8 months ago |
4 |
105 |
List_Link *last = list->back.link.prev; | ||
106 |
link->prev = last; | ||||
107 |
link->next = &list->back.link; | ||||
108 |
last->next = link; | ||||
109 |
list->back.link.prev = link; | ||||
110 |
} | ||||
111 |
|||||
8 months ago |
112 |
||||
113 |
static inline void List_Remove( | ||||
114 |
List_Link *link | ||||
115 |
){ | ||||
8 months ago |
4 |
116 |
link->prev->next = link->next; | ||
117 |
link->next->prev = link->prev; | ||||
118 |
} | ||||
119 |
|||||
120 |
/////////////////////////////////////////////////////////////////////////////// | ||||
121 |
// ...2-way linked lists | ||||
122 |
/////////////////////////////////////////////////////////////////////////////// | ||||
123 |
|||||
8 months ago |
124 |
typedef struct Coroutines Coroutines; | |||
8 months ago |
4 |
125 |
|||
126 |
enum { | ||||
127 |
Coroutines_Idle, | ||||
128 |
Coroutines_Starting, | ||||
129 |
Coroutines_Started, | ||||
130 |
Coroutines_Active, | ||||
131 |
Coroutines_Stopping | ||||
132 |
}; | ||||
133 |
|||||
134 |
enum { | ||||
135 |
Chunk_Initial, | ||||
136 |
Chunk_Create, | ||||
137 |
Chunk_Enter | ||||
138 |
}; | ||||
139 |
|||||
8 months ago |
140 |
typedef enum Coroutine_State { | |||
8 months ago |
4 |
141 |
Coroutine_Free, | ||
142 |
Coroutine_Idle, | ||||
143 |
Coroutine_Running, | ||||
144 |
Coroutine_Waiting, | ||||
145 |
Coroutine_Complete | ||||
8 months ago |
146 |
} Coroutine_State; | |||
8 months ago |
4 |
147 |
|||
148 |
enum { | ||||
149 |
Coroutines_Init, | ||||
150 |
Coroutines_AllocatedChunk, | ||||
151 |
Coroutines_CoroutineComplete, | ||||
152 |
}; | ||||
153 |
|||||
154 |
struct Coroutine { | ||||
6 months ago |
155 |
Coroutines *coroutines; // so can work with it off-thread | |||
156 |
List_Link link; // for whichever list it's on | ||||
157 |
jmp_buf buf; // how to get back to it | ||||
158 |
unsigned char *guard; // where the stack overrun guard is | ||||
159 |
Coroutine_Start start; // entry point | ||||
160 |
void *entry_param; // to pass to start | ||||
161 |
void *value; // yielded/returned | ||||
162 |
unsigned char *stack_top; // recorded at yield | ||||
8 months ago |
163 |
Coroutine_State state; | |||
8 months ago |
4 |
164 |
}; | ||
165 |
|||||
166 |
struct Coroutines { | ||||
7 months ago |
167 |
_Cor_Mutex mutex; | |||
8 months ago |
168 |
jmp_buf controller; // to return from Coroutine_Run | |||
169 |
jmp_buf chunk_allocated;// for chunk allocation | ||||
170 |
unsigned char *guard; // the stack guard for the startup sequence | ||||
8 months ago |
4 |
171 |
|||
172 |
// singletons | ||||
173 |
Coroutine *tip; // top of stack chunk | ||||
174 |
Coroutine *active; // currently running coroutine | ||||
175 |
Coroutine *primary; // Coroutine_Run coroutine | ||||
6 months ago |
176 |
unsigned char *stack_limit; // when not NULL, where the stack finishes | |||
8 months ago |
4 |
177 |
|||
178 |
// lists | ||||
179 |
List_Head free; | ||||
180 |
List_Head inactive; // idle or complete | ||||
181 |
List_Head runable; // running or waiting to run | ||||
182 |
List_Head waiting; // yielded / waiting to run | ||||
7 months ago |
183 |
_Cor_Mutex waiting_mutex; | |||
8 months ago |
4 |
184 |
|||
8 months ago |
185 |
// Summary of the system | |||
186 |
Coroutine_Report report; | ||||
187 |
|||||
8 months ago |
4 |
188 |
// state | ||
189 |
char state; | ||||
190 |
}; | ||||
191 |
|||||
7 months ago |
192 |
_Cor_thread_local Coroutines g_c; | |||
8 months ago |
4 |
193 |
|||
6 months ago |
194 |
static void stack_chunk_chunk(Coroutine *parent, size_t chunk_size); | |||
6 months ago |
195 |
static void stack_chunk_base(void); | |||
8 months ago |
4 |
196 |
|||
8 months ago |
197 |
||||
6 months ago |
198 |
#define GUARD_PATTERN_SIZE (4) | |||
8 months ago |
199 |
// Check whether the guard is intact | |||
200 |
static inline bool Check_Guard( | ||||
201 |
unsigned char *guard | ||||
202 |
){ | ||||
6 months ago |
203 |
return !guard || | |||
204 |
(guard[0] == 0xde && | ||||
205 |
guard[1] == 0xad && | ||||
206 |
guard[2] == 0xbe && | ||||
207 |
guard[3] == 0xef); | ||||
8 months ago |
208 |
} | |||
209 |
|||||
210 |
|||||
6 months ago |
211 |
static inline void Apply_Guard(unsigned char *guard){ | |||
212 |
guard[0] = 0xde; | ||||
213 |
guard[1] = 0xad; | ||||
214 |
guard[2] = 0xbe; | ||||
215 |
guard[3] = 0xef; | ||||
216 |
} | ||||
217 |
|||||
218 |
|||||
6 months ago |
219 |
static bool Coroutine_StackHasNotOverrun(void){ | |||
6 months ago |
220 |
unsigned char *stack_top = StackTopNow(); | |||
221 |
unsigned char *stack_limit = g_c.stack_limit; | ||||
222 |
if (stack_limit && stack_top < stack_limit){ | ||||
223 |
// current stack top is beyond limit - we are overrunning NOW | ||||
224 |
return false; | ||||
225 |
} | ||||
226 |
Coroutine *me = g_c.active; | ||||
227 |
if (!me){ | ||||
228 |
return true; | ||||
229 |
} | ||||
230 |
if (me->guard){ | ||||
231 |
return Check_Guard(me->guard); | ||||
232 |
} | ||||
233 |
unsigned char *coroutine_limit; | ||||
234 |
if (!stack_limit || stack_limit <= (unsigned char *)me - 2*COROUTINE_STACK_SIZE){ | ||||
235 |
// no stack limit, or can start a coroutine, so limit ourselves to one unit of coroutine stack | ||||
236 |
coroutine_limit = (unsigned char *)me - 1*COROUTINE_STACK_SIZE + GUARD_PATTERN_SIZE; | ||||
237 |
} else { | ||||
238 |
// can't start coroutine, and have a stack limit - use that | ||||
239 |
coroutine_limit = stack_limit; | ||||
240 |
} | ||||
241 |
return stack_top >= coroutine_limit; | ||||
242 |
} | ||||
243 |
|||||
244 |
|||||
7 months ago |
245 |
static void Coroutine_PrimeStackChunks(void) | |||
8 months ago |
4 |
246 |
{ | ||
6 months ago |
247 |
unsigned char chunk_of_stack[COROUTINE_STARTUP_STACK_SIZE + GUARD_PATTERN_SIZE]; | |||
248 |
Apply_Guard(chunk_of_stack); | ||||
8 months ago |
249 |
assert(Check_Guard(chunk_of_stack)); | |||
250 |
|||||
251 |
// Stacks grow down in memory (almost always), so if the caller of this function changes | ||||
252 |
// the guard before entering the coroutine system, it has overrun the startup stack | ||||
7 months ago |
253 |
g_c.guard = chunk_of_stack; | |||
8 months ago |
254 |
||||
6 months ago |
255 |
stack_chunk_base(); | |||
8 months ago |
4 |
256 |
} | ||
257 |
|||||
8 months ago |
258 |
||||
259 |
static void stack_chunk_chunk( | ||||
6 months ago |
260 |
Coroutine *parent, | |||
261 |
size_t chunk_size | ||||
8 months ago |
262 |
){ | |||
6 months ago |
263 |
unsigned char *chunk_of_stack = alloca(chunk_size); | |||
264 |
#if COROUTINE_RECORD_LOWEST_HEADROOM | ||||
265 |
for (size_t i = 0; i <= chunk_size-GUARD_PATTERN_SIZE; i += GUARD_PATTERN_SIZE){ | ||||
266 |
Apply_Guard(&chunk_of_stack[i]); | ||||
267 |
} | ||||
268 |
#else | ||||
6 months ago |
269 |
Apply_Guard(chunk_of_stack); | |||
6 months ago |
270 |
#endif | |||
271 |
parent->guard = chunk_of_stack; | ||||
272 |
stack_chunk_base(); | ||||
8 months ago |
4 |
273 |
} | ||
274 |
|||||
8 months ago |
275 |
||||
276 |
static void stack_chunk_base( | ||||
6 months ago |
277 |
void | |||
8 months ago |
278 |
){ | |||
8 months ago |
4 |
279 |
Coroutine here; | ||
6 months ago |
280 |
here.state = Coroutine_Free; | |||
281 |
here.guard = NULL; | ||||
282 |
here.coroutines = &g_c; | ||||
283 |
List_AddHead(&g_c.free, &here.link); | ||||
284 |
g_c.report.coroutines_pool_size += 1; | ||||
285 |
g_c.tip = &here; | ||||
6 months ago |
286 |
for(;;){ | |||
287 |
switch (setjmp(here.buf)) { | ||||
288 |
case Chunk_Initial: | ||||
6 months ago |
289 |
if (here.state == Coroutine_Free){ | |||
6 months ago |
290 |
// return to the coroutine allocator | |||
291 |
longjmp(g_c.chunk_allocated, 1); | ||||
292 |
} else { | ||||
293 |
assert(here.state == Coroutine_Complete); | ||||
294 |
// we finish here to ensure the setjmp is redone | ||||
295 |
if (g_c.primary == &here) { | ||||
296 |
// if primary coroutine - return to Coroutine_Run | ||||
297 |
longjmp(g_c.controller, Coroutines_CoroutineComplete); | ||||
298 |
} | ||||
299 |
_Cor_Mutex_Unlock(&g_c.mutex); | ||||
300 |
Coroutine_RunNext(); | ||||
301 |
assert(false); | ||||
302 |
} | ||||
303 |
case Chunk_Create: | ||||
6 months ago |
304 |
// Request to create a new chunk on the stack | |||
305 |
// We're here if the coroutine is: | ||||
306 |
// Allocated, but not 'run' (Coroutine_Idle) | ||||
307 |
// Run, but not not entered yet (Coroutine_Running) | ||||
308 |
// Completed (Coroutine_Complete) | ||||
309 |
assert(here.state == Coroutine_Idle || here.state == Coroutine_Running || here.state == Coroutine_Complete); | ||||
310 |
unsigned char *ideal_limit = (unsigned char *)&here - COROUTINE_STACK_SIZE; | ||||
311 |
stack_chunk_chunk(&here, StackTopNow() - ideal_limit); | ||||
6 months ago |
312 |
assert(false); | |||
313 |
case Chunk_Enter: | ||||
314 |
// request to start a coroutine (ie use the chunk for a coroutine) | ||||
315 |
// arrive here with mutex locked | ||||
316 |
assert(here.state == Coroutine_Running); | ||||
317 |
g_c.active = &here; | ||||
318 |
_Cor_Mutex_Unlock(&g_c.mutex); | ||||
319 |
here.value = here.start(here.entry_param); | ||||
8 months ago |
320 |
||||
6 months ago |
321 |
// check the guard | |||
322 |
assert(Check_Guard(here.guard)); | ||||
8 months ago |
323 |
||||
6 months ago |
324 |
_Cor_Mutex_Lock(&g_c.mutex); | |||
325 |
g_c.active = NULL; | ||||
326 |
assert(here.state == Coroutine_Running); | ||||
327 |
List_Remove(&here.link); | ||||
328 |
here.state = Coroutine_Complete; | ||||
329 |
List_AddTail(&g_c.inactive, &here.link); | ||||
330 |
// Coroutine has completed | ||||
331 |
// Loop round to redo the setjmp() - if this coroutine yielded, then the setjmp will | ||||
332 |
// need reseting | ||||
8 months ago |
4 |
333 |
} | ||
334 |
} | ||||
335 |
} | ||||
336 |
|||||
8 months ago |
337 |
||||
7 months ago |
338 |
static void Coroutine_RunNext(void) | |||
8 months ago |
339 |
{ | |||
340 |
// arrive here with mutex unlocked | ||||
7 months ago |
341 |
_Cor_Mutex_Lock(&g_c.waiting_mutex); | |||
342 |
_Cor_Mutex_Lock(&g_c.mutex); | ||||
343 |
Coroutine *next = List_Link_Container(Coroutine, link, List_GetHead(&g_c.runable)); | ||||
8 months ago |
344 |
assert(next->state == Coroutine_Running); | |||
345 |
longjmp(next->buf, Chunk_Enter); | ||||
346 |
assert(false); | ||||
347 |
} | ||||
348 |
|||||
349 |
|||||
7 months ago |
350 |
void Coroutine_StartSystem(void) | |||
8 months ago |
4 |
351 |
{ | ||
7 months ago |
352 |
assert(g_c.state == Coroutines_Idle); | |||
353 |
g_c.state = Coroutines_Starting; | ||||
8 months ago |
354 |
||||
7 months ago |
355 |
_Cor_Mutex_ctor(&g_c.mutex); | |||
8 months ago |
356 |
||||
7 months ago |
357 |
g_c.tip = NULL; | |||
358 |
g_c.active = NULL; | ||||
8 months ago |
359 |
||||
7 months ago |
360 |
List_Init(&g_c.free); | |||
361 |
List_Init(&g_c.inactive); | ||||
362 |
List_Init(&g_c.runable); | ||||
363 |
List_Init(&g_c.waiting); | ||||
364 |
_Cor_Mutex_ctor(&g_c.waiting_mutex); | ||||
365 |
_Cor_Mutex_Lock(&g_c.waiting_mutex); | ||||
8 months ago |
4 |
366 |
|||
7 months ago |
367 |
g_c.report.coroutines_created = 0; | |||
368 |
g_c.report.coroutines_pool_size = 0; | ||||
8 months ago |
4 |
369 |
|||
370 |
// prime the chunk system | ||||
7 months ago |
371 |
if (!setjmp(g_c.chunk_allocated)){ | |||
8 months ago |
4 |
372 |
Coroutine_PrimeStackChunks(); | ||
373 |
assert(false); | ||||
374 |
} | ||||
8 months ago |
375 |
||||
7 months ago |
376 |
assert(g_c.state == Coroutines_Starting); | |||
377 |
g_c.state = Coroutines_Started; | ||||
8 months ago |
4 |
378 |
} | ||
379 |
|||||
8 months ago |
380 |
||||
6 months ago |
381 |
void Coroutine_SetStackLimit(void *limit){ | |||
6 months ago |
382 |
assert(!limit || !(g_c.state == Coroutines_Started || g_c.state == Coroutines_Active) || (unsigned char *)limit < (unsigned char *)g_c.tip); | |||
6 months ago |
383 |
g_c.stack_limit = limit; | |||
384 |
} | ||||
385 |
|||||
386 |
|||||
7 months ago |
387 |
Coroutine_Report Coroutine_StopSystem(void) | |||
8 months ago |
4 |
388 |
{ | ||
7 months ago |
389 |
_Cor_Mutex_Lock(&g_c.mutex); | |||
390 |
assert(g_c.state == Coroutines_Started); | ||||
391 |
g_c.state = Coroutines_Stopping; | ||||
8 months ago |
4 |
392 |
|||
6 months ago |
393 |
uintptr_t stackminheadroom;; | |||
394 |
#if COROUTINE_RECORD_LOWEST_HEADROOM | ||||
395 |
stackminheadroom = COROUTINE_STACK_SIZE; | ||||
7 months ago |
396 |
for (List_Link *link = g_c.free.fwd.link.next; link->next; link = link->next){ | |||
8 months ago |
397 |
Coroutine *cor = List_Link_Container(Coroutine, link, link); | |||
6 months ago |
398 |
if (cor->guard){ | |||
399 |
for (uintptr_t i = 4; i < COROUTINE_STACK_SIZE-3; i += 4){ | ||||
400 |
if (!Check_Guard(&cor->guard[i])){ | ||||
401 |
stackminheadroom = i < stackminheadroom ? i : stackminheadroom; | ||||
402 |
break; | ||||
403 |
} | ||||
8 months ago |
404 |
} | |||
405 |
} | ||||
406 |
} | ||||
6 months ago |
407 |
#else | |||
408 |
stackminheadroom = 0; | ||||
409 |
#endif | ||||
7 months ago |
410 |
g_c.report.lowest_headroom = stackminheadroom; | |||
8 months ago |
411 |
||||
7 months ago |
412 |
assert(List_IsEmpty(&g_c.inactive)); | |||
413 |
_Cor_Mutex_Unlock(&g_c.waiting_mutex); | ||||
414 |
_Cor_Mutex_dtor(&g_c.waiting_mutex); | ||||
8 months ago |
4 |
415 |
|||
7 months ago |
416 |
assert(g_c.state == Coroutines_Stopping); | |||
417 |
g_c.state = Coroutines_Idle; | ||||
418 |
_Cor_Mutex_Unlock(&g_c.mutex); | ||||
419 |
_Cor_Mutex_dtor(&g_c.mutex); | ||||
8 months ago |
420 |
||||
7 months ago |
421 |
return g_c.report; | |||
8 months ago |
4 |
422 |
} | ||
423 |
|||||
8 months ago |
424 |
||||
8 months ago |
425 |
void Coroutine_Run_Coroutine( | |||
426 |
Coroutine *cor, | ||||
8 months ago |
427 |
void *value | |||
428 |
){ | ||||
429 |
Coroutines *cors = cor->coroutines; | ||||
7 months ago |
430 |
assert(&g_c == cors); | |||
7 months ago |
431 |
_Cor_Mutex_Lock(&cors->mutex); | |||
8 months ago |
432 |
assert(cors->state == Coroutines_Started); | |||
433 |
cors->state = Coroutines_Active; | ||||
434 |
cors->primary = cor; | ||||
8 months ago |
435 |
||||
7 months ago |
436 |
_Coroutine_Continue(cor, value, true); | |||
8 months ago |
4 |
437 |
|||
8 months ago |
438 |
if (!setjmp(cors->controller)){ | |||
7 months ago |
439 |
_Cor_Mutex_Unlock(&cors->mutex); | |||
8 months ago |
440 |
||||
441 |
// check the guard | ||||
7 months ago |
442 |
assert(Check_Guard(cors->guard)); | |||
8 months ago |
443 |
||||
8 months ago |
4 |
444 |
// start the first coroutine | ||
445 |
Coroutine_RunNext(); | ||||
446 |
} | ||||
8 months ago |
447 |
// arrive here with mutex locked | |||
8 months ago |
448 |
assert(List_IsEmpty(&cors->runable)); | |||
449 |
assert(List_IsEmpty(&cors->waiting)); | ||||
450 |
assert(cors->state == Coroutines_Active); | ||||
451 |
cors->state = Coroutines_Started; | ||||
7 months ago |
452 |
_Cor_Mutex_Unlock(&cors->mutex); | |||
8 months ago |
453 |
} | |||
454 |
|||||
455 |
|||||
456 |
void *Coroutine_Run( | ||||
457 |
Coroutine_Start start, | ||||
458 |
void *value | ||||
459 |
){ | ||||
6 months ago |
460 |
if (g_c.active){ | |||
461 |
return start(value); | ||||
462 |
} | ||||
463 |
assert(g_c.state == Coroutines_Idle || g_c.state == Coroutines_Started); | ||||
464 |
bool need_start = g_c.state == Coroutines_Idle; | ||||
465 |
if (need_start){ | ||||
466 |
Coroutine_StartSystem(); | ||||
467 |
} | ||||
8 months ago |
468 |
Coroutine *cor = Coroutine_New(start); | |||
5 months ago |
469 |
assert(cor); | |||
8 months ago |
470 |
Coroutine_Run_Coroutine(cor, value); | |||
8 months ago |
471 |
void *res = Coroutine_GetValue(cor); | |||
472 |
Coroutine_Delete(cor); | ||||
6 months ago |
473 |
if (need_start){ | |||
474 |
Coroutine_StopSystem(); | ||||
475 |
} | ||||
8 months ago |
476 |
return res; | |||
8 months ago |
4 |
477 |
} | ||
478 |
|||||
479 |
|||||
8 months ago |
480 |
Coroutine *Coroutine_New( | |||
481 |
Coroutine_Start start | ||||
482 |
){ | ||||
7 months ago |
483 |
assert((g_c.state == Coroutines_Started && List_IsEmpty(&g_c.inactive)) || g_c.state == Coroutines_Active); | |||
6 months ago |
484 |
assert(Coroutine_StackHasNotOverrun()); | |||
8 months ago |
485 |
||||
8 months ago |
4 |
486 |
// if none free - add one | ||
7 months ago |
487 |
if (List_IsEmpty(&g_c.free)){ | |||
5 months ago |
488 |
// no free stack blocks | |||
489 |
if (g_c.stack_limit && g_c.stack_limit > (unsigned char *)g_c.tip - 2*COROUTINE_STACK_SIZE){ | ||||
490 |
// no space for a new stack block | ||||
491 |
return NULL; | ||||
492 |
} | ||||
6 months ago |
493 |
Coroutine *tip = g_c.tip; | |||
6 months ago |
494 |
Coroutine *me = g_c.active; | |||
495 |
if (tip == me) { | ||||
496 |
if (!setjmp(g_c.chunk_allocated)){ | ||||
497 |
unsigned char *ideal_limit = (unsigned char *)me - COROUTINE_STACK_SIZE; | ||||
498 |
stack_chunk_chunk(me, StackTopNow() - ideal_limit); | ||||
499 |
} | ||||
500 |
} else { | ||||
501 |
if (!setjmp(g_c.chunk_allocated)){ | ||||
502 |
longjmp(tip->buf, Chunk_Create); | ||||
503 |
} | ||||
8 months ago |
4 |
504 |
} | ||
505 |
} | ||||
506 |
|||||
7 months ago |
507 |
Coroutine *cor = List_Link_Container(Coroutine, link, List_GetHead(&g_c.free)); | |||
8 months ago |
4 |
508 |
assert(cor->state == Coroutine_Free); | ||
509 |
cor->state = Coroutine_Idle; | ||||
510 |
cor->start = start; | ||||
511 |
cor->value = NULL; | ||||
512 |
List_Remove(&cor->link); | ||||
7 months ago |
513 |
List_AddHead(&g_c.inactive, &cor->link); | |||
8 months ago |
4 |
514 |
|||
7 months ago |
515 |
g_c.report.coroutines_created += 1; | |||
8 months ago |
516 |
||||
8 months ago |
4 |
517 |
return cor; | ||
518 |
} | ||||
519 |
|||||
8 months ago |
520 |
||||
521 |
void Coroutine_Delete( | ||||
522 |
Coroutine *cor | ||||
523 |
){ | ||||
6 months ago |
524 |
assert(Coroutine_StackHasNotOverrun()); | |||
5 months ago |
525 |
if (cor){ | |||
526 |
Coroutines *cors = cor->coroutines; | ||||
527 |
_Cor_Mutex_Lock(&cors->mutex); | ||||
528 |
assert(cor->state == Coroutine_Idle || cor->state == Coroutine_Complete); | ||||
529 |
cor->state = Coroutine_Free; | ||||
530 |
List_Remove(&cor->link); | ||||
531 |
List_AddTail(&cors->free, &cor->link); | ||||
532 |
_Cor_Mutex_Unlock(&cors->mutex); | ||||
533 |
} | ||||
8 months ago |
4 |
534 |
} | ||
535 |
|||||
8 months ago |
536 |
||||
7 months ago |
537 |
// Coroutine_Continue, assuming the mutex is claimed | |||
538 |
static void _Coroutine_Continue( | ||||
8 months ago |
539 |
Coroutine *cor, | |||
540 |
void *value, | ||||
541 |
bool early | ||||
542 |
){ | ||||
543 |
Coroutines *cors = cor->coroutines; | ||||
8 months ago |
4 |
544 |
assert(cor->state == Coroutine_Idle || cor->state == Coroutine_Waiting); | ||
545 |
cor->entry_param = value; | ||||
546 |
cor->state = Coroutine_Running; | ||||
547 |
List_Remove(&cor->link); | ||||
548 |
if ( early ) { | ||||
8 months ago |
549 |
List_AddHead(&cors->runable, &cor->link); | |||
8 months ago |
4 |
550 |
} else { | ||
8 months ago |
551 |
List_AddTail(&cors->runable, &cor->link); | |||
8 months ago |
4 |
552 |
} | ||
7 months ago |
553 |
_Cor_Mutex_Unlock(&cors->waiting_mutex); | |||
7 months ago |
554 |
} | |||
555 |
|||||
556 |
|||||
557 |
void Coroutine_Continue( | ||||
558 |
Coroutine *cor, | ||||
559 |
void *value, | ||||
560 |
bool early | ||||
561 |
){ | ||||
6 months ago |
562 |
assert(Coroutine_StackHasNotOverrun()); | |||
7 months ago |
563 |
Coroutines *cors = cor->coroutines; | |||
7 months ago |
564 |
_Cor_Mutex_Lock(&cors->mutex); | |||
7 months ago |
565 |
_Coroutine_Continue(cor, value, early); | |||
7 months ago |
566 |
_Cor_Mutex_Unlock(&cors->mutex); | |||
8 months ago |
4 |
567 |
} | ||
568 |
|||||
8 months ago |
569 |
||||
570 |
void *Coroutine_Yield( | ||||
571 |
void *value, | ||||
572 |
Coroutine_YieldCallback on_yield, | ||||
8 months ago |
573 |
void *yield_me | |||
8 months ago |
574 |
){ | |||
7 months ago |
575 |
Coroutine *me = g_c.active; | |||
6 months ago |
576 |
assert(me); | |||
577 |
assert(Coroutine_StackHasNotOverrun()); | ||||
8 months ago |
578 |
||||
7 months ago |
579 |
_Cor_Mutex_Lock(&g_c.mutex); | |||
8 months ago |
580 |
Coroutines *cors = me->coroutines; | |||
7 months ago |
581 |
assert(me && me->state == Coroutine_Running && cors == &g_c); | |||
6 months ago |
582 |
me->stack_top = StackTopNow(); | |||
8 months ago |
4 |
583 |
me->value = value; | ||
584 |
me->state = Coroutine_Waiting; | ||||
8 months ago |
585 |
||||
8 months ago |
4 |
586 |
List_Remove(&me->link); | ||
7 months ago |
587 |
if (!List_IsEmpty(&cors->runable)){ | |||
7 months ago |
588 |
_Cor_Mutex_Unlock(&cors->waiting_mutex); | |||
7 months ago |
589 |
} | |||
8 months ago |
590 |
List_AddTail(&cors->waiting, &me->link); | |||
8 months ago |
591 |
||||
8 months ago |
592 |
switch (setjmp(me->buf)){ | |||
593 |
case Chunk_Initial: | ||||
7 months ago |
594 |
_Cor_Mutex_Unlock(&cors->mutex); | |||
8 months ago |
595 |
on_yield(yield_me); | |||
8 months ago |
4 |
596 |
Coroutine_RunNext(); | ||
6 months ago |
597 |
assert(false); | |||
8 months ago |
598 |
case Chunk_Create: | |||
6 months ago |
599 |
assert(me == g_c.tip); | |||
600 |
unsigned char *ideal_limit = (unsigned char *)me - COROUTINE_STACK_SIZE; | ||||
601 |
stack_chunk_chunk(me, me->stack_top - ideal_limit); | ||||
8 months ago |
602 |
assert(false); | |||
603 |
case Chunk_Enter: | ||||
604 |
// arrive here with mutex locked | ||||
8 months ago |
605 |
cors->active = me; | |||
6 months ago |
606 |
assert(Coroutine_StackHasNotOverrun()); | |||
8 months ago |
607 |
// when we return here - we are running again | |||
608 |
assert(me->state == Coroutine_Running); | ||||
609 |
void *res = me->entry_param; | ||||
7 months ago |
610 |
_Cor_Mutex_Unlock(&cors->mutex); | |||
8 months ago |
611 |
return res; | |||
8 months ago |
4 |
612 |
} | ||
8 months ago |
613 |
return NULL; | |||
8 months ago |
4 |
614 |
} | ||
615 |
|||||
8 months ago |
616 |
||||
617 |
void *Coroutine_GetValue( | ||||
618 |
Coroutine *cor | ||||
619 |
){ | ||||
8 months ago |
4 |
620 |
return cor->value; | ||
621 |
} | ||||
622 |
|||||
8 months ago |
623 |
||||
7 months ago |
624 |
Coroutine *Coroutine_GetActive(void) | |||
8 months ago |
4 |
625 |
{ | ||
7 months ago |
626 |
return g_c.active; | |||
8 months ago |
4 |
627 |
} | ||
628 |
|||||
8 months ago |
629 |
||||
7 months ago |
630 |
intptr_t Coroutine_GetStackHeadroom(void){ | |||
6 months ago |
631 |
assert(Coroutine_StackHasNotOverrun()); | |||
632 |
Coroutine *me = g_c.active; | ||||
633 |
if (!me){ | ||||
634 |
// no active coroutine | ||||
635 |
unsigned char *stack_limit = g_c.stack_limit; | ||||
636 |
if (stack_limit){ | ||||
637 |
// no stack limit - assume we'll use COROUTINE_STACK_SIZE | ||||
638 |
return StackTopNow() - stack_limit; | ||||
639 |
} else { | ||||
640 |
// no information where the stack ends - return something | ||||
641 |
return COROUTINE_STACK_SIZE; | ||||
642 |
} | ||||
643 |
} | ||||
644 |
unsigned char *stack_top = StackTopNow(); | ||||
645 |
if (me->guard){ | ||||
646 |
// guard established - that's where we'll measure to | ||||
647 |
return stack_top - me->guard; | ||||
648 |
} | ||||
649 |
intptr_t used = (unsigned char *)me - stack_top; | ||||
650 |
unsigned char *stack_limit = g_c.stack_limit; | ||||
651 |
if (!stack_limit){ | ||||
652 |
// no stack limit - assume we'll use COROUTINE_STACK_SIZE | ||||
653 |
return COROUTINE_STACK_SIZE - used; | ||||
654 |
} | ||||
655 |
intptr_t available = (unsigned char *)me - stack_limit; | ||||
6 months ago |
656 |
if (available < (intptr_t)(2*COROUTINE_STACK_SIZE)){ | |||
6 months ago |
657 |
// can't start another coroutine, so whatever's left in the C stack is what we've got | |||
658 |
return available - used; | ||||
659 |
} | ||||
660 |
// can start another coroutine, so limit ourselves to a coroutine stack size's worth | ||||
661 |
return COROUTINE_STACK_SIZE - used; | ||||
8 months ago |
662 |
} | |||
663 |
|||||
664 |
|||||
6 months ago |
665 |
// This is used to avoid compiler warnings about returning the address of a local | |||
666 |
static inline void *StopAddressWarnings(void *p) | ||||
667 |
{ | ||||
668 |
return p; | ||||
8 months ago |
669 |
} | |||
670 |
|||||
671 |
|||||
6 months ago |
672 |
void *Coroutine_GetStackHWM(void){ | |||
673 |
assert(g_c.state == Coroutines_Active); | ||||
6 months ago |
674 |
assert(Coroutine_StackHasNotOverrun()); | |||
6 months ago |
675 |
// Find where the guards end | |||
676 |
unsigned char *guard; | ||||
677 |
for (guard = g_c.active->guard; Check_Guard(guard); guard += 4){ | ||||
678 |
// do nothing | ||||
679 |
} | ||||
680 |
return guard; | ||||
681 |
} | ||||
7 months ago |
682 |
||||
683 |
|||||
6 months ago |
684 |
void Coroutine_ClearStackForHWM(void){ | |||
685 |
assert(g_c.state == Coroutines_Active); | ||||
6 months ago |
686 |
assert(Coroutine_StackHasNotOverrun()); | |||
6 months ago |
687 |
unsigned char *end = StackTopNow() - GUARD_PATTERN_SIZE; | |||
688 |
for (unsigned char *guard = g_c.active->guard+GUARD_PATTERN_SIZE; guard <= end; guard += GUARD_PATTERN_SIZE){ | ||||
689 |
Apply_Guard(guard); | ||||
6 months ago |
690 |
} | |||
691 |
} | ||||
692 |
|||||
693 |
|||||
6 months ago |
694 |
bool Coroutine_CanStartCoroutine(void){ | |||
6 months ago |
695 |
assert(g_c.state == Coroutines_Started || g_c.state == Coroutines_Active); | |||
696 |
assert(Coroutine_StackHasNotOverrun()); | ||||
6 months ago |
697 |
if (!List_IsEmpty(&g_c.free)){ | |||
698 |
return true; | ||||
699 |
} | ||||
6 months ago |
700 |
||||
701 |
return !g_c.stack_limit || g_c.stack_limit <= (unsigned char *)g_c.tip - 2*COROUTINE_STACK_SIZE; | ||||
6 months ago |
702 |
} | |||
703 |
|||||
704 |
|||||
7 months ago |
705 |
void *Coroutine_GetCStackTop(void){ | |||
6 months ago |
706 |
assert(Coroutine_StackHasNotOverrun()); | |||
707 |
if ((g_c.state == Coroutines_Started || g_c.state == Coroutines_Active) && g_c.tip != g_c.active) { | ||||
708 |
return g_c.tip->stack_top; | ||||
709 |
} else { | ||||
710 |
return StackTopNow(); | ||||
711 |
} | ||||
8 months ago |
712 |
} | |||
713 |
|||||
714 |
|||||
6 months ago |
715 |
static unsigned char *StackTopNow(void){ | |||
6 months ago |
716 |
unsigned char here[4]; | |||
717 |
return StopAddressWarnings(here); | ||||
718 |
} | ||||
719 |
|||||
720 |
|||||
8 months ago |
721 |
struct Coroutine_ChainParam { | |||
722 |
Coroutine_Start start; | ||||
723 |
void *value; | ||||
724 |
Coroutine *ret; | ||||
725 |
}; | ||||
726 |
|||||
727 |
|||||
728 |
static void *Coroutine_ChainFn( | ||||
729 |
void *param | ||||
730 |
){ | ||||
731 |
struct Coroutine_ChainParam *params = (struct Coroutine_ChainParam *)param; | ||||
732 |
Coroutine_Continue(params->ret, params->start(params->value), true); | ||||
733 |
return NULL; | ||||
734 |
} | ||||
735 |
|||||
736 |
|||||
737 |
static void Coroutine_ChainYield( | ||||
738 |
void *unused | ||||
739 |
){ | ||||
740 |
(void)unused; | ||||
741 |
} | ||||
742 |
|||||
743 |
|||||
744 |
void *Coroutine_Chain( | ||||
745 |
Coroutine_Start start, | ||||
746 |
void *value | ||||
747 |
){ | ||||
7 months ago |
748 |
assert(Check_Guard(Coroutine_GetActive()->guard)); | |||
8 months ago |
749 |
Coroutine *cor = Coroutine_New(Coroutine_ChainFn); | |||
5 months ago |
750 |
assert(cor); | |||
8 months ago |
751 |
struct Coroutine_ChainParam params = { | |||
752 |
start, | ||||
753 |
value, | ||||
754 |
Coroutine_GetActive() | ||||
755 |
}; | ||||
756 |
Coroutine_Continue(cor, ¶ms, true); | ||||
757 |
void *res = Coroutine_Yield(NULL, Coroutine_ChainYield, NULL); | ||||
758 |
Coroutine_Delete(cor); | ||||
759 |
return res; | ||||
760 |
} | ||||
761 |
|||||
762 |
|||||
8 months ago |
763 |
bool Coroutine_IsRunning( | |||
764 |
Coroutine *cor | ||||
765 |
) | ||||
8 months ago |
4 |
766 |
{ | ||
8 months ago |
767 |
int state = cor->state; | |||
768 |
return state == Coroutine_Running || state == Coroutine_Waiting; | ||||
8 months ago |
4 |
769 |
} | ||
8 months ago |
770 |
||||
771 |
|||||
6 months ago |
772 |
bool Coroutine_IsComplete( | |||
773 |
Coroutine *cor | ||||
774 |
) | ||||
775 |
{ | ||||
776 |
int state = cor->state; | ||||
777 |
return state == Coroutine_Complete; | ||||
778 |
} | ||||
779 |
|||||
780 |
|||||
7 months ago |
781 |
bool Coroutine_IsStarted(void){ | |||
7 months ago |
782 |
return g_c.state == Coroutines_Active || g_c.state == Coroutines_Started; | |||
8 months ago |
783 |
} | |||
784 |