]>
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 | #ifndef BOOST_CONTEXT_EXECUTION_CONTEXT_H | |
8 | #define BOOST_CONTEXT_EXECUTION_CONTEXT_H | |
9 | ||
10 | #include <boost/context/detail/config.hpp> | |
11 | ||
12 | #include <algorithm> | |
13 | #include <atomic> | |
14 | #include <cstddef> | |
15 | #include <cstdint> | |
16 | #include <cstdlib> | |
17 | #include <functional> | |
18 | #include <memory> | |
19 | #include <ostream> | |
20 | #include <tuple> | |
21 | #include <utility> | |
22 | ||
23 | #include <boost/assert.hpp> | |
24 | #include <boost/config.hpp> | |
25 | #include <boost/intrusive_ptr.hpp> | |
26 | ||
27 | #include <boost/context/detail/apply.hpp> | |
28 | #include <boost/context/detail/disable_overload.hpp> | |
29 | #include <boost/context/detail/fcontext.hpp> | |
30 | #include <boost/context/fixedsize_stack.hpp> | |
31 | #include <boost/context/flags.hpp> | |
32 | #include <boost/context/preallocated.hpp> | |
33 | #include <boost/context/segmented_stack.hpp> | |
34 | #include <boost/context/stack_context.hpp> | |
35 | ||
36 | #ifdef BOOST_HAS_ABI_HEADERS | |
37 | # include BOOST_ABI_PREFIX | |
38 | #endif | |
39 | ||
40 | #if defined(BOOST_USE_SEGMENTED_STACKS) | |
41 | extern "C" { | |
42 | void __splitstack_getcontext( void * [BOOST_CONTEXT_SEGMENTS]); | |
43 | void __splitstack_setcontext( void * [BOOST_CONTEXT_SEGMENTS]); | |
44 | } | |
45 | #endif | |
46 | ||
47 | namespace boost { | |
48 | namespace context { | |
49 | namespace detail { | |
50 | ||
51 | template< typename Fn > | |
52 | transfer_t context_ontop( transfer_t); | |
53 | ||
54 | struct activation_record; | |
55 | ||
56 | struct data_t { | |
57 | activation_record * from; | |
58 | void * data; | |
59 | }; | |
60 | ||
61 | struct activation_record { | |
62 | typedef boost::intrusive_ptr< activation_record > ptr_t; | |
63 | ||
64 | thread_local static ptr_t current_rec; | |
65 | ||
66 | std::atomic< std::size_t > use_count{ 0 }; | |
67 | fcontext_t fctx{ nullptr }; | |
68 | stack_context sctx{}; | |
69 | bool main_ctx{ true }; | |
70 | ||
71 | // used for toplevel-context | |
72 | // (e.g. main context, thread-entry context) | |
73 | constexpr activation_record() = default; | |
74 | ||
75 | activation_record( fcontext_t fctx_, stack_context sctx_) noexcept : | |
76 | fctx{ fctx_ }, | |
77 | sctx( sctx_ ), // sctx{ sctx_ } - clang-3.6: no viable conversion from 'boost::context::stack_context' to 'std::size_t' | |
78 | main_ctx{ false } { | |
79 | } | |
80 | ||
81 | virtual ~activation_record() = default; | |
82 | ||
83 | bool is_main_context() const noexcept { | |
84 | return main_ctx; | |
85 | } | |
86 | ||
87 | void * resume( void * vp) { | |
88 | // store current activation record in local variable | |
89 | auto from = current_rec.get(); | |
90 | // store `this` in static, thread local pointer | |
91 | // `this` will become the active (running) context | |
92 | // returned by execution_context::current() | |
93 | current_rec = this; | |
94 | #if defined(BOOST_USE_SEGMENTED_STACKS) | |
95 | // adjust segmented stack properties | |
96 | __splitstack_getcontext( from->sctx.segments_ctx); | |
97 | __splitstack_setcontext( sctx.segments_ctx); | |
98 | #endif | |
99 | data_t d = { from, vp }; | |
100 | // context switch from parent context to `this`-context | |
101 | transfer_t t = jump_fcontext( fctx, & d); | |
102 | data_t * dp = reinterpret_cast< data_t * >( t.data); | |
103 | dp->from->fctx = t.fctx; | |
104 | // parent context resumed | |
105 | return dp->data; | |
106 | } | |
107 | ||
108 | template< typename Fn > | |
109 | void * resume_ontop( void * data, Fn && fn) { | |
110 | // store current activation record in local variable | |
111 | activation_record * from = current_rec.get(); | |
112 | // store `this` in static, thread local pointer | |
113 | // `this` will become the active (running) context | |
114 | // returned by execution_context::current() | |
115 | current_rec = this; | |
116 | #if defined(BOOST_USE_SEGMENTED_STACKS) | |
117 | // adjust segmented stack properties | |
118 | __splitstack_getcontext( from->sctx.segments_ctx); | |
119 | __splitstack_setcontext( sctx.segments_ctx); | |
120 | #endif | |
121 | std::tuple< void *, Fn > p = std::forward_as_tuple( data, fn); | |
122 | data_t d = { from, & p }; | |
123 | // context switch from parent context to `this`-context | |
124 | // execute Fn( Tpl) on top of `this` | |
125 | transfer_t t = ontop_fcontext( fctx, & d, context_ontop< Fn >); | |
126 | data_t * dp = reinterpret_cast< data_t * >( t.data); | |
127 | dp->from->fctx = t.fctx; | |
128 | // parent context resumed | |
129 | return dp->data; | |
130 | } | |
131 | ||
132 | virtual void deallocate() noexcept { | |
133 | } | |
134 | ||
135 | friend void intrusive_ptr_add_ref( activation_record * ar) noexcept { | |
136 | ++ar->use_count; | |
137 | } | |
138 | ||
139 | friend void intrusive_ptr_release( activation_record * ar) noexcept { | |
140 | BOOST_ASSERT( nullptr != ar); | |
141 | if ( 0 == --ar->use_count) { | |
142 | ar->deallocate(); | |
143 | } | |
144 | } | |
145 | }; | |
146 | ||
147 | struct activation_record_initializer { | |
148 | activation_record_initializer() noexcept; | |
149 | ~activation_record_initializer(); | |
150 | }; | |
151 | ||
152 | template< typename Fn > | |
153 | transfer_t context_ontop( transfer_t t) { | |
154 | data_t * dp = reinterpret_cast< data_t * >( t.data); | |
155 | dp->from->fctx = t.fctx; | |
156 | auto tpl = reinterpret_cast< std::tuple< void *, Fn > * >( dp->data); | |
157 | BOOST_ASSERT( nullptr != tpl); | |
158 | auto data = std::get< 0 >( * tpl); | |
159 | typename std::decay< Fn >::type fn = std::forward< Fn >( std::get< 1 >( * tpl) ); | |
160 | dp->data = apply( fn, std::tie( data) ); | |
161 | return { t.fctx, dp }; | |
162 | } | |
163 | ||
164 | template< typename StackAlloc, typename Fn, typename ... Args > | |
165 | class capture_record : public activation_record { | |
166 | private: | |
167 | StackAlloc salloc_; | |
168 | typename std::decay< Fn >::type fn_; | |
169 | std::tuple< typename std::decay< Args >::type ... > args_; | |
170 | activation_record * caller_; | |
171 | ||
172 | static void destroy( capture_record * p) noexcept { | |
173 | StackAlloc salloc = p->salloc_; | |
174 | stack_context sctx = p->sctx; | |
175 | // deallocate activation record | |
176 | p->~capture_record(); | |
177 | // destroy stack with stack allocator | |
178 | salloc.deallocate( sctx); | |
179 | } | |
180 | ||
181 | public: | |
182 | capture_record( stack_context sctx, StackAlloc const& salloc, | |
183 | fcontext_t fctx, | |
184 | activation_record * caller, | |
185 | Fn && fn, Args && ... args) noexcept : | |
186 | activation_record{ fctx, sctx }, | |
187 | salloc_{ salloc }, | |
188 | fn_( std::forward< Fn >( fn) ), | |
189 | args_( std::forward< Args >( args) ... ), | |
190 | caller_{ caller } { | |
191 | } | |
192 | ||
193 | void deallocate() noexcept override final { | |
194 | destroy( this); | |
195 | } | |
196 | ||
197 | void run() { | |
198 | auto data = caller_->resume( nullptr); | |
199 | apply( fn_, std::tuple_cat( args_, std::tie( data) ) ); | |
200 | BOOST_ASSERT_MSG( ! main_ctx, "main-context does not execute activation-record::run()"); | |
201 | } | |
202 | }; | |
203 | ||
204 | } | |
205 | ||
206 | class BOOST_CONTEXT_DECL execution_context { | |
207 | private: | |
208 | // tampoline function | |
209 | // entered if the execution context | |
210 | // is resumed for the first time | |
211 | template< typename AR > | |
212 | static void entry_func( detail::transfer_t t) noexcept { | |
213 | detail::data_t * dp = reinterpret_cast< detail::data_t * >( t.data); | |
214 | AR * ar = static_cast< AR * >( dp->data); | |
215 | BOOST_ASSERT( nullptr != ar); | |
216 | dp->from->fctx = t.fctx; | |
217 | // start execution of toplevel context-function | |
218 | ar->run(); | |
219 | } | |
220 | ||
221 | typedef boost::intrusive_ptr< detail::activation_record > ptr_t; | |
222 | ||
223 | ptr_t ptr_; | |
224 | ||
225 | template< typename StackAlloc, typename Fn, typename ... Args > | |
226 | static detail::activation_record * create_context( StackAlloc salloc, | |
227 | Fn && fn, Args && ... args) { | |
228 | typedef detail::capture_record< | |
229 | StackAlloc, Fn, Args ... | |
230 | > capture_t; | |
231 | ||
232 | auto sctx = salloc.allocate(); | |
233 | // reserve space for control structure | |
234 | #if defined(BOOST_NO_CXX11_CONSTEXPR) || defined(BOOST_NO_CXX11_STD_ALIGN) | |
235 | const std::size_t size = sctx.size - sizeof( capture_t); | |
236 | void * sp = static_cast< char * >( sctx.sp) - sizeof( capture_t); | |
237 | #else | |
238 | constexpr std::size_t func_alignment = 64; // alignof( capture_t); | |
239 | constexpr std::size_t func_size = sizeof( capture_t); | |
240 | // reserve space on stack | |
241 | void * sp = static_cast< char * >( sctx.sp) - func_size - func_alignment; | |
242 | // align sp pointer | |
243 | std::size_t space = func_size + func_alignment; | |
244 | sp = std::align( func_alignment, func_size, sp, space); | |
245 | BOOST_ASSERT( nullptr != sp); | |
246 | // calculate remaining size | |
247 | const std::size_t size = sctx.size - ( static_cast< char * >( sctx.sp) - static_cast< char * >( sp) ); | |
248 | #endif | |
249 | // create fast-context | |
250 | const detail::fcontext_t fctx = detail::make_fcontext( sp, size, & execution_context::entry_func< capture_t >); | |
251 | BOOST_ASSERT( nullptr != fctx); | |
252 | // get current activation record | |
253 | auto curr = execution_context::current().ptr_; | |
254 | // placment new for control structure on fast-context stack | |
255 | return ::new ( sp) capture_t{ | |
256 | sctx, salloc, fctx, curr.get(), std::forward< Fn >( fn), std::forward< Args >( args) ... }; | |
257 | } | |
258 | ||
259 | template< typename StackAlloc, typename Fn, typename ... Args > | |
260 | static detail::activation_record * create_context( preallocated palloc, StackAlloc salloc, | |
261 | Fn && fn, Args && ... args) { | |
262 | typedef detail::capture_record< | |
263 | StackAlloc, Fn, Args ... | |
264 | > capture_t; | |
265 | ||
266 | // reserve space for control structure | |
267 | #if defined(BOOST_NO_CXX11_CONSTEXPR) || defined(BOOST_NO_CXX11_STD_ALIGN) | |
268 | const std::size_t size = palloc.size - sizeof( capture_t); | |
269 | void * sp = static_cast< char * >( palloc.sp) - sizeof( capture_t); | |
270 | #else | |
271 | constexpr std::size_t func_alignment = 64; // alignof( capture_t); | |
272 | constexpr std::size_t func_size = sizeof( capture_t); | |
273 | // reserve space on stack | |
274 | void * sp = static_cast< char * >( palloc.sp) - func_size - func_alignment; | |
275 | // align sp pointer | |
276 | std::size_t space = func_size + func_alignment; | |
277 | sp = std::align( func_alignment, func_size, sp, space); | |
278 | BOOST_ASSERT( nullptr != sp); | |
279 | // calculate remaining size | |
280 | const std::size_t size = palloc.size - ( static_cast< char * >( palloc.sp) - static_cast< char * >( sp) ); | |
281 | #endif | |
282 | // create fast-context | |
283 | const detail::fcontext_t fctx = detail::make_fcontext( sp, size, & execution_context::entry_func< capture_t >); | |
284 | BOOST_ASSERT( nullptr != fctx); | |
285 | // get current activation record | |
286 | auto curr = execution_context::current().ptr_; | |
287 | // placment new for control structure on fast-context stack | |
288 | return ::new ( sp) capture_t{ | |
289 | palloc.sctx, salloc, fctx, curr.get(), std::forward< Fn >( fn), std::forward< Args >( args) ... }; | |
290 | } | |
291 | ||
292 | execution_context() noexcept : | |
293 | // default constructed with current activation_record | |
294 | ptr_{ detail::activation_record::current_rec } { | |
295 | } | |
296 | ||
297 | public: | |
298 | static execution_context current() noexcept; | |
299 | ||
300 | #if defined(BOOST_USE_SEGMENTED_STACKS) | |
301 | template< typename Fn, | |
302 | typename ... Args, | |
303 | typename = detail::disable_overload< execution_context, Fn > | |
304 | > | |
305 | execution_context( Fn && fn, Args && ... args) : | |
306 | // deferred execution of fn and its arguments | |
307 | // arguments are stored in std::tuple<> | |
308 | // non-type template parameter pack via std::index_sequence_for<> | |
309 | // preserves the number of arguments | |
310 | // used to extract the function arguments from std::tuple<> | |
311 | ptr_{ create_context( segmented_stack(), | |
312 | std::forward< Fn >( fn), | |
313 | std::forward< Args >( args) ...) } { | |
314 | ptr_->resume( ptr_.get() ); | |
315 | } | |
316 | ||
317 | template< typename Fn, | |
318 | typename ... Args | |
319 | > | |
320 | execution_context( std::allocator_arg_t, segmented_stack salloc, Fn && fn, Args && ... args) : | |
321 | // deferred execution of fn and its arguments | |
322 | // arguments are stored in std::tuple<> | |
323 | // non-type template parameter pack via std::index_sequence_for<> | |
324 | // preserves the number of arguments | |
325 | // used to extract the function arguments from std::tuple<> | |
326 | ptr_{ create_context( salloc, | |
327 | std::forward< Fn >( fn), | |
328 | std::forward< Args >( args) ...) } { | |
329 | ptr_->resume( ptr_.get() ); | |
330 | } | |
331 | ||
332 | template< typename Fn, | |
333 | typename ... Args | |
334 | > | |
335 | execution_context( std::allocator_arg_t, preallocated palloc, segmented_stack salloc, Fn && fn, Args && ... args) : | |
336 | // deferred execution of fn and its arguments | |
337 | // arguments are stored in std::tuple<> | |
338 | // non-type template parameter pack via std::index_sequence_for<> | |
339 | // preserves the number of arguments | |
340 | // used to extract the function arguments from std::tuple<> | |
341 | ptr_{ create_context( palloc, salloc, | |
342 | std::forward< Fn >( fn), | |
343 | std::forward< Args >( args) ...) } { | |
344 | ptr_->resume( ptr_.get() ); | |
345 | } | |
346 | #else | |
347 | template< typename Fn, | |
348 | typename ... Args, | |
349 | typename = detail::disable_overload< execution_context, Fn > | |
350 | > | |
351 | execution_context( Fn && fn, Args && ... args) : | |
352 | // deferred execution of fn and its arguments | |
353 | // arguments are stored in std::tuple<> | |
354 | // non-type template parameter pack via std::index_sequence_for<> | |
355 | // preserves the number of arguments | |
356 | // used to extract the function arguments from std::tuple<> | |
357 | ptr_{ create_context( fixedsize_stack(), | |
358 | std::forward< Fn >( fn), | |
359 | std::forward< Args >( args) ...) } { | |
360 | ptr_->resume( ptr_.get() ); | |
361 | } | |
362 | ||
363 | template< typename StackAlloc, | |
364 | typename Fn, | |
365 | typename ... Args | |
366 | > | |
367 | execution_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Args && ... args) : | |
368 | // deferred execution of fn and its arguments | |
369 | // arguments are stored in std::tuple<> | |
370 | // non-type template parameter pack via std::index_sequence_for<> | |
371 | // preserves the number of arguments | |
372 | // used to extract the function arguments from std::tuple<> | |
373 | ptr_{ create_context( salloc, | |
374 | std::forward< Fn >( fn), | |
375 | std::forward< Args >( args) ...) } { | |
376 | ptr_->resume( ptr_.get() ); | |
377 | } | |
378 | ||
379 | template< typename StackAlloc, | |
380 | typename Fn, | |
381 | typename ... Args | |
382 | > | |
383 | execution_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Args && ... args) : | |
384 | // deferred execution of fn and its arguments | |
385 | // arguments are stored in std::tuple<> | |
386 | // non-type template parameter pack via std::index_sequence_for<> | |
387 | // preserves the number of arguments | |
388 | // used to extract the function arguments from std::tuple<> | |
389 | ptr_{ create_context( palloc, salloc, | |
390 | std::forward< Fn >( fn), | |
391 | std::forward< Args >( args) ...) } { | |
392 | ptr_->resume( ptr_.get() ); | |
393 | } | |
394 | #endif | |
395 | ||
396 | execution_context( execution_context const& other) noexcept : | |
397 | ptr_{ other.ptr_ } { | |
398 | } | |
399 | ||
400 | execution_context( execution_context && other) noexcept : | |
401 | ptr_{ other.ptr_ } { | |
402 | other.ptr_.reset(); | |
403 | } | |
404 | ||
405 | execution_context & operator=( execution_context const& other) noexcept { | |
406 | // intrusive_ptr<> does not test for self-assignment | |
407 | if ( this == & other) return * this; | |
408 | ptr_ = other.ptr_; | |
409 | return * this; | |
410 | } | |
411 | ||
412 | execution_context & operator=( execution_context && other) noexcept { | |
413 | if ( this == & other) return * this; | |
414 | execution_context tmp{ std::move( other) }; | |
415 | swap( tmp); | |
416 | return * this; | |
417 | } | |
418 | ||
419 | void * operator()( void * vp = nullptr) { | |
420 | return ptr_->resume( vp); | |
421 | } | |
422 | ||
423 | template< typename Fn > | |
424 | void * operator()( exec_ontop_arg_t, Fn && fn, void * vp = nullptr) { | |
425 | return ptr_->resume_ontop( vp, | |
426 | std::forward< Fn >( fn) ); | |
427 | } | |
428 | ||
429 | explicit operator bool() const noexcept { | |
430 | return nullptr != ptr_.get(); | |
431 | } | |
432 | ||
433 | bool operator!() const noexcept { | |
434 | return nullptr == ptr_.get(); | |
435 | } | |
436 | ||
437 | bool operator==( execution_context const& other) const noexcept { | |
438 | return ptr_ == other.ptr_; | |
439 | } | |
440 | ||
441 | bool operator!=( execution_context const& other) const noexcept { | |
442 | return ptr_ != other.ptr_; | |
443 | } | |
444 | ||
445 | bool operator<( execution_context const& other) const noexcept { | |
446 | return ptr_ < other.ptr_; | |
447 | } | |
448 | ||
449 | bool operator>( execution_context const& other) const noexcept { | |
450 | return other.ptr_ < ptr_; | |
451 | } | |
452 | ||
453 | bool operator<=( execution_context const& other) const noexcept { | |
454 | return ! ( * this > other); | |
455 | } | |
456 | ||
457 | bool operator>=( execution_context const& other) const noexcept { | |
458 | return ! ( * this < other); | |
459 | } | |
460 | ||
461 | template< typename charT, class traitsT > | |
462 | friend std::basic_ostream< charT, traitsT > & | |
463 | operator<<( std::basic_ostream< charT, traitsT > & os, execution_context const& other) { | |
464 | if ( nullptr != other.ptr_) { | |
465 | return os << other.ptr_; | |
466 | } else { | |
467 | return os << "{not-a-context}"; | |
468 | } | |
469 | } | |
470 | ||
471 | void swap( execution_context & other) noexcept { | |
472 | ptr_.swap( other.ptr_); | |
473 | } | |
474 | }; | |
475 | ||
476 | inline | |
477 | void swap( execution_context & l, execution_context & r) noexcept { | |
478 | l.swap( r); | |
479 | } | |
480 | ||
481 | }} | |
482 | ||
483 | #ifdef BOOST_HAS_ABI_HEADERS | |
484 | # include BOOST_ABI_SUFFIX | |
485 | #endif | |
486 | ||
487 | #endif // BOOST_CONTEXT_EXECUTION_CONTEXT_H |