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_FIBER_H
8#define BOOST_CONTEXT_FIBER_H
9
10#include <boost/context/detail/config.hpp>
11
12#include <algorithm>
13#include <cstddef>
14#include <cstdint>
15#include <cstdlib>
16#include <exception>
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#if defined(BOOST_NO_CXX14_STD_EXCHANGE)
28#include <boost/context/detail/exchange.hpp>
29#endif
30#if defined(BOOST_NO_CXX17_STD_INVOKE)
31#include <boost/context/detail/invoke.hpp>
32#endif
33#include <boost/context/detail/disable_overload.hpp>
34#include <boost/context/detail/exception.hpp>
35#include <boost/context/detail/fcontext.hpp>
36#include <boost/context/detail/tuple.hpp>
37#include <boost/context/fixedsize_stack.hpp>
38#include <boost/context/flags.hpp>
39#include <boost/context/preallocated.hpp>
40#include <boost/context/segmented_stack.hpp>
41#include <boost/context/stack_context.hpp>
42
43#ifdef BOOST_HAS_ABI_HEADERS
44# include BOOST_ABI_PREFIX
45#endif
46
47#if defined(__CET__) && defined(__unix__)
48# include <cet.h>
49# include <sys/mman.h>
50# define SHSTK_ENABLED (__CET__ & 0x2)
51# define BOOST_CONTEXT_SHADOW_STACK (SHSTK_ENABLED && SHADOW_STACK_SYSCALL)
52# define __NR_map_shadow_stack 451
53#ifndef SHADOW_STACK_SET_TOKEN
54# define SHADOW_STACK_SET_TOKEN 0x1
55#endif
56#endif
57
58#if defined(BOOST_MSVC)
59# pragma warning(push)
60# pragma warning(disable: 4702)
61#endif
62
63namespace boost {
64namespace context {
65namespace detail {
66
67inline
68transfer_t fiber_unwind( transfer_t t) {
69 throw forced_unwind( t.fctx);
70 return { .fctx: nullptr, .data: nullptr };
71}
72
73template< typename Rec >
74transfer_t fiber_exit( transfer_t t) noexcept {
75 Rec * rec = static_cast< Rec * >( t.data);
76#if BOOST_CONTEXT_SHADOW_STACK
77 // destory shadow stack
78 std::size_t ss_size = *((unsigned long*)(reinterpret_cast< uintptr_t >( rec)- 16));
79 long unsigned int ss_base = *((unsigned long*)(reinterpret_cast< uintptr_t >( rec)- 8));
80 munmap((void *)ss_base, ss_size);
81#endif
82 // destroy context stack
83 rec->deallocate();
84 return { .fctx: nullptr, .data: nullptr };
85}
86
87template< typename Rec >
88void fiber_entry( transfer_t t) noexcept {
89 // transfer control structure to the context-stack
90 Rec * rec = static_cast< Rec * >( t.data);
91 BOOST_ASSERT( nullptr != t.fctx);
92 BOOST_ASSERT( nullptr != rec);
93 try {
94 // jump back to `create_context()`
95 t = jump_fcontext( to: t.fctx, vp: nullptr);
96 // start executing
97 t.fctx = rec->run( t.fctx);
98 } catch ( forced_unwind const& ex) {
99 t = { .fctx: ex.fctx, .data: nullptr };
100 }
101 BOOST_ASSERT( nullptr != t.fctx);
102 // destroy context-stack of `this`context on next context
103 ontop_fcontext( t.fctx, rec, fiber_exit< Rec >);
104 BOOST_ASSERT_MSG( false, "context already terminated");
105}
106
107template< typename Ctx, typename Fn >
108transfer_t fiber_ontop( transfer_t t) {
109 BOOST_ASSERT( nullptr != t.data);
110 auto p = *static_cast< Fn * >( t.data);
111 t.data = nullptr;
112 // execute function, pass fiber via reference
113 Ctx c = p( Ctx{ t.fctx } );
114#if defined(BOOST_NO_CXX14_STD_EXCHANGE)
115 return { exchange( c.fctx_, nullptr), nullptr };
116#else
117 return { std::exchange( c.fctx_, nullptr), nullptr };
118#endif
119}
120
121template< typename Ctx, typename StackAlloc, typename Fn >
122class fiber_record {
123private:
124 stack_context sctx_;
125 typename std::decay< StackAlloc >::type salloc_;
126 typename std::decay< Fn >::type fn_;
127
128 static void destroy( fiber_record * p) noexcept {
129 typename std::decay< StackAlloc >::type salloc = std::move( p->salloc_);
130 stack_context sctx = p->sctx_;
131 // deallocate fiber_record
132 p->~fiber_record();
133 // destroy stack with stack allocator
134 salloc.deallocate( sctx);
135 }
136
137public:
138 fiber_record( stack_context sctx, StackAlloc && salloc,
139 Fn && fn) noexcept :
140 sctx_( sctx),
141 salloc_( std::forward< StackAlloc >( salloc)),
142 fn_( std::forward< Fn >( fn) ) {
143 }
144
145 fiber_record( fiber_record const&) = delete;
146 fiber_record & operator=( fiber_record const&) = delete;
147
148 void deallocate() noexcept {
149 destroy( p: this);
150 }
151
152 fcontext_t run( fcontext_t fctx) {
153 // invoke context-function
154#if defined(BOOST_NO_CXX17_STD_INVOKE)
155 Ctx c = boost::context::detail::invoke( fn_, Ctx{ fctx } );
156#else
157 Ctx c = std::invoke( fn_, Ctx{ fctx } );
158#endif
159#if defined(BOOST_NO_CXX14_STD_EXCHANGE)
160 return exchange( c.fctx_, nullptr);
161#else
162 return std::exchange( c.fctx_, nullptr);
163#endif
164 }
165};
166
167template< typename Record, typename StackAlloc, typename Fn >
168fcontext_t create_fiber1( StackAlloc && salloc, Fn && fn) {
169 auto sctx = salloc.allocate();
170 // reserve space for control structure
171 void * storage = reinterpret_cast< void * >(
172 ( reinterpret_cast< uintptr_t >( sctx.sp) - static_cast< uintptr_t >( sizeof( Record) ) )
173 & ~static_cast< uintptr_t >( 0xff) );
174 // placment new for control structure on context stack
175 Record * record = new ( storage) Record{
176 sctx, std::forward< StackAlloc >( salloc), std::forward< Fn >( fn) };
177 // 64byte gab between control structure and stack top
178 // should be 16byte aligned
179 void * stack_top = reinterpret_cast< void * >(
180 reinterpret_cast< uintptr_t >( storage) - static_cast< uintptr_t >( 64) );
181 void * stack_bottom = reinterpret_cast< void * >(
182 reinterpret_cast< uintptr_t >( sctx.sp) - static_cast< uintptr_t >( sctx.size) );
183 // create fast-context
184 const std::size_t size = reinterpret_cast< uintptr_t >( stack_top) - reinterpret_cast< uintptr_t >( stack_bottom);
185
186#if BOOST_CONTEXT_SHADOW_STACK
187 std::size_t ss_size = size >> 5;
188 // align shadow stack to 8 bytes.
189 ss_size = (ss_size + 7) & ~7;
190 // Todo: shadow stack occupies at least 4KB
191 ss_size = (ss_size > 4096) ? size : 4096;
192 // create shadow stack
193 void *ss_base = (void *)syscall(__NR_map_shadow_stack, 0, ss_size, SHADOW_STACK_SET_TOKEN);
194 BOOST_ASSERT(ss_base != -1);
195 unsigned long ss_sp = (unsigned long)ss_base + ss_size;
196 /* pass the shadow stack pointer to make_fcontext
197 i.e., link the new shadow stack with the new fcontext
198 TODO should be a better way? */
199 *((unsigned long*)(reinterpret_cast< uintptr_t >( stack_top)- 8)) = ss_sp;
200 /* Todo: place shadow stack info in 64byte gap */
201 *((unsigned long*)(reinterpret_cast< uintptr_t >( storage)- 8)) = (unsigned long) ss_base;
202 *((unsigned long*)(reinterpret_cast< uintptr_t >( storage)- 16)) = ss_size;
203#endif
204 const fcontext_t fctx = make_fcontext( stack_top, size, & fiber_entry< Record >);
205 BOOST_ASSERT( nullptr != fctx);
206 // transfer control structure to context-stack
207 return jump_fcontext( fctx, record).fctx;
208}
209
210template< typename Record, typename StackAlloc, typename Fn >
211fcontext_t create_fiber2( preallocated palloc, StackAlloc && salloc, Fn && fn) {
212 // reserve space for control structure
213 void * storage = reinterpret_cast< void * >(
214 ( reinterpret_cast< uintptr_t >( palloc.sp) - static_cast< uintptr_t >( sizeof( Record) ) )
215 & ~ static_cast< uintptr_t >( 0xff) );
216 // placment new for control structure on context-stack
217 Record * record = new ( storage) Record{
218 palloc.sctx, std::forward< StackAlloc >( salloc), std::forward< Fn >( fn) };
219 // 64byte gab between control structure and stack top
220 void * stack_top = reinterpret_cast< void * >(
221 reinterpret_cast< uintptr_t >( storage) - static_cast< uintptr_t >( 64) );
222 void * stack_bottom = reinterpret_cast< void * >(
223 reinterpret_cast< uintptr_t >( palloc.sctx.sp) - static_cast< uintptr_t >( palloc.sctx.size) );
224 // create fast-context
225 const std::size_t size = reinterpret_cast< uintptr_t >( stack_top) - reinterpret_cast< uintptr_t >( stack_bottom);
226
227#if BOOST_CONTEXT_SHADOW_STACK
228 std::size_t ss_size = size >> 5;
229 // align shadow stack to 8 bytes.
230 ss_size = (ss_size + 7) & ~7;
231 // Todo: shadow stack occupies at least 4KB
232 ss_size = (ss_size > 4096) ? size : 4096;
233 // create shadow stack
234 void *ss_base = (void *)syscall(__NR_map_shadow_stack, 0, ss_size, SHADOW_STACK_SET_TOKEN);
235 BOOST_ASSERT(ss_base != -1);
236 unsigned long ss_sp = (unsigned long)ss_base + ss_size;
237 /* pass the shadow stack pointer to make_fcontext
238 i.e., link the new shadow stack with the new fcontext
239 TODO should be a better way? */
240 *((unsigned long*)(reinterpret_cast< uintptr_t >( stack_top)- 8)) = ss_sp;
241 /* Todo: place shadow stack info in 64byte gap */
242 *((unsigned long*)(reinterpret_cast< uintptr_t >( storage)- 8)) = (unsigned long) ss_base;
243 *((unsigned long*)(reinterpret_cast< uintptr_t >( storage)- 16)) = ss_size;
244#endif
245 const fcontext_t fctx = make_fcontext( stack_top, size, & fiber_entry< Record >);
246 BOOST_ASSERT( nullptr != fctx);
247 // transfer control structure to context-stack
248 return jump_fcontext( fctx, record).fctx;
249}
250
251}
252
253class fiber {
254private:
255 template< typename Ctx, typename StackAlloc, typename Fn >
256 friend class detail::fiber_record;
257
258 template< typename Ctx, typename Fn >
259 friend detail::transfer_t
260 detail::fiber_ontop( detail::transfer_t);
261
262 detail::fcontext_t fctx_{ nullptr };
263
264 fiber( detail::fcontext_t fctx) noexcept :
265 fctx_{ fctx } {
266 }
267
268public:
269 fiber() noexcept = default;
270
271 template< typename Fn, typename = detail::disable_overload< fiber, Fn > >
272 fiber( Fn && fn) :
273 fiber{ std::allocator_arg, fixedsize_stack(), std::forward< Fn >( fn) } {
274 }
275
276 template< typename StackAlloc, typename Fn >
277 fiber( std::allocator_arg_t, StackAlloc && salloc, Fn && fn) :
278 fctx_{ detail::create_fiber1< detail::fiber_record< fiber, StackAlloc, Fn > >(
279 std::forward< StackAlloc >( salloc), std::forward< Fn >( fn) ) } {
280 }
281
282 template< typename StackAlloc, typename Fn >
283 fiber( std::allocator_arg_t, preallocated palloc, StackAlloc && salloc, Fn && fn) :
284 fctx_{ detail::create_fiber2< detail::fiber_record< fiber, StackAlloc, Fn > >(
285 palloc, std::forward< StackAlloc >( salloc), std::forward< Fn >( fn) ) } {
286 }
287
288#if defined(BOOST_USE_SEGMENTED_STACKS)
289 template< typename Fn >
290 fiber( std::allocator_arg_t, segmented_stack, Fn &&);
291
292 template< typename StackAlloc, typename Fn >
293 fiber( std::allocator_arg_t, preallocated, segmented_stack, Fn &&);
294#endif
295
296 ~fiber() {
297 if ( BOOST_UNLIKELY( nullptr != fctx_) ) {
298 detail::ontop_fcontext(
299#if defined(BOOST_NO_CXX14_STD_EXCHANGE)
300 detail::exchange( fctx_, nullptr),
301#else
302 to: std::exchange( obj&: fctx_, new_val: nullptr),
303#endif
304 vp: nullptr,
305 fn: detail::fiber_unwind);
306 }
307 }
308
309 fiber( fiber && other) noexcept {
310 swap( other);
311 }
312
313 fiber & operator=( fiber && other) noexcept {
314 if ( BOOST_LIKELY( this != & other) ) {
315 fiber tmp = std::move( other);
316 swap( other&: tmp);
317 }
318 return * this;
319 }
320
321 fiber( fiber const& other) noexcept = delete;
322 fiber & operator=( fiber const& other) noexcept = delete;
323
324 fiber resume() && {
325 BOOST_ASSERT( nullptr != fctx_);
326 return { detail::jump_fcontext(
327#if defined(BOOST_NO_CXX14_STD_EXCHANGE)
328 detail::exchange( fctx_, nullptr),
329#else
330 to: std::exchange( obj&: fctx_, new_val: nullptr),
331#endif
332 vp: nullptr).fctx };
333 }
334
335 template< typename Fn >
336 fiber resume_with( Fn && fn) && {
337 BOOST_ASSERT( nullptr != fctx_);
338 auto p = std::forward< Fn >( fn);
339 return { detail::ontop_fcontext(
340#if defined(BOOST_NO_CXX14_STD_EXCHANGE)
341 detail::exchange( fctx_, nullptr),
342#else
343 to: std::exchange( obj&: fctx_, new_val: nullptr),
344#endif
345 vp: & p,
346 fn: detail::fiber_ontop< fiber, decltype(p) >).fctx };
347 }
348
349 explicit operator bool() const noexcept {
350 return nullptr != fctx_;
351 }
352
353 bool operator!() const noexcept {
354 return nullptr == fctx_;
355 }
356
357 bool operator<( fiber const& other) const noexcept {
358 return fctx_ < other.fctx_;
359 }
360
361 #if !defined(BOOST_EMBTC)
362
363 template< typename charT, class traitsT >
364 friend std::basic_ostream< charT, traitsT > &
365 operator<<( std::basic_ostream< charT, traitsT > & os, fiber const& other) {
366 if ( nullptr != other.fctx_) {
367 return os << other.fctx_;
368 } else {
369 return os << "{not-a-context}";
370 }
371 }
372
373 #else
374
375 template< typename charT, class traitsT >
376 friend std::basic_ostream< charT, traitsT > &
377 operator<<( std::basic_ostream< charT, traitsT > & os, fiber const& other);
378
379 #endif
380
381 void swap( fiber & other) noexcept {
382 std::swap( a&: fctx_, b&: other.fctx_);
383 }
384};
385
386#if defined(BOOST_EMBTC)
387
388 template< typename charT, class traitsT >
389 inline std::basic_ostream< charT, traitsT > &
390 operator<<( std::basic_ostream< charT, traitsT > & os, fiber const& other) {
391 if ( nullptr != other.fctx_) {
392 return os << other.fctx_;
393 } else {
394 return os << "{not-a-context}";
395 }
396 }
397
398#endif
399
400inline
401void swap( fiber & l, fiber & r) noexcept {
402 l.swap( other&: r);
403}
404
405typedef fiber fiber_context;
406
407}}
408
409#if defined(BOOST_MSVC)
410# pragma warning(pop)
411#endif
412
413#ifdef BOOST_HAS_ABI_HEADERS
414# include BOOST_ABI_SUFFIX
415#endif
416
417#endif // BOOST_CONTEXT_FIBER_H
418

source code of boost/libs/context/include/boost/context/fiber_fcontext.hpp