1 contributor
195 lines7.6 KB
1#ifndef COROUTINE_H
2#define COROUTINE_H
3
4#include <stdbool.h>
5#include <stddef.h>
6#include <stdint.h>
7#include "cor_platform_inc.h"
8
9///////////////////////////////////////////////////////////////////////////////
10// Coroutine
11//
12// Coroutines for C, based on setjmp/longjmp.
13// Thread safe - each thread has its own coroutine system
14// Coroutines are cooperatively scheduled
15// Coroutines have their own stack (currently 16K each)
16// A coroutine can be continued, queried, or deleted on a different thread.
17//
18// Usage:
19// Coroutine_StartSystem(); // call once per thread before using coroutines
20// Coroutine *co = Coroutine_New(start_function);
21// void *result;
22// if (Coroutine_Run(co, initial_value, &result)) {
23// // Handle the failure
24// }
25// Coroutine_Delete(co);
26// Coroutine_StopSystem(); // call once per thread when done with coroutines
27//
28// Inside the coroutine function:
29// void *value = Coroutine_Yield(yield_value, on_yield, this);
30// ...
31// return return_value;
32//
33// To create a coroutine:
34// Coroutine *co = Coroutine_New(start_function);
35// To start or continue a coroutine:
36// void *result = Coroutine_Continue(co, value, early);
37// // early=true puts the coroutine at the head of the run queue
38// // early=false puts the coroutine at the tail of the run queue
39// To yield from inside a coroutine:
40// void *value = Coroutine_Yield(yield_value, on_yield, this);
41// // on_yield is called before the next coroutine is run
42// // 'this' is passed to on_yield as its parameter
43// // value is the value passed to Coroutine_Continue
44// To delete a coroutine:
45// Coroutine_Delete(co);
46// To get the value yielded from, or returned by a corotuine:
47// void *value = Coroutine_GetValue(co);
48// To get the currently running coroutine (NULL if none):
49// Coroutine *co = Coroutine_GetActive();
50// To check if a coroutine is currently running:
51// bool running = Coroutine_IsRunning(co);
52//
53// Notes:
54// Coroutine is not expected to be used directly, but as a foundation for
55// higher level constructs such as Generators, Async, etc.
56//
57///////////////////////////////////////////////////////////////////////////////
58
59
60// The stack is used as follows:
61// +------------------+ <- stack top
62// | coroutine header | <- more claimed as needed in Coroutine_New
63// +------------------+ <-
64// | coroutine stack | <-
65// +------------------+ <-
66// | coroutine header |
67// +------------------+
68// | coroutine stack |
69// +------------------+
70// | coroutine header |
71// +------------------+
72// | coroutine stack |
73// +------------------+
74// | coroutine header |
75// +------------------+
76// | coroutine stack |
77// +------------------+
78// | coroutine header |
79// +------------------+
80// | startup space | <- set aside by Coroutine_StartSystem
81// +------------------+
82// | caller | <- This calls Coroutine_StartSystem etc
83// +------------------+
84// | used stack |
85// +------------------+ <- stack bottom
86
87// Each coroutine has this much stack:
88// For Python, we set it to 17 * (enough for a PyEval_EvalDefault), so we get at least 7
89// calls deep before we need a new chunk, ie maximum multi-chunk wastage is under 6% address space.
90//
91// There's a trade-off between smaller chunk sizes, which allow more async tasks to co-exist
92// on a thread, and larger chunk sizes which waste less memory in part-used chunks.
93//
94// ... which means 10000 async tasks need a 2.6 GB stack, which fits comfortably in the address map.
95//
96// Note, when developing the use of Coroutine in Python, the author found the following used
97// excessive amounts of stack space:
98// Tk_Init: on an Intel 64 bit Mac it used 72k.
99// _decimal multplies of big decimal numbers: 256k+640 (2 x 128k buffers in squaretrans_pow2() + workings)
100//
101// On 64 bit macos, PYOS_STACK_MARGIN_BYTES is 2k * sizeof(void *), ie 16k, or 17 of those, 272k, should give enough slack to operate well.
102
103// This allows you to rename all Coroutine things with your own namespace.
104#ifndef Coroutine_NS
105 #define Coroutine_NS(N) Coroutine_##N
106#endif
107
108#ifndef Coroutine_API_FUNC
109 #define Coroutine_API_FUNC(T) extern T
110#endif
111
112// No coroutine will ask for less stack than this
113#ifndef COROUTINE_MINIMUM_STACK_SIZE
114 #define COROUTINE_MINIMUM_STACK_SIZE (4096 * sizeof(void *))
115#endif
116
117// When Coroutine is started, an amount of stack is set aside to give
118// the caller of Coroutine_StartSystem a bit of room to work before calling
119// Coroutine_Run(), that is this amount:
120#ifndef COROUTINE_STARTUP_STACK_SIZE
121 #ifndef _NDEBUG
122 #define COROUTINE_STARTUP_STACK_SIZE (1024 * sizeof(void *))
123 #else
124 #define COROUTINE_STARTUP_STACK_SIZE (128 * sizeof(void *))
125 #endif
126#endif
127
128// This is *expensive* to turn on, especially if you have lots of stack pieces (eg when there's lots of Tasks)
129#ifndef COROUTINE_CHECK_INTEGRITY_ON_STACK_CHECK
130 #define COROUTINE_CHECK_INTEGRITY_ON_STACK_CHECK 0
131#endif
132
133#ifndef COROUTINE_RECORD_LOWEST_HEADROOM
134 #define COROUTINE_RECORD_LOWEST_HEADROOM 1
135#endif
136
137// Returned by Coroutine_StopSystem(), this summarises the coroutine session
138typedef struct Coroutine_Report {
139 unsigned coroutines_created;
140 unsigned coroutines_pool_size;
141 size_t lowest_headroom;
142 size_t largest_stack;
143} Coroutine_Report;
144
145typedef enum Coroutine_Err {
146 Coroutine_OK = 0,
147 Coroutine_Err_SystemNotRunning,
148 Coroutine_Err_SystemRunning,
149 Coroutine_Err_NoStack,
150 Coroutine_Err_CoroutineFromWrongThread,
151 Coroutine_Err_ACoroutineIsAlreadyRunning,
152 Coroutine_Err_ExitWithRunningCoroutines,
153 Coroutine_Err_StackOverrun,
154 Coroutine_Err_InternalInsistency,
155 Coroutine_Err_CouldNotInitialiseSystem,
156 Coroutine_Err_WrongState,
157 Coroutine_Err_Canceled
158} Coroutine_Err;
159
160typedef struct Coroutine Coroutine;
161
162typedef void (*Coroutine_YieldCallback)(void *me);
163typedef Coroutine_Err (*Coroutine_SystemStart)(void *);
164typedef void *(*Coroutine_Start)(void *);
165
166Coroutine_API_FUNC(void) Coroutine_NS(SetStackLimit)(void *);
167Coroutine_API_FUNC(Coroutine_Report) Coroutine_NS(GetReport)(void);
168#ifndef NDEBUG
169 Coroutine_API_FUNC(Coroutine_Err) Coroutine_NS(CheckIntegrity)(void);
170#else
171 static inline Coroutine_Err Coroutine_NS(CheckIntegrity)(void){return Coroutine_OK;}
172#endif
173Coroutine_API_FUNC(Coroutine *) Coroutine_NS(New)(size_t size, Coroutine_Start start);
174Coroutine_API_FUNC(Coroutine_Err) Coroutine_NS(Run_Coroutine)(Coroutine *cor, void *value);
175Coroutine_API_FUNC(Coroutine_Err) Coroutine_NS(RunSystem)(Coroutine_SystemStart start, void *value);
176Coroutine_API_FUNC(Coroutine_Err) Coroutine_NS(Run)(size_t size, Coroutine_Start start, void *value, void **result);
177Coroutine_API_FUNC(void) Coroutine_NS(Delete)(Coroutine *cor);
178Coroutine_API_FUNC(Coroutine_Err) Coroutine_NS(Continue)(Coroutine *cor, void *value, bool early);
179Coroutine_API_FUNC(void *) Coroutine_NS(Yield)(void *value, Coroutine_YieldCallback on_yield, void *me);
180Coroutine_API_FUNC(void *) Coroutine_NS(GetValue)(Coroutine *cor);
181Coroutine_API_FUNC(Coroutine *) Coroutine_NS(GetActive)(void);
182Coroutine_API_FUNC(intptr_t) Coroutine_NS(GetStackHeadroom)(void);
183Coroutine_API_FUNC(void *) Coroutine_NS(GetStackHWM)(void);
184Coroutine_API_FUNC(void) Coroutine_NS(ClearStackForHWM)(void);
185Coroutine_API_FUNC(bool) Coroutine_NS(CanStartCoroutine)(size_t size);
186Coroutine_API_FUNC(void *) Coroutine_NS(GetCStackTop)(void);
187Coroutine_API_FUNC(Coroutine_Err) Coroutine_NS(Chain)(size_t size, Coroutine_Start start, void *value, void **result);
188Coroutine_API_FUNC(bool) Coroutine_NS(IsStarted)(void);
189Coroutine_API_FUNC(bool) Coroutine_NS(IsRunning)(Coroutine *cor);
190Coroutine_API_FUNC(bool) Coroutine_NS(IsComplete)(Coroutine *cor);
191
192Coroutine_API_FUNC(void) Coroutine_NS(Dump_)(void);
193
194#endif
195