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#if ! defined(BOOST_CONTEXT_NO_EXECUTION_CONTEXT)
13
14#include <windows.h>
15
16#include <cstddef>
17#include <cstdint>
18#include <cstdlib>
19#include <memory>
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/invoke.hpp>
28#include <boost/context/stack_context.hpp>
29
30#ifdef BOOST_HAS_ABI_HEADERS
31# include BOOST_ABI_PREFIX
32#endif
33
34namespace boost {
35namespace context {
36
37struct preallocated {
38    void        *   sp;
39    std::size_t     size;
40    stack_context   sctx;
41
42    preallocated( void * sp_, std::size_t size_, stack_context sctx_) noexcept :
43        sp( sp_), size( size_), sctx( sctx_) {
44    }
45};
46
47class BOOST_CONTEXT_DECL execution_context {
48private:
49    struct activation_record {
50        typedef boost::intrusive_ptr< activation_record >    ptr_t;
51
52        enum flag_t {
53            flag_main_ctx   = 1 << 1,
54            flag_preserve_fpu = 1 << 2,
55            flag_segmented_stack = 1 << 3
56        };
57
58        thread_local static activation_record       toplevel_rec;
59        thread_local static ptr_t                   current_rec;
60
61        std::size_t             use_count;
62        LPVOID                  fiber;
63        stack_context           sctx;
64        int                     flags;
65
66        // used for toplevel-context
67        // (e.g. main context, thread-entry context)
68        activation_record() noexcept :
69            use_count( 1),
70            fiber( nullptr),
71            sctx(),
72            flags( flag_main_ctx) {
73        }
74
75        activation_record( stack_context sctx_, bool use_segmented_stack) noexcept :
76            use_count( 0),
77            fiber( nullptr),
78            sctx( sctx_),
79            flags( use_segmented_stack ? flag_segmented_stack : 0) {
80        }
81
82        virtual ~activation_record() noexcept = default;
83
84        void resume() noexcept {
85            // store current activation record in local variable
86            activation_record * from = current_rec.get();
87            // store `this` in static, thread local pointer
88            // `this` will become the active (running) context
89            // returned by execution_context::current()
90            current_rec = this;
91            // context switch from parent context to `this`-context
92#if ( _WIN32_WINNT > 0x0600)
93            if ( ::IsThreadAFiber() ) {
94                from->fiber = ::GetCurrentFiber();
95            } else {
96                from->fiber = ::ConvertThreadToFiber( nullptr);
97            }
98#else
99            from->fiber = ::ConvertThreadToFiber( nullptr);
100            if ( nullptr == from->fiber) {
101                DWORD err = ::GetLastError();
102                BOOST_ASSERT( ERROR_ALREADY_FIBER == err);
103                from->fiber = ::GetCurrentFiber();
104                BOOST_ASSERT( nullptr != from->fiber);
105                BOOST_ASSERT( reinterpret_cast< LPVOID >( 0x1E00) != from->fiber);
106            }
107#endif
108            ::SwitchToFiber( fiber);
109        }
110
111        virtual void deallocate() {}
112
113        friend void intrusive_ptr_add_ref( activation_record * ar) {
114            ++ar->use_count;
115        }
116
117        friend void intrusive_ptr_release( activation_record * ar) {
118            BOOST_ASSERT( nullptr != ar);
119
120            if ( 0 == --ar->use_count) {
121                ar->deallocate();
122            }
123        }
124    };
125
126    template< typename Fn, typename StackAlloc >
127    class capture_record : public activation_record {
128    private:
129        StackAlloc      salloc_;
130        Fn              fn_;
131
132        static void destroy( capture_record * p) {
133            StackAlloc salloc( p->salloc_);
134            stack_context sctx( p->sctx);
135            // deallocate activation record
136            p->~capture_record();
137            // destroy stack with stack allocator
138            salloc.deallocate( sctx);
139        }
140
141    public:
142        explicit capture_record( stack_context sctx, StackAlloc const& salloc, Fn && fn, bool use_segmented_stack) noexcept :
143            activation_record( sctx, use_segmented_stack),
144            salloc_( salloc),
145            fn_( std::forward< Fn >( fn) ) {
146        }
147
148        void deallocate() override final {
149            destroy( this);
150        }
151
152        void run() noexcept {
153            try {
154                fn_();
155            } catch (...) {
156                std::terminate();
157            }
158            BOOST_ASSERT( 0 == (flags & flag_main_ctx) );
159        }
160    };
161
162    // tampoline function
163    // entered if the execution context
164    // is resumed for the first time
165    template< typename AR >
166    static VOID WINAPI entry_func( LPVOID p) {
167        BOOST_ASSERT( 0 != p);
168
169        AR * ar( reinterpret_cast< AR * >( p) );
170        // start execution of toplevel context-function
171        ar->run();
172        //ctx->fn_(ctx->param_);
173        ::DeleteFiber( ar->fiber);
174    }
175
176    typedef boost::intrusive_ptr< activation_record >    ptr_t;
177
178    ptr_t   ptr_;
179
180    template< typename StackAlloc, typename Fn >
181    static activation_record * create_context( StackAlloc salloc, Fn && fn, bool use_segmented_stack) {
182        typedef capture_record< Fn, StackAlloc >  capture_t;
183
184        // hackish
185        std::size_t fsize = salloc.size_;
186        salloc.size_ = sizeof( capture_t);
187
188        stack_context sctx( salloc.allocate() );
189        // reserve space for control structure
190        void * sp = static_cast< char * >( sctx.sp) - sizeof( capture_t);
191        // placment new for control structure on fast-context stack
192        capture_t * cr = new ( sp) capture_t( sctx, salloc, std::forward< Fn >( fn), use_segmented_stack);
193        // create fiber
194        // use default stacksize
195        cr->fiber = ::CreateFiber( fsize, execution_context::entry_func< capture_t >, cr);
196        BOOST_ASSERT( nullptr != cr->fiber);
197        return cr;
198    }
199
200    template< typename StackAlloc, typename Fn >
201    static activation_record * create_context( preallocated palloc, StackAlloc salloc, Fn && fn, bool use_segmented_stack) {
202        typedef capture_record< Fn, StackAlloc >  capture_t;
203
204        // hackish
205        std::size_t fsize = salloc.size_;
206        salloc.size_ = sizeof( capture_t);
207
208        // reserve space for control structure
209        void * sp = static_cast< char * >( palloc.sp) - sizeof( capture_t);
210        // placment new for control structure on fast-context stack
211        capture_t * cr = new ( sp) capture_t( palloc.sctx, salloc, std::forward< Fn >( fn), use_segmented_stack);
212        // create fiber
213        // use default stacksize
214        cr->fiber = ::CreateFiber( fsize, execution_context::entry_func< capture_t >, cr);
215        BOOST_ASSERT( nullptr != cr->fiber);
216        return cr;
217    }
218
219    template< typename StackAlloc, typename Fn, typename Tpl, std::size_t ... I >
220    static activation_record * create_capture_record( StackAlloc salloc,
221                                                      Fn && fn_, Tpl && tpl_,
222                                                      std::index_sequence< I ... >,
223                                                      bool use_segmented_stack) {
224        return create_context(
225            salloc,
226            // lambda, executed in new execution context
227            [fn=std::forward< Fn >( fn_),tpl=std::forward< Tpl >( tpl_)] () mutable -> decltype( auto) {
228                 detail::invoke( fn,
229                        // non-type template parameter pack used to extract the
230                        // parameters (arguments) from the tuple and pass them to fn
231                        // via parameter pack expansion
232                        // std::tuple_element<> does not perfect forwarding
233                        std::forward< decltype( std::get< I >( std::declval< Tpl >() ) ) >(
234                             std::get< I >( std::forward< Tpl >( tpl) ) ) ... );
235            },
236            use_segmented_stack);
237    }
238
239    template< typename StackAlloc, typename Fn, typename Tpl, std::size_t ... I >
240    static activation_record * create_capture_record( preallocated palloc, StackAlloc salloc,
241                                                      Fn && fn_, Tpl && tpl_,
242                                                      std::index_sequence< I ... >,
243                                                      bool use_segmented_stack) {
244        return create_context(
245            palloc, salloc,
246            // lambda, executed in new execution context
247            [fn=std::forward< Fn >( fn_),tpl=std::forward< Tpl >( tpl_)] () mutable -> decltype( auto) {
248                 detail::invoke( fn,
249                        // non-type template parameter pack used to extract the
250                        // parameters (arguments) from the tuple and pass them to fn
251                        // via parameter pack expansion
252                        // std::tuple_element<> does not perfect forwarding
253                        std::forward< decltype( std::get< I >( std::declval< Tpl >() ) ) >(
254                             std::get< I >( std::forward< Tpl >( tpl) ) ) ... );
255            },
256            use_segmented_stack);
257    }
258
259    execution_context() :
260        // default constructed with current activation_record
261        ptr_( activation_record::current_rec) {
262    }
263
264public:
265    static execution_context current() noexcept {
266        return execution_context();
267    }
268
269    template< typename StackAlloc, typename Fn, typename ... Args >
270    explicit execution_context( StackAlloc salloc, Fn && fn, Args && ... args) :
271        // deferred execution of fn and its arguments
272        // arguments are stored in std::tuple<>
273        // non-type template parameter pack via std::index_sequence_for<>
274        // preserves the number of arguments
275        // used to extract the function arguments from std::tuple<>
276        ptr_( create_capture_record( salloc,
277                                     std::forward< Fn >( fn),
278                                     std::make_tuple( std::forward< Args >( args) ... ),
279                                     std::index_sequence_for< Args ... >(), false) ) {
280    }
281
282    template< typename StackAlloc, typename Fn, typename ... Args >
283    explicit execution_context( preallocated palloc, StackAlloc salloc, Fn && fn, Args && ... args) :
284        // deferred execution of fn and its arguments
285        // arguments are stored in std::tuple<>
286        // non-type template parameter pack via std::index_sequence_for<>
287        // preserves the number of arguments
288        // used to extract the function arguments from std::tuple<>
289        ptr_( create_capture_record( palloc, salloc,
290                                     std::forward< Fn >( fn),
291                                     std::make_tuple( std::forward< Args >( args) ... ),
292                                     std::index_sequence_for< Args ... >(), false) ) {
293    }
294
295    void operator()( bool preserve_fpu = false) noexcept {
296        ptr_->resume();
297    }
298};
299
300}}
301
302# ifdef BOOST_HAS_ABI_HEADERS
303# include BOOST_ABI_SUFFIX
304# endif
305
306#endif
307
308#endif // BOOST_CONTEXT_EXECUTION_CONTEXT_H
309