/
ContextC.c
344 lines (274 loc) · 8.48 KB
/
ContextC.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
/*
* Coroutines for Modula-3 !
*
* Author : Mika Nystrom <mika.nystroem@intel.com>
* April, 2018
*
* Much of this code is a hybrid of the PTHREADS code by Tony Hosking
* and the POSIX code by DEC-SRC.
*/
#include <ucontext.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
#define M3_RETRY(expr) \
r = (expr); \
if (r == EAGAIN || r == ENOMEM || r == ENOSPC) \
{ \
/* try again right away */ \
r = (expr); \
if (r == EAGAIN || r == ENOMEM || r == ENOSPC) \
{ \
/* try again after short delay */ \
sleep(1); \
r = (expr); \
} \
}
#define INTEGER long int
#define WORD_T unsigned long int
#define ARG void *
#define DEBUG 1
#define stack_grows_downward 1 /* AMD64_LINUX */
typedef struct {
void (*p)(ARG);
void *arg;
} Closure;
typedef struct {
void *stackaddr;
WORD_T stacksize;
void *stackbase; /* this is the base reported to M3's GC */
ucontext_t uc;
ucontext_t pc; /* post context for cleanup */
int alive;
} Context;
/* a truly clever implementation would:
not store uc in the Context, but instead put it on the stack. This
ought to be possible: uc is used for two purposes. One is to keep
track of where we are in the coroutine so we can restart properly.
The second is so that GC can scan the registers of the thread.
Note that we need the uc on the stack at the same time (when the
coroutine is inactive). When the coroutine is active, the code in
ThreadPThread will *ensure* that the context is pushed anyhow.
We're not this clever yet.
*/
static void
trampoline(int lo, int hi)
{
/* take two 32-bit pointers as ints and turn them into a 64-bit pointer */
Closure *cl = (Closure *)(((unsigned long)(unsigned int)hi << 32UL) | (unsigned long)(unsigned int)lo);
cl->p(cl->arg);
}
static void *
ContextC__New(void)
{
Context *res=calloc(1,sizeof(Context));
res->alive = 1;
return res;
}
void
ContextC__SetLink(Context *tgt, Context *src)
{
tgt->uc.uc_link = &(src->uc);
}
static void
cleanup(int lo, int hi)
{
/* this is the cleanup routine
it is called when a context falls off the end (apply ends)
*/
Context *c = (Context *)(((unsigned long)(unsigned int)hi << 32UL) | (unsigned long)(unsigned int)lo);
c->alive = 0;
assert(c->uc.uc_link);
/* this is the only tricky part:
the semantics of creating the context are that the followon context
must be set when the context is created.
this is not the semantics we are going for here. In our implementation,
when a coroutine "falls off the end", we resume with the context that
called it (and none other!) This next coroutine is not known when
the dying coroutine is created, only when it is called.
this is implemented by updating uc_link on every coroutine call.
but it can't take effect like that, so we make a special catcher context
(in which we are here in cleanup) that jumps explicitly to that context.
An alternate design would call in here to cleanup directly from the end
of CoroutineUcontext.Run . That would probably be simpler and work
OK, too.
*/
setcontext(c->uc.uc_link);
}
void *
ContextC__MakeContext(void (*p)(ARG),
INTEGER words,
void *arg)
{
/* from ThreadPosixC.c */
Context *c = (Context *)calloc (1, sizeof(*c));
INTEGER size = sizeof(void *) * words;
INTEGER pagesize = getpagesize();
char *sp = { 0 };
INTEGER pages = { 0 };
int er = { 0 };
Closure *cl = (Closure *)calloc(1, sizeof(*cl));
int lo, hi;
char *slim, *sbeg; /* limits of stack, for bookkeeping */
if (c == NULL || cl == NULL)
goto Error;
c->alive = 1;
if (size <= 0) return c;
if (size < MINSIGSTKSZ) size = MINSIGSTKSZ;
/*
* Round up to a whole number of pages, and
* allocate three extra pages. Two pages for the redzone and one for
* the arg pointer
*/
pages = (size + pagesize - 1) / pagesize + 3;
size = pages * pagesize;
sp = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
if (DEBUG) fprintf(stderr, "creating coroutine stack %#lx\n", sp);
if (sp == NULL) goto Error;
c->stackaddr = sp;
c->stacksize = size;
/* range for the stack is [sp, sp + size) */
sbeg = sp;
slim = sp + size;
/*
* redzone one page at the start
* and one at the end, don't allow accessing
* either one (catch stack overflow and underflow).
*/
/* mprotect first and last page */
if (mprotect(sbeg , pagesize, PROT_NONE)) abort();
if (mprotect(slim - pagesize , pagesize, PROT_NONE)) abort();
/* adjust ends of stack region accordingly */
sbeg += pagesize;
slim -= pagesize;
/* the stack is now ready for use */
if (stack_grows_downward) {
c->stackbase = slim;
} else {
c->stackbase = sbeg;
}
/*
* Modula-3 GC interaction tricks:
* we need to push arg on the stack to pin it from the gc
* we do this by writing it at what could be the initial sp, and
* adjust the sp by the size of arg
*
* in other words, the sp given to the garbage collector will include
* arg but the sp given to swapcontext will not
*/
if (stack_grows_downward) {
*(void **)(slim - sizeof(void *)) = arg;
/* adjust stack region accordingly */
slim -= sizeof(void *);
} else {
*(void **)(sbeg) = arg;
/* adjust stack region accordingly */
sbeg += sizeof(void *);
}
if (getcontext(&(c->uc))) abort();
if (getcontext(&(c->pc))) abort();
c->uc.uc_stack.ss_sp = sbeg;
c->uc.uc_stack.ss_size = slim - sbeg;
cl->p = p;
cl->arg = arg;
/* define the post context for cleanup */
c->pc.uc_stack = c->uc.uc_stack; /* reuse stack */
c->pc.uc_link = (void *)0; /* will never go through here */
c->uc.uc_link = &(c->pc); /* set up cleanup linkage */
lo = (int)(unsigned long)c;
hi = (int)(((unsigned long)c) >> 32UL);
makecontext(&(c->pc), (void (*)())cleanup, 2, lo, hi);
lo = (int)(unsigned long)cl;
hi = (int)(((unsigned long)cl) >> 32UL);
makecontext(&(c->uc), (void (*)())trampoline, 2, lo, hi);
return c;
Error:
er = errno;
if (c) free(c);
if (cl) free(cl);
if (sp) munmap(sp, size);
errno = er;
return NULL;
}
void
ContextC__SwapContext (Context *from, Context *to)
{
if(!to->alive) {
fprintf(stderr,
"WARNING: calling dead coroutine context %#lx\n",to);
return;
}
if (swapcontext(&(from->uc), &(to->uc))) abort();
}
void
ContextC__DisposeContext (Context *c)
{
if (munmap((c)->stackaddr, (c)->stacksize)) abort();
free(c);
}
void *
ContextC__Current(void)
{
Context *new=ContextC__New();
if (getcontext(&(new->uc))) abort();
return new;
}
static pthread_key_t current_coroutine;
void
ContextC__SetCurrentCoroutine(INTEGER *value)
{
int r = { 0 };
M3_RETRY(pthread_setspecific(current_coroutine, (void *)value));
assert(r == 0);
}
INTEGER *
ContextC__GetCurrentCoroutine(void)
{
INTEGER *res = (INTEGER *)pthread_getspecific(current_coroutine);
return res;
}
void
ContextC__InitC(void) /* should be void *bottom? */
{
int r = { 0 };
M3_RETRY(pthread_key_create(¤t_coroutine, NULL)); assert(r == 0);
}
void *
ContextC__GetStackBase(Context *c)
{
return c->stackbase;
}
/* in the following functions, we use "auto" to signify that
it is essential to the operation of the program that these variables
are indeed placed on the stack */
static void *
stack_here(void)
{
auto char *top=(char *)⊤
return top;
}
static void *
ContextC__PushContext1(Context *c)
{
auto ucontext_t uc=c->uc; /* write it on the stack */
void *top;
top = stack_here();
return top;
}
#define STACK_GAP 256
void *
ContextC__PushContext(Context *c)
{
auto char a[STACK_GAP];
/* the purpose of the gap is to allow other routines to do some small
amount of work between when we return and the next GC event,
without clobbering the context we are about to push */
(void)memset(a, 0, STACK_GAP);
return ContextC__PushContext1(c);
}