]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | [/ |
2 | Copyright Oliver Kowalke 2014. | |
3 | Distributed under the Boost Software License, Version 1.0. | |
4 | (See accompanying file LICENSE_1_0.txt or copy at | |
5 | http://www.boost.org/LICENSE_1_0.txt | |
6 | ] | |
7 | ||
8 | [#ecv1] | |
9 | [section:ecv1 Class execution_context (version 1)] | |
10 | ||
11 | [note This class is only enabled if property ['segmented-stacks=on] (enables | |
12 | segmented stacks) or compiler flag ['BOOST_EXECUTION_CONTEXT=1] is specified | |
13 | at b2-commandline.] | |
14 | ||
15 | Class __econtext__ encapsulates context switching and manages the associated | |
16 | context' stack (allocation/deallocation). | |
17 | ||
18 | __econtext__ allocates the context stack (using its [link stack | |
19 | __stack_allocator__] argument) and creates a control structure on top of it. | |
20 | This structure is responsible for managing context' stack. Instances of | |
21 | __econtext__, associated with a specific context, share the ownership of the | |
22 | control structure. If the last reference goes out of scope, the control | |
23 | structure is destroyed and the stack gets deallocated via the | |
24 | __stack_allocator__. | |
25 | ||
26 | __econtext__ is copy-constructible, move-constructible, copy-assignable and | |
27 | move-assignable. | |
28 | ||
29 | __econtext__ maintains a static (thread-local) pointer, accessed by | |
30 | __ec_current__, pointing to the active context. On each context switch the | |
31 | pointer is updated. | |
32 | The usage of this global pointer makes the context switch a little bit slower | |
33 | (due access of thread local storage) but has some advantages. It allows to | |
34 | access the control structure of the current active context from arbitrary code | |
35 | paths required in order to support segmented stacks, which require to call | |
36 | certain maintenance functions (like __splitstack_getcontext() etc.) before each | |
37 | context switch (each context switch exchanges the stack). | |
38 | ||
39 | __econtext__ expects a function/functor with signature `void(void* vp)` (`vp` | |
40 | is the data passed at the first invocation of | |
41 | [operator_link ecv1 operator_call operator()]). | |
42 | ||
43 | ||
44 | [heading usage of __econtext__] | |
45 | ||
46 | int n=35; | |
47 | boost::context::execution_context sink(boost::context::execution_context::current()); | |
48 | boost::context::execution_context source( | |
49 | [n,&sink](void*)mutable{ | |
50 | int a=0; | |
51 | int b=1; | |
52 | while(n-->0){ | |
53 | sink(&a); | |
54 | auto next=a+b; | |
55 | a=b; | |
56 | b=next; | |
57 | } | |
58 | }); | |
59 | for(int i=0;i<10;++i){ | |
60 | std::cout<<*(int*)source()<<" "; | |
61 | } | |
62 | ||
63 | output: | |
64 | 0 1 1 2 3 5 8 13 21 34 | |
65 | ||
66 | This simple example demonstrates the basic usage of __econtext__. The context | |
67 | `sink`, returned by __ec_current__, represents the ['main]-context (function | |
68 | ['main()] running) and is one of the captured parameters in the lambda | |
69 | expression. The lambda that calculates the Fibonacci numbers is executed inside | |
70 | the context represented by `source`. Calculated Fibonacci numbers are | |
71 | transferred between the two context' via expression ['sink(&a)] (and returned by | |
72 | ['source()]). | |
73 | ||
74 | The locale variables `a`, `b` and ` next` remain their values during each | |
75 | context switch (['yield(a)]). This is possible because `ctx` owns a stack | |
76 | (exchanged by context switch). | |
77 | ||
78 | [heading inverting the control flow] | |
79 | ||
80 | /* | |
81 | * grammar: | |
82 | * P ---> E '\0' | |
83 | * E ---> T {('+'|'-') T} | |
84 | * T ---> S {('*'|'/') S} | |
85 | * S ---> digit | '(' E ')' | |
86 | */ | |
87 | class Parser{ | |
88 | // implementation omitted; see examples directory | |
89 | }; | |
90 | ||
91 | std::istringstream is("1+1"); | |
92 | bool done=false; | |
93 | std::exception_ptr except; | |
94 | ||
95 | // create handle to main execution context | |
96 | auto main_ctx(boost::context::execution_context::current()); | |
97 | // execute parser in new execution context | |
98 | boost::context::execution_context source( | |
99 | [&sink,&is,&done,&except](void*){ | |
100 | // create parser with callback function | |
101 | Parser p(is, | |
102 | [&sink](char ch){ | |
103 | // resume main execution context | |
104 | sink(&ch); | |
105 | }); | |
106 | try { | |
107 | // start recursive parsing | |
108 | p.run(); | |
109 | } catch (...) { | |
110 | // store other exceptions in exception-pointer | |
111 | except = std::current_exception(); | |
112 | } | |
113 | // set termination flag | |
114 | done=true; | |
115 | // resume main execution context | |
116 | sink(); | |
117 | }); | |
118 | ||
119 | // user-code pulls parsed data from parser | |
120 | // invert control flow | |
121 | void* vp = source(); | |
122 | if (except) { | |
123 | std::rethrow_exception(except); | |
124 | } | |
125 | while( ! done) { | |
126 | printf("Parsed: %c\n",* static_cast<char*>(vp)); | |
127 | vp = source(); | |
128 | if (except) { | |
129 | std::rethrow_exception(except); | |
130 | } | |
131 | } | |
132 | ||
133 | output: | |
134 | Parsed: 1 | |
135 | Parsed: + | |
136 | Parsed: 1 | |
137 | ||
138 | ||
139 | In this example a recursive descent parser uses a callback to emit a newly | |
140 | passed symbol. Using __econtext__ the control flow can be inverted, e.g. the | |
141 | user-code pulls parsed symbols from the parser - instead to get pushed from the | |
142 | parser (via callback). | |
143 | ||
144 | The data (character) is transferred between the two __econtext__. | |
145 | ||
146 | If the code executed by __econtext__ emits an exception, the application is | |
147 | terminated. ['std::exception_ptr] can be used to transfer exceptions between | |
148 | different execution contexts. | |
149 | ||
150 | [heading stack unwinding] | |
151 | Sometimes it is necessary to unwind the stack of an unfinished context to | |
152 | destroy local stack variables so they can release allocated resources (RAII | |
153 | pattern). The user is responsible for this task. | |
154 | ||
155 | [#ecv1_prealloc] | |
156 | [heading allocating control structures on top of stack] | |
157 | Allocating control structures on top of the stack requires to allocated the | |
158 | __stack_context__ and create the control structure with placement new before | |
159 | __econtext__ is created. | |
160 | [note The user is responsible for destructing the control structure at the top | |
161 | of the stack.] | |
162 | ||
163 | // stack-allocator used for (de-)allocating stack | |
164 | fixedsize_stack salloc( 4048); | |
165 | // allocate stack space | |
166 | stack_context sctx( salloc.allocate() ); | |
167 | // reserve space for control structure on top of the stack | |
168 | void * sp = static_cast< char * >( sctx.sp) - sizeof( my_control_structure); | |
169 | std::size_t size = sctx.size - sizeof( my_control_structure); | |
170 | // placement new creates control structure on reserved space | |
171 | my_control_structure * cs = new ( sp) my_control_structure( sp, size, sctx, salloc); | |
172 | ... | |
173 | // destructing the control structure | |
174 | cs->~my_control_structure(); | |
175 | ... | |
176 | struct my_control_structure { | |
177 | // execution context | |
178 | execution_context ectx; | |
179 | ||
180 | template< typename StackAllocator > | |
181 | my_control_structure( void * sp, std::size_t size, stack_context sctx, StackAllocator salloc) : | |
182 | // create execution context | |
183 | ectx( std::allocator_arg, preallocated( sp, size, sctx), salloc, entry_func) { | |
184 | } | |
185 | ... | |
186 | }; | |
187 | ||
188 | [heading exception handling] | |
189 | If the function executed inside a __econtext__ emits ans exception, the | |
190 | application is terminated by calling ['std::terminate()]. ['std::exception_ptr] | |
191 | can be used to transfer exceptions between different execution contexts. | |
192 | ||
193 | [important Do not jump from inside a catch block and then re-throw the exception | |
194 | in another execution context.] | |
195 | ||
196 | [heading parameter passing] | |
197 | The void pointer argument passed to __ec_op__, in one context, is passed as | |
198 | the last argument of the __context_fn__ if the context is started for the | |
199 | first time. | |
200 | In all following invocations of __ec_op__ the void pointer passed to | |
201 | __ec_op__, in one context, is returned by __ec_op__ in the other context. | |
202 | ||
203 | class X { | |
204 | private: | |
205 | std::exception_ptr excptr_; | |
206 | boost::context::execution_context caller_; | |
207 | boost::context::execution_context callee_; | |
208 | ||
209 | public: | |
210 | X() : | |
211 | excptr_(), | |
212 | caller_( boost::context::execution_context::current() ), | |
213 | callee_( [=] (void * vp) { | |
214 | try { | |
215 | int i = * static_cast< int * >( vp); | |
216 | std::string str = boost::lexical_cast<std::string>(i); | |
217 | caller_( & str); | |
218 | } catch (std::bad_cast const&) { | |
219 | excptr_=std::current_exception(); | |
220 | } | |
221 | }) | |
222 | {} | |
223 | ||
224 | std::string operator()( int i) { | |
225 | void * ret = callee_( & i); | |
226 | if(excptr_){ | |
227 | std::rethrow_exception(excptr_); | |
228 | } | |
229 | return * static_cast< std::string * >( ret); | |
230 | } | |
231 | }; | |
232 | ||
233 | X x; | |
234 | std::cout << x( 7) << std::endl; | |
235 | ||
236 | output: | |
237 | 7 | |
238 | ||
239 | ||
240 | [heading Class `execution_context`] | |
241 | ||
242 | class execution_context { | |
243 | public: | |
244 | static execution_context current() noexcept; | |
245 | ||
246 | template< typename Fn, typename ... Args > | |
247 | execution_context( Fn && fn, Args && ... args); | |
248 | ||
249 | template< typename StackAlloc, typename Fn, typename ... Args > | |
250 | execution_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Args && ... args); | |
251 | ||
252 | template< typename StackAlloc, typename Fn, typename ... Args > | |
253 | execution_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Args && ... args); | |
254 | ||
255 | execution_context( execution_context const& other) noexcept; | |
256 | execution_context( execution_context && other) noexcept; | |
257 | ||
258 | execution_context & operator=( execution_context const& other) noexcept; | |
259 | execution_context & operator=( execution_context && other) noexcept; | |
260 | ||
261 | explicit operator bool() const noexcept; | |
262 | bool operator!() const noexcept; | |
263 | ||
264 | void * operator()( void * vp = nullptr); | |
265 | ||
266 | template< typename Fn > | |
267 | void * operator()( exec_ontop_arg_t, Fn && fn, void * vp = nullptr); | |
268 | ||
269 | bool operator==( execution_context const& other) const noexcept; | |
270 | ||
271 | bool operator!=( execution_context const& other) const noexcept; | |
272 | ||
273 | bool operator<( execution_context const& other) const noexcept; | |
274 | ||
275 | bool operator>( execution_context const& other) const noexcept; | |
276 | ||
277 | bool operator<=( execution_context const& other) const noexcept; | |
278 | ||
279 | bool operator>=( execution_context const& other) const noexcept; | |
280 | ||
281 | template< typename charT, class traitsT > | |
282 | friend std::basic_ostream< charT, traitsT > & | |
283 | operator<<( std::basic_ostream< charT, traitsT > & os, execution_context const& other); | |
284 | }; | |
285 | ||
286 | [static_member_heading ecv1..current] | |
287 | ||
288 | static execution_context current() noexcept; | |
289 | ||
290 | [variablelist | |
291 | [[Returns:] [Returns an instance of excution_context pointing to the active | |
292 | execution context.]] | |
293 | [[Throws:] [Nothing.]] | |
294 | ] | |
295 | ||
296 | [constructor_heading ecv1..constructor] | |
297 | ||
298 | template< typename Fn, typename ... Args > | |
299 | execution_context( Fn && fn, Args && ... args); | |
300 | ||
301 | template< typename StackAlloc, typename Fn, typename ... Args > | |
302 | execution_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Args && ... args); | |
303 | ||
304 | template< typename StackAlloc, typename Fn, typename ... Args > | |
305 | execution_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Args && ... args); | |
306 | ||
307 | [variablelist | |
308 | [[Effects:] [Creates a new execution context and prepares the context to execute | |
309 | `fn`. `fixedsize_stack` is used as default stack allocator | |
310 | (stack size == fixedsize_stack::traits::default_size()). | |
311 | The constructor with argument type `preallocated`, is used to create a user | |
312 | defined data [link ecv1_prealloc (for instance additional control structures)] on | |
313 | top of the stack.]] | |
314 | ] | |
315 | ||
316 | [copy_constructor_heading ecv1..copy constructor] | |
317 | ||
318 | execution_context( execution_context const& other) noexcept; | |
319 | ||
320 | [variablelist | |
321 | [[Effects:] [Copies `other`, e.g. underlying control structure is shared with | |
322 | `*this`.]] | |
323 | [[Throws:] [Nothing.]] | |
324 | ] | |
325 | ||
326 | [move_constructor_heading ecv1..move constructor] | |
327 | ||
328 | execution_context( execution_context && other) noexcept; | |
329 | ||
330 | [variablelist | |
331 | [[Effects:] [Moves underlying control structure to `*this`.]] | |
332 | [[Throws:] [Nothing.]] | |
333 | ] | |
334 | ||
335 | [copy_assignment_heading ecv1..copy assignment] | |
336 | ||
337 | execution_context & operator=( execution_context const& other) noexcept; | |
338 | ||
339 | [variablelist | |
340 | [[Effects:] [Copies the state of `other` to `*this`, control structure is | |
341 | shared.]] | |
342 | [[Throws:] [Nothing.]] | |
343 | ] | |
344 | ||
345 | [move_assignment_heading ecv1..move assignment] | |
346 | ||
347 | execution_context & operator=( execution_context && other) noexcept; | |
348 | ||
349 | [variablelist | |
350 | [[Effects:] [Moves the control structure of `other` to `*this` using move | |
351 | semantics.]] | |
352 | [[Throws:] [Nothing.]] | |
353 | ] | |
354 | ||
355 | [operator_heading ecv1..operator_bool..operator bool] | |
356 | ||
357 | explicit operator bool() const noexcept; | |
358 | ||
359 | [variablelist | |
360 | [[Returns:] [`true` if `*this` points to a control structure.]] | |
361 | [[Throws:] [Nothing.]] | |
362 | ] | |
363 | ||
364 | [operator_heading ecv1..operator_not..operator!] | |
365 | ||
366 | bool operator!() const noexcept; | |
367 | ||
368 | [variablelist | |
369 | [[Returns:] [`true` if `*this` does not point to a control structure.]] | |
370 | [[Throws:] [Nothing.]] | |
371 | ] | |
372 | ||
373 | [operator_heading ecv1..operator_call..operator()] | |
374 | ||
375 | void * operator()( void * vp = nullptr) noexcept; | |
376 | ||
377 | [variablelist | |
378 | [[Effects:] [Stores internally the current context data (stack pointer, | |
379 | instruction pointer, and CPU registers) of the current active context and | |
380 | restores the context data from `*this`, which implies jumping to `*this`'s | |
381 | context. | |
382 | The void pointer argument, `vp`, is passed to the current context to be returned | |
383 | by the most recent call to `execution_context::operator()` in the same thread. | |
384 | `fn` is executed with arguments `args` on top of the stack of `this`.]] | |
385 | [[Note:] [The behaviour is undefined if `operator()()` is called while | |
386 | __ec_current__ returns `*this` (e.g. resuming an already running context). If | |
387 | the top-level context function returns, `std::exit()` is called.]] | |
388 | [[Returns:] [The void pointer argument passed to the most recent call to | |
389 | __ec_op__, if any.]] | |
390 | ] | |
391 | ||
392 | [operator_heading ecv1..operator_call_ontop..operator(exec_ontop_arg_t)] | |
393 | ||
394 | template< typename Fn > | |
395 | void * operator()( exec_ontop_arg_t, Fn && fn, void * vp = nullptr); | |
396 | ||
397 | [variablelist | |
398 | [[Effects:] [Same as __ec_op__. Additionally, function `fn` is executed with | |
399 | arguments `vp` in the context of `*this` (e.g. the stack frame of `fn` is | |
400 | allocated on stack of `*this`).]] | |
401 | [[Returns:] [The void pointer argument passed to the most recent call to | |
402 | __ec_op__, if any.]] | |
403 | ] | |
404 | ||
405 | [operator_heading ecv1..operator_equal..operator==] | |
406 | ||
407 | bool operator==( execution_context const& other) const noexcept; | |
408 | ||
409 | [variablelist | |
410 | [[Returns:] [`true` if `*this` and `other` represent the same execution context, | |
411 | `false` otherwise.]] | |
412 | [[Throws:] [Nothing.]] | |
413 | ] | |
414 | ||
415 | [operator_heading ecv1..operator_notequal..operator!=] | |
416 | ||
417 | bool operator!=( execution_context const& other) const noexcept; | |
418 | ||
419 | [variablelist | |
420 | [[Returns:] [[`! (other == * this)]]] | |
421 | [[Throws:] [Nothing.]] | |
422 | ] | |
423 | ||
424 | [operator_heading ecv1..operator_less..operator<] | |
425 | ||
426 | bool operator<( execution_context const& other) const noexcept; | |
427 | ||
428 | [variablelist | |
429 | [[Returns:] [`true` if `*this != other` is true and the | |
430 | implementation-defined total order of `execution_context` values places `*this` | |
431 | before `other`, false otherwise.]] | |
432 | [[Throws:] [Nothing.]] | |
433 | ] | |
434 | ||
435 | [operator_heading ecv1..operator_greater..operator>] | |
436 | ||
437 | bool operator>( execution_context const& other) const noexcept; | |
438 | ||
439 | [variablelist | |
440 | [[Returns:] [`other < * this`]] | |
441 | [[Throws:] [Nothing.]] | |
442 | ] | |
443 | ||
444 | [operator_heading ecv1..operator_lesseq..operator<=] | |
445 | ||
446 | bool operator<=( execution_context const& other) const noexcept; | |
447 | ||
448 | [variablelist | |
449 | [[Returns:] [`! (other < * this)`]] | |
450 | [[Throws:] [Nothing.]] | |
451 | ] | |
452 | ||
453 | [operator_heading ecv1..operator_greatereq..operator>=] | |
454 | ||
455 | bool operator>=( execution_context const& other) const noexcept; | |
456 | ||
457 | [variablelist | |
458 | [[Returns:] [`! (* this < other)`]] | |
459 | [[Throws:] [Nothing.]] | |
460 | ] | |
461 | ||
462 | [hding ecv1_..Non-member function [`operator<<()]] | |
463 | ||
464 | template< typename charT, class traitsT > | |
465 | std::basic_ostream< charT, traitsT > & | |
466 | operator<<( std::basic_ostream< charT, traitsT > & os, execution_context const& other); | |
467 | ||
468 | [variablelist | |
469 | [[Efects:] [Writes the representation of `other` to stream `os`.]] | |
470 | [[Returns:] [`os`]] | |
471 | ] | |
472 | ||
473 | ||
474 | [endsect] |