1589 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 ptrdiff_t
389TrimmableSize(
390 Coroutine *cor
391){
392 ptrdiff_t current_size = StackPointerDiff(cor->stack_top, cor->base);
393 ptrdiff_t min_size = current_size + cor->min_headroom;
394 if (min_size < (ptrdiff_t)cor->min_size){
395 min_size = (ptrdiff_t)cor->min_size;
396 }
397
398 if (cor->limit){
399 if (current_size < min_size + (ptrdiff_t)COROUTINE_MINIMUM_STACK_SIZE){
400 return 0;
401 }
402 }
403 return min_size;
404}
405
406
407#define GUARD_PATTERN_SIZE (4)
408/// @brief Checks whether a guard pattern is OK
409/// @param guard The address of the stack base end of the pattern
410/// @return true pattern is ok; false pattern is broken
411static inline bool
412Guard_Pattern_OK(
413 unsigned char *guard
414){
415 return !guard ||
416 (*StackPointerAdd(guard, 0) == 0xde &&
417 *StackPointerAdd(guard, 1) == 0xad &&
418 *StackPointerAdd(guard, 2) == 0xbe &&
419 *StackPointerAdd(guard, 3) == 0xef);
420}
421
422
423/// @brief Writes a guard pattern
424/// @param guard Where to write the guard pattern (stack base end)
425static inline void
426Apply_Guard(unsigned char *guard){
427 *StackPointerAdd(guard, 0) = 0xde;
428 *StackPointerAdd(guard, 1) = 0xad;
429 *StackPointerAdd(guard, 2) = 0xbe;
430 *StackPointerAdd(guard, 3) = 0xef;
431}
432
433
434#ifndef NDEBUG
435/// @brief Checks a list of Coroutines to see whether it's OK
436/// @param head The list to check
437/// @param state1 One of the states Coroutines in the list are allowed
438/// @param state2 One of the states Coroutines in the list are allowed
439/// @return Coroutine_OK the is OK; other the list is broken
440static Coroutine_Err
441CheckListIntegrity(
442 List_Head *head,
443 Coroutine_State state1,
444 Coroutine_State state2
445){
446 for (List_Link *link = List_Begin(head); Link_NextIsLink(link); link = Link_Next(link)){
447 Coroutine *candidate = List_Link_Container(Coroutine, link, link);
448 if (candidate->coroutines != g_c){
449 return Coroutine_Err_InternalInsistency;
450 }
451 if(candidate->state != state1 && candidate->state != state2){
452 return Coroutine_Err_InternalInsistency;
453 }
454 bool found = false;
455 for (List_Link *link = List_Begin(&g_c->all); Link_NextIsLink(link); link = Link_Next(link)){
456 Coroutine *candidate2 = List_Link_Container(Coroutine, all_link, link);
457 if (candidate == candidate2){
458 found = true;
459 }
460 }
461 if (!found){
462 return Coroutine_Err_InternalInsistency;
463 }
464 }
465 return Coroutine_OK;
466}
467
468
469/// @brief Check the integrity of this thread's coroutine system
470/// @return Coroutine_OK everything's OK; other something was wrong
471static Coroutine_Err
472Coroutine_NS(CheckIntegrity_)(
473 void
474){
475 Coroutine_Err err;
476 err = CheckListIntegrity(&g_c->free, Coroutine_Free, Coroutine_Free);
477 if (err){
478 return err;
479 }
480 err = CheckListIntegrity(&g_c->inactive, Coroutine_Idle, Coroutine_Complete);
481 if (err){
482 return err;
483 }
484 err = CheckListIntegrity(&g_c->runable, Coroutine_Running, Coroutine_Running);
485 if (err){
486 return err;
487 }
488 err = CheckListIntegrity(&g_c->waiting, Coroutine_Waiting, Coroutine_Waiting);
489 return err;
490}
491#endif
492
493
494/// @brief Check whether the stack has overrun
495/// @return Coroutine_OK the stack hasn't; other it has
496static Coroutine_Err
497Coroutine_NS(StackHasOverrun)(
498 void
499){
500 unsigned char *stack_top = (unsigned char *)StackTopNow();
501 unsigned char *stack_limit = g_c ? g_c->stack_limit : NULL;
502 if (stack_limit && StackPointerDiff(stack_limit, stack_top) < 0){
503 // printf("top %p < limit %p\n", stack_top, stack_limit);
504 // current stack top is beyond limit - we are overrunning NOW
505 return Coroutine_Err_StackOverrun;
506 }
507 Coroutine *me = g_c ? g_c->active : NULL;
508 if (!me){
509 return Coroutine_OK;
510 }
511#if COROUTINE_CHECK_INTEGRITY_ON_STACK_CHECK
512 // Check all coroutines integrity
513 Coroutine_Err err = Coroutine_NS(CheckIntegrity_)();
514 if (err){
515 return err;
516 }
517#endif
518 if (me->guard){
519 if (StackPointerDiff(me->guard, stack_top) < 0){
520 printf("Stack top beyond active stack limit\n");
521 return Coroutine_Err_StackOverrun;
522 }
523 if (!Guard_Pattern_OK(me->guard)){
524 printf("Guard pattern trampled\n");
525 return Coroutine_Err_StackOverrun;
526 }
527 }
528 return Coroutine_OK;
529}
530
531#ifndef NDEBUG
532/// @brief Check system integrity - does stack overrun check and internals check
533/// @return Coroutine_OK all is OK; other something was wrong
534Coroutine_Err
535Coroutine_NS(CheckIntegrity)(
536 void
537){
538 Coroutine_Err err = Coroutine_NS(StackHasOverrun)();
539#if !COROUTINE_CHECK_INTEGRITY_ON_STACK_CHECK
540 if (!err && g_c){
541 err = Coroutine_NS(CheckIntegrity_)();
542 }
543#endif
544 return err;
545}
546#endif
547
548
549static size_t
550Coroutine_Size
551(
552 Coroutine *cor
553){
554 return (size_t)StackPointerDiff(cor->limit, cor->base);
555}
556
557
558static void
559ReserveStackSpace(
560 Coroutines *cors,
561 Coroutine *parent,
562 size_t chunk_size,
563 unsigned char *childs_limit
564){
565 unsigned char *chunk_of_stack = alloca(chunk_size);
566 unsigned char *limit = StackLimitEnd(chunk_of_stack, chunk_of_stack+chunk_size);
567 unsigned char *guard = StackPointerAdd(limit, -GUARD_PATTERN_SIZE);
568#if COROUTINE_RECORD_LOWEST_HEADROOM
569 for (size_t i = 0; i <= chunk_size-GUARD_PATTERN_SIZE; i += GUARD_PATTERN_SIZE){
570 Apply_Guard(StackPointerAdd(guard, -i));
571 }
572#else
573 Apply_Guard(guard);
574#endif
575 if (parent){
576 parent->limit = limit;
577 parent->guard = guard;
578 }
579 stack_chunk_base(cors, parent, limit, childs_limit);
580}
581
582
583static void
584stack_chunk_base(
585 Coroutines *cors,
586 Coroutine *parent,
587 unsigned char *prev_limit,
588 unsigned char *limit
589){
590 MyAssert(cors->spare);
591 Coroutine *here = cors->spare;
592 cors->spare = NULL;
593 here->coroutines = cors;
594 here->sequence = cors->sequence++;
595 here->state = Coroutine_Free;
596 here->base = prev_limit;
597 here->limit = limit;
598 here->stack_top = (unsigned char *)StackTopNow();
599 if (limit){
600 here->guard = StackPointerAdd(limit, -GUARD_PATTERN_SIZE);
601 Apply_Guard(limit);
602 } else {
603 here->guard = NULL;
604 }
605
606 // insert into all list
607 if (parent){
608 Link_AddAfter(&here->all_link, &parent->all_link);
609 } else {
610 List_AddHead(&cors->all, &here->all_link);
611 }
612 // add to free list
613 List_AddTail(&cors->free, &here->link);
614
615 cors->report.coroutines_pool_size += 1;
616
617 if (!cors->tip || !Link_NextIsLink(Link_Next(&here->all_link))){
618 cors->tip = here;
619 }
620
621 for(;;){
622 switch (setjmp(here->buf)) {
623 case Chunk_Initial:
624 ready_jmp_buf(here->buf);
625 if (here->state == Coroutine_Free){
626 // return to the coroutine allocator
627 longjmp(cors->chunk_allocated, 1);
628 } else {
629 MyAssert(here->state == Coroutine_Complete);
630 // we finish here to ensure the setjmp is redone
631 if (cors->primary == here) {
632 // if primary coroutine - return to Coroutine_NS(Run)
633 longjmp(cors->controller, Coroutines_CoroutineComplete);
634 }
635 _Cor_Mutex_Unlock(&cors->mutex);
636 Coroutine_NS(RunNext)();
637 }
638 MyAssert(false);
639 break;
640 case Chunk_Split:
641 // Request to split this idle block into two
642 // g_c->size_to_retain will be set to our shorter size
643 ReserveStackSpace(here->coroutines, here, g_c->size_to_retain, here->limit);
644 MyAssert(false);
645 break;
646 case Chunk_Enter:
647 // request to start a coroutine (ie use the chunk for a coroutine)
648 // arrive here with mutex locked
649 MyAssert(here->state == Coroutine_Running);
650 here->coroutines->active = here;
651 _Cor_Mutex_Unlock(&cors->mutex);
652 here->value = here->start(here->entry_param);
653
654 // check the guard
655 MyAssert(Guard_Pattern_OK(here->guard));
656
657 here->stack_top = (unsigned char *)StackTopNow();
658 _Cor_Mutex_Lock(&here->coroutines->mutex);
659 here->coroutines->active = NULL;
660 MyAssert(here->state == Coroutine_Running);
661 Link_Remove(&here->link);
662 here->state = Coroutine_Complete;
663 List_AddTail(&here->coroutines->inactive, &here->link);
664 // Coroutine has completed
665 // Loop round to redo the setjmp() - if this coroutine yielded, then the setjmp will
666 // need reseting
667 break;
668 }
669 }
670}
671
672
673static void
674Coroutine_NS(RunNext)(
675 void
676){
677 // arrive here with mutex unlocked
678 _Cor_Mutex_Lock(&g_c->waiting_mutex);
679 _Cor_Mutex_Lock(&g_c->mutex);
680 Coroutine *next = List_Link_Container(Coroutine, link, List_GetHead(&g_c->runable));
681 MyAssert(next->state == Coroutine_Running);
682 longjmp(next->buf, Chunk_Enter);
683 MyAssert(false);
684}
685
686
687/// @brief Ensures there's a spare Coroutine for cors
688/// @param cors The Coroutines to ensure a spare for
689/// @return true there is a spare; false there isn't
690static inline bool
691EnsureSpare
692(
693 Coroutines *cors
694){
695 if (cors->spare){
696 return true;
697 }
698 cors->spare = malloc(sizeof(*cors->spare));
699 return cors->spare != NULL;
700}
701
702
703static Coroutine_Err
704Coroutines_ctor(
705 Coroutines *cors,
706 size_t min_size,
707 size_t min_headroom
708){
709 cors->state = Coroutines_Starting;
710 if (_Cor_Mutex_ctor(&cors->mutex)){
711 goto error;
712 }
713 cors->primary = NULL;
714 cors->stack_limit = g_stack_limit;
715
716 List_Init(&cors->all);
717 List_Init(&cors->free);
718 List_Init(&cors->inactive);
719 List_Init(&cors->runable);
720 List_Init(&cors->waiting);
721 if (_Cor_Mutex_ctor(&cors->waiting_mutex)){
722 goto error1;
723 }
724 if (_Cor_Mutex_Lock(&cors->waiting_mutex)){
725 goto error2;
726 }
727
728 cors->report.coroutines_created = 0;
729 cors->report.coroutines_pool_size = 0;
730 cors->report.largest_stack = 0;
731 cors->spare = NULL;
732 cors->sequence = 0;
733
734 Coroutine *cor = malloc(sizeof(*cor));
735 cor->min_size = min_size;
736 cor->min_headroom = min_headroom;
737 cor->coroutines = cors;
738 cor->sequence = cors->sequence++;
739 cor->state = Coroutine_Running;
740 cor->base = (unsigned char *)&cor;
741 if (cors->stack_limit){
742 cor->limit = cors->stack_limit;
743 cor->guard = StackPointerAdd(cor->limit, -GUARD_PATTERN_SIZE);
744 Apply_Guard(cor->guard);
745 } else {
746 cor->limit = cor->guard = NULL;
747 }
748 cors->root = cor;
749 List_AddHead(&cors->all, &cor->all_link);
750 List_AddTail(&cors->runable, &cor->link);
751 cors->tip = cors->active = cor;
752
753 cors->report.coroutines_pool_size += 1;
754
755 cors->state = Coroutines_Started;
756 return Coroutine_OK;
757error2:
758 _Cor_Mutex_dtor(&cors->waiting_mutex);
759error1:
760 _Cor_Mutex_dtor(&cors->mutex);
761error:
762 return Coroutine_Err_CouldNotInitialiseSystem;
763}
764
765static void
766Coroutines_dtor(
767 Coroutines *cors
768){
769 _Cor_Mutex_Lock(&cors->mutex);
770 cors->state = Coroutines_Stopping;
771
772 MyAssert(List_IsEmpty(&cors->inactive));
773
774 // free the spare
775 if (cors->spare){
776 free(cors->spare);
777 }
778
779 // free all non-spares
780 List_Link *link;
781 List_Link *next;
782 for (link = List_Begin(&cors->all); Link_NextIsLink(link); link = next){
783 next = Link_Next(link);
784 Coroutine *cor = List_Link_Container(Coroutine, all_link, link) ;
785 free(cor);
786 }
787
788 _Cor_Mutex_Unlock(&cors->waiting_mutex);
789 _Cor_Mutex_dtor(&cors->waiting_mutex);
790
791 MyAssert(cors->state == Coroutines_Stopping);
792 _Cor_Mutex_Unlock(&cors->mutex);
793 _Cor_Mutex_dtor(&cors->mutex);
794}
795
796
797Coroutine_Err
798Coroutine_NS(RunSystem)(
799 size_t min_size,
800 size_t min_headroom,
801 Coroutine_SystemStart start,
802 void *value
803){
804 CHECK_SYSTEM_NOT_RUNNING
805
806 Coroutines cors;
807 Coroutine_Err err = Coroutines_ctor(&cors, min_size, min_headroom);
808 if (err){
809 return err;
810 }
811 g_c = &cors;
812 err = start(value, cors.root);
813 g_c = NULL;
814 Coroutines_dtor(&cors);
815 return err;
816}
817
818
819void
820Coroutine_NS(SetStackLimit)(
821 void *limit
822){
823 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);
824 g_stack_limit = limit;
825 if (g_c){
826 g_c->stack_limit = limit;
827 if (g_c->tip){
828 g_c->tip->limit = limit;
829 g_c->tip->guard = StackPointerAdd(limit, -GUARD_PATTERN_SIZE);
830 Apply_Guard(g_c->tip->guard);
831 }
832 }
833}
834
835
836#if COROUTINE_RECORD_LOWEST_HEADROOM
837static size_t
838Coroutine_NS(UpdateMinimumHeadroom)(
839 List_Head *list,
840 size_t headroom
841){
842 for (List_Link *link = List_Begin(list); Link_NextIsLink(link); link = Link_Next(link)){
843 Coroutine *cor = List_Link_Container(Coroutine, link, link);
844 if (cor->guard){
845 size_t chunk_size = Coroutine_Size(cor);
846 for (size_t i = 0; i <= chunk_size-GUARD_PATTERN_SIZE; i += GUARD_PATTERN_SIZE){
847 if (!Guard_Pattern_OK(StackPointerAdd(cor->guard, -i))){
848 headroom = i < headroom ? i : headroom;
849 break;
850 }
851 }
852 }
853 }
854 return headroom;
855}
856#endif
857
858
859Coroutine_Report
860Coroutine_NS(GetReport)(
861 void
862){
863 if (g_c){
864 size_t headroom;
865#if COROUTINE_RECORD_LOWEST_HEADROOM
866 _Cor_Mutex_Lock(&g_c->mutex);
867 headroom = g_c->report.lowest_headroom;
868 headroom = Coroutine_NS(UpdateMinimumHeadroom)(&g_c->inactive, headroom);
869 headroom = Coroutine_NS(UpdateMinimumHeadroom)(&g_c->runable, headroom);
870 headroom = Coroutine_NS(UpdateMinimumHeadroom)(&g_c->waiting, headroom);
871 _Cor_Mutex_Unlock(&g_c->mutex);
872#else
873 headroom = 0;
874#endif
875 g_c->report.lowest_headroom = headroom;
876
877 return g_c->report;
878 } else {
879 Coroutine_Report ret = {0, 0, 0, 0};
880 return ret;
881 }
882}
883
884
885#ifndef NDEBUG
886static void
887Coroutine_NS(ReportNonEmptyList)(
888 List_Head const *head,
889 char const *tag
890){
891 List_Link *link;
892 for (link = List_Begin(head); Link_NextIsLink(link); link = Link_Next(link)){
893 Coroutine *cor = List_Link_Container(Coroutine, link, link);
894 printf("%s: %p %p %p\n", tag, cor, cor->start, cor->entry_param);
895 }
896}
897#endif
898
899Coroutine_Err
900Coroutine_NS(Run_Coroutine)(
901 Coroutine *cor,
902 void *value
903){
904 CHECK_SYSTEM_RUNNING
905 CHECK_COROUTINE_THREAD
906 CHECK_NO_COROUTINE_RUNNING
907
908 Coroutines *cors = cor->coroutines;
909 _Cor_Mutex_Lock(&cors->mutex);
910 cors->state = Coroutines_Active;
911 cors->primary = cor;
912
913 Coroutine_NS(Continue_)(cors, cor, value, true);
914
915 if (!setjmp(cors->controller)){
916 ready_jmp_buf(cors->controller);
917 _Cor_Mutex_Unlock(&cors->mutex);
918
919 // start the first coroutine
920 Coroutine_NS(RunNext)();
921 }
922 // arrive here with mutex locked
923 if (!List_IsEmpty(&cors->runable) || !List_IsEmpty(&cors->waiting)){
924#ifndef NDEBUG
925 Coroutine_NS(ReportNonEmptyList)(&cors->runable, "runable");
926 Coroutine_NS(ReportNonEmptyList)(&cors->waiting, "waiting");
927#endif
928 return Coroutine_Err_ExitWithRunningCoroutines;
929 }
930 MyAssert(cors->state == Coroutines_Active);
931 cors->state = Coroutines_Started;
932 _Cor_Mutex_Unlock(&cors->mutex);
933
934 return Coroutine_OK;
935}
936
937
938struct Coroutine_Run_Params {
939 Coroutine_Start start;
940 void *value;
941 void **result;
942};
943
944static Coroutine_Err
945Coroutine_NS(Run_Starter)(
946 void *_params,
947 Coroutine *root
948){
949 (void)root;
950 struct Coroutine_Run_Params *params = (struct Coroutine_Run_Params *)_params;
951
952 void *res = params->start(params->value);
953 if (params->result){
954 *params->result = res;
955 }
956
957 return Coroutine_OK;
958}
959
960
961Coroutine_Err Coroutine_NS(Run)(
962 size_t min_size,
963 size_t min_headroom,
964 Coroutine_Start start,
965 void *value,
966 void **result
967){
968 if (!g_c){
969 struct Coroutine_Run_Params params = {start, value, result};
970 return Coroutine_NS(RunSystem)(min_size, min_headroom, Coroutine_NS(Run_Starter), &params);
971 }
972
973 // We are in an active coroutine, so call start() directly
974 CHECK_STACK_OVERRUN
975 void *res = start(value);
976 if (result){
977 *result = res;
978 }
979
980 // no failures, so...
981 return Coroutine_OK;
982}
983
984
985static Coroutine *Coroutine_NS(New_Lock_Assumed)(
986 size_t min_size,
987 size_t min_headroom,
988 Coroutine_Start start
989){
990 List_Link *link;
991
992 if (!EnsureSpare(g_c)){
993 return NULL;
994 }
995
996 Coroutine *cor = NULL;
997 for (link = List_Begin(&g_c->free); Link_NextIsLink(link); link = Link_Next(link)){
998 Coroutine *candidate = List_Link_Container(Coroutine, link, link);
999 MyAssert(candidate->coroutines == g_c);
1000 MyAssert(candidate->state == Coroutine_Free);
1001 if (candidate->limit){
1002 size_t candidate_size = Coroutine_Size(candidate);
1003 if (candidate_size < min_size){
1004 continue;
1005 }
1006 }
1007 // candidate has no limit, or is big enough
1008
1009 // check if it's 'better' (lower in the C stack) than cor
1010 if (!cor || StackPointerDiff(candidate->base, cor->base) < 0){
1011 // chunk big enough, and a better choice than cor
1012 cor = candidate;
1013 }
1014 }
1015
1016 if (!cor){
1017 return NULL;
1018 }
1019
1020 // use the whole block - the tail will be freed in (New) or (Yield)
1021 cor->min_size = min_size;
1022 cor->min_headroom = min_headroom;
1023 cor->state = Coroutine_Idle;
1024 cor->start = start;
1025 cor->value = NULL;
1026 Link_Remove(&cor->link);
1027 List_AddHead(&g_c->inactive, &cor->link);
1028
1029 // trim down to an appropriate size
1030 size_t trimmable_size = TrimmableSize(cor);
1031 if (trimmable_size){
1032 // split block into two
1033 if (EnsureSpare(g_c)){
1034 g_c->size_to_retain = trimmable_size;
1035 if (!setjmp(g_c->chunk_allocated)){
1036 ready_jmp_buf(g_c->chunk_allocated);
1037 longjmp(cor->buf, Chunk_Split);
1038 }
1039 }
1040 }
1041
1042 g_c->report.coroutines_created += 1;
1043
1044 return cor;
1045}
1046
1047
1048static void
1049TrimActiveIfPossible
1050(
1051 void
1052){
1053 MyAssert(g_c);
1054
1055 // If we're the active coroutine, free the tail
1056 Coroutine *active = g_c->active;
1057 MyAssert(active);
1058
1059 active->stack_top = (unsigned char *)StackTopNow();
1060 ptrdiff_t timmablesize = TrimmableSize(active);
1061 if (!timmablesize){
1062 return;
1063 }
1064
1065 if (!EnsureSpare(g_c)){
1066 return;
1067 }
1068
1069 // enough space for a second coroutine so free the unused stack space from active
1070 if (!setjmp(g_c->chunk_allocated)){
1071 ready_jmp_buf(g_c->chunk_allocated);
1072 ReserveStackSpace(g_c, active, timmablesize - StackPointerDiff(active->stack_top, active->base), active->limit);
1073 MyAssert(false);
1074 }
1075}
1076
1077
1078static void
1079EnlargeActiveIfPossible(
1080 void
1081){
1082 MyAssert(g_c);
1083 Coroutine *active = g_c->active;
1084 MyAssert(active);
1085
1086 List_Link *next = Link_Next(&active->all_link);
1087 if (!Link_NextIsLink(next)){
1088 return;
1089 }
1090 Coroutine *cor = List_Link_Container(Coroutine, all_link, next);
1091 if (cor->state != Coroutine_Free){
1092 return;
1093 }
1094
1095 // Following block is free, merge with this
1096 active->limit = cor->limit;
1097 active->guard = cor->guard;
1098 Link_Remove(&cor->link);
1099 Link_Remove(&cor->all_link);
1100 if (g_c->tip == cor){
1101 g_c->tip = active;
1102 }
1103 if (g_c->spare){
1104 free(cor);
1105 } else {
1106 g_c->spare = cor;
1107 }
1108}
1109
1110
1111Coroutine *
1112Coroutine_NS(New)(
1113 size_t min_size,
1114 size_t min_headroom,
1115 Coroutine_Start start
1116){
1117 MyAssert(g_c);
1118 MyAssert((g_c->state == Coroutines_Started && List_IsEmpty(&g_c->inactive)) || g_c->state == Coroutines_Active);
1119 MyAssert(!Coroutine_NS(StackHasOverrun)());
1120
1121 // Make the paramaters make sense
1122 if (min_size < min_headroom){
1123 min_size = min_headroom;
1124 }
1125
1126 _Cor_Mutex_Lock(&g_c->mutex);
1127
1128 TrimActiveIfPossible();
1129
1130 Coroutine *cor = Coroutine_NS(New_Lock_Assumed)(min_size, min_headroom, start);
1131
1132 if (cor && Coroutine_Size(cor) > g_c->report.largest_stack){
1133 g_c->report.largest_stack = Coroutine_Size(cor);
1134 }
1135
1136 EnlargeActiveIfPossible();
1137
1138 _Cor_Mutex_Unlock(&g_c->mutex);
1139
1140 return cor;
1141}
1142
1143
1144void
1145Coroutine_NS(Delete)(
1146 Coroutine *cor
1147){
1148 MyAssert(!Coroutine_NS(StackHasOverrun)());
1149 if (cor){
1150 Coroutines *cors = cor->coroutines;
1151 _Cor_Mutex_Lock(&cors->mutex);
1152 MyAssert(cor->state == Coroutine_Idle || cor->state == Coroutine_Complete);
1153
1154#if COROUTINE_RECORD_LOWEST_HEADROOM
1155 if (cor->guard){
1156 unsigned char *guard = cor->guard;
1157 ptrdiff_t chunk_size = Coroutine_Size(cor);
1158 ptrdiff_t myheadroom;
1159 for (myheadroom = 0; myheadroom <= chunk_size-(ptrdiff_t)GUARD_PATTERN_SIZE; myheadroom += GUARD_PATTERN_SIZE){
1160 if (!Guard_Pattern_OK(StackPointerAdd(guard, -myheadroom))){
1161 break;
1162 }
1163 }
1164 if (myheadroom < (ptrdiff_t)g_c->report.lowest_headroom || g_c->report.lowest_headroom == 0){
1165 g_c->report.lowest_headroom = myheadroom;
1166 }
1167 }
1168#endif
1169
1170 cor->state = Coroutine_Free;
1171 Link_Remove(&cor->link);
1172
1173 // insert into free list
1174 List_AddHead(&cors->free, &cor->link);
1175
1176 // Check for merge with following Coroutine
1177 List_Link *link = Link_Next(&cor->all_link);
1178 if (Link_NextIsLink(link)){
1179 Coroutine *listcor = List_Link_Container(Coroutine, all_link, link);
1180 if (listcor->state == Coroutine_Free){
1181 // merge
1182 cor->limit = listcor->limit;
1183 cor->guard = listcor->guard;
1184 Link_Remove(&listcor->all_link);
1185 Link_Remove(&listcor->link);
1186 if (g_c->tip == listcor){
1187 g_c->tip = cor;
1188 }
1189
1190 // free up the now unused struct
1191 if (g_c->spare){
1192 free(listcor);
1193 } else {
1194 g_c->spare = listcor;
1195 }
1196 }
1197 }
1198
1199 // check for merge with prev coroutine
1200 link = Link_Prev(&cor->all_link);
1201 if (Link_PrevIsLink(link)){
1202 Coroutine *listcor = List_Link_Container(Coroutine, all_link, link);
1203 if (listcor->state == Coroutine_Free){
1204 // merge
1205 listcor->limit = cor->limit;
1206 listcor->guard = cor->guard;
1207 Link_Remove(&cor->all_link);
1208 Link_Remove(&cor->link);
1209 if (g_c->tip == cor){
1210 g_c->tip = listcor;
1211 }
1212
1213 // free up the now unused struct
1214 if (g_c->spare){
1215 free(cor);
1216 } else {
1217 g_c->spare = cor;
1218 }
1219 }
1220 }
1221
1222 EnlargeActiveIfPossible();
1223
1224 _Cor_Mutex_Unlock(&cors->mutex);
1225 }
1226}
1227
1228
1229// Coroutine_NS(Continue), assuming the mutex is claimed
1230// return false for success, true for something went wrong
1231static Coroutine_Err
1232Coroutine_NS(Continue_)(
1233 Coroutines *cors,
1234 Coroutine *cor,
1235 void *value,
1236 bool early
1237){
1238 if (cor->state == Coroutine_Running){
1239 // already running
1240 return Coroutine_OK;
1241 }
1242 if (cor->state != Coroutine_Idle && cor->state != Coroutine_Waiting){
1243 return Coroutine_Err_WrongState;
1244 }
1245 cor->entry_param = value;
1246 cor->state = Coroutine_Running;
1247 Link_Remove(&cor->link);
1248 if ( early ) {
1249 List_AddHead(&cors->runable, &cor->link);
1250 } else {
1251 List_AddTail(&cors->runable, &cor->link);
1252 }
1253 _Cor_Mutex_Unlock(&cors->waiting_mutex);
1254 return Coroutine_OK;
1255}
1256
1257
1258Coroutine_Err
1259Coroutine_NS(Continue)(
1260 Coroutine *cor,
1261 void *value,
1262 bool early
1263){
1264 MyAssert(!Coroutine_NS(StackHasOverrun)());
1265 Coroutines *cors = cor->coroutines;
1266 _Cor_Mutex_Lock(&cors->mutex);
1267 Coroutine_Err err = Coroutine_NS(Continue_)(cors, cor, value, early);
1268 _Cor_Mutex_Unlock(&cors->mutex);
1269 return err;
1270}
1271
1272
1273void *
1274Coroutine_NS(Yield)(
1275 void *value,
1276 Coroutine_YieldCallback on_yield,
1277 void *yield_me
1278){
1279 MyAssert(g_c);
1280 Coroutine *me = g_c->active;
1281 MyAssert(me);
1282 MyAssert(!Coroutine_NS(StackHasOverrun)());
1283
1284 _Cor_Mutex_Lock(&g_c->mutex);
1285 Coroutines *cors = me->coroutines;
1286 MyAssert(me && me->state == Coroutine_Running && cors == g_c);
1287 me->stack_top = (unsigned char *)StackTopNow();
1288 me->value = value;
1289 me->state = Coroutine_Waiting;
1290
1291 TrimActiveIfPossible();
1292
1293 Link_Remove(&me->link);
1294 if (!List_IsEmpty(&cors->runable)){
1295 _Cor_Mutex_Unlock(&cors->waiting_mutex);
1296 }
1297 List_AddTail(&cors->waiting, &me->link);
1298
1299 switch (setjmp(me->buf)){
1300 case Chunk_Initial:
1301 ready_jmp_buf(me->buf);
1302 _Cor_Mutex_Unlock(&cors->mutex);
1303 on_yield(yield_me);
1304 Coroutine_NS(RunNext)();
1305 MyAssert(false);
1306 break;
1307 case Chunk_Enter:
1308 // arrive here with mutex locked
1309 cors->active = me;
1310 MyAssert(!Coroutine_NS(StackHasOverrun)());
1311 // when we return here - we are running again
1312 MyAssert(me->state == Coroutine_Running);
1313 EnlargeActiveIfPossible();
1314 void *res = me->entry_param;
1315 _Cor_Mutex_Unlock(&cors->mutex);
1316 return res;
1317 }
1318 MyAssert(false);
1319 return NULL;
1320}
1321
1322
1323void *
1324Coroutine_NS(GetValue)(
1325 Coroutine *cor
1326){
1327 return cor->value;
1328}
1329
1330
1331Coroutine *
1332Coroutine_NS(GetActive)(
1333 void
1334){
1335 return g_c ? g_c->active : NULL;
1336}
1337
1338
1339intptr_t
1340Coroutine_NS(GetStackHeadroom)(
1341 void
1342){
1343 Coroutine *me = g_c ? g_c->active : NULL;
1344 if (!me){
1345 // no active coroutine
1346 if (g_stack_limit){
1347 return StackPointerDiff(g_stack_limit, (unsigned char *)StackTopNow());
1348 } else {
1349 // no information where the stack ends - return something
1350 // The biggest intptr_t possible
1351 return (intptr_t)(((uintptr_t)-1)>>1);
1352 }
1353 } else {
1354 if (me->guard){
1355 return StackPointerDiff(me->guard, (unsigned char *)StackTopNow());
1356 } else {
1357 // The biggest intptr_t possible
1358 return (intptr_t)(((uintptr_t)-1)>>1);
1359 }
1360 }
1361}
1362
1363
1364void *
1365Coroutine_NS(GetStackHWM)(
1366 void
1367){
1368 MyAssert(g_c);
1369 MyAssert(g_c->state == Coroutines_Active);
1370 MyAssert(!Coroutine_NS(StackHasOverrun)());
1371 // Find where the guards end
1372 unsigned char *guard = g_c->active->guard;
1373 if (guard){
1374 ptrdiff_t headroom = Coroutine_NS(GetStackHeadroom)();
1375 for (ptrdiff_t i = 0; i <= headroom; i += GUARD_PATTERN_SIZE){
1376 if (!Guard_Pattern_OK(StackPointerAdd(guard, -i))){
1377 return StackPointerAdd(guard, i);
1378 }
1379 }
1380 }
1381 return guard;
1382}
1383
1384
1385void
1386Coroutine_NS(ClearStackForHWM)(
1387 void
1388){
1389 MyAssert(g_c);
1390 MyAssert(g_c->state == Coroutines_Active);
1391 MyAssert(!Coroutine_NS(StackHasOverrun)());
1392 unsigned char *guard = g_c->active->guard;
1393 if (guard){
1394 ptrdiff_t headroom = Coroutine_NS(GetStackHeadroom)();
1395 for (ptrdiff_t i = 0; i <= headroom; i += GUARD_PATTERN_SIZE){
1396 Apply_Guard(StackPointerAdd(guard, -i));
1397 }
1398 }
1399}
1400
1401
1402static bool
1403Coroutine_NS(CanStartCoroutine_Lock_Assumed)(
1404 size_t min_size
1405){
1406 if (!g_c->stack_limit){
1407 return true;
1408 }
1409
1410 // check free list
1411 List_Link *link;
1412 for (link = List_Begin(&g_c->free); Link_NextIsLink(link); link = Link_Next(link)){
1413 Coroutine *cor = List_Link_Container(Coroutine, link, link);
1414 size_t cor_size = Coroutine_Size(cor);
1415 if (cor_size >= min_size){
1416 return true;
1417 }
1418 }
1419
1420 // Check if this coroutine has enough size
1421 Coroutine *me = g_c->active;
1422 me->stack_top = (unsigned char *)StackTopNow();
1423 ptrdiff_t reduced_size = TrimmableSize(g_c->active);
1424 if (reduced_size && StackPointerDiff(g_c->active->limit, g_c->active->base) - reduced_size > (ptrdiff_t)min_size){
1425 return true;
1426 }
1427
1428 return false;
1429}
1430
1431
1432bool
1433Coroutine_NS(CanStartCoroutine)(
1434 size_t size
1435){
1436 MyAssert(g_c);
1437 MyAssert(g_c->state == Coroutines_Started || g_c->state == Coroutines_Active);
1438 MyAssert(!Coroutine_NS(StackHasOverrun)());
1439
1440 _Cor_Mutex_Lock(&g_c->mutex);
1441
1442 bool result = Coroutine_NS(CanStartCoroutine_Lock_Assumed)(size);
1443
1444 _Cor_Mutex_Unlock(&g_c->mutex);
1445
1446 return result;
1447}
1448
1449void *
1450Coroutine_NS(GetCStackTop)(
1451 void
1452){
1453 if (!g_c || g_c->tip == g_c->active){
1454 return (void *)StackTopNow();
1455 }
1456
1457 return g_c->tip->stack_top;
1458}
1459
1460
1461// Inspired by cpython...
1462#ifdef __has_builtin
1463# define Coroutine__has_builtin(x) __has_builtin(x)
1464#else
1465# define Coroutine__has_builtin(x) 0
1466#endif
1467
1468#if !Coroutine__has_builtin(__builtin_frame_address) && !defined(__GNUC__) && !defined(_MSC_VER)
1469static uintptr_t return_pointer_as_int(char* p) {
1470 return (uintptr_t)p;
1471}
1472#endif
1473
1474static inline uintptr_t
1475StackTopNow(void) {
1476#if Coroutine__has_builtin(__builtin_frame_address) || defined(__GNUC__)
1477 return (uintptr_t)__builtin_frame_address(0);
1478#elif defined(_MSC_VER)
1479 return (uintptr_t)_AddressOfReturnAddress();
1480#else
1481 char here;
1482 /* Avoid compiler warning about returning stack address */
1483 return return_pointer_as_int(&here);
1484#endif
1485}
1486// ...inspired by cpython
1487
1488
1489struct Coroutine_ChainParam {
1490 Coroutine_Start start;
1491 void *value;
1492 Coroutine *ret;
1493};
1494
1495
1496static void *
1497Coroutine_NS(ChainFn)(
1498 void *param
1499){
1500 struct Coroutine_ChainParam *params = (struct Coroutine_ChainParam *)param;
1501 return (void *)(uintptr_t)Coroutine_NS(Continue)(params->ret, params->start(params->value), true);
1502}
1503
1504
1505static void
1506Coroutine_NS(ChainYield)(
1507 void *unused
1508){
1509 (void)unused;
1510}
1511
1512
1513Coroutine_Err
1514Coroutine_NS(Chain)(
1515 size_t min_size,
1516 size_t min_headroom,
1517 Coroutine_Start start,
1518 void *value,
1519 void **result
1520){
1521 MyAssert(!Coroutine_NS(StackHasOverrun)());
1522 Coroutine *cor = Coroutine_NS(New)(min_size, min_headroom, Coroutine_NS(ChainFn));
1523 if (!cor){
1524 // failed
1525 return Coroutine_Err_NoStack;
1526 }
1527 struct Coroutine_ChainParam params = {
1528 start,
1529 value,
1530 Coroutine_NS(GetActive)()
1531 };
1532 Coroutine_Err err = Coroutine_NS(Continue)(cor, &params, true);
1533 if (err){
1534 return err;
1535 }
1536 void *res = Coroutine_NS(Yield)(NULL, Coroutine_NS(ChainYield), NULL);
1537 err = (Coroutine_Err)(uintptr_t)Coroutine_NS(GetValue)(cor);
1538 Coroutine_NS(Delete)(cor);
1539 if (!err && result){
1540 *result = res;
1541 }
1542 // success! ...probably
1543 return err;
1544}
1545
1546
1547bool
1548Coroutine_NS(IsRunning)(
1549 Coroutine *cor
1550){
1551 int state = cor->state;
1552 return state == Coroutine_Running || state == Coroutine_Waiting;
1553}
1554
1555
1556bool Coroutine_NS(IsComplete)(
1557 Coroutine *cor
1558){
1559 int state = cor->state;
1560 return state == Coroutine_Complete;
1561}
1562
1563
1564bool
1565Coroutine_NS(IsStarted)(
1566 void
1567){
1568 return g_c && (g_c->state == Coroutines_Active || g_c->state == Coroutines_Started);
1569}
1570
1571void
1572Coroutine_NS(Dump_)(
1573 void
1574){
1575 char *state_to_text[] = {
1576 "Free",
1577 "Idle",
1578 "Running",
1579 "Waiting",
1580 "Complete"
1581 };
1582 unsigned idx = 0;
1583 List_Link *link;
1584 for (link = List_Begin(&g_c->all); Link_NextIsLink(link); link = Link_Next(link)){
1585 Coroutine *cor = List_Link_Container(Coroutine, all_link, link);
1586 printf("%d) %p (%s) %s\n", idx++, cor, state_to_text[cor->state], cor == g_c->tip ? " (TIP)" : "");
1587 }
1588}
1589