1 contributor
1101 lines29.3 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>
5 months ago
6
#include <stdio.h>
7 months ago
7
#include "cor_platform.h"
8 months ago
4
8
6 months ago
9
// see CPython again, this time from ctypes.h
10
#if (defined (__SVR4) && defined (__sun)) || defined(COROUTINE_HAVE_ALLOCA_H)
11
# include <alloca.h>
12
#elif defined(MS_WIN32)
13
# include <malloc.h>
14
#endif
8 months ago
4
15
6 months ago
16
/* If the system does not define alloca(), we have to hope for a compiler builtin. */
17
#ifndef alloca
18
# if defined __GNUC__ || (__clang_major__ >= 4)
19
# define alloca __builtin_alloca
20
# else
21
# error "Could not define alloca() on your platform."
22
# endif
23
#endif
24
7 months ago
25
static void Coroutine_RunNext(void);
7 months ago
26
static void _Coroutine_Continue(Coroutine *cor, void *value, bool early);
6 months ago
27
static unsigned char *StackTopNow(void);
8 months ago
28
8 months ago
4
29
///////////////////////////////////////////////////////////////////////////////
30
// 2-way linked lists...
31
//
8 months ago
32
// Brought inline here to avoid namespace polution
8 months ago
4
33
///////////////////////////////////////////////////////////////////////////////
34
35
typedef struct List_Link List_Link;
36
struct List_Link {
37
List_Link *next;
38
List_Link *prev;
39
};
40
41
typedef struct List_Head List_Head;
42
struct List_Head {
43
union {
44
struct {
45
List_Link link;
46
List_Link *filler;
47
} fwd;
48
struct {
49
List_Link *filler;
50
List_Link link;
51
} back;
52
};
53
};
54
8 months ago
55
56
static inline bool List_IsEmpty(
57
const List_Head *list
58
){
8 months ago
4
59
return list->fwd.link.next == &list->back.link;
60
}
61
8 months ago
62
63
static inline List_Link *List_GetHead(
64
const List_Head *list
65
){
8 months ago
4
66
return List_IsEmpty(list) ? NULL : list->fwd.link.next;
67
}
8 months ago
68
69
5 months ago
70
static inline List_Link *List_Begin(
71
const List_Head *list
72
){
73
return list->fwd.link.next;
74
}
75
76
77
static inline bool Link_NextIsLink(
78
const List_Link *link
79
){
80
return link->next != NULL;
81
}
82
83
84
static inline List_Link *Link_Next(
85
List_Link *link
86
){
87
return link->next;
88
}
89
90
91
static inline bool Link_PrevIsLink(
92
const List_Link *link
93
){
94
return link->prev != NULL;
95
}
96
97
98
static inline List_Link *Link_Prev(
99
List_Link *link
100
){
101
return link->prev;
102
}
103
5 months ago
104
static inline List_Link *List_GetTail(
105
const List_Head *list
106
){
107
return List_IsEmpty(list) ? NULL : list->back.link.prev;
108
}
8 months ago
109
110
8 months ago
4
111
#define OFFSETOF(Container, Field) ((char *)&((Container *)4)->Field - (char *)(Container *)4)
112
#define List_Link_Container(Container, Link, link) ((Container *)((char *)(link) - OFFSETOF(Container, Link)))
113
8 months ago
114
115
static inline void List_Init(
116
List_Head *list
117
){
8 months ago
4
118
list->fwd.link.next = &list->back.link;
119
list->fwd.link.prev = NULL;
120
list->back.link.prev = &list->fwd.link;
121
}
122
8 months ago
123
5 months ago
124
static inline void Link_AddAfter(
125
List_Link *link,
126
List_Link *after
127
){
128
link->next = after->next;
129
link->prev = after;
130
after->next->prev = link;
131
after->next = link;
132
}
133
134
8 months ago
135
static inline void List_AddHead(
136
List_Head *list,
137
List_Link *link
138
){
5 months ago
139
Link_AddAfter(link, &list->fwd.link);
8 months ago
4
140
}
141
8 months ago
142
5 months ago
143
static inline void Link_AddBefore(
144
List_Link *link,
145
List_Link *before
146
){
147
link->prev = before->prev;
148
link->next = before;
149
before->prev->next = link;
150
before->prev = link;
151
}
152
153
8 months ago
154
static inline void List_AddTail(
155
List_Head *list,
156
List_Link *link
157
){
5 months ago
158
Link_AddBefore(link, &list->back.link);
8 months ago
4
159
}
160
8 months ago
161
5 months ago
162
static inline void Link_Remove(
8 months ago
163
List_Link *link
164
){
8 months ago
4
165
link->prev->next = link->next;
166
link->next->prev = link->prev;
167
}
168
169
///////////////////////////////////////////////////////////////////////////////
170
// ...2-way linked lists
171
///////////////////////////////////////////////////////////////////////////////
172
8 months ago
173
typedef struct Coroutines Coroutines;
8 months ago
4
174
175
enum {
176
Coroutines_Starting,
177
Coroutines_Started,
178
Coroutines_Active,
179
Coroutines_Stopping
180
};
181
182
enum {
183
Chunk_Initial,
184
Chunk_Create,
5 months ago
185
Chunk_Split,
8 months ago
4
186
Chunk_Enter
187
};
188
8 months ago
189
typedef enum Coroutine_State {
8 months ago
4
190
Coroutine_Free,
191
Coroutine_Idle,
192
Coroutine_Running,
193
Coroutine_Waiting,
194
Coroutine_Complete
8 months ago
195
} Coroutine_State;
8 months ago
4
196
197
enum {
198
Coroutines_Init,
199
Coroutines_AllocatedChunk,
200
Coroutines_CoroutineComplete,
201
};
202
203
struct Coroutine {
6 months ago
204
Coroutines *coroutines; // so can work with it off-thread
205
List_Link link; // for whichever list it's on
5 months ago
206
List_Link all_link; // list of all Coroutines
6 months ago
207
jmp_buf buf; // how to get back to it
5 months ago
208
unsigned char *prev_limit; // the previous Coroutine's stack limit
209
unsigned char *base; // where the base (high address) of this Coroutine's stack is
210
unsigned char *limit; // where the limit (low address) of this Coroutine's stack is
6 months ago
211
unsigned char *guard; // where the stack overrun guard is
5 months ago
212
size_t size;
6 months ago
213
Coroutine_Start start; // entry point
214
void *entry_param; // to pass to start
215
void *value; // yielded/returned
216
unsigned char *stack_top; // recorded at yield
8 months ago
217
Coroutine_State state;
8 months ago
4
218
};
219
220
struct Coroutines {
7 months ago
221
_Cor_Mutex mutex;
8 months ago
222
jmp_buf controller; // to return from Coroutine_Run
223
jmp_buf chunk_allocated;// for chunk allocation
5 months ago
224
size_t gap_before; // bytes between previous's stack_top and next's Coroutine
225
size_t gap_after; // bytes between Coroutine and stack_base
8 months ago
4
226
227
// singletons
228
Coroutine *tip; // top of stack chunk
229
Coroutine *active; // currently running coroutine
230
Coroutine *primary; // Coroutine_Run coroutine
6 months ago
231
unsigned char *stack_limit; // when not NULL, where the stack finishes
8 months ago
4
232
233
// lists
5 months ago
234
List_Head all; // all Coroutines (in address order)
235
List_Head free; // free Coroutines
8 months ago
4
236
List_Head inactive; // idle or complete
237
List_Head runable; // running or waiting to run
238
List_Head waiting; // yielded / waiting to run
7 months ago
239
_Cor_Mutex waiting_mutex;
8 months ago
4
240
8 months ago
241
// Summary of the system
242
Coroutine_Report report;
243
8 months ago
4
244
// state
245
char state;
246
};
247
5 months ago
248
_Cor_thread_local Coroutines *g_c;
8 months ago
4
249
5 months ago
250
static void ReserveStackSpace(Coroutines *cors, Coroutine *parent, size_t chunk_size, unsigned char *childs_limit);
5 months ago
251
static void stack_chunk_base(Coroutines *cors, Coroutine *parent, unsigned char *prev_limit, unsigned char *limit);
8 months ago
4
252
8 months ago
253
6 months ago
254
#define GUARD_PATTERN_SIZE (4)
8 months ago
255
// Check whether the guard is intact
256
static inline bool Check_Guard(
257
unsigned char *guard
258
){
6 months ago
259
return !guard ||
260
(guard[0] == 0xde &&
261
guard[1] == 0xad &&
262
guard[2] == 0xbe &&
263
guard[3] == 0xef);
8 months ago
264
}
265
266
6 months ago
267
static inline void Apply_Guard(unsigned char *guard){
268
guard[0] = 0xde;
269
guard[1] = 0xad;
270
guard[2] = 0xbe;
271
guard[3] = 0xef;
272
}
273
274
5 months ago
275
static bool Coroutine_StackHasOverrun(void){
6 months ago
276
unsigned char *stack_top = StackTopNow();
5 months ago
277
unsigned char *stack_limit = g_c ? g_c->stack_limit : NULL;
6 months ago
278
if (stack_limit && stack_top < stack_limit){
279
// current stack top is beyond limit - we are overrunning NOW
5 months ago
280
return true;
6 months ago
281
}
5 months ago
282
Coroutine *me = g_c ? g_c->active : NULL;
6 months ago
283
if (!me){
5 months ago
284
return false;
6 months ago
285
}
286
if (me->guard){
5 months ago
287
return !Check_Guard(me->guard);
6 months ago
288
}
5 months ago
289
return stack_top < me->limit;
6 months ago
290
}
291
292
5 months ago
293
static void ReserveStackSpace(
294
Coroutines *cors,
6 months ago
295
Coroutine *parent,
5 months ago
296
size_t chunk_size,
297
unsigned char *childs_limit
8 months ago
298
){
6 months ago
299
unsigned char *chunk_of_stack = alloca(chunk_size);
300
#if COROUTINE_RECORD_LOWEST_HEADROOM
301
for (size_t i = 0; i <= chunk_size-GUARD_PATTERN_SIZE; i += GUARD_PATTERN_SIZE){
302
Apply_Guard(&chunk_of_stack[i]);
303
}
304
#else
6 months ago
305
Apply_Guard(chunk_of_stack);
6 months ago
306
#endif
5 months ago
307
if (parent){
308
parent->guard = chunk_of_stack;
309
parent->limit = chunk_of_stack;
310
parent->base = chunk_of_stack + chunk_size;
311
}
5 months ago
312
stack_chunk_base(cors, parent, chunk_of_stack, childs_limit);
8 months ago
4
313
}
314
8 months ago
315
316
static void stack_chunk_base(
5 months ago
317
Coroutines *cors,
5 months ago
318
Coroutine *parent,
5 months ago
319
unsigned char *prev_limit,
320
unsigned char *limit
8 months ago
321
){
8 months ago
4
322
Coroutine here;
5 months ago
323
here.coroutines = cors;
6 months ago
324
here.state = Coroutine_Free;
5 months ago
325
here.prev_limit = prev_limit;
326
here.size = 0;
327
here.base = NULL;
328
here.guard = limit;
329
here.limit = limit;
330
if (limit){
331
here.base = (unsigned char *)&here - cors->gap_after;
332
here.size = here.base - here.limit;
333
Apply_Guard(limit);
334
}
335
5 months ago
336
// insert into all list
337
if (parent){
338
Link_AddAfter(&here.all_link, &parent->all_link);
339
} else {
340
List_AddHead(&cors->all, &here.all_link);
5 months ago
341
}
5 months ago
342
// add to free list
343
List_AddTail(&cors->free, &here.link);
5 months ago
344
345
cors->report.coroutines_pool_size += 1;
346
347
if (!cors->tip || &here < cors->tip){
348
cors->tip = &here;
349
}
350
6 months ago
351
for(;;){
352
switch (setjmp(here.buf)) {
353
case Chunk_Initial:
6 months ago
354
if (here.state == Coroutine_Free){
6 months ago
355
// return to the coroutine allocator
5 months ago
356
longjmp(cors->chunk_allocated, 1);
6 months ago
357
} else {
358
assert(here.state == Coroutine_Complete);
359
// we finish here to ensure the setjmp is redone
5 months ago
360
if (cors->primary == &here) {
6 months ago
361
// if primary coroutine - return to Coroutine_Run
5 months ago
362
longjmp(cors->controller, Coroutines_CoroutineComplete);
6 months ago
363
}
5 months ago
364
_Cor_Mutex_Unlock(&cors->mutex);
6 months ago
365
Coroutine_RunNext();
366
assert(false);
367
}
368
case Chunk_Create:
6 months ago
369
// Request to create a new chunk on the stack
370
// We're here if the coroutine is:
371
// Allocated, but not 'run' (Coroutine_Idle)
372
// Run, but not not entered yet (Coroutine_Running)
373
// Completed (Coroutine_Complete)
5 months ago
374
// Free, and the coroutines system is starting - we're characterising the system
375
assert(here.state == Coroutine_Idle ||
376
here.state == Coroutine_Running ||
377
here.state == Coroutine_Complete ||
378
(here.state == Coroutine_Free && cors->state == Coroutines_Starting));
379
ReserveStackSpace(here.coroutines, &here, here.size, NULL);
6 months ago
380
assert(false);
5 months ago
381
case Chunk_Split:
382
// Request to split this free block into two
383
// here.size will be set to our shorter size
384
ReserveStackSpace(here.coroutines, &here, here.size, here.limit);
385
assert(false);
6 months ago
386
case Chunk_Enter:
387
// request to start a coroutine (ie use the chunk for a coroutine)
388
// arrive here with mutex locked
389
assert(here.state == Coroutine_Running);
5 months ago
390
here.coroutines->active = &here;
391
_Cor_Mutex_Unlock(&cors->mutex);
6 months ago
392
here.value = here.start(here.entry_param);
8 months ago
393
6 months ago
394
// check the guard
395
assert(Check_Guard(here.guard));
8 months ago
396
5 months ago
397
_Cor_Mutex_Lock(&here.coroutines->mutex);
398
here.coroutines->active = NULL;
6 months ago
399
assert(here.state == Coroutine_Running);
5 months ago
400
Link_Remove(&here.link);
6 months ago
401
here.state = Coroutine_Complete;
5 months ago
402
List_AddTail(&here.coroutines->inactive, &here.link);
6 months ago
403
// Coroutine has completed
404
// Loop round to redo the setjmp() - if this coroutine yielded, then the setjmp will
405
// need reseting
8 months ago
4
406
}
407
}
408
}
409
8 months ago
410
7 months ago
411
static void Coroutine_RunNext(void)
8 months ago
412
{
413
// arrive here with mutex unlocked
5 months ago
414
_Cor_Mutex_Lock(&g_c->waiting_mutex);
415
_Cor_Mutex_Lock(&g_c->mutex);
416
Coroutine *next = List_Link_Container(Coroutine, link, List_GetHead(&g_c->runable));
8 months ago
417
assert(next->state == Coroutine_Running);
418
longjmp(next->buf, Chunk_Enter);
419
assert(false);
420
}
421
422
7 months ago
423
void Coroutine_StartSystem(void)
8 months ago
4
424
{
5 months ago
425
assert(!g_c);
8 months ago
426
5 months ago
427
Coroutines *cors = _Cor_Malloc(sizeof(*g_c));
428
assert(cors);
8 months ago
429
5 months ago
430
cors->state = Coroutines_Starting;
431
_Cor_Mutex_ctor(&cors->mutex);
8 months ago
432
5 months ago
433
cors->tip = NULL;
434
cors->active = NULL;
8 months ago
4
435
5 months ago
436
List_Init(&cors->all);
5 months ago
437
List_Init(&cors->free);
438
List_Init(&cors->inactive);
439
List_Init(&cors->runable);
440
List_Init(&cors->waiting);
441
_Cor_Mutex_ctor(&cors->waiting_mutex);
442
_Cor_Mutex_Lock(&cors->waiting_mutex);
8 months ago
4
443
5 months ago
444
cors->report.coroutines_created = 0;
445
cors->report.coroutines_pool_size = 0;
446
cors->report.largest_stack = 0;
447
448
// Charactersize the system...
449
if (!setjmp(cors->chunk_allocated)){
450
ReserveStackSpace(cors, NULL, COROUTINE_STARTUP_STACK_SIZE, NULL);
8 months ago
4
451
}
5 months ago
452
Coroutine *cor = List_Link_Container(Coroutine, link, List_GetHead(&cors->free));
453
cor->size = COROUTINE_STARTUP_STACK_SIZE;
454
if (!setjmp(cors->chunk_allocated)){
455
longjmp(cor->buf, Chunk_Create);
456
}
457
cors->gap_before = cor->prev_limit - (unsigned char *)cor;
458
cors->gap_after = (unsigned char *)cor - cor->base;
459
// ...charactersize the system
8 months ago
460
5 months ago
461
// discard what we've just created
5 months ago
462
List_Init(&cors->all);
5 months ago
463
List_Init(&cors->free);
464
cors->tip = NULL;
465
466
cors->state = Coroutines_Started;
467
468
g_c = cors;
8 months ago
4
469
}
470
8 months ago
471
6 months ago
472
void Coroutine_SetStackLimit(void *limit){
5 months ago
473
assert(g_c);
5 months ago
474
assert(!limit || !(g_c->state == Coroutines_Started || g_c->state == Coroutines_Active) || (unsigned char *)limit < (unsigned char *)g_c->tip || !g_c->tip);
5 months ago
475
g_c->stack_limit = limit;
6 months ago
476
}
477
478
7 months ago
479
Coroutine_Report Coroutine_StopSystem(void)
8 months ago
4
480
{
5 months ago
481
assert(g_c);
482
assert(g_c->state == Coroutines_Started);
483
_Cor_Mutex_Lock(&g_c->mutex);
484
g_c->state = Coroutines_Stopping;
8 months ago
4
485
5 months ago
486
uintptr_t stackminheadroom;
6 months ago
487
#if COROUTINE_RECORD_LOWEST_HEADROOM
5 months ago
488
stackminheadroom = g_c->report.largest_stack;
489
for (List_Link *link = g_c->free.fwd.link.next; Link_NextIsLink(link); link = Link_Next(link)){
8 months ago
490
Coroutine *cor = List_Link_Container(Coroutine, link, link);
6 months ago
491
if (cor->guard){
5 months ago
492
for (uintptr_t i = 4; i < cor->size-3; i += 4){
6 months ago
493
if (!Check_Guard(&cor->guard[i])){
494
stackminheadroom = i < stackminheadroom ? i : stackminheadroom;
495
break;
496
}
8 months ago
497
}
498
}
499
}
6 months ago
500
#else
501
stackminheadroom = 0;
502
#endif
5 months ago
503
g_c->report.lowest_headroom = stackminheadroom;
8 months ago
504
5 months ago
505
assert(List_IsEmpty(&g_c->inactive));
506
_Cor_Mutex_Unlock(&g_c->waiting_mutex);
507
_Cor_Mutex_dtor(&g_c->waiting_mutex);
8 months ago
4
508
5 months ago
509
assert(g_c->state == Coroutines_Stopping);
510
_Cor_Mutex_Unlock(&g_c->mutex);
511
_Cor_Mutex_dtor(&g_c->mutex);
8 months ago
512
5 months ago
513
Coroutine_Report ret = g_c->report;
514
515
_Cor_Free(g_c);
516
g_c = NULL;
517
518
return ret;
8 months ago
4
519
}
520
8 months ago
521
8 months ago
522
void Coroutine_Run_Coroutine(
523
Coroutine *cor,
8 months ago
524
void *value
525
){
526
Coroutines *cors = cor->coroutines;
5 months ago
527
528
// Can't Coroutine_Run_Coroutine() off-thread
529
assert(g_c == cors);
530
7 months ago
531
_Cor_Mutex_Lock(&cors->mutex);
8 months ago
532
assert(cors->state == Coroutines_Started);
533
cors->state = Coroutines_Active;
534
cors->primary = cor;
8 months ago
535
7 months ago
536
_Coroutine_Continue(cor, value, true);
8 months ago
4
537
8 months ago
538
if (!setjmp(cors->controller)){
7 months ago
539
_Cor_Mutex_Unlock(&cors->mutex);
8 months ago
540
8 months ago
4
541
// start the first coroutine
542
Coroutine_RunNext();
543
}
8 months ago
544
// arrive here with mutex locked
8 months ago
545
assert(List_IsEmpty(&cors->runable));
546
assert(List_IsEmpty(&cors->waiting));
547
assert(cors->state == Coroutines_Active);
548
cors->state = Coroutines_Started;
7 months ago
549
_Cor_Mutex_Unlock(&cors->mutex);
8 months ago
550
}
551
552
5 months ago
553
bool Coroutine_Run(
5 months ago
554
size_t stack,
8 months ago
555
Coroutine_Start start,
5 months ago
556
void *value,
557
void **result
8 months ago
558
){
5 months ago
559
if (g_c && g_c->active){
5 months ago
560
void *res = start(value);
561
if (result){
562
*result = res;
563
}
564
// no failures, so...
565
return false;
6 months ago
566
}
5 months ago
567
assert(!g_c || g_c->state == Coroutines_Started);
568
bool need_start = !g_c;
6 months ago
569
if (need_start){
570
Coroutine_StartSystem();
571
}
5 months ago
572
Coroutine *cor = Coroutine_New(stack, start);
5 months ago
573
if (!cor){
574
// that didn't work
575
return true;
576
}
8 months ago
577
Coroutine_Run_Coroutine(cor, value);
5 months ago
578
if (result){
579
*result = Coroutine_GetValue(cor);
580
}
8 months ago
581
Coroutine_Delete(cor);
6 months ago
582
if (need_start){
583
Coroutine_StopSystem();
584
}
5 months ago
585
// no failures, so...
586
return false;
8 months ago
4
587
}
588
589
5 months ago
590
static void Coroutine_FreeToIdle(
591
Coroutine *cor,
8 months ago
592
Coroutine_Start start
593
){
5 months ago
594
assert(cor->state == Coroutine_Free);
595
cor->state = Coroutine_Idle;
596
cor->start = start;
597
cor->value = NULL;
598
Link_Remove(&cor->link);
599
List_AddHead(&g_c->inactive, &cor->link);
8 months ago
600
5 months ago
601
g_c->report.coroutines_created += 1;
602
}
603
604
605
static void Coroutine_FreeToIdleSize(
606
Coroutine *cor,
607
Coroutine_Start start,
608
size_t size
609
){
610
assert(!cor->guard);
611
cor->size = size;
612
cor->base = (unsigned char *)cor - g_c->gap_after;
613
cor->limit = cor->base - cor->size;
614
Coroutine_FreeToIdle(cor, start);
615
}
616
617
618
static Coroutine *Coroutine_New_Lock_Assumed(
619
size_t size,
620
Coroutine_Start start
621
){
622
List_Link *link;
623
624
if (!g_c->tip){
625
// no tip - time to create one
626
627
// we're the non-Coroutine which starts the Coroutine system.
628
// Add a single free block
629
if (!setjmp(g_c->chunk_allocated)){
630
ReserveStackSpace(g_c, NULL, COROUTINE_STARTUP_STACK_SIZE, NULL);
5 months ago
631
}
5 months ago
632
}
633
5 months ago
634
Coroutine *cor = NULL;
5 months ago
635
for (link = List_Begin(&g_c->free); Link_NextIsLink(link); link = Link_Next(link)){
5 months ago
636
Coroutine *candidate = List_Link_Container(Coroutine, link, link);
637
if (!candidate->guard) {
5 months ago
638
// this must be the tip
5 months ago
639
assert(candidate == g_c->tip);
5 months ago
640
641
// If this is the only Coroutine in the system, go ahead and use it regardless of size.
642
// Note: there can only be one free block if there's no other sort of blocks as we merge on free
643
if (List_IsEmpty(&g_c->inactive) &&
644
List_IsEmpty(&g_c->runable) &&
645
List_IsEmpty(&g_c->waiting) ){
646
if (g_c->stack_limit){
5 months ago
647
size_t available = (unsigned char *)candidate - g_c->stack_limit - g_c->gap_after;
5 months ago
648
size = available < size ? available : size;
649
}
5 months ago
650
Coroutine_FreeToIdleSize(candidate, start, size);
651
return candidate;
6 months ago
652
}
5 months ago
653
654
// Not the only coroutine in the system - check size
655
if (g_c->stack_limit){
656
// there's a limit - see what that space allows....
5 months ago
657
size_t available = (unsigned char *)candidate - g_c->stack_limit - g_c->gap_after;
5 months ago
658
659
if (available < size){
660
// not enough space for this coroutine
661
return NULL;
662
}
663
664
if (available >= size + g_c->gap_before + g_c->gap_after + COROUTINE_MINIMUM_STACK_SIZE) {
665
// not enough space for another coroutine - use all the space for this one
666
size = available;
667
}
6 months ago
668
}
5 months ago
669
Coroutine_FreeToIdleSize(candidate, start, size);
670
return candidate;
8 months ago
4
671
}
5 months ago
672
if (candidate->size >= size && candidate > cor){
673
// chunk big enough, and a better choice than cor
674
cor = candidate;
675
}
676
}
677
678
if (cor){
679
// - work out whether we're splitting or using the whole chunk
680
if (cor->size >= size + g_c->gap_before + g_c->gap_after + COROUTINE_MINIMUM_STACK_SIZE){
681
// enough space for a second coroutine so split this free block
682
cor->size = size;
683
if (!setjmp(g_c->chunk_allocated)){
684
longjmp(cor->buf, Chunk_Split);
5 months ago
685
}
686
}
5 months ago
687
// cor now ready to use
688
Coroutine_FreeToIdle(cor, start);
689
return cor;
8 months ago
4
690
}
691
5 months ago
692
// No big-enough free blocks - check if there's space beyond the tip block
693
694
if (g_c->stack_limit && g_c->stack_limit > (unsigned char *)g_c->tip->limit - sizeof(Coroutine) - size){
695
// no space for a new stack block
696
return NULL;
697
}
698
Coroutine *tip = g_c->tip;
699
Coroutine *me = g_c->active;
700
if (tip == me) {
701
if (!setjmp(g_c->chunk_allocated)){
702
ReserveStackSpace(g_c, me, StackTopNow() - me->limit, NULL);
703
}
704
} else {
705
if (!setjmp(g_c->chunk_allocated)){
706
longjmp(tip->buf, Chunk_Create);
707
}
708
}
709
5 months ago
710
cor = List_Link_Container(Coroutine, link, List_GetTail(&g_c->free));
8 months ago
4
711
assert(cor->state == Coroutine_Free);
5 months ago
712
cor->size = size;
713
cor->limit = (unsigned char *)cor - g_c->gap_after - size;
8 months ago
4
714
cor->state = Coroutine_Idle;
715
cor->start = start;
716
cor->value = NULL;
5 months ago
717
Link_Remove(&cor->link);
718
List_AddHead(&g_c->inactive, &cor->link);
8 months ago
4
719
5 months ago
720
g_c->report.coroutines_created += 1;
721
return cor;
722
}
8 months ago
723
5 months ago
724
725
Coroutine *Coroutine_New(
726
size_t stack,
727
Coroutine_Start start
728
){
729
assert(g_c);
730
assert((g_c->state == Coroutines_Started && List_IsEmpty(&g_c->inactive)) || g_c->state == Coroutines_Active);
731
assert(!Coroutine_StackHasOverrun());
732
733
_Cor_Mutex_Lock(&g_c->mutex);
734
735
Coroutine *cor = Coroutine_New_Lock_Assumed(stack, start);
736
737
if (cor && cor->size > g_c->report.largest_stack){
738
g_c->report.largest_stack = cor->size;
739
}
740
741
_Cor_Mutex_Unlock(&g_c->mutex);
742
8 months ago
4
743
return cor;
744
}
745
8 months ago
746
747
void Coroutine_Delete(
748
Coroutine *cor
749
){
5 months ago
750
assert(!Coroutine_StackHasOverrun());
5 months ago
751
if (cor){
752
Coroutines *cors = cor->coroutines;
753
_Cor_Mutex_Lock(&cors->mutex);
754
assert(cor->state == Coroutine_Idle || cor->state == Coroutine_Complete);
755
cor->state = Coroutine_Free;
5 months ago
756
Link_Remove(&cor->link);
757
5 months ago
758
// insert into free list
759
List_AddHead(&cors->free, &cor->link);
5 months ago
760
761
// Check for merge with following Coroutine
5 months ago
762
List_Link *link = Link_Next(&cor->all_link);
5 months ago
763
if (Link_NextIsLink(link)){
5 months ago
764
Coroutine *listcor = List_Link_Container(Coroutine, all_link, link);
765
if (listcor->state == Coroutine_Free){
5 months ago
766
// merge
5 months ago
767
cor->size += cor->limit - listcor->limit;
5 months ago
768
cor->limit = listcor->limit;
769
cor->guard = listcor->guard;
5 months ago
770
Link_Remove(&listcor->all_link);
5 months ago
771
Link_Remove(&listcor->link);
5 months ago
772
if (g_c->tip == listcor){
773
g_c->tip = cor;
774
}
5 months ago
775
}
776
}
777
778
// check for merge with prev coroutine
5 months ago
779
link = Link_Prev(&cor->all_link);
5 months ago
780
if (Link_PrevIsLink(link)){
5 months ago
781
Coroutine *listcor = List_Link_Container(Coroutine, all_link, link);
782
if (listcor->state == Coroutine_Free){
5 months ago
783
// merge
5 months ago
784
listcor->size += listcor->limit - cor->limit;
5 months ago
785
listcor->limit = cor->limit;
786
listcor->guard = cor->guard;
5 months ago
787
Link_Remove(&cor->all_link);
5 months ago
788
Link_Remove(&cor->link);
5 months ago
789
if (g_c->tip == cor){
790
g_c->tip = listcor;
791
}
5 months ago
792
}
793
}
794
5 months ago
795
_Cor_Mutex_Unlock(&cors->mutex);
796
}
8 months ago
4
797
}
798
8 months ago
799
7 months ago
800
// Coroutine_Continue, assuming the mutex is claimed
801
static void _Coroutine_Continue(
8 months ago
802
Coroutine *cor,
803
void *value,
804
bool early
805
){
806
Coroutines *cors = cor->coroutines;
8 months ago
4
807
assert(cor->state == Coroutine_Idle || cor->state == Coroutine_Waiting);
808
cor->entry_param = value;
809
cor->state = Coroutine_Running;
5 months ago
810
Link_Remove(&cor->link);
8 months ago
4
811
if ( early ) {
8 months ago
812
List_AddHead(&cors->runable, &cor->link);
8 months ago
4
813
} else {
8 months ago
814
List_AddTail(&cors->runable, &cor->link);
8 months ago
4
815
}
7 months ago
816
_Cor_Mutex_Unlock(&cors->waiting_mutex);
7 months ago
817
}
818
819
820
void Coroutine_Continue(
821
Coroutine *cor,
822
void *value,
823
bool early
824
){
5 months ago
825
assert(!Coroutine_StackHasOverrun());
7 months ago
826
Coroutines *cors = cor->coroutines;
7 months ago
827
_Cor_Mutex_Lock(&cors->mutex);
7 months ago
828
_Coroutine_Continue(cor, value, early);
7 months ago
829
_Cor_Mutex_Unlock(&cors->mutex);
8 months ago
4
830
}
831
8 months ago
832
833
void *Coroutine_Yield(
834
void *value,
835
Coroutine_YieldCallback on_yield,
8 months ago
836
void *yield_me
8 months ago
837
){
5 months ago
838
assert(g_c);
839
Coroutine *me = g_c->active;
6 months ago
840
assert(me);
5 months ago
841
assert(!Coroutine_StackHasOverrun());
8 months ago
842
5 months ago
843
_Cor_Mutex_Lock(&g_c->mutex);
8 months ago
844
Coroutines *cors = me->coroutines;
5 months ago
845
assert(me && me->state == Coroutine_Running && cors == g_c);
6 months ago
846
me->stack_top = StackTopNow();
8 months ago
4
847
me->value = value;
848
me->state = Coroutine_Waiting;
8 months ago
849
5 months ago
850
Link_Remove(&me->link);
7 months ago
851
if (!List_IsEmpty(&cors->runable)){
7 months ago
852
_Cor_Mutex_Unlock(&cors->waiting_mutex);
7 months ago
853
}
8 months ago
854
List_AddTail(&cors->waiting, &me->link);
8 months ago
855
8 months ago
856
switch (setjmp(me->buf)){
857
case Chunk_Initial:
7 months ago
858
_Cor_Mutex_Unlock(&cors->mutex);
8 months ago
859
on_yield(yield_me);
8 months ago
4
860
Coroutine_RunNext();
6 months ago
861
assert(false);
8 months ago
862
case Chunk_Create:
5 months ago
863
assert(me == g_c->tip);
864
ReserveStackSpace(me->coroutines, me, me->stack_top - me->limit, NULL);
8 months ago
865
assert(false);
866
case Chunk_Enter:
867
// arrive here with mutex locked
8 months ago
868
cors->active = me;
5 months ago
869
assert(!Coroutine_StackHasOverrun());
8 months ago
870
// when we return here - we are running again
871
assert(me->state == Coroutine_Running);
872
void *res = me->entry_param;
7 months ago
873
_Cor_Mutex_Unlock(&cors->mutex);
8 months ago
874
return res;
8 months ago
4
875
}
8 months ago
876
return NULL;
8 months ago
4
877
}
878
8 months ago
879
880
void *Coroutine_GetValue(
881
Coroutine *cor
882
){
8 months ago
4
883
return cor->value;
884
}
885
8 months ago
886
7 months ago
887
Coroutine *Coroutine_GetActive(void)
8 months ago
4
888
{
5 months ago
889
return g_c ? g_c->active : NULL;
8 months ago
4
890
}
891
8 months ago
892
7 months ago
893
intptr_t Coroutine_GetStackHeadroom(void){
5 months ago
894
assert(g_c);
895
assert(!Coroutine_StackHasOverrun());
896
Coroutine *me = g_c->active;
6 months ago
897
if (!me){
898
// no active coroutine
5 months ago
899
unsigned char *stack_limit = g_c->stack_limit;
6 months ago
900
if (stack_limit){
901
// no stack limit - assume we'll use COROUTINE_STACK_SIZE
902
return StackTopNow() - stack_limit;
903
} else {
904
// no information where the stack ends - return something
5 months ago
905
return COROUTINE_MINIMUM_STACK_SIZE;
6 months ago
906
}
907
}
5 months ago
908
return StackTopNow() - me->limit;
8 months ago
909
}
910
911
6 months ago
912
// This is used to avoid compiler warnings about returning the address of a local
913
static inline void *StopAddressWarnings(void *p)
914
{
915
return p;
8 months ago
916
}
917
918
6 months ago
919
void *Coroutine_GetStackHWM(void){
5 months ago
920
assert(g_c);
921
assert(g_c->state == Coroutines_Active);
922
assert(!Coroutine_StackHasOverrun());
6 months ago
923
// Find where the guards end
924
unsigned char *guard;
5 months ago
925
for (guard = g_c->active->guard; Check_Guard(guard); guard += 4){
6 months ago
926
// do nothing
927
}
928
return guard;
929
}
7 months ago
930
931
6 months ago
932
void Coroutine_ClearStackForHWM(void){
5 months ago
933
assert(g_c);
934
assert(g_c->state == Coroutines_Active);
935
assert(!Coroutine_StackHasOverrun());
6 months ago
936
unsigned char *end = StackTopNow() - GUARD_PATTERN_SIZE;
5 months ago
937
for (unsigned char *guard = g_c->active->guard+GUARD_PATTERN_SIZE; guard <= end; guard += GUARD_PATTERN_SIZE){
6 months ago
938
Apply_Guard(guard);
6 months ago
939
}
940
}
941
942
5 months ago
943
static bool Coroutine_CanStartCoroutine_Lock_Assumed(
944
size_t size
945
){
946
assert(g_c);
947
assert(g_c->state == Coroutines_Started || g_c->state == Coroutines_Active);
948
assert(!Coroutine_StackHasOverrun());
949
950
if (!g_c->stack_limit){
6 months ago
951
return true;
952
}
6 months ago
953
5 months ago
954
if (!g_c->tip){
955
return true;
956
}
957
958
if (g_c->tip->state == Coroutine_Free){
959
// last block is free
960
if ((unsigned char *)g_c->tip - g_c->stack_limit >= (ptrdiff_t)(g_c->gap_after + size)){
961
// enough room in free block, which is the last block
962
return true;
963
}
964
} else {
965
// last block is allocated
966
if (g_c->tip->limit - g_c->stack_limit >= (ptrdiff_t)(g_c->gap_before + g_c->gap_after + size)){
967
// enough room after the last block, which is allocated
968
return true;
969
}
970
}
971
972
// not enough room between allocated blocks and stack limit, so check free list
973
List_Link *link;
974
for (link = List_Begin(&g_c->free); Link_NextIsLink(link); link = Link_Next(link)){
975
Coroutine *cor = List_Link_Container(Coroutine, link, link);
976
if (cor->size >= size){
977
return true;
978
}
979
}
980
981
return false;
6 months ago
982
}
983
984
5 months ago
985
bool Coroutine_CanStartCoroutine(
986
size_t size
987
){
988
_Cor_Mutex_Lock(&g_c->mutex);
989
990
bool result = Coroutine_CanStartCoroutine_Lock_Assumed(size);
991
992
_Cor_Mutex_Unlock(&g_c->mutex);
993
994
return result;
995
}
996
7 months ago
997
void *Coroutine_GetCStackTop(void){
5 months ago
998
assert(!Coroutine_StackHasOverrun());
999
if ((g_c->state == Coroutines_Started || g_c->state == Coroutines_Active) && g_c->tip != g_c->active) {
1000
return g_c->tip->stack_top;
6 months ago
1001
} else {
1002
return StackTopNow();
1003
}
8 months ago
1004
}
1005
1006
6 months ago
1007
static unsigned char *StackTopNow(void){
6 months ago
1008
unsigned char here[4];
1009
return StopAddressWarnings(here);
1010
}
1011
1012
8 months ago
1013
struct Coroutine_ChainParam {
1014
Coroutine_Start start;
1015
void *value;
1016
Coroutine *ret;
1017
};
1018
1019
1020
static void *Coroutine_ChainFn(
1021
void *param
1022
){
1023
struct Coroutine_ChainParam *params = (struct Coroutine_ChainParam *)param;
1024
Coroutine_Continue(params->ret, params->start(params->value), true);
1025
return NULL;
1026
}
1027
1028
1029
static void Coroutine_ChainYield(
1030
void *unused
1031
){
1032
(void)unused;
1033
}
1034
1035
5 months ago
1036
bool Coroutine_Chain(
5 months ago
1037
size_t size,
8 months ago
1038
Coroutine_Start start,
5 months ago
1039
void *value,
1040
void **result
8 months ago
1041
){
7 months ago
1042
assert(Check_Guard(Coroutine_GetActive()->guard));
5 months ago
1043
Coroutine *cor = Coroutine_New(size, Coroutine_ChainFn);
5 months ago
1044
if (!cor){
1045
// failed
1046
return true;
1047
}
8 months ago
1048
struct Coroutine_ChainParam params = {
1049
start,
1050
value,
1051
Coroutine_GetActive()
1052
};
1053
Coroutine_Continue(cor, &params, true);
1054
void *res = Coroutine_Yield(NULL, Coroutine_ChainYield, NULL);
1055
Coroutine_Delete(cor);
5 months ago
1056
if (result){
1057
*result = res;
1058
}
1059
// success!
1060
return false;
8 months ago
1061
}
1062
1063
8 months ago
1064
bool Coroutine_IsRunning(
1065
Coroutine *cor
1066
)
8 months ago
4
1067
{
8 months ago
1068
int state = cor->state;
1069
return state == Coroutine_Running || state == Coroutine_Waiting;
8 months ago
4
1070
}
8 months ago
1071
1072
6 months ago
1073
bool Coroutine_IsComplete(
1074
Coroutine *cor
1075
)
1076
{
1077
int state = cor->state;
1078
return state == Coroutine_Complete;
1079
}
1080
1081
7 months ago
1082
bool Coroutine_IsStarted(void){
5 months ago
1083
return g_c && (g_c->state == Coroutines_Active || g_c->state == Coroutines_Started);
8 months ago
1084
}
5 months ago
1085
1086
void _Coroutine_Dump(void){
1087
char *state_to_text[] = {
1088
"Free",
1089
"Idle",
1090
"Running",
1091
"Waiting",
1092
"Complete"
1093
};
1094
unsigned idx = 0;
1095
List_Link *link;
1096
for (link = List_Begin(&g_c->all); Link_NextIsLink(link); link = Link_Next(link)){
1097
Coroutine *cor = List_Link_Container(Coroutine, all_link, link);
1098
printf("%d) %p (%s) %ld%s\n", idx++, cor, state_to_text[cor->state], cor->size, cor == g_c->tip ? " (TIP)" : "");
1099
}
1100
}
1101