1 
2 //          Copyright Oliver Kowalke 2017.
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_CONTINUATION_H
8 #define BOOST_CONTEXT_CONTINUATION_H
9 
10 #include <windows.h>
11 
12 #include <boost/context/detail/config.hpp>
13 
14 #include <algorithm>
15 #include <cstddef>
16 #include <cstdint>
17 #include <cstdlib>
18 #include <cstring>
19 #include <functional>
20 #include <memory>
21 #include <ostream>
22 #include <system_error>
23 #include <tuple>
24 #include <utility>
25 
26 #include <boost/assert.hpp>
27 #include <boost/config.hpp>
28 
29 #include <boost/context/detail/disable_overload.hpp>
30 #if defined(BOOST_NO_CXX14_STD_EXCHANGE)
31 #include <boost/context/detail/exchange.hpp>
32 #endif
33 #if defined(BOOST_NO_CXX17_STD_INVOKE)
34 #include <boost/context/detail/invoke.hpp>
35 #endif
36 #include <boost/context/fixedsize_stack.hpp>
37 #include <boost/context/flags.hpp>
38 #include <boost/context/preallocated.hpp>
39 #include <boost/context/stack_context.hpp>
40 
41 #ifdef BOOST_HAS_ABI_HEADERS
42 # include BOOST_ABI_PREFIX
43 #endif
44 
45 #if defined(BOOST_MSVC)
46 # pragma warning(push)
47 # pragma warning(disable: 4702)
48 #endif
49 
50 namespace boost {
51 namespace context {
52 namespace detail {
53 
54 // tampoline function
55 // entered if the execution context
56 // is resumed for the first time
57 template< typename Record >
entry_func(LPVOID data)58 static VOID WINAPI entry_func( LPVOID data) noexcept {
59     Record * record = static_cast< Record * >( data);
60     BOOST_ASSERT( nullptr != record);
61     // start execution of toplevel context-function
62     record->run();
63 }
64 
65 struct BOOST_CONTEXT_DECL activation_record {
66     LPVOID                                                      fiber{ nullptr };
67     stack_context                                               sctx{};
68     bool                                                        main_ctx{ true };
69     activation_record                                       *   from{ nullptr };
70     std::function< activation_record*(activation_record*&) >    ontop{};
71     bool                                                        terminated{ false };
72     bool                                                        force_unwind{ false };
73 
74     static activation_record *& current() noexcept;
75 
76     // used for toplevel-context
77     // (e.g. main context, thread-entry context)
activation_recordboost::context::detail::activation_record78     activation_record() noexcept {
79 #if ( _WIN32_WINNT > 0x0600)
80         if ( ::IsThreadAFiber() ) {
81             fiber = ::GetCurrentFiber();
82         } else {
83             fiber = ::ConvertThreadToFiber( nullptr);
84         }
85 #else
86         fiber = ::ConvertThreadToFiber( nullptr);
87         if ( BOOST_UNLIKELY( nullptr == fiber) ) {
88             DWORD err = ::GetLastError();
89             BOOST_ASSERT( ERROR_ALREADY_FIBER == err);
90             fiber = ::GetCurrentFiber();
91             BOOST_ASSERT( nullptr != fiber);
92             BOOST_ASSERT( reinterpret_cast< LPVOID >( 0x1E00) != fiber);
93         }
94 #endif
95     }
96 
activation_recordboost::context::detail::activation_record97     activation_record( stack_context sctx_) noexcept :
98         sctx{ sctx_ },
99         main_ctx{ false } {
100     }
101 
~activation_recordboost::context::detail::activation_record102     virtual ~activation_record() {
103         if ( BOOST_UNLIKELY( main_ctx) ) {
104             ::ConvertFiberToThread();
105         } else {
106             ::DeleteFiber( fiber);
107         }
108     }
109 
110     activation_record( activation_record const&) = delete;
111     activation_record & operator=( activation_record const&) = delete;
112 
is_main_contextboost::context::detail::activation_record113     bool is_main_context() const noexcept {
114         return main_ctx;
115     }
116 
resumeboost::context::detail::activation_record117     activation_record * resume() {
118         from = current();
119         // store `this` in static, thread local pointer
120         // `this` will become the active (running) context
121         current() = this;
122         // context switch from parent context to `this`-context
123         // context switch
124         ::SwitchToFiber( fiber);
125 #if defined(BOOST_NO_CXX14_STD_EXCHANGE)
126         return detail::exchange( current()->from, nullptr);
127 #else
128         return std::exchange( current()->from, nullptr);
129 #endif
130     }
131 
132     template< typename Ctx, typename Fn >
resume_withboost::context::detail::activation_record133     activation_record * resume_with( Fn && fn) {
134         from = current();
135         // store `this` in static, thread local pointer
136         // `this` will become the active (running) context
137         // returned by continuation::current()
138         current() = this;
139 #if defined(BOOST_NO_CXX14_GENERIC_LAMBDAS)
140         current()->ontop = std::bind(
141                 [](typename std::decay< Fn >::type & fn, activation_record *& ptr){
142                     Ctx c{ ptr };
143                     c = fn( std::move( c) );
144                     if ( ! c) {
145                         ptr = nullptr;
146                     }
147 #if defined(BOOST_NO_CXX14_STD_EXCHANGE)
148                     return exchange( c.ptr_, nullptr);
149 #else
150                     return std::exchange( c.ptr_, nullptr);
151 #endif
152                 },
153                 std::forward< Fn >( fn),
154                 std::placeholders::_1);
155 #else
156         current()->ontop = [fn=std::forward<Fn>(fn)](activation_record *& ptr){
157             Ctx c{ ptr };
158             c = fn( std::move( c) );
159             if ( ! c) {
160                 ptr = nullptr;
161             }
162 #if defined(BOOST_NO_CXX14_STD_EXCHANGE)
163             return exchange( c.ptr_, nullptr);
164 #else
165             return std::exchange( c.ptr_, nullptr);
166 #endif
167         };
168 #endif
169         // context switch
170         ::SwitchToFiber( fiber);
171 #if defined(BOOST_NO_CXX14_STD_EXCHANGE)
172         return detail::exchange( current()->from, nullptr);
173 #else
174         return std::exchange( current()->from, nullptr);
175 #endif
176     }
177 
deallocateboost::context::detail::activation_record178     virtual void deallocate() noexcept {
179     }
180 };
181 
182 struct BOOST_CONTEXT_DECL activation_record_initializer {
183     activation_record_initializer() noexcept;
184     ~activation_record_initializer();
185 };
186 
187 struct forced_unwind {
188     activation_record   *   from{ nullptr };
189 #ifndef BOOST_ASSERT_IS_VOID
190     bool                    caught{ false };
191 #endif
192 
forced_unwindboost::context::detail::forced_unwind193     explicit forced_unwind( activation_record * from_) :
194         from{ from_ } {
195     }
196 
197 #ifndef BOOST_ASSERT_IS_VOID
~forced_unwindboost::context::detail::forced_unwind198     ~forced_unwind() {
199         BOOST_ASSERT( caught);
200     }
201 #endif
202 };
203 
204 template< typename Ctx, typename StackAlloc, typename Fn >
205 class capture_record : public activation_record {
206 private:
207     typename std::decay< StackAlloc >::type             salloc_;
208     typename std::decay< Fn >::type                     fn_;
209 
destroy(capture_record * p)210     static void destroy( capture_record * p) noexcept {
211         typename std::decay< StackAlloc >::type salloc = std::move( p->salloc_);
212         stack_context sctx = p->sctx;
213         // deallocate activation record
214         p->~capture_record();
215         // destroy stack with stack allocator
216         salloc.deallocate( sctx);
217     }
218 
219 public:
capture_record(stack_context sctx,StackAlloc && salloc,Fn && fn)220     capture_record( stack_context sctx, StackAlloc && salloc, Fn && fn) noexcept :
221         activation_record( sctx),
222         salloc_( std::forward< StackAlloc >( salloc)),
223         fn_( std::forward< Fn >( fn) ) {
224     }
225 
deallocate()226     void deallocate() noexcept override final {
227         BOOST_ASSERT( main_ctx || ( ! main_ctx && terminated) );
228         destroy( this);
229     }
230 
run()231     void run() {
232         Ctx c{ from };
233         try {
234             // invoke context-function
235 #if defined(BOOST_NO_CXX17_STD_INVOKE)
236             c = boost::context::detail::invoke( fn_, std::move( c) );
237 #else
238             c = std::invoke( fn_, std::move( c) );
239 #endif
240         } catch ( forced_unwind const& ex) {
241             c = Ctx{ ex.from };
242 #ifndef BOOST_ASSERT_IS_VOID
243             const_cast< forced_unwind & >( ex).caught = true;
244 #endif
245         }
246         // this context has finished its task
247         from = nullptr;
248         ontop = nullptr;
249         terminated = true;
250         force_unwind = false;
251         c.resume();
252         BOOST_ASSERT_MSG( false, "continuation already terminated");
253     }
254 };
255 
256 template< typename Ctx, typename StackAlloc, typename Fn >
create_context1(StackAlloc && salloc,Fn && fn)257 static activation_record * create_context1( StackAlloc && salloc, Fn && fn) {
258     typedef capture_record< Ctx, StackAlloc, Fn >  capture_t;
259 
260     auto sctx = salloc.allocate();
261     BOOST_ASSERT( ( sizeof( capture_t) ) < sctx.size);
262     // reserve space for control structure
263     void * storage = reinterpret_cast< void * >(
264             ( reinterpret_cast< uintptr_t >( sctx.sp) - static_cast< uintptr_t >( sizeof( capture_t) ) )
265             & ~ static_cast< uintptr_t >( 0xff) );
266     // placment new for control structure on context stack
267     capture_t * record = new ( storage) capture_t{
268             sctx, std::forward< StackAlloc >( salloc), std::forward< Fn >( fn) };
269     // create user-context
270     record->fiber = ::CreateFiber( sctx.size, & detail::entry_func< capture_t >, record);
271     return record;
272 }
273 
274 template< typename Ctx, typename StackAlloc, typename Fn >
create_context2(preallocated palloc,StackAlloc && salloc,Fn && fn)275 static activation_record * create_context2( preallocated palloc, StackAlloc && salloc, Fn && fn) {
276     typedef capture_record< Ctx, StackAlloc, Fn >  capture_t;
277 
278     BOOST_ASSERT( ( sizeof( capture_t) ) < palloc.size);
279     // reserve space for control structure
280     void * storage = reinterpret_cast< void * >(
281             ( reinterpret_cast< uintptr_t >( palloc.sp) - static_cast< uintptr_t >( sizeof( capture_t) ) )
282             & ~ static_cast< uintptr_t >( 0xff) );
283     // placment new for control structure on context stack
284     capture_t * record = new ( storage) capture_t{
285             palloc.sctx, std::forward< StackAlloc >( salloc), std::forward< Fn >( fn) };
286     // create user-context
287     record->fiber = ::CreateFiber( palloc.sctx.size, & detail::entry_func< capture_t >, record);
288     return record;
289 }
290 
291 }
292 
293 class BOOST_CONTEXT_DECL continuation {
294 private:
295     friend struct detail::activation_record;
296 
297     template< typename Ctx, typename StackAlloc, typename Fn >
298     friend class detail::capture_record;
299 
300     template< typename Ctx, typename StackAlloc, typename Fn >
301     friend detail::activation_record * detail::create_context1( StackAlloc &&, Fn &&);
302 
303     template< typename Ctx, typename StackAlloc, typename Fn >
304     friend detail::activation_record * detail::create_context2( preallocated, StackAlloc &&, Fn &&);
305 
306     template< typename StackAlloc, typename Fn >
307     friend continuation
308     callcc( std::allocator_arg_t, StackAlloc &&, Fn &&);
309 
310     template< typename StackAlloc, typename Fn >
311     friend continuation
312     callcc( std::allocator_arg_t, preallocated, StackAlloc &&, Fn &&);
313 
314     detail::activation_record   *   ptr_{ nullptr };
315 
continuation(detail::activation_record * ptr)316     continuation( detail::activation_record * ptr) noexcept :
317         ptr_{ ptr } {
318     }
319 
320 public:
321     continuation() = default;
322 
~continuation()323     ~continuation() {
324         if ( BOOST_UNLIKELY( nullptr != ptr_) && ! ptr_->main_ctx) {
325             if ( BOOST_LIKELY( ! ptr_->terminated) ) {
326                 ptr_->force_unwind = true;
327                 ptr_->resume();
328                 BOOST_ASSERT( ptr_->terminated);
329             }
330             ptr_->deallocate();
331         }
332     }
333 
334     continuation( continuation const&) = delete;
335     continuation & operator=( continuation const&) = delete;
336 
continuation(continuation && other)337     continuation( continuation && other) noexcept {
338         swap( other);
339     }
340 
operator =(continuation && other)341     continuation & operator=( continuation && other) noexcept {
342         if ( BOOST_LIKELY( this != & other) ) {
343             continuation tmp = std::move( other);
344             swap( tmp);
345         }
346         return * this;
347     }
348 
resume()349     continuation resume() & {
350         return std::move( * this).resume();
351     }
352 
resume()353     continuation resume() && {
354 #if defined(BOOST_NO_CXX14_STD_EXCHANGE)
355         detail::activation_record * ptr = detail::exchange( ptr_, nullptr)->resume();
356 #else
357         detail::activation_record * ptr = std::exchange( ptr_, nullptr)->resume();
358 #endif
359         if ( BOOST_UNLIKELY( detail::activation_record::current()->force_unwind) ) {
360             throw detail::forced_unwind{ ptr};
361         } else if ( BOOST_UNLIKELY( nullptr != detail::activation_record::current()->ontop) ) {
362             ptr = detail::activation_record::current()->ontop( ptr);
363             detail::activation_record::current()->ontop = nullptr;
364         }
365         return { ptr };
366     }
367 
368     template< typename Fn >
resume_with(Fn && fn)369     continuation resume_with( Fn && fn) & {
370         return std::move( * this).resume_with( std::forward< Fn >( fn) );
371     }
372 
373     template< typename Fn >
resume_with(Fn && fn)374     continuation resume_with( Fn && fn) && {
375 #if defined(BOOST_NO_CXX14_STD_EXCHANGE)
376         detail::activation_record * ptr =
377             detail::exchange( ptr_, nullptr)->resume_with< continuation >( std::forward< Fn >( fn) );
378 #else
379         detail::activation_record * ptr =
380             std::exchange( ptr_, nullptr)->resume_with< continuation >( std::forward< Fn >( fn) );
381 #endif
382         if ( BOOST_UNLIKELY( detail::activation_record::current()->force_unwind) ) {
383             throw detail::forced_unwind{ ptr};
384         } else if ( BOOST_UNLIKELY( nullptr != detail::activation_record::current()->ontop) ) {
385             ptr = detail::activation_record::current()->ontop( ptr);
386             detail::activation_record::current()->ontop = nullptr;
387         }
388         return { ptr };
389     }
390 
operator bool() const391     explicit operator bool() const noexcept {
392         return nullptr != ptr_ && ! ptr_->terminated;
393     }
394 
operator !() const395     bool operator!() const noexcept {
396         return nullptr == ptr_ || ptr_->terminated;
397     }
398 
operator <(continuation const & other) const399     bool operator<( continuation const& other) const noexcept {
400         return ptr_ < other.ptr_;
401     }
402 
403     #if !defined(BOOST_EMBTC)
404 
405     template< typename charT, class traitsT >
406     friend std::basic_ostream< charT, traitsT > &
operator <<(std::basic_ostream<charT,traitsT> & os,continuation const & other)407     operator<<( std::basic_ostream< charT, traitsT > & os, continuation const& other) {
408         if ( nullptr != other.ptr_) {
409             return os << other.ptr_;
410         } else {
411             return os << "{not-a-context}";
412         }
413     }
414 
415     #else
416 
417     template< typename charT, class traitsT >
418     friend std::basic_ostream< charT, traitsT > &
419     operator<<( std::basic_ostream< charT, traitsT > & os, continuation const& other);
420 
421     #endif
422 
swap(continuation & other)423     void swap( continuation & other) noexcept {
424         std::swap( ptr_, other.ptr_);
425     }
426 };
427 
428 #if defined(BOOST_EMBTC)
429 
430     template< typename charT, class traitsT >
431     inline std::basic_ostream< charT, traitsT > &
operator <<(std::basic_ostream<charT,traitsT> & os,continuation const & other)432     operator<<( std::basic_ostream< charT, traitsT > & os, continuation const& other) {
433         if ( nullptr != other.ptr_) {
434             return os << other.ptr_;
435         } else {
436             return os << "{not-a-context}";
437         }
438     }
439 
440 #endif
441 
442 template<
443     typename Fn,
444     typename = detail::disable_overload< continuation, Fn >
445 >
446 continuation
callcc(Fn && fn)447 callcc( Fn && fn) {
448     return callcc(
449             std::allocator_arg,
450             fixedsize_stack(),
451             std::forward< Fn >( fn) );
452 }
453 
454 template< typename StackAlloc, typename Fn >
455 continuation
callcc(std::allocator_arg_t,StackAlloc && salloc,Fn && fn)456 callcc( std::allocator_arg_t, StackAlloc && salloc, Fn && fn) {
457     return continuation{
458         detail::create_context1< continuation >(
459                 std::forward< StackAlloc >( salloc), std::forward< Fn >( fn) ) }.resume();
460 }
461 
462 template< typename StackAlloc, typename Fn >
463 continuation
callcc(std::allocator_arg_t,preallocated palloc,StackAlloc && salloc,Fn && fn)464 callcc( std::allocator_arg_t, preallocated palloc, StackAlloc && salloc, Fn && fn) {
465     return continuation{
466         detail::create_context2< continuation >(
467                 palloc, std::forward< StackAlloc >( salloc), std::forward< Fn >( fn) ) }.resume();
468 }
469 
470 inline
swap(continuation & l,continuation & r)471 void swap( continuation & l, continuation & r) noexcept {
472     l.swap( r);
473 }
474 
475 }}
476 
477 #if defined(BOOST_MSVC)
478 # pragma warning(pop)
479 #endif
480 
481 #ifdef BOOST_HAS_ABI_HEADERS
482 # include BOOST_ABI_SUFFIX
483 #endif
484 
485 #endif // BOOST_CONTEXT_CONTINUATION_H
486