source: libcfa/src/concurrency/coroutine.cfa@ c76bd34

ADT arm-eh ast-experimental enum forall-pointer-decay jacob/cs343-translation new-ast-unique-expr pthread-emulation qualifiedEnum
Last change on this file since c76bd34 was 1c01c58, checked in by Andrew Beach <ajbeach@…>, 5 years ago

Rather large commit to get coroutine cancellation working.

This includes what you would expect, like new code in exceptions and a new
test, but it also includes a bunch of other things.

New coroutine state, currently just marks that the stack was cancelled. New
helpers for checking code structure and generating vtables. Changes to the
coroutine interface so resume may throw exceptions on cancellation, plus the
exception type that is thrown. Changes to the coroutine keyword generation to
generate exception code for each type of coroutine.

  • Property mode set to 100644
File size: 8.4 KB
Line 
1//
2// Cforall Version 1.0.0 Copyright (C) 2016 University of Waterloo
3//
4// The contents of this file are covered under the licence agreement in the
5// file "LICENCE" distributed with Cforall.
6//
7// coroutine.c --
8//
9// Author : Thierry Delisle
10// Created On : Mon Nov 28 12:27:26 2016
11// Last Modified By : Peter A. Buhr
12// Last Modified On : Tue May 26 22:06:09 2020
13// Update Count : 21
14//
15
16#define __cforall_thread__
17
18#include "coroutine.hfa"
19
20#include <stddef.h>
21#include <malloc.h>
22#include <errno.h>
23#include <string.h>
24#include <unistd.h>
25#include <sys/mman.h> // mprotect
26extern "C" {
27// use this define to make unwind.h play nice, definitely a hack
28#define HIDE_EXPORTS
29#include <unwind.h>
30#undef HIDE_EXPORTS
31}
32
33#include "kernel_private.hfa"
34
35#define __CFA_INVOKE_PRIVATE__
36#include "invoke.h"
37
38extern "C" {
39 void _CtxCoroutine_Unwind(struct _Unwind_Exception * storage, struct $coroutine *) __attribute__ ((__noreturn__));
40 static void _CtxCoroutine_UnwindCleanup(_Unwind_Reason_Code, struct _Unwind_Exception *) __attribute__ ((__noreturn__));
41 static void _CtxCoroutine_UnwindCleanup(_Unwind_Reason_Code, struct _Unwind_Exception *) {
42 abort();
43 }
44
45 extern void CtxRet( struct __stack_context_t * to ) asm ("CtxRet") __attribute__ ((__noreturn__));
46}
47
48//-----------------------------------------------------------------------------
49FORALL_DATA_INSTANCE(CoroutineCancelled,
50 (dtype coroutine_t | sized(coroutine_t)), (coroutine_t))
51
52struct __cfaehm_node {
53 struct _Unwind_Exception unwind_exception;
54 struct __cfaehm_node * next;
55 int handler_index;
56};
57
58forall(dtype T)
59void mark_exception(CoroutineCancelled(T) *) {}
60
61forall(dtype T | sized(T))
62void copy(CoroutineCancelled(T) * dst, CoroutineCancelled(T) * src) {
63 dst->the_coroutine = src->the_coroutine;
64 dst->the_exception = src->the_exception;
65}
66
67forall(dtype T)
68const char * msg(CoroutineCancelled(T) *) {
69 return "CoroutineCancelled(...)";
70}
71
72// This code should not be inlined. It is the error path on resume.
73forall(dtype T | is_coroutine(T))
74void __cfaehm_cancelled_coroutine( T & cor, $coroutine * desc ) {
75 verify( desc->cancellation );
76 desc->state = Cancelled;
77 exception_t * except = (exception_t *)(1 + (__cfaehm_node *)desc->cancellation);
78
79 CoroutineCancelled(T) except;
80 except.the_coroutine = &cor;
81 except.the_exception = except;
82 throwResume except;
83
84 except->virtual_table->free( except );
85 free( desc->cancellation );
86 desc->cancellation = 0p;
87}
88
89//-----------------------------------------------------------------------------
90// Global state variables
91
92// minimum feasible stack size in bytes
93#define MinStackSize 1000
94extern size_t __page_size; // architecture pagesize HACK, should go in proper runtime singleton
95
96void __stack_prepare( __stack_info_t * this, size_t create_size );
97
98//-----------------------------------------------------------------------------
99// Coroutine ctors and dtors
100void ?{}( __stack_info_t & this, void * storage, size_t storageSize ) {
101 this.storage = (__stack_t *)storage;
102
103 // Did we get a piece of storage ?
104 if (this.storage || storageSize != 0) {
105 // We either got a piece of storage or the user asked for a specific size
106 // Immediately create the stack
107 // (This is slightly unintuitive that non-default sized coroutines create are eagerly created
108 // but it avoids that all coroutines carry an unnecessary size)
109 verify( storageSize != 0 );
110 __stack_prepare( &this, storageSize );
111 }
112}
113
114void ^?{}(__stack_info_t & this) {
115 bool userStack = ((intptr_t)this.storage & 0x1) != 0;
116 if ( ! userStack && this.storage ) {
117 __attribute__((may_alias)) intptr_t * istorage = (intptr_t *)&this.storage;
118 *istorage &= (intptr_t)-1;
119
120 void * storage = this.storage->limit;
121 __cfaabi_dbg_debug_do(
122 storage = (char*)(storage) - __page_size;
123 if ( mprotect( storage, __page_size, PROT_READ | PROT_WRITE ) == -1 ) {
124 abort( "(coStack_t *)%p.^?{}() : internal error, mprotect failure, error(%d) %s.", &this, errno, strerror( errno ) );
125 }
126 );
127 __cfaabi_dbg_print_safe("Kernel : Deleting stack %p\n", storage);
128 free( storage );
129 }
130}
131
132void ?{}( $coroutine & this, const char name[], void * storage, size_t storageSize ) with( this ) {
133 (this.context){0p, 0p};
134 (this.stack){storage, storageSize};
135 this.name = name;
136 state = Start;
137 starter = 0p;
138 last = 0p;
139 cancellation = 0p;
140}
141
142void ^?{}($coroutine& this) {
143 if(this.state != Halted && this.state != Start && this.state != Primed) {
144 $coroutine * src = TL_GET( this_thread )->curr_cor;
145 $coroutine * dst = &this;
146
147 struct _Unwind_Exception storage;
148 storage.exception_class = -1;
149 storage.exception_cleanup = _CtxCoroutine_UnwindCleanup;
150 this.cancellation = &storage;
151 this.last = src;
152
153 // not resuming self ?
154 if ( src == dst ) {
155 abort( "Attempt by coroutine %.256s (%p) to terminate itself.\n", src->name, src );
156 }
157
158 $ctx_switch( src, dst );
159 }
160}
161
162// Part of the Public API
163// Not inline since only ever called once per coroutine
164forall(dtype T | is_coroutine(T))
165void prime(T& cor) {
166 $coroutine* this = get_coroutine(cor);
167 assert(this->state == Start);
168
169 this->state = Primed;
170 resume(cor);
171}
172
173[void *, size_t] __stack_alloc( size_t storageSize ) {
174 const size_t stack_data_size = libCeiling( sizeof(__stack_t), 16 ); // minimum alignment
175 assert(__page_size != 0l);
176 size_t size = libCeiling( storageSize, 16 ) + stack_data_size;
177
178 // If we are running debug, we also need to allocate a guardpage to catch stack overflows.
179 void * storage;
180 __cfaabi_dbg_debug_do(
181 storage = memalign( __page_size, size + __page_size );
182 );
183 __cfaabi_dbg_no_debug_do(
184 storage = (void*)malloc(size);
185 );
186
187 __cfaabi_dbg_print_safe("Kernel : Created stack %p of size %zu\n", storage, size);
188 __cfaabi_dbg_debug_do(
189 if ( mprotect( storage, __page_size, PROT_NONE ) == -1 ) {
190 abort( "__stack_alloc : internal error, mprotect failure, error(%d) %s.", (int)errno, strerror( (int)errno ) );
191 }
192 storage = (void *)(((intptr_t)storage) + __page_size);
193 );
194
195 verify( ((intptr_t)storage & (libAlign() - 1)) == 0ul );
196 return [storage, size];
197}
198
199void __stack_prepare( __stack_info_t * this, size_t create_size ) {
200 const size_t stack_data_size = libCeiling( sizeof(__stack_t), 16 ); // minimum alignment
201 bool userStack;
202 void * storage;
203 size_t size;
204 if ( !this->storage ) {
205 userStack = false;
206 [storage, size] = __stack_alloc( create_size );
207 } else {
208 userStack = true;
209 __cfaabi_dbg_print_safe("Kernel : stack obj %p using user stack %p(%zd bytes)\n", this, this->storage, (intptr_t)this->storage->limit - (intptr_t)this->storage->base);
210
211 // The stack must be aligned, advance the pointer to the next align data
212 storage = (void*)libCeiling( (intptr_t)this->storage, libAlign());
213
214 // The size needs to be shrinked to fit all the extra data structure and be aligned
215 ptrdiff_t diff = (intptr_t)storage - (intptr_t)this->storage;
216 size = libFloor(create_size - stack_data_size - diff, libAlign());
217 } // if
218 assertf( size >= MinStackSize, "Stack size %zd provides less than minimum of %d bytes for a stack.", size, MinStackSize );
219
220 this->storage = (__stack_t *)((intptr_t)storage + size);
221 this->storage->limit = storage;
222 this->storage->base = (void*)((intptr_t)storage + size);
223 this->storage->exception_context.top_resume = 0p;
224 this->storage->exception_context.current_exception = 0p;
225 __attribute__((may_alias)) intptr_t * istorage = (intptr_t*)&this->storage;
226 *istorage |= userStack ? 0x1 : 0x0;
227}
228
229// We need to call suspend from invoke.c, so we expose this wrapper that
230// is not inline (We can't inline Cforall in C)
231extern "C" {
232 void __cfactx_cor_leave( struct $coroutine * src ) {
233 $coroutine * starter = src->cancellation != 0 ? src->last : src->starter;
234
235 src->state = Halted;
236
237 assertf( starter != 0,
238 "Attempt to suspend/leave coroutine \"%.256s\" (%p) that has never been resumed.\n"
239 "Possible cause is a suspend executed in a member called by a coroutine user rather than by the coroutine main.",
240 src->name, src );
241 assertf( starter->state != Halted,
242 "Attempt by coroutine \"%.256s\" (%p) to suspend/leave back to terminated coroutine \"%.256s\" (%p).\n"
243 "Possible cause is terminated coroutine's main routine has already returned.",
244 src->name, src, starter->name, starter );
245
246 $ctx_switch( src, starter );
247 }
248
249 struct $coroutine * __cfactx_cor_finish(void) {
250 struct $coroutine * cor = kernelTLS.this_thread->curr_cor;
251
252 if(cor->state == Primed) {
253 __cfactx_suspend();
254 }
255
256 cor->state = Active;
257
258 return cor;
259 }
260}
261
262// Local Variables: //
263// mode: c //
264// tab-width: 4 //
265// End: //
Note: See TracBrowser for help on using the repository browser.