1593 lines39.7 KB
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 <stdlib.h>
8#include "cor_platform.h"
9
10// see CPython again, this time from ctypes.h
11#if (defined (__SVR4) && defined (__sun)) || defined(COROUTINE_HAVE_ALLOCA_H)
12# include <alloca.h>
13#elif defined(MS_WIN32)
14# include <malloc.h>
15#endif
16
17/* If the system does not define alloca(), we have to hope for a compiler builtin. */
18#ifndef alloca
19# if defined __GNUC__ || (__clang_major__ >= 4)
20# define alloca __builtin_alloca
21# else
22# error "Could not define alloca() on your platform."
23# endif
24#endif
25
26typedef struct Coroutines Coroutines;
27
28static void Coroutine_NS(RunNext)(void);
29static Coroutine_Err Coroutine_NS(Continue_)(Coroutines *cors, Coroutine *cor, void *value, bool early);
30static uintptr_t StackTopNow(void);
31
32#ifndef NDEBUG
33 // In debug builds, use the built-in assert
34 #define MyAssert assert
35#else
36 #if 1
37 // In non-debug builds, normally use this - all the asserts are disabled
38 #define MyAssert(cond)
39 #else
40 // In non-debug builds with stack problems, you can use this.
41 // This activates all the asserts, and gives a line to put a
42 // breakpoint in your debugger.
43 static void _MyAssert(bool cond, char const *msg)
44 {
45 if (!cond){
46 fputs("Assertion failed: ", stdout);
47 fputs(msg, stdout);
48 fputs("\n", stdout);
49 }
50 }
51 #define MyAssert(cond) _MyAssert(cond, #cond)
52 #endif
53#endif
54
55static inline ptrdiff_t
56StackPointerDiff
57(
58 unsigned char *a,
59 unsigned char *b
60){
61#if COROUTINE_STACK_GROWS_UP
62 return a - b;
63#else
64 return b - a;
65#endif
66}
67
68static inline unsigned char *
69StackPointerAdd
70(
71 unsigned char *a,
72 ptrdiff_t b
73){
74#if COROUTINE_STACK_GROWS_UP
75 return a + b;
76#else
77 return a - b;
78#endif
79}
80
81// Unused...
82// static inline unsigned char *
83// StackBaseEnd
84// (
85// unsigned char *memlo,
86// unsigned char *memhi
87// ){
88// #if COROUTINE_STACK_GROWS_UP
89// (void)memhi;
90// return memlo;
91// #else
92// (void)memlo;
93// return memhi-1;
94// #endif
95// }
96// ...unused
97
98static inline unsigned char *
99StackLimitEnd
100(
101 unsigned char *memlo,
102 unsigned char *memhi
103){
104#if COROUTINE_STACK_GROWS_UP
105 (void)memlo;
106 return memhi;
107#else
108 (void)memhi;
109 return memlo-1;
110#endif
111}
112
113#define CHECK_SYSTEM_RUNNING \
114 if (!g_c){ \
115 return Coroutine_Err_SystemNotRunning; \
116 }
117#define CHECK_SYSTEM_NOT_RUNNING \
118 if (g_c){ \
119 return Coroutine_Err_SystemRunning; \
120 }
121#define CHECK_COROUTINE_THREAD \
122 if (cor->coroutines != g_c){ \
123 return Coroutine_Err_CoroutineFromWrongThread; \
124 }
125#define CHECK_NO_COROUTINE_RUNNING \
126 if (g_c->state != Coroutines_Started){ \
127 return Coroutine_Err_ACoroutineIsAlreadyRunning; \
128 }
129#define CHECK_STACK_OVERRUN \
130 { \
131 Coroutine_Err err = Coroutine_NS(StackHasOverrun)(); \
132 if (err){ \
133 return err; \
134 } \
135 } while (0);
136
137
138static inline void ready_jmp_buf(jmp_buf buf) {
139#if defined(_M_X64) || defined(_M_ARM64)
140 // Win64:
141 // Set Frame to 0 on Windows 64 bit to prevent C++ stack unwinding in longjmp().
142 // Win32:
143 // Doesn't do this, so only needed on the 2 64 bit Windows versions
144 ((_JUMP_BUFFER*)buf)->Frame = 0;
145#else
146 (void)buf;
147#endif
148}
149
150///////////////////////////////////////////////////////////////////////////////
151// 2-way linked lists...
152//
153// Brought inline here to avoid namespace polution
154///////////////////////////////////////////////////////////////////////////////
155
156typedef struct List_Link List_Link;
157struct List_Link {
158 List_Link *next;
159 List_Link *prev;
160};
161
162typedef struct List_Head List_Head;
163struct List_Head {
164 union {
165 struct {
166 List_Link link;
167 List_Link *filler;
168 } fwd;
169 struct {
170 List_Link *filler;
171 List_Link link;
172 } back;
173 };
174};
175
176
177static inline bool List_IsEmpty(
178 const List_Head *list
179){
180 return list->fwd.link.next == &list->back.link;
181}
182
183
184static inline List_Link *List_GetHead(
185 const List_Head *list
186){
187 return List_IsEmpty(list) ? NULL : list->fwd.link.next;
188}
189
190
191static inline List_Link *List_Begin(
192 const List_Head *list
193){
194 return list->fwd.link.next;
195}
196
197
198static inline bool Link_NextIsLink(
199 const List_Link *link
200){
201 return link->next != NULL;
202}
203
204
205static inline List_Link *Link_Next(
206 List_Link *link
207){
208 return link->next;
209}
210
211
212static inline bool Link_PrevIsLink(
213 const List_Link *link
214){
215 return link->prev != NULL;
216}
217
218
219static inline List_Link *Link_Prev(
220 List_Link *link
221){
222 return link->prev;
223}
224
225// Unused...
226// static inline List_Link *List_GetTail(
227// const List_Head *list
228// ){
229// return List_IsEmpty(list) ? NULL : list->back.link.prev;
230// }
231// ...unused
232
233
234#define OFFSETOF(Container, Field) ((char *)&((Container *)4)->Field - (char *)(Container *)4)
235#define List_Link_Container(Container, Link, link) ((Container *)((char *)(link) - OFFSETOF(Container, Link)))
236
237
238static inline void
239List_Init(
240 List_Head *list
241){
242 list->fwd.link.next = &list->back.link;
243 list->fwd.link.prev = NULL;
244 list->back.link.prev = &list->fwd.link;
245}
246
247
248static inline void
249Link_AddAfter(
250 List_Link *link,
251 List_Link *after
252){
253 link->next = after->next;
254 link->prev = after;
255 after->next->prev = link;
256 after->next = link;
257}
258
259
260static inline void
261List_AddHead(
262 List_Head *list,
263 List_Link *link
264){
265 Link_AddAfter(link, &list->fwd.link);
266}
267
268
269static inline void
270Link_AddBefore(
271 List_Link *link,
272 List_Link *before
273){
274 link->prev = before->prev;
275 link->next = before;
276 before->prev->next = link;
277 before->prev = link;
278}
279
280
281static inline void
282List_AddTail(
283 List_Head *list,
284 List_Link *link
285){
286 Link_AddBefore(link, &list->back.link);
287}
288
289
290static inline void
291Link_Remove(
292 List_Link *link
293){
294 link->prev->next = link->next;
295 link->next->prev = link->prev;
296}
297
298///////////////////////////////////////////////////////////////////////////////
299// ...2-way linked lists
300///////////////////////////////////////////////////////////////////////////////
301
302enum {
303 Coroutines_Starting,
304 Coroutines_Started,
305 Coroutines_Active,
306 Coroutines_Stopping
307};
308
309enum {
310 Chunk_Initial,
311 Chunk_Split,
312 Chunk_Enter
313};
314
315typedef enum Coroutine_State {
316 Coroutine_Free,
317 Coroutine_Idle,
318 Coroutine_Running,
319 Coroutine_Waiting,
320 Coroutine_Complete
321} Coroutine_State;
322
323enum {
324 Coroutines_Init,
325 Coroutines_AllocatedChunk,
326 Coroutines_CoroutineComplete,
327};
328
329struct Coroutine {
330 size_t min_size;
331 size_t min_headroom;
332 Coroutines *coroutines; // so can work with it off-thread
333 List_Link link; // for whichever list it's on
334 List_Link all_link; // list of all Coroutines
335 jmp_buf buf; // how to get back to it
336 unsigned char *base; // where the base of this Coroutine's stack is (= previous block's limit)
337 unsigned char *limit; // where the limit of this Coroutine's stack is (= next block's base)
338 unsigned char *guard; // where the stack overrun guard is
339 Coroutine_Start start; // entry point
340 void *entry_param; // to pass to start
341 void *value; // yielded/returned
342 unsigned char *stack_top; // recorded at yield
343 Coroutine_State state;
344
345 int sequence;
346};
347
348struct Coroutines {
349 _Cor_Mutex mutex;
350 jmp_buf controller; // to return from Coroutine_NS(Run)
351 jmp_buf chunk_allocated;// for chunk allocation
352 size_t size_to_retain; // for Chunk_Split
353
354 // singletons
355 Coroutine *tip; // top of stack chunk
356 Coroutine *active; // currently running coroutine
357 Coroutine *primary; // Coroutine_NS(Run) coroutine
358 unsigned char *stack_limit; // when not NULL, where the stack finishes
359
360 Coroutine *spare; // spare struct Coroutine (instead of having to malloc & fail)
361
362 // lists
363 List_Head all; // all Coroutines (in address order)
364 List_Head free; // free Coroutines
365 List_Head inactive; // idle or complete
366 List_Head runable; // running or waiting to run
367 List_Head waiting; // yielded / waiting to run
368 _Cor_Mutex waiting_mutex;
369
370 Coroutine *root; // The coroutine for the thread which started Coroutines
371
372 // Summary of the system
373 Coroutine_Report report;
374
375 // state
376 char state;
377
378 int sequence;
379};
380
381_Cor_thread_local Coroutines *g_c;
382_Cor_thread_local unsigned char *g_stack_limit;
383
384static void ReserveStackSpace(Coroutines *cors, Coroutine *parent, size_t chunk_size, unsigned char *childs_limit);
385static void stack_chunk_base(Coroutines *cors, Coroutine *parent, unsigned char *prev_limit, unsigned char *limit);
386
387
388static size_t
389Coroutine_Size
390(
391 Coroutine *cor
392){
393 if (cor->limit){
394 return (size_t)StackPointerDiff(cor->limit, cor->base);
395 } else {
396 return SIZE_MAX;
397 }
398}
399
400
401static size_t
402TrimmableSize(
403 Coroutine *cor
404){
405 size_t current_used = StackPointerDiff(cor->stack_top, cor->base);
406 size_t min_size = current_used + cor->min_headroom;
407 if (min_size < cor->min_size){
408 min_size = cor->min_size;
409 }
410
411 if (cor->limit){
412 if (Coroutine_Size(cor) < min_size + COROUTINE_MINIMUM_STACK_SIZE){
413 return 0;
414 }
415 }
416 return min_size;
417}
418
419
420#define GUARD_PATTERN_SIZE (4)
421/// @brief Checks whether a guard pattern is OK
422/// @param guard The address of the stack base end of the pattern
423/// @return true pattern is ok; false pattern is broken
424static inline bool
425Guard_Pattern_OK(
426 unsigned char *guard
427){
428 return !guard ||
429 (*StackPointerAdd(guard, 0) == 0xde &&
430 *StackPointerAdd(guard, 1) == 0xad &&
431 *StackPointerAdd(guard, 2) == 0xbe &&
432 *StackPointerAdd(guard, 3) == 0xef);
433}
434
435
436/// @brief Writes a guard pattern
437/// @param guard Where to write the guard pattern (stack base end)
438static inline void
439Apply_Guard(unsigned char *guard){
440 *StackPointerAdd(guard, 0) = 0xde;
441 *StackPointerAdd(guard, 1) = 0xad;
442 *StackPointerAdd(guard, 2) = 0xbe;
443 *StackPointerAdd(guard, 3) = 0xef;
444}
445
446
447#ifndef NDEBUG
448/// @brief Checks a list of Coroutines to see whether it's OK
449/// @param head The list to check
450/// @param state1 One of the states Coroutines in the list are allowed
451/// @param state2 One of the states Coroutines in the list are allowed
452/// @return Coroutine_OK the is OK; other the list is broken
453static Coroutine_Err
454CheckListIntegrity(
455 List_Head *head,
456 Coroutine_State state1,
457 Coroutine_State state2
458){
459 for (List_Link *link = List_Begin(head); Link_NextIsLink(link); link = Link_Next(link)){
460 Coroutine *candidate = List_Link_Container(Coroutine, link, link);
461 if (candidate->coroutines != g_c){
462 return Coroutine_Err_InternalInsistency;
463 }
464 if(candidate->state != state1 && candidate->state != state2){
465 return Coroutine_Err_InternalInsistency;
466 }
467 bool found = false;
468 for (List_Link *link = List_Begin(&g_c->all); Link_NextIsLink(link); link = Link_Next(link)){
469 Coroutine *candidate2 = List_Link_Container(Coroutine, all_link, link);
470 if (candidate == candidate2){
471 found = true;
472 }
473 }
474 if (!found){
475 return Coroutine_Err_InternalInsistency;
476 }
477 }
478 return Coroutine_OK;
479}
480
481
482/// @brief Check the integrity of this thread's coroutine system
483/// @return Coroutine_OK everything's OK; other something was wrong
484static Coroutine_Err
485Coroutine_NS(CheckIntegrity_)(
486 void
487){
488 Coroutine_Err err;
489 err = CheckListIntegrity(&g_c->free, Coroutine_Free, Coroutine_Free);
490 if (err){
491 return err;
492 }
493 err = CheckListIntegrity(&g_c->inactive, Coroutine_Idle, Coroutine_Complete);
494 if (err){
495 return err;
496 }
497 err = CheckListIntegrity(&g_c->runable, Coroutine_Running, Coroutine_Running);
498 if (err){
499 return err;
500 }
501 err = CheckListIntegrity(&g_c->waiting, Coroutine_Waiting, Coroutine_Waiting);
502 return err;
503}
504#endif
505
506
507/// @brief Check whether the stack has overrun
508/// @return Coroutine_OK the stack hasn't; other it has
509static Coroutine_Err
510Coroutine_NS(StackHasOverrun)(
511 void
512){
513 unsigned char *stack_top = (unsigned char *)StackTopNow();
514 unsigned char *stack_limit = g_c ? g_c->stack_limit : NULL;
515 if (stack_limit && StackPointerDiff(stack_limit, stack_top) < 0){
516 // printf("top %p < limit %p\n", stack_top, stack_limit);
517 // current stack top is beyond limit - we are overrunning NOW
518 return Coroutine_Err_StackOverrun;
519 }
520 Coroutine *me = g_c ? g_c->active : NULL;
521 if (!me){
522 return Coroutine_OK;
523 }
524#if COROUTINE_CHECK_INTEGRITY_ON_STACK_CHECK
525 // Check all coroutines integrity
526 Coroutine_Err err = Coroutine_NS(CheckIntegrity_)();
527 if (err){
528 return err;
529 }
530#endif
531 if (me->guard){
532 if (StackPointerDiff(me->guard, stack_top) < 0){
533 printf("Stack top beyond active stack limit\n");
534 return Coroutine_Err_StackOverrun;
535 }
536 if (!Guard_Pattern_OK(me->guard)){
537 printf("Guard pattern trampled\n");
538 return Coroutine_Err_StackOverrun;
539 }
540 }
541 return Coroutine_OK;
542}
543
544#ifndef NDEBUG
545/// @brief Check system integrity - does stack overrun check and internals check
546/// @return Coroutine_OK all is OK; other something was wrong
547Coroutine_Err
548Coroutine_NS(CheckIntegrity)(
549 void
550){
551 Coroutine_Err err = Coroutine_NS(StackHasOverrun)();
552#if !COROUTINE_CHECK_INTEGRITY_ON_STACK_CHECK
553 if (!err && g_c){
554 err = Coroutine_NS(CheckIntegrity_)();
555 }
556#endif
557 return err;
558}
559#endif
560
561
562static void
563ReserveStackSpace(
564 Coroutines *cors,
565 Coroutine *parent,
566 size_t chunk_size,
567 unsigned char *childs_limit
568){
569 unsigned char *chunk_of_stack = alloca(chunk_size);
570 unsigned char *limit = StackLimitEnd(chunk_of_stack, chunk_of_stack+chunk_size);
571 unsigned char *guard = StackPointerAdd(limit, -GUARD_PATTERN_SIZE);
572#if COROUTINE_RECORD_LOWEST_HEADROOM
573 for (size_t i = 0; i <= chunk_size-GUARD_PATTERN_SIZE; i += GUARD_PATTERN_SIZE){
574 Apply_Guard(StackPointerAdd(guard, -i));
575 }
576#else
577 Apply_Guard(guard);
578#endif
579 if (parent){
580 parent->limit = limit;
581 parent->guard = guard;
582 }
583 stack_chunk_base(cors, parent, limit, childs_limit);
584}
585
586
587static void
588stack_chunk_base(
589 Coroutines *cors,
590 Coroutine *parent,
591 unsigned char *prev_limit,
592 unsigned char *limit
593){
594 MyAssert(cors->spare);
595 Coroutine *here = cors->spare;
596 cors->spare = NULL;
597 here->coroutines = cors;
598 here->sequence = cors->sequence++;
599 here->state = Coroutine_Free;
600 here->base = prev_limit;
601 here->limit = limit;
602 here->stack_top = (unsigned char *)StackTopNow();
603 if (limit){
604 here->guard = StackPointerAdd(limit, -GUARD_PATTERN_SIZE);
605 Apply_Guard(limit);
606 } else {
607 here->guard = NULL;
608 }
609
610 // insert into all list
611 if (parent){
612 Link_AddAfter(&here->all_link, &parent->all_link);
613 } else {
614 List_AddHead(&cors->all, &here->all_link);
615 }
616 // add to free list
617 List_AddTail(&cors->free, &here->link);
618
619 cors->report.coroutines_pool_size += 1;
620
621 if (!cors->tip || !Link_NextIsLink(Link_Next(&here->all_link))){
622 cors->tip = here;
623 }
624
625 for(;;){
626 switch (setjmp(here->buf)) {
627 case Chunk_Initial:
628 ready_jmp_buf(here->buf);
629 if (here->state == Coroutine_Free){
630 // return to the coroutine allocator
631 longjmp(cors->chunk_allocated, 1);
632 } else {
633 MyAssert(here->state == Coroutine_Complete);
634 // we finish here to ensure the setjmp is redone
635 if (cors->primary == here) {
636 // if primary coroutine - return to Coroutine_NS(Run)
637 longjmp(cors->controller, Coroutines_CoroutineComplete);
638 }
639 _Cor_Mutex_Unlock(&cors->mutex);
640 Coroutine_NS(RunNext)();
641 }
642 MyAssert(false);
643 break;
644 case Chunk_Split:
645 // Request to split this idle block into two
646 // g_c->size_to_retain will be set to our shorter size
647 ReserveStackSpace(here->coroutines, here, g_c->size_to_retain, here->limit);
648 MyAssert(false);
649 break;
650 case Chunk_Enter:
651 // request to start a coroutine (ie use the chunk for a coroutine)
652 // arrive here with mutex locked
653 MyAssert(here->state == Coroutine_Running);
654 here->coroutines->active = here;
655 _Cor_Mutex_Unlock(&cors->mutex);
656 here->value = here->start(here->entry_param);
657
658 // check the guard
659 MyAssert(Guard_Pattern_OK(here->guard));
660
661 here->stack_top = (unsigned char *)StackTopNow();
662 _Cor_Mutex_Lock(&here->coroutines->mutex);
663 here->coroutines->active = NULL;
664 MyAssert(here->state == Coroutine_Running);
665 Link_Remove(&here->link);
666 here->state = Coroutine_Complete;
667 List_AddTail(&here->coroutines->inactive, &here->link);
668 // Coroutine has completed
669 // Loop round to redo the setjmp() - if this coroutine yielded, then the setjmp will
670 // need reseting
671 break;
672 }
673 }
674}
675
676
677static void
678Coroutine_NS(RunNext)(
679 void
680){
681 // arrive here with mutex unlocked
682 _Cor_Mutex_Lock(&g_c->waiting_mutex);
683 _Cor_Mutex_Lock(&g_c->mutex);
684 Coroutine *next = List_Link_Container(Coroutine, link, List_GetHead(&g_c->runable));
685 MyAssert(next->state == Coroutine_Running);
686 longjmp(next->buf, Chunk_Enter);
687 MyAssert(false);
688}
689
690
691/// @brief Ensures there's a spare Coroutine for cors
692/// @param cors The Coroutines to ensure a spare for
693/// @return true there is a spare; false there isn't
694static inline bool
695EnsureSpare
696(
697 Coroutines *cors
698){
699 if (cors->spare){
700 return true;
701 }
702 cors->spare = malloc(sizeof(*cors->spare));
703 return cors->spare != NULL;
704}
705
706
707static Coroutine_Err
708Coroutines_ctor(
709 Coroutines *cors,
710 size_t min_size,
711 size_t min_headroom
712){
713 cors->state = Coroutines_Starting;
714 if (_Cor_Mutex_ctor(&cors->mutex)){
715 goto error;
716 }
717 cors->primary = NULL;
718 cors->stack_limit = g_stack_limit;
719
720 List_Init(&cors->all);
721 List_Init(&cors->free);
722 List_Init(&cors->inactive);
723 List_Init(&cors->runable);
724 List_Init(&cors->waiting);
725 if (_Cor_Mutex_ctor(&cors->waiting_mutex)){
726 goto error1;
727 }
728 if (_Cor_Mutex_Lock(&cors->waiting_mutex)){
729 goto error2;
730 }
731
732 cors->report.coroutines_created = 0;
733 cors->report.coroutines_pool_size = 0;
734 cors->report.largest_stack = 0;
735 cors->spare = NULL;
736 cors->sequence = 0;
737
738 Coroutine *cor = malloc(sizeof(*cor));
739 cor->min_size = min_size;
740 cor->min_headroom = min_headroom;
741 cor->coroutines = cors;
742 cor->sequence = cors->sequence++;
743 cor->state = Coroutine_Running;
744 cor->base = (unsigned char *)&cor;
745 if (cors->stack_limit){
746 cor->limit = cors->stack_limit;
747 cor->guard = StackPointerAdd(cor->limit, -GUARD_PATTERN_SIZE);
748 Apply_Guard(cor->guard);
749 } else {
750 cor->limit = cor->guard = NULL;
751 }
752 cors->root = cor;
753 List_AddHead(&cors->all, &cor->all_link);
754 List_AddTail(&cors->runable, &cor->link);
755 cors->tip = cors->active = cor;
756
757 cors->report.coroutines_pool_size += 1;
758
759 cors->state = Coroutines_Started;
760 return Coroutine_OK;
761error2:
762 _Cor_Mutex_dtor(&cors->waiting_mutex);
763error1:
764 _Cor_Mutex_dtor(&cors->mutex);
765error:
766 return Coroutine_Err_CouldNotInitialiseSystem;
767}
768
769static void
770Coroutines_dtor(
771 Coroutines *cors
772){
773 _Cor_Mutex_Lock(&cors->mutex);
774 cors->state = Coroutines_Stopping;
775
776 MyAssert(List_IsEmpty(&cors->inactive));
777
778 // free the spare
779 if (cors->spare){
780 free(cors->spare);
781 }
782
783 // free all non-spares
784 List_Link *link;
785 List_Link *next;
786 for (link = List_Begin(&cors->all); Link_NextIsLink(link); link = next){
787 next = Link_Next(link);
788 Coroutine *cor = List_Link_Container(Coroutine, all_link, link) ;
789 free(cor);
790 }
791
792 _Cor_Mutex_Unlock(&cors->waiting_mutex);
793 _Cor_Mutex_dtor(&cors->waiting_mutex);
794
795 MyAssert(cors->state == Coroutines_Stopping);
796 _Cor_Mutex_Unlock(&cors->mutex);
797 _Cor_Mutex_dtor(&cors->mutex);
798}
799
800
801Coroutine_Err
802Coroutine_NS(RunSystem)(
803 size_t min_size,
804 size_t min_headroom,
805 Coroutine_SystemStart start,
806 void *value
807){
808 CHECK_SYSTEM_NOT_RUNNING
809
810 Coroutines cors;
811 Coroutine_Err err = Coroutines_ctor(&cors, min_size, min_headroom);
812 if (err){
813 return err;
814 }
815 g_c = &cors;
816 err = start(value, cors.root);
817 g_c = NULL;
818 Coroutines_dtor(&cors);
819 return err;
820}
821
822
823void
824Coroutine_NS(SetStackLimit)(
825 void *limit
826){
827 MyAssert(!limit || !g_c || !(g_c->state == Coroutines_Started || g_c->state == Coroutines_Active) || (unsigned char *)limit < (unsigned char *)g_c->tip || !g_c->tip);
828 g_stack_limit = limit;
829 if (g_c){
830 g_c->stack_limit = limit;
831 if (g_c->tip){
832 g_c->tip->limit = limit;
833 g_c->tip->guard = StackPointerAdd(limit, -GUARD_PATTERN_SIZE);
834 Apply_Guard(g_c->tip->guard);
835 }
836 }
837}
838
839
840#if COROUTINE_RECORD_LOWEST_HEADROOM
841static size_t
842Coroutine_NS(UpdateMinimumHeadroom)(
843 List_Head *list,
844 size_t headroom
845){
846 for (List_Link *link = List_Begin(list); Link_NextIsLink(link); link = Link_Next(link)){
847 Coroutine *cor = List_Link_Container(Coroutine, link, link);
848 if (cor->guard){
849 size_t chunk_size = Coroutine_Size(cor);
850 for (size_t i = 0; i <= chunk_size-GUARD_PATTERN_SIZE; i += GUARD_PATTERN_SIZE){
851 if (!Guard_Pattern_OK(StackPointerAdd(cor->guard, -i))){
852 headroom = i < headroom ? i : headroom;
853 break;
854 }
855 }
856 }
857 }
858 return headroom;
859}
860#endif
861
862
863Coroutine_Report
864Coroutine_NS(GetReport)(
865 void
866){
867 if (g_c){
868 size_t headroom;
869#if COROUTINE_RECORD_LOWEST_HEADROOM
870 _Cor_Mutex_Lock(&g_c->mutex);
871 headroom = g_c->report.lowest_headroom;
872 headroom = Coroutine_NS(UpdateMinimumHeadroom)(&g_c->inactive, headroom);
873 headroom = Coroutine_NS(UpdateMinimumHeadroom)(&g_c->runable, headroom);
874 headroom = Coroutine_NS(UpdateMinimumHeadroom)(&g_c->waiting, headroom);
875 _Cor_Mutex_Unlock(&g_c->mutex);
876#else
877 headroom = 0;
878#endif
879 g_c->report.lowest_headroom = headroom;
880
881 return g_c->report;
882 } else {
883 Coroutine_Report ret = {0, 0, 0, 0};
884 return ret;
885 }
886}
887
888
889#ifndef NDEBUG
890static void
891Coroutine_NS(ReportNonEmptyList)(
892 List_Head const *head,
893 char const *tag
894){
895 List_Link *link;
896 for (link = List_Begin(head); Link_NextIsLink(link); link = Link_Next(link)){
897 Coroutine *cor = List_Link_Container(Coroutine, link, link);
898 printf("%s: %p %p %p\n", tag, cor, cor->start, cor->entry_param);
899 }
900}
901#endif
902
903Coroutine_Err
904Coroutine_NS(Run_Coroutine)(
905 Coroutine *cor,
906 void *value
907){
908 CHECK_SYSTEM_RUNNING
909 CHECK_COROUTINE_THREAD
910 CHECK_NO_COROUTINE_RUNNING
911
912 Coroutines *cors = cor->coroutines;
913 _Cor_Mutex_Lock(&cors->mutex);
914 cors->state = Coroutines_Active;
915 cors->primary = cor;
916
917 Coroutine_NS(Continue_)(cors, cor, value, true);
918
919 if (!setjmp(cors->controller)){
920 ready_jmp_buf(cors->controller);
921 _Cor_Mutex_Unlock(&cors->mutex);
922
923 // start the first coroutine
924 Coroutine_NS(RunNext)();
925 }
926 // arrive here with mutex locked
927 if (!List_IsEmpty(&cors->runable) || !List_IsEmpty(&cors->waiting)){
928#ifndef NDEBUG
929 Coroutine_NS(ReportNonEmptyList)(&cors->runable, "runable");
930 Coroutine_NS(ReportNonEmptyList)(&cors->waiting, "waiting");
931#endif
932 return Coroutine_Err_ExitWithRunningCoroutines;
933 }
934 MyAssert(cors->state == Coroutines_Active);
935 cors->state = Coroutines_Started;
936 _Cor_Mutex_Unlock(&cors->mutex);
937
938 return Coroutine_OK;
939}
940
941
942struct Coroutine_Run_Params {
943 Coroutine_Start start;
944 void *value;
945 void **result;
946};
947
948static Coroutine_Err
949Coroutine_NS(Run_Starter)(
950 void *_params,
951 Coroutine *root
952){
953 (void)root;
954 struct Coroutine_Run_Params *params = (struct Coroutine_Run_Params *)_params;
955
956 void *res = params->start(params->value);
957 if (params->result){
958 *params->result = res;
959 }
960
961 return Coroutine_OK;
962}
963
964
965Coroutine_Err Coroutine_NS(Run)(
966 size_t min_size,
967 size_t min_headroom,
968 Coroutine_Start start,
969 void *value,
970 void **result
971){
972 if (!g_c){
973 struct Coroutine_Run_Params params = {start, value, result};
974 return Coroutine_NS(RunSystem)(min_size, min_headroom, Coroutine_NS(Run_Starter), &params);
975 }
976
977 // We are in an active coroutine, so call start() directly
978 CHECK_STACK_OVERRUN
979 void *res = start(value);
980 if (result){
981 *result = res;
982 }
983
984 // no failures, so...
985 return Coroutine_OK;
986}
987
988
989static Coroutine *Coroutine_NS(New_Lock_Assumed)(
990 size_t min_size,
991 size_t min_headroom,
992 Coroutine_Start start
993){
994 List_Link *link;
995
996 if (!EnsureSpare(g_c)){
997 return NULL;
998 }
999
1000 Coroutine *cor = NULL;
1001 for (link = List_Begin(&g_c->free); Link_NextIsLink(link); link = Link_Next(link)){
1002 Coroutine *candidate = List_Link_Container(Coroutine, link, link);
1003 MyAssert(candidate->coroutines == g_c);
1004 MyAssert(candidate->state == Coroutine_Free);
1005 if (candidate->limit){
1006 size_t candidate_size = Coroutine_Size(candidate);
1007 if (candidate_size < min_size){
1008 continue;
1009 }
1010 }
1011 // candidate has no limit, or is big enough
1012
1013 // check if it's 'better' (lower in the C stack) than cor
1014 if (!cor || StackPointerDiff(candidate->base, cor->base) < 0){
1015 // chunk big enough, and a better choice than cor
1016 cor = candidate;
1017 }
1018 }
1019
1020 if (!cor){
1021 return NULL;
1022 }
1023
1024 // use the whole block - the tail will be freed in (New) or (Yield)
1025 cor->min_size = min_size;
1026 cor->min_headroom = min_headroom;
1027 cor->state = Coroutine_Idle;
1028 cor->start = start;
1029 cor->value = NULL;
1030 Link_Remove(&cor->link);
1031 List_AddHead(&g_c->inactive, &cor->link);
1032
1033 // trim down to an appropriate size
1034 size_t trimmable_size = TrimmableSize(cor);
1035 if (trimmable_size){
1036 // split block into two
1037 if (EnsureSpare(g_c)){
1038 g_c->size_to_retain = trimmable_size;
1039 if (!setjmp(g_c->chunk_allocated)){
1040 ready_jmp_buf(g_c->chunk_allocated);
1041 longjmp(cor->buf, Chunk_Split);
1042 }
1043 }
1044 }
1045
1046 g_c->report.coroutines_created += 1;
1047
1048 return cor;
1049}
1050
1051
1052static void
1053TrimActiveIfPossible
1054(
1055 void
1056){
1057 MyAssert(g_c);
1058
1059 // If we're the active coroutine, free the tail
1060 Coroutine *active = g_c->active;
1061 MyAssert(active);
1062
1063 active->stack_top = (unsigned char *)StackTopNow();
1064 ptrdiff_t timmablesize = TrimmableSize(active);
1065 if (!timmablesize){
1066 return;
1067 }
1068
1069 if (!EnsureSpare(g_c)){
1070 return;
1071 }
1072
1073 // enough space for a second coroutine so free the unused stack space from active
1074 if (!setjmp(g_c->chunk_allocated)){
1075 ready_jmp_buf(g_c->chunk_allocated);
1076 ReserveStackSpace(g_c, active, timmablesize - StackPointerDiff(active->stack_top, active->base), active->limit);
1077 MyAssert(false);
1078 }
1079}
1080
1081
1082static void
1083EnlargeActiveIfPossible(
1084 void
1085){
1086 MyAssert(g_c);
1087 Coroutine *active = g_c->active;
1088 MyAssert(active);
1089
1090 List_Link *next = Link_Next(&active->all_link);
1091 if (!Link_NextIsLink(next)){
1092 return;
1093 }
1094 Coroutine *cor = List_Link_Container(Coroutine, all_link, next);
1095 if (cor->state != Coroutine_Free){
1096 return;
1097 }
1098
1099 // Following block is free, merge with this
1100 active->limit = cor->limit;
1101 active->guard = cor->guard;
1102 Link_Remove(&cor->link);
1103 Link_Remove(&cor->all_link);
1104 if (g_c->tip == cor){
1105 g_c->tip = active;
1106 }
1107 if (g_c->spare){
1108 free(cor);
1109 } else {
1110 g_c->spare = cor;
1111 }
1112}
1113
1114
1115Coroutine *
1116Coroutine_NS(New)(
1117 size_t min_size,
1118 size_t min_headroom,
1119 Coroutine_Start start
1120){
1121 MyAssert(g_c);
1122 MyAssert((g_c->state == Coroutines_Started && List_IsEmpty(&g_c->inactive)) || g_c->state == Coroutines_Active);
1123 MyAssert(!Coroutine_NS(StackHasOverrun)());
1124
1125 // Make the paramaters make sense
1126 if (min_size < min_headroom){
1127 min_size = min_headroom;
1128 }
1129
1130 _Cor_Mutex_Lock(&g_c->mutex);
1131
1132 TrimActiveIfPossible();
1133
1134 Coroutine *cor = Coroutine_NS(New_Lock_Assumed)(min_size, min_headroom, start);
1135
1136 if (cor && Coroutine_Size(cor) > g_c->report.largest_stack){
1137 g_c->report.largest_stack = Coroutine_Size(cor);
1138 }
1139
1140 EnlargeActiveIfPossible();
1141
1142 _Cor_Mutex_Unlock(&g_c->mutex);
1143
1144 return cor;
1145}
1146
1147
1148void
1149Coroutine_NS(Delete)(
1150 Coroutine *cor
1151){
1152 MyAssert(!Coroutine_NS(StackHasOverrun)());
1153 if (cor){
1154 Coroutines *cors = cor->coroutines;
1155 _Cor_Mutex_Lock(&cors->mutex);
1156 MyAssert(cor->state == Coroutine_Idle || cor->state == Coroutine_Complete);
1157
1158#if COROUTINE_RECORD_LOWEST_HEADROOM
1159 if (cor->guard){
1160 unsigned char *guard = cor->guard;
1161 ptrdiff_t chunk_size = Coroutine_Size(cor);
1162 ptrdiff_t myheadroom;
1163 for (myheadroom = 0; myheadroom <= chunk_size-(ptrdiff_t)GUARD_PATTERN_SIZE; myheadroom += GUARD_PATTERN_SIZE){
1164 if (!Guard_Pattern_OK(StackPointerAdd(guard, -myheadroom))){
1165 break;
1166 }
1167 }
1168 if (myheadroom < (ptrdiff_t)g_c->report.lowest_headroom || g_c->report.lowest_headroom == 0){
1169 g_c->report.lowest_headroom = myheadroom;
1170 }
1171 }
1172#endif
1173
1174 cor->state = Coroutine_Free;
1175 Link_Remove(&cor->link);
1176
1177 // insert into free list
1178 List_AddHead(&cors->free, &cor->link);
1179
1180 // Check for merge with following Coroutine
1181 List_Link *link = Link_Next(&cor->all_link);
1182 if (Link_NextIsLink(link)){
1183 Coroutine *listcor = List_Link_Container(Coroutine, all_link, link);
1184 if (listcor->state == Coroutine_Free){
1185 // merge
1186 cor->limit = listcor->limit;
1187 cor->guard = listcor->guard;
1188 Link_Remove(&listcor->all_link);
1189 Link_Remove(&listcor->link);
1190 if (g_c->tip == listcor){
1191 g_c->tip = cor;
1192 }
1193
1194 // free up the now unused struct
1195 if (g_c->spare){
1196 free(listcor);
1197 } else {
1198 g_c->spare = listcor;
1199 }
1200 }
1201 }
1202
1203 // check for merge with prev coroutine
1204 link = Link_Prev(&cor->all_link);
1205 if (Link_PrevIsLink(link)){
1206 Coroutine *listcor = List_Link_Container(Coroutine, all_link, link);
1207 if (listcor->state == Coroutine_Free){
1208 // merge
1209 listcor->limit = cor->limit;
1210 listcor->guard = cor->guard;
1211 Link_Remove(&cor->all_link);
1212 Link_Remove(&cor->link);
1213 if (g_c->tip == cor){
1214 g_c->tip = listcor;
1215 }
1216
1217 // free up the now unused struct
1218 if (g_c->spare){
1219 free(cor);
1220 } else {
1221 g_c->spare = cor;
1222 }
1223 }
1224 }
1225
1226 EnlargeActiveIfPossible();
1227
1228 _Cor_Mutex_Unlock(&cors->mutex);
1229 }
1230}
1231
1232
1233// Coroutine_NS(Continue), assuming the mutex is claimed
1234// return false for success, true for something went wrong
1235static Coroutine_Err
1236Coroutine_NS(Continue_)(
1237 Coroutines *cors,
1238 Coroutine *cor,
1239 void *value,
1240 bool early
1241){
1242 if (cor->state == Coroutine_Running){
1243 // already running
1244 return Coroutine_OK;
1245 }
1246 if (cor->state != Coroutine_Idle && cor->state != Coroutine_Waiting){
1247 return Coroutine_Err_WrongState;
1248 }
1249 cor->entry_param = value;
1250 cor->state = Coroutine_Running;
1251 Link_Remove(&cor->link);
1252 if ( early ) {
1253 List_AddHead(&cors->runable, &cor->link);
1254 } else {
1255 List_AddTail(&cors->runable, &cor->link);
1256 }
1257 _Cor_Mutex_Unlock(&cors->waiting_mutex);
1258 return Coroutine_OK;
1259}
1260
1261
1262Coroutine_Err
1263Coroutine_NS(Continue)(
1264 Coroutine *cor,
1265 void *value,
1266 bool early
1267){
1268 MyAssert(!Coroutine_NS(StackHasOverrun)());
1269 Coroutines *cors = cor->coroutines;
1270 _Cor_Mutex_Lock(&cors->mutex);
1271 Coroutine_Err err = Coroutine_NS(Continue_)(cors, cor, value, early);
1272 _Cor_Mutex_Unlock(&cors->mutex);
1273 return err;
1274}
1275
1276
1277void *
1278Coroutine_NS(Yield)(
1279 void *value,
1280 Coroutine_YieldCallback on_yield,
1281 void *yield_me
1282){
1283 MyAssert(g_c);
1284 Coroutine *me = g_c->active;
1285 MyAssert(me);
1286 MyAssert(!Coroutine_NS(StackHasOverrun)());
1287
1288 _Cor_Mutex_Lock(&g_c->mutex);
1289 Coroutines *cors = me->coroutines;
1290 MyAssert(me && me->state == Coroutine_Running && cors == g_c);
1291 me->stack_top = (unsigned char *)StackTopNow();
1292 me->value = value;
1293 me->state = Coroutine_Waiting;
1294
1295 TrimActiveIfPossible();
1296
1297 Link_Remove(&me->link);
1298 if (!List_IsEmpty(&cors->runable)){
1299 _Cor_Mutex_Unlock(&cors->waiting_mutex);
1300 }
1301 List_AddTail(&cors->waiting, &me->link);
1302
1303 switch (setjmp(me->buf)){
1304 case Chunk_Initial:
1305 ready_jmp_buf(me->buf);
1306 _Cor_Mutex_Unlock(&cors->mutex);
1307 on_yield(yield_me);
1308 Coroutine_NS(RunNext)();
1309 MyAssert(false);
1310 break;
1311 case Chunk_Enter:
1312 // arrive here with mutex locked
1313 cors->active = me;
1314 MyAssert(!Coroutine_NS(StackHasOverrun)());
1315 // when we return here - we are running again
1316 MyAssert(me->state == Coroutine_Running);
1317 EnlargeActiveIfPossible();
1318 void *res = me->entry_param;
1319 _Cor_Mutex_Unlock(&cors->mutex);
1320 return res;
1321 }
1322 MyAssert(false);
1323 return NULL;
1324}
1325
1326
1327void *
1328Coroutine_NS(GetValue)(
1329 Coroutine *cor
1330){
1331 return cor->value;
1332}
1333
1334
1335Coroutine *
1336Coroutine_NS(GetActive)(
1337 void
1338){
1339 return g_c ? g_c->active : NULL;
1340}
1341
1342
1343ptrdiff_t
1344Coroutine_NS(GetStackHeadroom)(
1345 void
1346){
1347 Coroutine *me = g_c ? g_c->active : NULL;
1348 if (!me){
1349 // no active coroutine
1350 if (g_stack_limit){
1351 return StackPointerDiff(g_stack_limit, (unsigned char *)StackTopNow());
1352 } else {
1353 // no information where the stack ends - return something
1354 // The biggest ptrdiff_t possible
1355 return PTRDIFF_MAX;
1356 }
1357 } else {
1358 if (me->guard){
1359 return StackPointerDiff(me->guard, (unsigned char *)StackTopNow());
1360 } else {
1361 // The biggest ptrdiff_t possible
1362 return PTRDIFF_MAX;
1363 }
1364 }
1365}
1366
1367
1368void *
1369Coroutine_NS(GetStackHWM)(
1370 void
1371){
1372 MyAssert(g_c);
1373 MyAssert(g_c->state == Coroutines_Active);
1374 MyAssert(!Coroutine_NS(StackHasOverrun)());
1375 // Find where the guards end
1376 unsigned char *guard = g_c->active->guard;
1377 if (guard){
1378 ptrdiff_t headroom = Coroutine_NS(GetStackHeadroom)();
1379 for (ptrdiff_t i = 0; i <= headroom; i += GUARD_PATTERN_SIZE){
1380 if (!Guard_Pattern_OK(StackPointerAdd(guard, -i))){
1381 return StackPointerAdd(guard, i);
1382 }
1383 }
1384 }
1385 return guard;
1386}
1387
1388
1389void
1390Coroutine_NS(ClearStackForHWM)(
1391 void
1392){
1393 MyAssert(g_c);
1394 MyAssert(g_c->state == Coroutines_Active);
1395 MyAssert(!Coroutine_NS(StackHasOverrun)());
1396 unsigned char *guard = g_c->active->guard;
1397 if (guard){
1398 ptrdiff_t headroom = Coroutine_NS(GetStackHeadroom)();
1399 for (ptrdiff_t i = 0; i <= headroom; i += GUARD_PATTERN_SIZE){
1400 Apply_Guard(StackPointerAdd(guard, -i));
1401 }
1402 }
1403}
1404
1405
1406static bool
1407Coroutine_NS(CanStartCoroutine_Lock_Assumed)(
1408 size_t min_size
1409){
1410 if (!g_c->stack_limit){
1411 return true;
1412 }
1413
1414 // check free list
1415 List_Link *link;
1416 for (link = List_Begin(&g_c->free); Link_NextIsLink(link); link = Link_Next(link)){
1417 Coroutine *cor = List_Link_Container(Coroutine, link, link);
1418 size_t cor_size = Coroutine_Size(cor);
1419 if (cor_size >= min_size){
1420 return true;
1421 }
1422 }
1423
1424 // Check if this coroutine has enough size
1425 Coroutine *me = g_c->active;
1426 me->stack_top = (unsigned char *)StackTopNow();
1427 ptrdiff_t reduced_size = TrimmableSize(g_c->active);
1428 if (reduced_size && StackPointerDiff(g_c->active->limit, g_c->active->base) - reduced_size > (ptrdiff_t)min_size){
1429 return true;
1430 }
1431
1432 return false;
1433}
1434
1435
1436bool
1437Coroutine_NS(CanStartCoroutine)(
1438 size_t size
1439){
1440 MyAssert(g_c);
1441 MyAssert(g_c->state == Coroutines_Started || g_c->state == Coroutines_Active);
1442 MyAssert(!Coroutine_NS(StackHasOverrun)());
1443
1444 _Cor_Mutex_Lock(&g_c->mutex);
1445
1446 bool result = Coroutine_NS(CanStartCoroutine_Lock_Assumed)(size);
1447
1448 _Cor_Mutex_Unlock(&g_c->mutex);
1449
1450 return result;
1451}
1452
1453void *
1454Coroutine_NS(GetCStackTop)(
1455 void
1456){
1457 if (!g_c || g_c->tip == g_c->active){
1458 return (void *)StackTopNow();
1459 }
1460
1461 return g_c->tip->stack_top;
1462}
1463
1464
1465// Inspired by cpython...
1466#ifdef __has_builtin
1467# define Coroutine__has_builtin(x) __has_builtin(x)
1468#else
1469# define Coroutine__has_builtin(x) 0
1470#endif
1471
1472#if !Coroutine__has_builtin(__builtin_frame_address) && !defined(__GNUC__) && !defined(_MSC_VER)
1473static uintptr_t return_pointer_as_int(char* p) {
1474 return (uintptr_t)p;
1475}
1476#endif
1477
1478static inline uintptr_t
1479StackTopNow(void) {
1480#if Coroutine__has_builtin(__builtin_frame_address) || defined(__GNUC__)
1481 return (uintptr_t)__builtin_frame_address(0);
1482#elif defined(_MSC_VER)
1483 return (uintptr_t)_AddressOfReturnAddress();
1484#else
1485 char here;
1486 /* Avoid compiler warning about returning stack address */
1487 return return_pointer_as_int(&here);
1488#endif
1489}
1490// ...inspired by cpython
1491
1492
1493struct Coroutine_ChainParam {
1494 Coroutine_Start start;
1495 void *value;
1496 Coroutine *ret;
1497};
1498
1499
1500static void *
1501Coroutine_NS(ChainFn)(
1502 void *param
1503){
1504 struct Coroutine_ChainParam *params = (struct Coroutine_ChainParam *)param;
1505 return (void *)(uintptr_t)Coroutine_NS(Continue)(params->ret, params->start(params->value), true);
1506}
1507
1508
1509static void
1510Coroutine_NS(ChainYield)(
1511 void *unused
1512){
1513 (void)unused;
1514}
1515
1516
1517Coroutine_Err
1518Coroutine_NS(Chain)(
1519 size_t min_size,
1520 size_t min_headroom,
1521 Coroutine_Start start,
1522 void *value,
1523 void **result
1524){
1525 MyAssert(!Coroutine_NS(StackHasOverrun)());
1526 Coroutine *cor = Coroutine_NS(New)(min_size, min_headroom, Coroutine_NS(ChainFn));
1527 if (!cor){
1528 // failed
1529 return Coroutine_Err_NoStack;
1530 }
1531 struct Coroutine_ChainParam params = {
1532 start,
1533 value,
1534 Coroutine_NS(GetActive)()
1535 };
1536 Coroutine_Err err = Coroutine_NS(Continue)(cor, &params, true);
1537 if (err){
1538 return err;
1539 }
1540 void *res = Coroutine_NS(Yield)(NULL, Coroutine_NS(ChainYield), NULL);
1541 err = (Coroutine_Err)(uintptr_t)Coroutine_NS(GetValue)(cor);
1542 Coroutine_NS(Delete)(cor);
1543 if (!err && result){
1544 *result = res;
1545 }
1546 // success! ...probably
1547 return err;
1548}
1549
1550
1551bool
1552Coroutine_NS(IsRunning)(
1553 Coroutine *cor
1554){
1555 int state = cor->state;
1556 return state == Coroutine_Running || state == Coroutine_Waiting;
1557}
1558
1559
1560bool Coroutine_NS(IsComplete)(
1561 Coroutine *cor
1562){
1563 int state = cor->state;
1564 return state == Coroutine_Complete;
1565}
1566
1567
1568bool
1569Coroutine_NS(IsStarted)(
1570 void
1571){
1572 return g_c && (g_c->state == Coroutines_Active || g_c->state == Coroutines_Started);
1573}
1574
1575void
1576Coroutine_NS(Dump_)(
1577 void
1578){
1579 char *state_to_text[] = {
1580 "Free",
1581 "Idle",
1582 "Running",
1583 "Waiting",
1584 "Complete"
1585 };
1586 unsigned idx = 0;
1587 List_Link *link;
1588 for (link = List_Begin(&g_c->all); Link_NextIsLink(link); link = Link_Next(link)){
1589 Coroutine *cor = List_Link_Container(Coroutine, all_link, link);
1590 printf("%d) %p (%s) %s\n", idx++, cor, state_to_text[cor->state], cor == g_c->tip ? " (TIP)" : "");
1591 }
1592}
1593