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

ADTarm-ehast-experimentalenumforall-pointer-decayjacob/cs343-translationnew-ast-unique-exprpthread-emulationqualifiedEnum
Last change on this file since e235429 was 1c01c58, checked in by Andrew Beach <ajbeach@…>, 4 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.