11 months ago |
1 |
#ifndef COROUTINE_H | |||
2 |
#define COROUTINE_H | ||||
3 |
|||||
12 months ago |
2 |
4 |
#include <stdbool.h> | ||
8 months ago |
5 |
#include <stddef.h> | |||
10 months ago |
6 |
#include <stdint.h> | |||
2 months ago |
7 |
#include "cor_platform_inc.h" | |||
2 months ago |
8 |
#include "coroutine_names_def.h" | |||
12 months ago |
2 |
9 |
|||
11 months ago |
10 |
/////////////////////////////////////////////////////////////////////////////// | |||
11 |
// Coroutine | ||||
12 |
// | ||||
13 |
// Coroutines for C, based on setjmp/longjmp. | ||||
14 |
// Thread safe - each thread has its own coroutine system | ||||
15 |
// Coroutines are cooperatively scheduled | ||||
16 |
// Coroutines have their own stack (currently 16K each) | ||||
17 |
// A coroutine can be continued, queried, or deleted on a different thread. | ||||
18 |
// | ||||
19 |
// Usage: | ||||
20 |
// Coroutine_StartSystem(); // call once per thread before using coroutines | ||||
21 |
// Coroutine *co = Coroutine_New(start_function); | ||||
8 months ago |
22 |
// void *result; | |||
6 months ago |
23 |
// if (Coroutine_Run(co, initial_value, &result)) { | |||
8 months ago |
24 |
// // Handle the failure | |||
6 months ago |
25 |
// } | |||
11 months ago |
26 |
// Coroutine_Delete(co); | |||
27 |
// Coroutine_StopSystem(); // call once per thread when done with coroutines | ||||
28 |
// | ||||
29 |
// Inside the coroutine function: | ||||
30 |
// void *value = Coroutine_Yield(yield_value, on_yield, this); | ||||
31 |
// ... | ||||
32 |
// return return_value; | ||||
33 |
// | ||||
34 |
// To create a coroutine: | ||||
35 |
// Coroutine *co = Coroutine_New(start_function); | ||||
36 |
// To start or continue a coroutine: | ||||
37 |
// void *result = Coroutine_Continue(co, value, early); | ||||
38 |
// // early=true puts the coroutine at the head of the run queue | ||||
39 |
// // early=false puts the coroutine at the tail of the run queue | ||||
40 |
// To yield from inside a coroutine: | ||||
41 |
// void *value = Coroutine_Yield(yield_value, on_yield, this); | ||||
42 |
// // on_yield is called before the next coroutine is run | ||||
43 |
// // 'this' is passed to on_yield as its parameter | ||||
44 |
// // value is the value passed to Coroutine_Continue | ||||
45 |
// To delete a coroutine: | ||||
46 |
// Coroutine_Delete(co); | ||||
47 |
// To get the value yielded from, or returned by a corotuine: | ||||
48 |
// void *value = Coroutine_GetValue(co); | ||||
49 |
// To get the currently running coroutine (NULL if none): | ||||
50 |
// Coroutine *co = Coroutine_GetActive(); | ||||
51 |
// To check if a coroutine is currently running: | ||||
52 |
// bool running = Coroutine_IsRunning(co); | ||||
53 |
// | ||||
54 |
// Notes: | ||||
55 |
// Coroutine is not expected to be used directly, but as a foundation for | ||||
56 |
// higher level constructs such as Generators, Async, etc. | ||||
57 |
// | ||||
58 |
/////////////////////////////////////////////////////////////////////////////// | ||||
59 |
|||||
60 |
|||||
61 |
// The stack is used as follows: | ||||
6 months ago |
62 |
// +------------------+ <- stack top | |||
63 |
// | coroutine header | <- more claimed as needed in Coroutine_New | ||||
64 |
// +------------------+ <- | ||||
65 |
// | coroutine stack | <- | ||||
66 |
// +------------------+ <- | ||||
67 |
// | coroutine header | | ||||
11 months ago |
68 |
// +------------------+ | |||
69 |
// | coroutine stack | | ||||
70 |
// +------------------+ | ||||
6 months ago |
71 |
// | coroutine header | | |||
11 months ago |
72 |
// +------------------+ | |||
73 |
// | coroutine stack | | ||||
74 |
// +------------------+ | ||||
75 |
// | coroutine header | | ||||
76 |
// +------------------+ | ||||
77 |
// | coroutine stack | | ||||
78 |
// +------------------+ | ||||
79 |
// | coroutine header | | ||||
80 |
// +------------------+ | ||||
81 |
// | startup space | <- set aside by Coroutine_StartSystem | ||||
82 |
// +------------------+ | ||||
83 |
// | caller | <- This calls Coroutine_StartSystem etc | ||||
84 |
// +------------------+ | ||||
85 |
// | used stack | | ||||
6 months ago |
86 |
// +------------------+ <- stack bottom | |||
11 months ago |
87 |
||||
6 months ago |
88 |
// Each coroutine has this much stack: | |||
89 |
// For Python, we set it to 17 * (enough for a PyEval_EvalDefault), so we get at least 7 | ||||
90 |
// calls deep before we need a new chunk, ie maximum multi-chunk wastage is under 6% address space. | ||||
91 |
// | ||||
92 |
// There's a trade-off between smaller chunk sizes, which allow more async tasks to co-exist | ||||
93 |
// on a thread, and larger chunk sizes which waste less memory in part-used chunks. | ||||
94 |
// | ||||
95 |
// ... which means 10000 async tasks need a 2.6 GB stack, which fits comfortably in the address map. | ||||
96 |
// | ||||
97 |
// Note, when developing the use of Coroutine in Python, the author found the following used | ||||
98 |
// excessive amounts of stack space: | ||||
99 |
// Tk_Init: on an Intel 64 bit Mac it used 72k. | ||||
100 |
// _decimal multplies of big decimal numbers: 256k+640 (2 x 128k buffers in squaretrans_pow2() + workings) | ||||
101 |
// | ||||
102 |
// 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. | ||||
103 |
|||||
3 months ago |
104 |
||||
3 months ago |
105 |
#ifndef Coroutine_API_FUNC | |||
106 |
#define Coroutine_API_FUNC(T) extern T | ||||
107 |
#endif | ||||
108 |
|||||
8 months ago |
109 |
// No coroutine will ask for less stack than this | |||
110 |
#ifndef COROUTINE_MINIMUM_STACK_SIZE | ||||
111 |
#define COROUTINE_MINIMUM_STACK_SIZE (4096 * sizeof(void *)) | ||||
11 months ago |
112 |
#endif | |||
11 months ago |
113 |
||||
4 days ago |
114 |
// A guard pattern is placed at the end of every stack chunk to detect stack overruns. | |||
115 |
// The initial stack chunk ends at the C stack's limit, and is the only chunk not created | ||||
116 |
// by calling a routine and using alloca(). | ||||
117 |
// On Windows, the OS assigns real memory pages for the used stack only (not unusual), but | ||||
118 |
// restricts when new pages are assigned. My guess is that it assumes read/write of an | ||||
119 |
// unassigned page which is also beyond the stack pointer is probably a bug. The consequence | ||||
120 |
// is that you can't write a guard pattern at the C stack's limit, hence this configuration | ||||
121 |
// control exists. | ||||
122 |
#ifndef COROUTINE_GUARD_AT_C_STACK_LIMIT | ||||
123 |
#if defined(MS_WIN32) | ||||
124 |
#define COROUTINE_GUARD_AT_C_STACK_LIMIT 0 | ||||
125 |
#else | ||||
126 |
#define COROUTINE_GUARD_AT_C_STACK_LIMIT 1 | ||||
127 |
#endif | ||||
128 |
#endif | ||||
129 |
|||||
6 months ago |
130 |
// When Coroutine is started, an amount of stack is set aside to give | |||
131 |
// the caller of Coroutine_StartSystem a bit of room to work before calling | ||||
132 |
// Coroutine_Run(), that is this amount: | ||||
11 months ago |
133 |
#ifndef COROUTINE_STARTUP_STACK_SIZE | |||
3 months ago |
134 |
#ifndef _NDEBUG | |||
135 |
#define COROUTINE_STARTUP_STACK_SIZE (1024 * sizeof(void *)) | ||||
136 |
#else | ||||
137 |
#define COROUTINE_STARTUP_STACK_SIZE (128 * sizeof(void *)) | ||||
138 |
#endif | ||||
11 months ago |
139 |
#endif | |||
11 months ago |
140 |
||||
6 months ago |
141 |
// This is *expensive* to turn on, especially if you have lots of stack pieces (eg when there's lots of Tasks) | |||
142 |
#ifndef COROUTINE_CHECK_INTEGRITY_ON_STACK_CHECK | ||||
143 |
#define COROUTINE_CHECK_INTEGRITY_ON_STACK_CHECK 0 | ||||
9 months ago |
144 |
#endif | |||
145 |
|||||
6 months ago |
146 |
#ifndef COROUTINE_RECORD_LOWEST_HEADROOM | |||
147 |
#define COROUTINE_RECORD_LOWEST_HEADROOM 1 | ||||
148 |
#endif | ||||
149 |
|||||
11 months ago |
150 |
// Returned by Coroutine_StopSystem(), this summarises the coroutine session | |||
151 |
typedef struct Coroutine_Report { | ||||
152 |
unsigned coroutines_created; | ||||
153 |
unsigned coroutines_pool_size; | ||||
8 months ago |
154 |
size_t lowest_headroom; | |||
155 |
size_t largest_stack; | ||||
11 months ago |
156 |
} Coroutine_Report; | |||
157 |
|||||
6 months ago |
158 |
typedef enum Coroutine_Err { | |||
159 |
Coroutine_OK = 0, | ||||
160 |
Coroutine_Err_SystemNotRunning, | ||||
161 |
Coroutine_Err_SystemRunning, | ||||
162 |
Coroutine_Err_NoStack, | ||||
163 |
Coroutine_Err_CoroutineFromWrongThread, | ||||
164 |
Coroutine_Err_ACoroutineIsAlreadyRunning, | ||||
165 |
Coroutine_Err_ExitWithRunningCoroutines, | ||||
166 |
Coroutine_Err_StackOverrun, | ||||
167 |
Coroutine_Err_InternalInsistency, | ||||
168 |
Coroutine_Err_CouldNotInitialiseSystem, | ||||
169 |
Coroutine_Err_WrongState, | ||||
170 |
Coroutine_Err_Canceled | ||||
171 |
} Coroutine_Err; | ||||
172 |
|||||
12 months ago |
2 |
173 |
typedef struct Coroutine Coroutine; | ||
174 |
|||||
3 months ago |
175 |
typedef void (*Coroutine_YieldCallback)(void *me); | |||
2 months ago |
176 |
typedef Coroutine_Err (*Coroutine_SystemStart)(void *, Coroutine *); | |||
12 months ago |
2 |
177 |
typedef void *(*Coroutine_Start)(void *); | ||
178 |
|||||
2 months ago |
179 |
Coroutine_API_FUNC(void) Coroutine_SetStackLimit(void *); | |||
180 |
Coroutine_API_FUNC(Coroutine_Report) Coroutine_GetReport(void); | ||||
6 months ago |
181 |
#ifndef NDEBUG | |||
2 months ago |
182 |
Coroutine_API_FUNC(Coroutine_Err) Coroutine_CheckIntegrity(void); | |||
6 months ago |
183 |
#else | |||
2 months ago |
184 |
static inline Coroutine_Err Coroutine_CheckIntegrity(void){return Coroutine_OK;} | |||
6 months ago |
185 |
#endif | |||
2 months ago |
186 |
Coroutine_API_FUNC(Coroutine *) Coroutine_New(size_t min_size, size_t min_headroom, Coroutine_Start start); | |||
187 |
Coroutine_API_FUNC(Coroutine_Err) Coroutine_RunSystem(size_t min_size, size_t min_headroom, Coroutine_SystemStart start, void *value); | ||||
188 |
Coroutine_API_FUNC(Coroutine_Err) Coroutine_Run(size_t min_size, size_t min_headroom, Coroutine_Start start, void *value, void **result); | ||||
189 |
Coroutine_API_FUNC(void) Coroutine_Delete(Coroutine *cor); | ||||
190 |
Coroutine_API_FUNC(Coroutine_Err) Coroutine_Continue(Coroutine *cor, void *value, bool early); | ||||
191 |
Coroutine_API_FUNC(void *) Coroutine_Yield(void *value, Coroutine_YieldCallback on_yield, void *me); | ||||
192 |
Coroutine_API_FUNC(void *) Coroutine_GetValue(Coroutine *cor); | ||||
193 |
Coroutine_API_FUNC(Coroutine *) Coroutine_GetActive(void); | ||||
194 |
Coroutine_API_FUNC(ptrdiff_t) Coroutine_GetStackHeadroom(void); | ||||
195 |
Coroutine_API_FUNC(void *) Coroutine_GetStackHWM(void); | ||||
196 |
Coroutine_API_FUNC(void) Coroutine_ClearStackForHWM(void); | ||||
197 |
Coroutine_API_FUNC(bool) Coroutine_CanStartCoroutine(size_t size); | ||||
2 months ago |
198 |
Coroutine_API_FUNC(size_t) Coroutine_GetUsefulFreeSpace(size_t min_size, size_t overhead); | |||
2 months ago |
199 |
Coroutine_API_FUNC(void *) Coroutine_GetCStackTop(void); | |||
200 |
Coroutine_API_FUNC(Coroutine_Err) Coroutine_Chain(size_t min_size, size_t min_headroom, Coroutine_Start start, void *value, void **result); | ||||
2 months ago |
201 |
Coroutine_API_FUNC(Coroutine_Err) Coroutine_CallWithMaxStack(Coroutine_Start start, void *value, void **result); | |||
2 months ago |
202 |
Coroutine_API_FUNC(bool) Coroutine_IsStarted(void); | |||
203 |
Coroutine_API_FUNC(bool) Coroutine_IsRunning(Coroutine *cor); | ||||
204 |
Coroutine_API_FUNC(bool) Coroutine_IsComplete(Coroutine *cor); | ||||
11 months ago |
205 |
||||
2 months ago |
206 |
Coroutine_API_FUNC(void) Coroutine_Dump_(void); | |||
8 months ago |
207 |
||||
2 months ago |
208 |
#include "coroutine_names_undef.h" | |||
11 months ago |
209 |
#endif | |||
210 |