From 4b8a730919c160fece5b561d3d22a7169dcdd8c4 Mon Sep 17 00:00:00 2001 From: Oliver Kowalke Date: Mon, 15 May 2017 21:29:41 +0200 Subject: [PATCH] support ucontext_t in callcc() --- build/Jamfile.v2 | 3 + doc/architectures.qbk | 7 +- doc/callcc.qbk | 24 +- doc/context.qbk | 3 +- doc/execution_context_v1.qbk | 2 +- doc/overview.qbk | 15 +- doc/performance.qbk | 22 +- doc/rationale.qbk | 8 +- doc/requirements.qbk | 10 +- doc/stack.qbk | 8 +- example/callcc/Jamfile.v2 | 4 + example/callcc/endless_loop.cpp | 4 +- example/callcc/segmented.cpp | 51 ++ include/boost/context/continuation.hpp | 551 +------------- .../boost/context/continuation_fcontext.hpp | 556 ++++++++++++++ .../boost/context/continuation_ucontext.hpp | 696 ++++++++++++++++++ include/boost/context/detail/exception.hpp | 4 +- .../boost/context/pooled_fixedsize_stack.hpp | 1 + performance/callcc/Jamfile.v2 | 1 - src/execution_context.cpp | 43 +- test/test_callcc.cpp | 1 - 21 files changed, 1419 insertions(+), 595 deletions(-) create mode 100644 example/callcc/segmented.cpp create mode 100644 include/boost/context/continuation_fcontext.hpp create mode 100644 include/boost/context/continuation_ucontext.hpp diff --git a/build/Jamfile.v2 b/build/Jamfile.v2 index 4eae7a9..e6cbf88 100644 --- a/build/Jamfile.v2 +++ b/build/Jamfile.v2 @@ -25,6 +25,9 @@ feature.compose on : BOOST_USE_VALGRIND ; feature.feature context-switch : cc ec : optional propagated composite ; feature.compose ec : BOOST_USE_EXECUTION_CONTEXT ; +feature.feature context : ucontext : optional propagated composite ; +feature.compose ucontext : BOOST_USE_UCONTEXT ; + project boost/context : requirements gcc,on:-fsplit-stack diff --git a/doc/architectures.qbk b/doc/architectures.qbk index 77d4d1b..3e97b15 100644 --- a/doc/architectures.qbk +++ b/doc/architectures.qbk @@ -7,7 +7,8 @@ [section:architectures Architectures] -__boost_context__ supports following architectures: +__boost_context__, using [link implementation ['fcontext_t]], supports following +architectures: [table Supported architectures () [[Architecture] [LINUX (UNIX)] [Windows] [MacOS X] [iOS]] @@ -21,6 +22,10 @@ __boost_context__ supports following architectures: [[x86_64] [SYSV,X32|ELF] [MS|PE] [SYSV|MACH-O] [-]] ] +[note If the architecture is not supported but the platform provides +[link implementation __ucontext__], __boost_context__ should be +compiled with `BOOST_USE_UCONTEXT` and b2 property `context=ucontext`.] + [section:crosscompiling Cross compiling] Cross compiling the library requires to specify the build properties diff --git a/doc/callcc.qbk b/doc/callcc.qbk index ace3338..31c3004 100644 --- a/doc/callcc.qbk +++ b/doc/callcc.qbk @@ -14,7 +14,7 @@ continuation as a first-class object and pass it as an argument to another continuation. A continuation (abstract concept of functional programming languages) -represents the state of the cotnrol flow of a program at a given point in time. +represents the state of the control flow of a program at a given point in time. Continuations can be suspended and resumed later in order to change the control flow of a program. @@ -52,7 +52,9 @@ not yet returned, the stack is traversed in order to access the control structure (address stored at the first stack frame) and continuation's stack is deallocated via the __stack_allocator__. -[important Segmented stacks are not supported by __callcc__.] +[note [link segmented ['Segmented stacks]] are supported by __cc__ using +[link implementation ['ucontext_t]].] + [heading __con__] @@ -451,6 +453,24 @@ parser (via callback). The data (character) is transferred between the two continuations. +[#implementation] +[heading Implementation: fcontext_t/ucontext_t] + +The implementation uses __fcontext__ per default. fcontext_t is based on +assembler and not available for all platforms. It provides a much better +performance than __ucontext__ +(the context switch takes two magnitutes of order less CPU cycles; see section +[link performance ['performance]]). + +As an alternative, [@https://en.wikipedia.org/wiki/Setcontext __ucontext__] +can be used by compiling with `BOOST_USE_UCONTEXT` and b2 property `context=ucontext`. +__ucontext__ might be available on a broader range of POSIX-platforms but has +some [link ucontext ['disadvantages]] (for instance deprecated snce POSIX.1-2003). + +[link segmented ['Segmented stacks]] are supported by __cc__ only with +__ucontext__ as its implementation. + + [heading Class `continuation`] #include diff --git a/doc/context.qbk b/doc/context.qbk index 5effd5a..98a6c02 100644 --- a/doc/context.qbk +++ b/doc/context.qbk @@ -134,7 +134,8 @@ [def __fixedsize__ ['fixedsize_stack]] [def __pooled_fixedsize__ ['pooled_fixedsize_stack]] [def __protected_fixedsize__ ['protected_fixedsize_stack]] -[def __segmented__ ['segmented_stack]] +[def __segmented__ [link segmented ['segmented_stack]]] +[def __segmented_stack__ ['segmented_stack]] [def __stack_context__ ['stack_context]] [def __fls_alloc__ ['::FlsAlloc()]] diff --git a/doc/execution_context_v1.qbk b/doc/execution_context_v1.qbk index 1ee003d..cc090f6 100644 --- a/doc/execution_context_v1.qbk +++ b/doc/execution_context_v1.qbk @@ -14,7 +14,7 @@ at compilers command-line in order to use __econtext__ (v1).] [note Segmented stacks (['segmented-stacks=on]), e.g. on demand growing stacks, -can only be used with __econtext__ (v1).] +can be used with __econtext__ (v1).] Class __econtext__ encapsulates context switching and manages the associated context' stack (allocation/deallocation). diff --git a/doc/overview.qbk b/doc/overview.qbk index 4ae31df..4b687b3 100644 --- a/doc/overview.qbk +++ b/doc/overview.qbk @@ -11,23 +11,24 @@ __boost_context__ is a foundational library that provides a sort of cooperative multitasking on a single thread. By providing an abstraction of the current execution state in the current thread, including the stack (with local variables) and stack pointer, all registers and CPU flags, and the instruction -pointer, a __econtext__ represents a specific point in the application's +pointer, a execution context represents a specific point in the application's execution path. This is useful for building higher-level abstractions, like __coroutines__, __coop_threads__ or an equivalent to [@http://msdn.microsoft.com/en-us/library/9k7k7cf0%28v=vs.80%29.aspx C# keyword __yield__] in C++. -__econtext__ provides the means to suspend the current execution path and to +__cc__/__con__ provides the means to suspend the current execution path and to transfer execution control, thereby permitting another context to run on the current thread. This state full transfer mechanism enables a context to suspend execution from within nested functions and, later, to resume from where it was -suspended. While the execution path represented by a __econtext__ only runs on a +suspended. While the execution path represented by a __con__ only runs on a single thread, it can be migrated to another thread at any given time. -A context switch between threads requires system calls (involving the OS -kernel), which can cost more than thousand CPU cycles on x86 CPUs. By contrast, -transferring control among them requires only few CPU cycles because it does not -involve system calls as it is done within a single thread. +A [@http://en.wikipedia.org/wiki/Context_switch context switch] between threads +requires system calls (involving the OS kernel), which can cost more than +thousand CPU cycles on x86 CPUs. By contrast, transferring control vias +__cc__/__con__ requires only few CPU cycles because it does not involve system +calls as it is done within a single thread. In order to use the classes and functions described here, you can either include the specific headers specified by the descriptions of each class or function, or diff --git a/doc/performance.qbk b/doc/performance.qbk index d7e7387..fd73531 100644 --- a/doc/performance.qbk +++ b/doc/performance.qbk @@ -5,23 +5,21 @@ http://www.boost.org/LICENSE_1_0.txt ] +[#performance] [section:performance Performance] -Performance of __boost_context__ was measured on the platforms shown in the -following table. Performance measurements were taken using `rdtsc` and -`boost::chrono::high_resolution_clock`, with -overhead corrections, on x86 platforms. In each case, cache warm-up was -accounted for, and the one running thread was pinned -to a single CPU. The code was compiled using the build options, -'variant = release cxxflags = -DBOOST_DISABLE_ASSERTS'. +Performance measurements were taken using `std::chrono::highresolution_clock`, +with overhead corrections. +The code was compiled with gcc-6.3.1, using build options: +variant = release, optimization = speed. +Tests were executed on dual Intel XEON E5 2620 2.2GHz, 16C/32T, 64GB RAM, +running Linux (x86_64). [table Performance of context switch - [[Platform] [ucontext_t] [execution_context (v1)] [execution_context (v2)]] + [[callcc()/continuation (fcontext_t)] [callcc()/continuation (ucontext_t)]] [ - [x86_64 [footnote Intel Core i7-4770S 3.10GHz]] - [547 ns / 1433 cycles] - [30 ns / 80 cycles] - [8 ns / 25 cycles] + [18 ns / 37 CPU cycles] + [547 ns / 1130 CPU cycles] ] ] diff --git a/doc/rationale.qbk b/doc/rationale.qbk index 8d40e5b..f71dce1 100644 --- a/doc/rationale.qbk +++ b/doc/rationale.qbk @@ -39,9 +39,10 @@ into a function which was exited via a call to ['longjmp()] is undefined [footnote ISO/IEC 9899:1999, 2005, 7.13.2.1:2]. +[#ucontext] [heading ucontext_t] -Since POSIX.1-2003 `ucontext_t` is deprecated and was removed in POSIX.1-2008! +Since POSIX.1-2004 `ucontext_t` is deprecated and was removed in POSIX.1-2008! The function signature of `makecontext()` is: void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...); @@ -56,8 +57,9 @@ in var-arg list is not guaranteed to work, especially it will fail for architectures where pointers are larger than integers. `ucontext_t` preserves signal mask between context switches which involves system -calls consuming a lot of CPU cycles (ucontext_t is slower by -perfomance_link[factor 13x] relative to `fcontext_t`). +calls consuming a lot of CPU cycles (ucontext_t is slower; a context switch +takes [link performance ['two magnitutes of order more CPU cycles]] more than +__fcontext__). [heading Windows fibers] diff --git a/doc/requirements.qbk b/doc/requirements.qbk index f7993e7..fb58530 100644 --- a/doc/requirements.qbk +++ b/doc/requirements.qbk @@ -7,10 +7,12 @@ [section:requirements Requirements] -__boost_context__ must be built for the particular compiler(s) and CPU -architecture(s)s being targeted. __boost_context__ includes assembly code and, -therefore, requires GNU as and GNU preprocesspr for supported POSIX systems, -MASM for Windows/x86 systems and ARMasm for Windows/arm systems. +If __boost_context__ uses fcontext_t (the default) as its implementation, +it must be built for the particular compiler(s) and CPU architecture(s) +being targeted. +Using [link implementation ['fcontext_t]], __boost_context__ includes assembly +code and, therefore, requires GNU as and GNU preprocessor for supported POSIX +systems, MASM for Windows/x86 systems and ARMasm for Windows/arm systems. [note MASM64 (ml64.exe) is a part of Microsoft's Windows Driver Kit.] diff --git a/doc/stack.qbk b/doc/stack.qbk index 65f1bed..435a2e5 100644 --- a/doc/stack.qbk +++ b/doc/stack.qbk @@ -199,6 +199,7 @@ address of the stack.]] [endsect] +[#segmented] [section:segmented Class ['segmented_stack]] __boost_context__ supports usage of a __segmented__, e. g. the size of @@ -211,10 +212,11 @@ stack which grows on demand. [note Segmented stacks are currently only supported by [*gcc] from version [*4.7] [*clang] from version [*3.4] onwards. In order to use a __segmented_stack__ __boost_context__ must be built with -property `segmented-stacks`, e.g. [*toolset=gcc segmented-stacks=on] at b2/bjam -command line.] +property `segmented-stacks`, e.g. [*toolset=gcc segmented-stacks=on] and +applying `BOOST_USE_SEGMENTED_STACKS` at b2/bjam command line.] -[note Segmented stacks can only be used with __econtext__ (v1)]. +[note Segmented stacks can only be used with __cc__ (using ucontext_t) and +__econtext__ (v1)]. #include diff --git a/example/callcc/Jamfile.v2 b/example/callcc/Jamfile.v2 index 942dee1..7059a76 100644 --- a/example/callcc/Jamfile.v2 +++ b/example/callcc/Jamfile.v2 @@ -70,6 +70,10 @@ exe endless_loop : endless_loop.cpp ; +exe segmented + : segmented.cpp + ; + #exe backtrace # : backtrace.cpp # ; diff --git a/example/callcc/endless_loop.cpp b/example/callcc/endless_loop.cpp index 0a3ce79..318bb88 100644 --- a/example/callcc/endless_loop.cpp +++ b/example/callcc/endless_loop.cpp @@ -14,7 +14,7 @@ namespace ctx = boost::context; ctx::continuation foo( ctx::continuation && c) { do { std::cout << "foo\n"; - } while ( c = c.resume() ); + } while ( ( c = c.resume() ) ); return std::move( c); } @@ -22,7 +22,7 @@ int main() { ctx::continuation c = ctx::callcc( foo); do { std::cout << "bar\n"; - } while ( c = c.resume() ); + } while ( ( c = c.resume() ) ); std::cout << "main: done" << std::endl; return EXIT_SUCCESS; } diff --git a/example/callcc/segmented.cpp b/example/callcc/segmented.cpp new file mode 100644 index 0000000..bacda9c --- /dev/null +++ b/example/callcc/segmented.cpp @@ -0,0 +1,51 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include + +#include + +namespace ctx = boost::context; + +#ifdef BOOST_MSVC //MS VisualStudio +__declspec(noinline) void access( char *buf); +#else // GCC +void access( char *buf) __attribute__ ((noinline)); +#endif +void access( char *buf) { + buf[0] = '\0'; +} + +void bar( int i) { + char buf[4 * 1024]; + if ( i > 0) { + access( buf); + std::cout << i << ". iteration" << std::endl; + bar( i - 1); + } +} + +int main() { + int count = 384; +#if defined(BOOST_USE_SEGMENTED_STACKS) + std::cout << "using segmented_stack stacks: allocates " << count << " * 4kB == " << 4 * count << "kB on stack, "; + std::cout << "initial stack size = " << boost::context::segmented_stack::traits_type::default_size() / 1024 << "kB" << std::endl; + std::cout << "application should not fail" << std::endl; +#else + std::cout << "using standard stacks: allocates " << count << " * 4kB == " << 4 * count << "kB on stack, "; + std::cout << "initial stack size = " << boost::context::fixedsize_stack::traits_type::default_size() / 1024 << "kB" << std::endl; + std::cout << "application might fail" << std::endl; +#endif + ctx::continuation c = ctx::callcc( + [count](ctx::continuation && c){ + bar( count); + return std::move( c); + }); + std::cout << "main: done" << std::endl; + return EXIT_SUCCESS; +} diff --git a/include/boost/context/continuation.hpp b/include/boost/context/continuation.hpp index 9ad02f1..0484a39 100644 --- a/include/boost/context/continuation.hpp +++ b/include/boost/context/continuation.hpp @@ -4,553 +4,8 @@ // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#ifndef BOOST_CONTEXT_CONTINUATION_H -#define BOOST_CONTEXT_CONTINUATION_H - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#if defined(BOOST_NO_CXX17_STD_APPLY) -#include -#endif -#if defined(BOOST_NO_CXX14_STD_EXCHANGE) -#include -#endif -#if defined(BOOST_NO_CXX17_STD_INVOKE) -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef BOOST_HAS_ABI_HEADERS -# include BOOST_ABI_PREFIX -#endif - -#if defined(BOOST_MSVC) -# pragma warning(push) -# pragma warning(disable: 4702) -#endif - -namespace boost { -namespace context { -namespace detail { - -template< typename U > -struct helper { - template< typename T > - static T convert( T && t) noexcept { - return std::forward< T >( t); - } -}; - -template< typename U > -struct helper< std::tuple< U > > { - template< typename T > - static std::tuple< T > convert( T && t) noexcept { - return std::make_tuple( std::forward< T >( t) ); - } -}; - -inline -transfer_t context_unwind( transfer_t t) { - throw forced_unwind( t.fctx); - return { nullptr, nullptr }; -} - -template< typename Rec > -transfer_t context_exit( transfer_t t) noexcept { - Rec * rec = static_cast< Rec * >( t.data); - // destroy context stack - rec->deallocate(); - return { nullptr, nullptr }; -} - -template< typename Rec > -void context_entry( transfer_t t_) noexcept { - // transfer control structure to the context-stack - Rec * rec = static_cast< Rec * >( t_.data); - BOOST_ASSERT( nullptr != t_.fctx); - BOOST_ASSERT( nullptr != rec); - transfer_t t = { nullptr, nullptr }; - try { - // jump back to `context_create()` - t = jump_fcontext( t_.fctx, nullptr); - // start executing - t = rec->run( t); - } catch ( forced_unwind const& e) { - t = { e.fctx, nullptr }; - } - BOOST_ASSERT( nullptr != t.fctx); - // destroy context-stack of `this`context on next context - ontop_fcontext( t.fctx, rec, context_exit< Rec >); - BOOST_ASSERT_MSG( false, "context already terminated"); -} - -template< - typename Ctx, - typename StackAlloc, - typename Fn -> -class record { -private: - StackAlloc salloc_; - stack_context sctx_; - typename std::decay< Fn >::type fn_; - - static void destroy( record * p) noexcept { - StackAlloc salloc = p->salloc_; - stack_context sctx = p->sctx_; - // deallocate record - p->~record(); - // destroy stack with stack allocator - salloc.deallocate( sctx); - } - -public: - record( stack_context sctx, StackAlloc const& salloc, - Fn && fn) noexcept : - salloc_( salloc), - sctx_( sctx), - fn_( std::forward< Fn >( fn) ) { - } - - record( record const&) = delete; - record & operator=( record const&) = delete; - - void deallocate() noexcept { - destroy( this); - } - - transfer_t run( transfer_t t) { - Ctx from{ t }; - // invoke context-function -#if defined(BOOST_NO_CXX17_STD_INVOKE) - Ctx cc = invoke( fn_, std::move( from) ); +#if defined(BOOST_USE_UCONTEXT) +#include #else - Ctx cc = std::invoke( fn_, std::move( from) ); +#include #endif -#if defined(BOOST_NO_CXX14_STD_EXCHANGE) - return { exchange( cc.t_.fctx, nullptr), nullptr }; -#else - return { std::exchange( cc.t_.fctx, nullptr), nullptr }; -#endif - } -}; - -template< typename Record, typename StackAlloc, typename Fn > -fcontext_t context_create( StackAlloc salloc, Fn && fn) { - auto sctx = salloc.allocate(); - // reserve space for control structure -#if defined(BOOST_NO_CXX11_CONSTEXPR) || defined(BOOST_NO_CXX11_STD_ALIGN) - const std::size_t size = sctx.size - sizeof( Record); - void * sp = static_cast< char * >( sctx.sp) - sizeof( Record); -#else - constexpr std::size_t func_alignment = 64; // alignof( Record); - constexpr std::size_t func_size = sizeof( Record); - // reserve space on stack - void * sp = static_cast< char * >( sctx.sp) - func_size - func_alignment; - // align sp pointer - std::size_t space = func_size + func_alignment; - sp = std::align( func_alignment, func_size, sp, space); - BOOST_ASSERT( nullptr != sp); - // calculate remaining size - const std::size_t size = sctx.size - ( static_cast< char * >( sctx.sp) - static_cast< char * >( sp) ); -#endif - // create fast-context - const fcontext_t fctx = make_fcontext( sp, size, & context_entry< Record >); - BOOST_ASSERT( nullptr != fctx); - // placment new for control structure on context-stack - auto rec = ::new ( sp) Record{ - sctx, salloc, std::forward< Fn >( fn) }; - // transfer control structure to context-stack - return jump_fcontext( fctx, rec).fctx; -} - -template< typename Record, typename StackAlloc, typename Fn > -fcontext_t context_create( preallocated palloc, StackAlloc salloc, Fn && fn) { - // reserve space for control structure -#if defined(BOOST_NO_CXX11_CONSTEXPR) || defined(BOOST_NO_CXX11_STD_ALIGN) - const std::size_t size = palloc.size - sizeof( Record); - void * sp = static_cast< char * >( palloc.sp) - sizeof( Record); -#else - constexpr std::size_t func_alignment = 64; // alignof( Record); - constexpr std::size_t func_size = sizeof( Record); - // reserve space on stack - void * sp = static_cast< char * >( palloc.sp) - func_size - func_alignment; - // align sp pointer - std::size_t space = func_size + func_alignment; - sp = std::align( func_alignment, func_size, sp, space); - BOOST_ASSERT( nullptr != sp); - // calculate remaining size - const std::size_t size = palloc.size - ( static_cast< char * >( palloc.sp) - static_cast< char * >( sp) ); -#endif - // create fast-context - const fcontext_t fctx = make_fcontext( sp, size, & context_entry< Record >); - BOOST_ASSERT( nullptr != fctx); - // placment new for control structure on context-stack - auto rec = ::new ( sp) Record{ - palloc.sctx, salloc, std::forward< Fn >( fn) }; - // transfer control structure to context-stack - return jump_fcontext( fctx, rec).fctx; -} - -template< typename ... Arg > -struct result_type { - typedef std::tuple< Arg ... > type; - - static - type get( detail::transfer_t & t) { - auto p = static_cast< std::tuple< Arg ... > * >( t.data); - return std::move( * p); - } -}; - -template< typename Arg > -struct result_type< Arg > { - typedef Arg type; - - static - type get( detail::transfer_t & t) { - auto p = static_cast< std::tuple< Arg > * >( t.data); - return std::forward< Arg >( std::get< 0 >( * p) ); - } -}; - -} - -template< typename Ctx, typename Fn, typename Arg > -detail::transfer_t context_ontop( detail::transfer_t t) { - auto p = static_cast< Arg * >( t.data); - BOOST_ASSERT( nullptr != p); - typename std::decay< Fn >::type fn = std::forward< Fn >( std::get< 0 >( * p) ); - t.data = & std::get< 1 >( * p); - Ctx c{ t }; - // execute function, pass continuation via reference - typedef typename std::decay< decltype( std::get< 1 >( * p) )>::type Tpl; - std::get< 1 >( * p) = detail::helper< Tpl >::convert( fn( std::move( c) ) ); -#if defined(BOOST_NO_CXX14_STD_EXCHANGE) - return { detail::exchange( c.t_.fctx, nullptr), & std::get< 1 >( * p) }; -#else - return { std::exchange( c.t_.fctx, nullptr), & std::get< 1 >( * p) }; -#endif -} - -template< typename Ctx, typename Fn > -detail::transfer_t context_ontop_void( detail::transfer_t t) { - auto p = static_cast< std::tuple< Fn > * >( t.data); - BOOST_ASSERT( nullptr != p); - typename std::decay< Fn >::type fn = std::forward< Fn >( std::get< 0 >( * p) ); - Ctx c{ t }; - // execute function, pass continuation via reference - fn( std::move( c) ); -#if defined(BOOST_NO_CXX14_STD_EXCHANGE) - return { detail::exchange( c.t_.fctx, nullptr), nullptr }; -#else - return { std::exchange( c.t_.fctx, nullptr), nullptr }; -#endif -} - -class continuation { -private: - template< typename Ctx, typename StackAlloc, typename Fn > - friend class detail::record; - - template< typename Ctx, typename Fn, typename Arg > - friend detail::transfer_t - context_ontop( detail::transfer_t); - - template< typename Ctx, typename Fn > - friend detail::transfer_t - context_ontop_void( detail::transfer_t); - - template< typename StackAlloc, typename Fn, typename ... Arg > - friend continuation - callcc( std::allocator_arg_t, StackAlloc, Fn &&, Arg ...); - - template< typename StackAlloc, typename Fn, typename ... Arg > - friend continuation - callcc( std::allocator_arg_t, preallocated, StackAlloc, Fn &&, Arg ...); - - template< typename StackAlloc, typename Fn > - friend continuation - callcc( std::allocator_arg_t, StackAlloc, Fn &&); - - template< typename StackAlloc, typename Fn > - friend continuation - callcc( std::allocator_arg_t, preallocated, StackAlloc, Fn &&); - - detail::transfer_t t_{ nullptr, nullptr }; - - continuation( detail::fcontext_t fctx) noexcept : - t_{ fctx, nullptr } { - } - - continuation( detail::transfer_t t) noexcept : - t_{ t.fctx, t.data } { - } - -public: - continuation() noexcept = default; - - ~continuation() { - if ( nullptr != t_.fctx) { -#if defined(BOOST_NO_CXX14_STD_EXCHANGE) - detail::ontop_fcontext( detail::exchange( t_.fctx, nullptr), nullptr, detail::context_unwind); -#else - detail::ontop_fcontext( std::exchange( t_.fctx, nullptr), nullptr, detail::context_unwind); -#endif - } - } - - continuation( continuation && other) noexcept : - t_{ other.t_.fctx, other.t_.data } { - other.t_ = { nullptr, nullptr }; - } - - continuation & operator=( continuation && other) noexcept { - if ( this != & other) { - continuation tmp = std::move( other); - swap( tmp); - } - return * this; - } - - continuation( continuation const& other) noexcept = delete; - continuation & operator=( continuation const& other) noexcept = delete; - - template< typename ... Arg > - continuation resume( Arg ... arg) { - BOOST_ASSERT( nullptr != t_.fctx); - auto tpl = std::make_tuple( std::forward< Arg >( arg) ... ); - return detail::jump_fcontext( -#if defined(BOOST_NO_CXX14_STD_EXCHANGE) - detail::exchange( t_.fctx, nullptr), -#else - std::exchange( t_.fctx, nullptr), -#endif - & tpl); - } - - template< typename Fn, typename ... Arg > - continuation resume_with( Fn && fn, Arg ... arg) { - BOOST_ASSERT( nullptr != t_.fctx); - auto tpl = std::make_tuple( std::forward< Fn >( fn), std::make_tuple( std::forward< Arg >( arg) ... )); - return detail::ontop_fcontext( -#if defined(BOOST_NO_CXX14_STD_EXCHANGE) - detail::exchange( t_.fctx, nullptr), -#else - std::exchange( t_.fctx, nullptr), -#endif - & tpl, - context_ontop< continuation, Fn, decltype(tpl) >); - } - - continuation resume() { - BOOST_ASSERT( nullptr != t_.fctx); - return detail::jump_fcontext( -#if defined(BOOST_NO_CXX14_STD_EXCHANGE) - detail::exchange( t_.fctx, nullptr), -#else - std::exchange( t_.fctx, nullptr), -#endif - nullptr); - } - - template< typename Fn > - continuation resume_with( Fn && fn) { - BOOST_ASSERT( nullptr != t_.fctx); - auto p = std::make_tuple( std::forward< Fn >( fn) ); - return detail::ontop_fcontext( -#if defined(BOOST_NO_CXX14_STD_EXCHANGE) - detail::exchange( t_.fctx, nullptr), -#else - std::exchange( t_.fctx, nullptr), -#endif - & p, - context_ontop_void< continuation, Fn >); - } - - bool data_available() noexcept { - return * this && nullptr != t_.data; - } - - template< typename ... Arg > - typename detail::result_type< Arg ... >::type get_data() { - BOOST_ASSERT( nullptr != t_.data); - return detail::result_type< Arg ... >::get( t_); - } - - explicit operator bool() const noexcept { - return nullptr != t_.fctx; - } - - bool operator!() const noexcept { - return nullptr == t_.fctx; - } - - bool operator==( continuation const& other) const noexcept { - return t_.fctx == other.t_.fctx; - } - - bool operator!=( continuation const& other) const noexcept { - return t_.fctx != other.t_.fctx; - } - - bool operator<( continuation const& other) const noexcept { - return t_.fctx < other.t_.fctx; - } - - bool operator>( continuation const& other) const noexcept { - return other.t_.fctx < t_.fctx; - } - - bool operator<=( continuation const& other) const noexcept { - return ! ( * this > other); - } - - bool operator>=( continuation const& other) const noexcept { - return ! ( * this < other); - } - - template< typename charT, class traitsT > - friend std::basic_ostream< charT, traitsT > & - operator<<( std::basic_ostream< charT, traitsT > & os, continuation const& other) { - if ( nullptr != other.t_.fctx) { - return os << other.t_.fctx; - } else { - return os << "{not-a-context}"; - } - } - - void swap( continuation & other) noexcept { - std::swap( t_, other.t_); - } -}; - -// Arg -template< - typename Fn, - typename ... Arg, - typename = detail::disable_overload< continuation, Fn >, - typename = detail::disable_overload< std::allocator_arg_t, Fn > -> -continuation -callcc( Fn && fn, Arg ... arg) { - return callcc( - std::allocator_arg, fixedsize_stack(), - std::forward< Fn >( fn), std::forward< Arg >( arg) ...); -} - -template< - typename StackAlloc, - typename Fn, - typename ... Arg -> -continuation -callcc( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Arg ... arg) { - using Record = detail::record< continuation, StackAlloc, Fn >; - return continuation{ - detail::context_create< Record >( - salloc, std::forward< Fn >( fn) ) }.resume( - std::forward< Arg >( arg) ... ); -} - -template< - typename StackAlloc, - typename Fn, - typename ... Arg -> -continuation -callcc( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Arg ... arg) { - using Record = detail::record< continuation, StackAlloc, Fn >; - return continuation{ - detail::context_create< Record >( - palloc, salloc, std::forward< Fn >( fn) ) }.resume( - std::forward< Arg >( arg) ... ); -} - -// void -template< - typename Fn, - typename = detail::disable_overload< continuation, Fn > -> -continuation -callcc( Fn && fn) { - return callcc( - std::allocator_arg, fixedsize_stack(), - std::forward< Fn >( fn) ); -} - -template< typename StackAlloc, typename Fn > -continuation -callcc( std::allocator_arg_t, StackAlloc salloc, Fn && fn) { - using Record = detail::record< continuation, StackAlloc, Fn >; - return continuation{ - detail::context_create< Record >( - salloc, std::forward< Fn >( fn) ) }.resume(); -} - -template< typename StackAlloc, typename Fn > -continuation -callcc( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn) { - using Record = detail::record< continuation, StackAlloc, Fn >; - return continuation{ - detail::context_create< Record >( - palloc, salloc, std::forward< Fn >( fn) ) }.resume(); -} - -#if defined(BOOST_USE_SEGMENTED_STACKS) -template< - typename Fn, - typename ... Arg -> -continuation -callcc( std::allocator_arg_t, segmented_stack, Fn &&, Arg ...); - -template< - typename StackAlloc, - typename Fn, - typename ... Arg -> -continuation -callcc( std::allocator_arg_t, preallocated, segmented_stack, Fn &&, Arg ...); -#endif - -// swap -inline -void swap( continuation & l, continuation & r) noexcept { - l.swap( r); -} - -}} - -#if defined(BOOST_MSVC) -# pragma warning(pop) -#endif - -#ifdef BOOST_HAS_ABI_HEADERS -# include BOOST_ABI_SUFFIX -#endif - -#endif // BOOST_CONTEXT_CONTINUATION_H diff --git a/include/boost/context/continuation_fcontext.hpp b/include/boost/context/continuation_fcontext.hpp new file mode 100644 index 0000000..9ad02f1 --- /dev/null +++ b/include/boost/context/continuation_fcontext.hpp @@ -0,0 +1,556 @@ + +// Copyright Oliver Kowalke 2017. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_CONTEXT_CONTINUATION_H +#define BOOST_CONTEXT_CONTINUATION_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#if defined(BOOST_NO_CXX17_STD_APPLY) +#include +#endif +#if defined(BOOST_NO_CXX14_STD_EXCHANGE) +#include +#endif +#if defined(BOOST_NO_CXX17_STD_INVOKE) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +#if defined(BOOST_MSVC) +# pragma warning(push) +# pragma warning(disable: 4702) +#endif + +namespace boost { +namespace context { +namespace detail { + +template< typename U > +struct helper { + template< typename T > + static T convert( T && t) noexcept { + return std::forward< T >( t); + } +}; + +template< typename U > +struct helper< std::tuple< U > > { + template< typename T > + static std::tuple< T > convert( T && t) noexcept { + return std::make_tuple( std::forward< T >( t) ); + } +}; + +inline +transfer_t context_unwind( transfer_t t) { + throw forced_unwind( t.fctx); + return { nullptr, nullptr }; +} + +template< typename Rec > +transfer_t context_exit( transfer_t t) noexcept { + Rec * rec = static_cast< Rec * >( t.data); + // destroy context stack + rec->deallocate(); + return { nullptr, nullptr }; +} + +template< typename Rec > +void context_entry( transfer_t t_) noexcept { + // transfer control structure to the context-stack + Rec * rec = static_cast< Rec * >( t_.data); + BOOST_ASSERT( nullptr != t_.fctx); + BOOST_ASSERT( nullptr != rec); + transfer_t t = { nullptr, nullptr }; + try { + // jump back to `context_create()` + t = jump_fcontext( t_.fctx, nullptr); + // start executing + t = rec->run( t); + } catch ( forced_unwind const& e) { + t = { e.fctx, nullptr }; + } + BOOST_ASSERT( nullptr != t.fctx); + // destroy context-stack of `this`context on next context + ontop_fcontext( t.fctx, rec, context_exit< Rec >); + BOOST_ASSERT_MSG( false, "context already terminated"); +} + +template< + typename Ctx, + typename StackAlloc, + typename Fn +> +class record { +private: + StackAlloc salloc_; + stack_context sctx_; + typename std::decay< Fn >::type fn_; + + static void destroy( record * p) noexcept { + StackAlloc salloc = p->salloc_; + stack_context sctx = p->sctx_; + // deallocate record + p->~record(); + // destroy stack with stack allocator + salloc.deallocate( sctx); + } + +public: + record( stack_context sctx, StackAlloc const& salloc, + Fn && fn) noexcept : + salloc_( salloc), + sctx_( sctx), + fn_( std::forward< Fn >( fn) ) { + } + + record( record const&) = delete; + record & operator=( record const&) = delete; + + void deallocate() noexcept { + destroy( this); + } + + transfer_t run( transfer_t t) { + Ctx from{ t }; + // invoke context-function +#if defined(BOOST_NO_CXX17_STD_INVOKE) + Ctx cc = invoke( fn_, std::move( from) ); +#else + Ctx cc = std::invoke( fn_, std::move( from) ); +#endif +#if defined(BOOST_NO_CXX14_STD_EXCHANGE) + return { exchange( cc.t_.fctx, nullptr), nullptr }; +#else + return { std::exchange( cc.t_.fctx, nullptr), nullptr }; +#endif + } +}; + +template< typename Record, typename StackAlloc, typename Fn > +fcontext_t context_create( StackAlloc salloc, Fn && fn) { + auto sctx = salloc.allocate(); + // reserve space for control structure +#if defined(BOOST_NO_CXX11_CONSTEXPR) || defined(BOOST_NO_CXX11_STD_ALIGN) + const std::size_t size = sctx.size - sizeof( Record); + void * sp = static_cast< char * >( sctx.sp) - sizeof( Record); +#else + constexpr std::size_t func_alignment = 64; // alignof( Record); + constexpr std::size_t func_size = sizeof( Record); + // reserve space on stack + void * sp = static_cast< char * >( sctx.sp) - func_size - func_alignment; + // align sp pointer + std::size_t space = func_size + func_alignment; + sp = std::align( func_alignment, func_size, sp, space); + BOOST_ASSERT( nullptr != sp); + // calculate remaining size + const std::size_t size = sctx.size - ( static_cast< char * >( sctx.sp) - static_cast< char * >( sp) ); +#endif + // create fast-context + const fcontext_t fctx = make_fcontext( sp, size, & context_entry< Record >); + BOOST_ASSERT( nullptr != fctx); + // placment new for control structure on context-stack + auto rec = ::new ( sp) Record{ + sctx, salloc, std::forward< Fn >( fn) }; + // transfer control structure to context-stack + return jump_fcontext( fctx, rec).fctx; +} + +template< typename Record, typename StackAlloc, typename Fn > +fcontext_t context_create( preallocated palloc, StackAlloc salloc, Fn && fn) { + // reserve space for control structure +#if defined(BOOST_NO_CXX11_CONSTEXPR) || defined(BOOST_NO_CXX11_STD_ALIGN) + const std::size_t size = palloc.size - sizeof( Record); + void * sp = static_cast< char * >( palloc.sp) - sizeof( Record); +#else + constexpr std::size_t func_alignment = 64; // alignof( Record); + constexpr std::size_t func_size = sizeof( Record); + // reserve space on stack + void * sp = static_cast< char * >( palloc.sp) - func_size - func_alignment; + // align sp pointer + std::size_t space = func_size + func_alignment; + sp = std::align( func_alignment, func_size, sp, space); + BOOST_ASSERT( nullptr != sp); + // calculate remaining size + const std::size_t size = palloc.size - ( static_cast< char * >( palloc.sp) - static_cast< char * >( sp) ); +#endif + // create fast-context + const fcontext_t fctx = make_fcontext( sp, size, & context_entry< Record >); + BOOST_ASSERT( nullptr != fctx); + // placment new for control structure on context-stack + auto rec = ::new ( sp) Record{ + palloc.sctx, salloc, std::forward< Fn >( fn) }; + // transfer control structure to context-stack + return jump_fcontext( fctx, rec).fctx; +} + +template< typename ... Arg > +struct result_type { + typedef std::tuple< Arg ... > type; + + static + type get( detail::transfer_t & t) { + auto p = static_cast< std::tuple< Arg ... > * >( t.data); + return std::move( * p); + } +}; + +template< typename Arg > +struct result_type< Arg > { + typedef Arg type; + + static + type get( detail::transfer_t & t) { + auto p = static_cast< std::tuple< Arg > * >( t.data); + return std::forward< Arg >( std::get< 0 >( * p) ); + } +}; + +} + +template< typename Ctx, typename Fn, typename Arg > +detail::transfer_t context_ontop( detail::transfer_t t) { + auto p = static_cast< Arg * >( t.data); + BOOST_ASSERT( nullptr != p); + typename std::decay< Fn >::type fn = std::forward< Fn >( std::get< 0 >( * p) ); + t.data = & std::get< 1 >( * p); + Ctx c{ t }; + // execute function, pass continuation via reference + typedef typename std::decay< decltype( std::get< 1 >( * p) )>::type Tpl; + std::get< 1 >( * p) = detail::helper< Tpl >::convert( fn( std::move( c) ) ); +#if defined(BOOST_NO_CXX14_STD_EXCHANGE) + return { detail::exchange( c.t_.fctx, nullptr), & std::get< 1 >( * p) }; +#else + return { std::exchange( c.t_.fctx, nullptr), & std::get< 1 >( * p) }; +#endif +} + +template< typename Ctx, typename Fn > +detail::transfer_t context_ontop_void( detail::transfer_t t) { + auto p = static_cast< std::tuple< Fn > * >( t.data); + BOOST_ASSERT( nullptr != p); + typename std::decay< Fn >::type fn = std::forward< Fn >( std::get< 0 >( * p) ); + Ctx c{ t }; + // execute function, pass continuation via reference + fn( std::move( c) ); +#if defined(BOOST_NO_CXX14_STD_EXCHANGE) + return { detail::exchange( c.t_.fctx, nullptr), nullptr }; +#else + return { std::exchange( c.t_.fctx, nullptr), nullptr }; +#endif +} + +class continuation { +private: + template< typename Ctx, typename StackAlloc, typename Fn > + friend class detail::record; + + template< typename Ctx, typename Fn, typename Arg > + friend detail::transfer_t + context_ontop( detail::transfer_t); + + template< typename Ctx, typename Fn > + friend detail::transfer_t + context_ontop_void( detail::transfer_t); + + template< typename StackAlloc, typename Fn, typename ... Arg > + friend continuation + callcc( std::allocator_arg_t, StackAlloc, Fn &&, Arg ...); + + template< typename StackAlloc, typename Fn, typename ... Arg > + friend continuation + callcc( std::allocator_arg_t, preallocated, StackAlloc, Fn &&, Arg ...); + + template< typename StackAlloc, typename Fn > + friend continuation + callcc( std::allocator_arg_t, StackAlloc, Fn &&); + + template< typename StackAlloc, typename Fn > + friend continuation + callcc( std::allocator_arg_t, preallocated, StackAlloc, Fn &&); + + detail::transfer_t t_{ nullptr, nullptr }; + + continuation( detail::fcontext_t fctx) noexcept : + t_{ fctx, nullptr } { + } + + continuation( detail::transfer_t t) noexcept : + t_{ t.fctx, t.data } { + } + +public: + continuation() noexcept = default; + + ~continuation() { + if ( nullptr != t_.fctx) { +#if defined(BOOST_NO_CXX14_STD_EXCHANGE) + detail::ontop_fcontext( detail::exchange( t_.fctx, nullptr), nullptr, detail::context_unwind); +#else + detail::ontop_fcontext( std::exchange( t_.fctx, nullptr), nullptr, detail::context_unwind); +#endif + } + } + + continuation( continuation && other) noexcept : + t_{ other.t_.fctx, other.t_.data } { + other.t_ = { nullptr, nullptr }; + } + + continuation & operator=( continuation && other) noexcept { + if ( this != & other) { + continuation tmp = std::move( other); + swap( tmp); + } + return * this; + } + + continuation( continuation const& other) noexcept = delete; + continuation & operator=( continuation const& other) noexcept = delete; + + template< typename ... Arg > + continuation resume( Arg ... arg) { + BOOST_ASSERT( nullptr != t_.fctx); + auto tpl = std::make_tuple( std::forward< Arg >( arg) ... ); + return detail::jump_fcontext( +#if defined(BOOST_NO_CXX14_STD_EXCHANGE) + detail::exchange( t_.fctx, nullptr), +#else + std::exchange( t_.fctx, nullptr), +#endif + & tpl); + } + + template< typename Fn, typename ... Arg > + continuation resume_with( Fn && fn, Arg ... arg) { + BOOST_ASSERT( nullptr != t_.fctx); + auto tpl = std::make_tuple( std::forward< Fn >( fn), std::make_tuple( std::forward< Arg >( arg) ... )); + return detail::ontop_fcontext( +#if defined(BOOST_NO_CXX14_STD_EXCHANGE) + detail::exchange( t_.fctx, nullptr), +#else + std::exchange( t_.fctx, nullptr), +#endif + & tpl, + context_ontop< continuation, Fn, decltype(tpl) >); + } + + continuation resume() { + BOOST_ASSERT( nullptr != t_.fctx); + return detail::jump_fcontext( +#if defined(BOOST_NO_CXX14_STD_EXCHANGE) + detail::exchange( t_.fctx, nullptr), +#else + std::exchange( t_.fctx, nullptr), +#endif + nullptr); + } + + template< typename Fn > + continuation resume_with( Fn && fn) { + BOOST_ASSERT( nullptr != t_.fctx); + auto p = std::make_tuple( std::forward< Fn >( fn) ); + return detail::ontop_fcontext( +#if defined(BOOST_NO_CXX14_STD_EXCHANGE) + detail::exchange( t_.fctx, nullptr), +#else + std::exchange( t_.fctx, nullptr), +#endif + & p, + context_ontop_void< continuation, Fn >); + } + + bool data_available() noexcept { + return * this && nullptr != t_.data; + } + + template< typename ... Arg > + typename detail::result_type< Arg ... >::type get_data() { + BOOST_ASSERT( nullptr != t_.data); + return detail::result_type< Arg ... >::get( t_); + } + + explicit operator bool() const noexcept { + return nullptr != t_.fctx; + } + + bool operator!() const noexcept { + return nullptr == t_.fctx; + } + + bool operator==( continuation const& other) const noexcept { + return t_.fctx == other.t_.fctx; + } + + bool operator!=( continuation const& other) const noexcept { + return t_.fctx != other.t_.fctx; + } + + bool operator<( continuation const& other) const noexcept { + return t_.fctx < other.t_.fctx; + } + + bool operator>( continuation const& other) const noexcept { + return other.t_.fctx < t_.fctx; + } + + bool operator<=( continuation const& other) const noexcept { + return ! ( * this > other); + } + + bool operator>=( continuation const& other) const noexcept { + return ! ( * this < other); + } + + template< typename charT, class traitsT > + friend std::basic_ostream< charT, traitsT > & + operator<<( std::basic_ostream< charT, traitsT > & os, continuation const& other) { + if ( nullptr != other.t_.fctx) { + return os << other.t_.fctx; + } else { + return os << "{not-a-context}"; + } + } + + void swap( continuation & other) noexcept { + std::swap( t_, other.t_); + } +}; + +// Arg +template< + typename Fn, + typename ... Arg, + typename = detail::disable_overload< continuation, Fn >, + typename = detail::disable_overload< std::allocator_arg_t, Fn > +> +continuation +callcc( Fn && fn, Arg ... arg) { + return callcc( + std::allocator_arg, fixedsize_stack(), + std::forward< Fn >( fn), std::forward< Arg >( arg) ...); +} + +template< + typename StackAlloc, + typename Fn, + typename ... Arg +> +continuation +callcc( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Arg ... arg) { + using Record = detail::record< continuation, StackAlloc, Fn >; + return continuation{ + detail::context_create< Record >( + salloc, std::forward< Fn >( fn) ) }.resume( + std::forward< Arg >( arg) ... ); +} + +template< + typename StackAlloc, + typename Fn, + typename ... Arg +> +continuation +callcc( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Arg ... arg) { + using Record = detail::record< continuation, StackAlloc, Fn >; + return continuation{ + detail::context_create< Record >( + palloc, salloc, std::forward< Fn >( fn) ) }.resume( + std::forward< Arg >( arg) ... ); +} + +// void +template< + typename Fn, + typename = detail::disable_overload< continuation, Fn > +> +continuation +callcc( Fn && fn) { + return callcc( + std::allocator_arg, fixedsize_stack(), + std::forward< Fn >( fn) ); +} + +template< typename StackAlloc, typename Fn > +continuation +callcc( std::allocator_arg_t, StackAlloc salloc, Fn && fn) { + using Record = detail::record< continuation, StackAlloc, Fn >; + return continuation{ + detail::context_create< Record >( + salloc, std::forward< Fn >( fn) ) }.resume(); +} + +template< typename StackAlloc, typename Fn > +continuation +callcc( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn) { + using Record = detail::record< continuation, StackAlloc, Fn >; + return continuation{ + detail::context_create< Record >( + palloc, salloc, std::forward< Fn >( fn) ) }.resume(); +} + +#if defined(BOOST_USE_SEGMENTED_STACKS) +template< + typename Fn, + typename ... Arg +> +continuation +callcc( std::allocator_arg_t, segmented_stack, Fn &&, Arg ...); + +template< + typename StackAlloc, + typename Fn, + typename ... Arg +> +continuation +callcc( std::allocator_arg_t, preallocated, segmented_stack, Fn &&, Arg ...); +#endif + +// swap +inline +void swap( continuation & l, continuation & r) noexcept { + l.swap( r); +} + +}} + +#if defined(BOOST_MSVC) +# pragma warning(pop) +#endif + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_CONTEXT_CONTINUATION_H diff --git a/include/boost/context/continuation_ucontext.hpp b/include/boost/context/continuation_ucontext.hpp new file mode 100644 index 0000000..ac3d844 --- /dev/null +++ b/include/boost/context/continuation_ucontext.hpp @@ -0,0 +1,696 @@ + +// Copyright Oliver Kowalke 2017. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_CONTEXT_CONTINUATION_H +#define BOOST_CONTEXT_CONTINUATION_H + +extern "C" { +#include +} + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if defined(BOOST_NO_CXX17_STD_APPLY) +#include +#endif +#include +#if defined(BOOST_NO_CXX14_STD_EXCHANGE) +#include +#endif +#include +#include +#include +#if defined(BOOST_USE_SEGMENTED_STACKS) +#include +#endif +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +#if defined(BOOST_USE_ASAN) +extern "C" { +void __sanitizer_start_switch_fiber( void **, const void *, size_t); +void __sanitizer_finish_switch_fiber( void *, const void **, size_t *); +} +#endif + +#if defined(BOOST_USE_SEGMENTED_STACKS) +extern "C" { +void __splitstack_getcontext( void * [BOOST_CONTEXT_SEGMENTS]); +void __splitstack_setcontext( void * [BOOST_CONTEXT_SEGMENTS]); +} +#endif + +namespace boost { +namespace context { +namespace detail { + +template< typename U > +struct helper { + template< typename T > + static T convert( T && t) noexcept { + return std::forward< T >( t); + } +}; + +template< typename U > +struct helper< std::tuple< U > > { + template< typename T > + static std::tuple< T > convert( T && t) noexcept { + return std::make_tuple( std::forward< T >( t) ); + } +}; + +// tampoline function +// entered if the execution context +// is resumed for the first time +template< typename Record > +static void entry_func( void * data) noexcept { + Record * record = static_cast< Record * >( data); + BOOST_ASSERT( nullptr != record); + // start execution of toplevel context-function + record->run(); +} + +struct BOOST_CONTEXT_DECL activation_record { + thread_local static activation_record * current_rec; + + ucontext_t uctx{}; + stack_context sctx{}; + bool main_ctx{ true }; + void * data{ nullptr }; + activation_record * from{ nullptr }; + std::function< void() > ontop{}; + bool terminated{ false }; + bool force_unwind{ false }; +#if defined(BOOST_USE_ASAN) + void * fake_stack{ nullptr }; + void * stack_bottom{ nullptr }; + std::size_t stack_size{ 0 }; + bool started{ false }; +#endif + + static activation_record *& current() noexcept; + + // used for toplevel-context + // (e.g. main context, thread-entry context) + activation_record() { + if ( 0 != ::getcontext( & uctx) ) { + throw std::system_error( + std::error_code( errno, std::system_category() ), + "getcontext() failed"); + } +#if defined(BOOST_USE_ASAN) + stack_bottom = uctx.uc_stack.ss_sp; + stack_size = uctx.uc_stack.ss_size; +#endif + } + + activation_record( stack_context sctx_) noexcept : + sctx{ sctx_ }, + main_ctx{ false } { + } + + virtual ~activation_record() { + } + + activation_record( activation_record const&) = delete; + activation_record & operator=( activation_record const&) = delete; + + bool is_main_context() const noexcept { + return main_ctx; + } + + detail::activation_record * resume( void * vp) { + data = vp; + from = current(); + from->data = nullptr; + // store `this` in static, thread local pointer + // `this` will become the active (running) context + current() = this; +#if defined(BOOST_USE_SEGMENTED_STACKS) + // adjust segmented stack properties + __splitstack_getcontext( from->sctx.segments_ctx); + __splitstack_setcontext( sctx.segments_ctx); +#endif +#if defined(BOOST_USE_ASAN) + if ( from->started) { + __sanitizer_finish_switch_fiber( from->fake_stack, (const void **) & from->stack_bottom, + & from->stack_size); + from->started = false; + } + __sanitizer_start_switch_fiber( & fake_stack, stack_bottom, stack_size); + started = true; +#endif + // context switch from parent context to `this`-context + ::swapcontext( & from->uctx, & uctx); +#if defined(BOOST_NO_CXX14_STD_EXCHANGE) + return detal::exchange( current()->from, nullptr); +#else + return std::exchange( current()->from, nullptr); +#endif + } + + template< typename Ctx, typename Fn, typename Tpl > + detail::activation_record * resume_with( Fn && fn, Tpl * tpl) { + data = nullptr; + from = current(); + from->data = nullptr; + // store `this` in static, thread local pointer + // `this` will become the active (running) context + // returned by continuation::current() + current() = this; +#if defined(BOOST_NO_CXX14_GENERIC_LAMBDAS) + auto from_ = current()->from; + current()->ontop = std::bind( + [tpl,from_](typename std::decay< Fn >::type & fn){ + current()->data = tpl; + * tpl = helper< Tpl >::convert( fn( Ctx{ from_ } ) ); + }, + std::forward< Fn >( fn) ); +#else + current()->ontop = [fn=std::forward(fn),tpl,from=current()->from]() { + current()->data = tpl; + * tpl = helper< Tpl >::convert( fn( Ctx{ from } ) ); + }; +#endif +#if defined(BOOST_USE_SEGMENTED_STACKS) + // adjust segmented stack properties + __splitstack_getcontext( from->sctx.segments_ctx); + __splitstack_setcontext( sctx.segments_ctx); +#endif +#if defined(BOOST_USE_ASAN) + if ( from->started) { + __sanitizer_finish_switch_fiber( from->fake_stack, (const void **) & from->stack_bottom, + & from->stack_size); + from->started = false; + } + __sanitizer_start_switch_fiber( & fake_stack, stack_bottom, stack_size); + started = true; +#endif + // context switch from parent context to `this`-context + ::swapcontext( & from->uctx, & uctx); +#if defined(BOOST_NO_CXX14_STD_EXCHANGE) + return detail::exchange( current()->from, nullptr); +#else + return std::exchange( current()->from, nullptr); +#endif + } + + template< typename Ctx, typename Fn > + detail::activation_record * resume_with( Fn && fn) { + data = nullptr; + from = current(); + from->data = nullptr; + // store `this` in static, thread local pointer + // `this` will become the active (running) context + // returned by continuation::current() + current() = this; +#if defined(BOOST_NO_CXX14_GENERIC_LAMBDAS) + auto from_ = current()->from; + current()->ontop = std::bind( + [from_](typename std::decay< Fn >::type & fn){ + fn( Ctx{ from_ } ); + current()->data = nullptr; + }, + std::forward< Fn >( fn) ); +#else + current()->ontop = [fn=std::forward(fn),from=current()->from]() { + fn( Ctx{ from } ); + current()->data = nullptr; + }; +#endif +#if defined(BOOST_USE_SEGMENTED_STACKS) + // adjust segmented stack properties + __splitstack_getcontext( from->sctx.segments_ctx); + __splitstack_setcontext( sctx.segments_ctx); +#endif +#if defined(BOOST_USE_ASAN) + if ( from->started) { + __sanitizer_finish_switch_fiber( from->fake_stack, (const void **) & from->stack_bottom, + & from->stack_size); + from->started = false; + } + __sanitizer_start_switch_fiber( & fake_stack, stack_bottom, stack_size); + started = true; +#endif + // context switch from parent context to `this`-context + ::swapcontext( & from->uctx, & uctx); +#if defined(BOOST_NO_CXX14_STD_EXCHANGE) + return detail::exchange( current()->from, nullptr); +#else + return std::exchange( current()->from, nullptr); +#endif + } + + virtual void deallocate() noexcept { + } +}; + +struct BOOST_CONTEXT_DECL activation_record_initializer { + activation_record_initializer() noexcept; + ~activation_record_initializer(); +}; + +struct forced_unwind { + activation_record * from{ nullptr }; + + forced_unwind( activation_record * from_) noexcept : + from{ from_ } { + } +}; + +template< typename Ctx, typename StackAlloc, typename Fn, typename ... Arg > +class capture_record : public activation_record { +private: + typename std::decay< Fn >::type fn_; + std::tuple< Arg ... > arg_; + StackAlloc salloc_; + + static void destroy( capture_record * p) noexcept { + StackAlloc salloc = p->salloc_; + stack_context sctx = p->sctx; + // deallocate activation record + p->~capture_record(); + // destroy stack with stack allocator + salloc.deallocate( sctx); + } + +public: + capture_record( stack_context sctx, StackAlloc const& salloc, + Fn && fn, Arg ... arg) noexcept : + activation_record{ sctx }, + fn_( std::forward< Fn >( fn) ), + arg_( std::forward< Arg >( arg) ... ), + salloc_{ salloc } { + } + + void deallocate() noexcept override final { + BOOST_ASSERT( main_ctx || ( ! main_ctx && terminated) ); + destroy( this); + } + + void run() { + Ctx c{ from }; + auto tpl = std::tuple_cat( + std::forward_as_tuple( std::move( c) ), + std::move( arg_) ); + try { + // invoke context-function +#if defined(BOOST_NO_CXX17_STD_APPLY) + c = apply( std::move( fn_), std::move( tpl) ); +#else + c = std::apply( std::move( fn_), std::move( tpl) ); +#endif + } catch ( forced_unwind const& ex) { + c = Ctx{ ex.from }; + } + // this context has finished its task + data = nullptr; + from = nullptr; + ontop = nullptr; + terminated = true; + force_unwind = false; + c.resume(); + BOOST_ASSERT_MSG( false, "continuation already terminated"); + } +}; + +template< typename Ctx, typename StackAlloc, typename Fn, typename ... Arg > +static activation_record * create_context( StackAlloc salloc, Fn && fn, Arg ... arg) { + typedef capture_record< Ctx, StackAlloc, Fn, Arg ... > capture_t; + + auto sctx = salloc.allocate(); + // reserve space for control structure + void * storage = static_cast< char * >( sctx.sp) - sizeof( capture_t); + // placment new for control structure on fast-context stack + capture_t * record = new ( storage) capture_t{ + sctx, salloc, std::forward< Fn >( fn), std::forward< Arg >( arg) ... }; + // create user-context + if ( 0 != ::getcontext( & record->uctx) ) { + throw std::system_error( + std::error_code( errno, std::system_category() ), + "getcontext() failed"); + } + record->uctx.uc_stack.ss_size = sctx.size - sizeof(capture_t) - 64; + record->uctx.uc_stack.ss_sp = static_cast< char * >( sctx.sp) - sctx.size; + record->uctx.uc_link = nullptr; + ::makecontext( & record->uctx, ( void (*)() ) & detail::entry_func< capture_t >, 1, record); +#if defined(BOOST_USE_ASAN) + record->stack_bottom = record->uctx.uc_stack.ss_sp; + record->stack_size = record->uctx.uc_stack.ss_size; +#endif + return record; +} + +template< typename Ctx, typename StackAlloc, typename Fn, typename ... Arg > +static activation_record * create_context( preallocated palloc, StackAlloc salloc, + Fn && fn, Arg ... arg) { + typedef capture_record< Ctx, StackAlloc, Fn, Arg ... > capture_t; + + // reserve space for control structure + void * storage = static_cast< char * >( palloc.sp) - sizeof( capture_t); + // placment new for control structure on fast-context stack + capture_t * record = new ( storage) capture_t{ + palloc.sctx, salloc, std::forward< Fn >( fn), std::forward< Arg >( arg) ... }; + // create user-context + if ( 0 != ::getcontext( & record->uctx) ) { + throw std::system_error( + std::error_code( errno, std::system_category() ), + "getcontext() failed"); + } + record->uctx.uc_stack.ss_size = palloc.size - sizeof(capture_t) - 64; + record->uctx.uc_stack.ss_sp = static_cast< char * >( palloc.sctx.sp) - palloc.sctx.size; + record->uctx.uc_link = nullptr; + ::makecontext( & record->uctx, ( void (*)() ) & detail::entry_func< capture_t >, 1, record); +#if defined(BOOST_USE_ASAN) + record->stack_bottom = record->uctx.uc_stack.ss_sp; + record->stack_size = record->uctx.uc_stack.ss_size; +#endif + return record; +} + +template< typename ... Arg > +struct result_type { + typedef std::tuple< Arg ... > type; + + static + type get( void * data) { + auto p = static_cast< std::tuple< Arg ... > * >( data); + return std::move( * p); + } +}; + +template< typename Arg > +struct result_type< Arg > { + typedef Arg type; + + static + type get( void * data) { + auto p = static_cast< std::tuple< Arg > * >( data); + return std::forward< Arg >( std::get< 0 >( * p) ); + } +}; + +} + +class BOOST_CONTEXT_DECL continuation { +private: + friend struct detail::activation_record; + + template< typename Ctx, typename StackAlloc, typename Fn, typename ... Arg > + friend class detail::capture_record; + + template< typename Ctx, typename StackAlloc, typename Fn, typename ... Arg > + friend detail::activation_record * detail::create_context( StackAlloc, Fn &&, Arg ...); + + template< typename Ctx, typename StackAlloc, typename Fn, typename ... Arg > + friend detail::activation_record * detail::create_context( preallocated, StackAlloc, Fn &&, Arg ...); + + template< typename StackAlloc, typename Fn, typename ... Arg > + friend continuation + callcc( std::allocator_arg_t, StackAlloc, Fn &&, Arg ...); + + template< typename StackAlloc, typename Fn, typename ... Arg > + friend continuation + callcc( std::allocator_arg_t, preallocated, StackAlloc, Fn &&, Arg ...); + + template< typename StackAlloc, typename Fn > + friend continuation + callcc( std::allocator_arg_t, StackAlloc, Fn &&); + + template< typename StackAlloc, typename Fn > + friend continuation + callcc( std::allocator_arg_t, preallocated, StackAlloc, Fn &&); + + detail::activation_record * ptr_{ nullptr }; + + continuation( detail::activation_record * ptr) noexcept : + ptr_{ ptr } { + } + +public: + continuation() = default; + + ~continuation() { + if ( nullptr != ptr_ && ! ptr_->main_ctx) { + if ( ! ptr_->terminated) { + ptr_->force_unwind = true; + ptr_->resume( nullptr); + BOOST_ASSERT( ptr_->terminated); + } + ptr_->deallocate(); + } + } + + continuation( continuation const&) = delete; + continuation & operator=( continuation const&) = delete; + + continuation( continuation && other) noexcept : + ptr_{ nullptr } { + swap( other); + } + + continuation & operator=( continuation && other) noexcept { + if ( this == & other) return * this; + continuation tmp{ std::move( other) }; + swap( tmp); + return * this; + } + + template< typename ... Arg > + continuation resume( Arg ... arg) { + auto tpl = std::make_tuple( std::forward< Arg >( arg) ... ); +#if defined(BOOST_NO_CXX14_STD_EXCHANGE) + detail::activation_record * ptr = detail::exchange( ptr_, nullptr)->resume( & tpl); +#else + detail::activation_record * ptr = std::exchange( ptr_, nullptr)->resume( & tpl); +#endif + if ( detail::activation_record::current()->force_unwind) { + throw detail::forced_unwind{ ptr}; + } else if ( detail::activation_record::current()->ontop) { + detail::activation_record::current()->ontop(); + detail::activation_record::current()->ontop = nullptr; + } + return continuation{ ptr }; + } + + template< typename Fn, typename ... Arg > + continuation resume_with( Fn && fn, Arg ... arg) { + auto tpl = std::make_tuple( std::forward< Arg >( arg) ... ); +#if defined(BOOST_NO_CXX14_STD_EXCHANGE) + detail::activation_record * ptr = + detail::exchange( ptr_, nullptr)->resume_with< continuation >( std::forward< Fn >( fn), & tpl); +#else + detail::activation_record * ptr = + std::exchange( ptr_, nullptr)->resume_with< continuation >( std::forward< Fn >( fn), & tpl); +#endif + if ( detail::activation_record::current()->force_unwind) { + throw detail::forced_unwind{ ptr }; + } else if ( detail::activation_record::current()->ontop) { + detail::activation_record::current()->ontop(); + detail::activation_record::current()->ontop = nullptr; + } + return continuation{ ptr }; + } + + continuation resume() { +#if defined(BOOST_NO_CXX14_STD_EXCHANGE) + detail::activation_record * ptr = detail::exchange( ptr_, nullptr)->resume( nullptr); +#else + detail::activation_record * ptr = std::exchange( ptr_, nullptr)->resume( nullptr); +#endif + if ( detail::activation_record::current()->force_unwind) { + throw detail::forced_unwind{ ptr }; + } else if ( detail::activation_record::current()->ontop) { + detail::activation_record::current()->ontop(); + detail::activation_record::current()->ontop = nullptr; + } + return continuation{ ptr }; + } + + template< typename Fn > + continuation resume_with( Fn && fn) { +#if defined(BOOST_NO_CXX14_STD_EXCHANGE) + detail::activation_record * ptr = + detail::exchange( ptr_, nullptr)->resume_with< continuation >( std::forward< Fn >( fn) ); +#else + detail::activation_record * ptr = + std::exchange( ptr_, nullptr)->resume_with< continuation >( std::forward< Fn >( fn) ); +#endif + if ( detail::activation_record::current()->force_unwind) { + throw detail::forced_unwind{ ptr }; + } else if ( detail::activation_record::current()->ontop) { + detail::activation_record::current()->ontop(); + detail::activation_record::current()->ontop = nullptr; + } + return continuation{ ptr }; + } + + bool data_available() noexcept { + return * this && nullptr != detail::activation_record::current()->data; + } + + template< typename ... Arg > + typename detail::result_type< Arg ... >::type get_data() { + BOOST_ASSERT( data_available() );; + return detail::result_type< Arg ... >::get( detail::activation_record::current()->data); + } + + explicit operator bool() const noexcept { + return nullptr != ptr_ && ! ptr_->terminated; + } + + bool operator!() const noexcept { + return nullptr == ptr_ || ptr_->terminated; + } + + bool operator==( continuation const& other) const noexcept { + return ptr_ == other.ptr_; + } + + bool operator!=( continuation const& other) const noexcept { + return ptr_ != other.ptr_; + } + + bool operator<( continuation const& other) const noexcept { + return ptr_ < other.ptr_; + } + + bool operator>( continuation const& other) const noexcept { + return other.ptr_ < ptr_; + } + + bool operator<=( continuation const& other) const noexcept { + return ! ( * this > other); + } + + bool operator>=( continuation const& other) const noexcept { + return ! ( * this < other); + } + + template< typename charT, class traitsT > + friend std::basic_ostream< charT, traitsT > & + operator<<( std::basic_ostream< charT, traitsT > & os, continuation const& other) { + if ( nullptr != other.ptr_) { + return os << other.ptr_; + } else { + return os << "{not-a-context}"; + } + } + + void swap( continuation & other) noexcept { + std::swap( ptr_, other.ptr_); + } +}; + +// Arg +template< + typename Fn, + typename ... Arg, + typename = detail::disable_overload< continuation, Fn >, + typename = detail::disable_overload< std::allocator_arg_t, Fn > +> +continuation +callcc( Fn && fn, Arg ... arg) { + return callcc( + std::allocator_arg, +#if defined(BOOST_USE_SEGMENTED_STACKS) + segmented_stack(), +#else + fixedsize_stack(), +#endif + std::forward< Fn >( fn), std::forward< Arg >( arg) ...); +} + +template< + typename StackAlloc, + typename Fn, + typename ... Arg +> +continuation +callcc( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Arg ... arg) { + return continuation{ + detail::create_context< continuation >( + salloc, std::forward< Fn >( fn) ) }.resume( + std::forward< Arg >( arg) ... ); +} + +template< + typename StackAlloc, + typename Fn, + typename ... Arg +> +continuation +callcc( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Arg ... arg) { + return continuation{ + detail::create_context< continuation >( + palloc, salloc, std::forward< Fn >( fn) ) }.resume( + std::forward< Arg >( arg) ... ); +} + +// void +template< + typename Fn, + typename = detail::disable_overload< continuation, Fn > +> +continuation +callcc( Fn && fn) { + return callcc( + std::allocator_arg, +#if defined(BOOST_USE_SEGMENTED_STACKS) + segmented_stack(), +#else + fixedsize_stack(), +#endif + std::forward< Fn >( fn) ); +} + +template< typename StackAlloc, typename Fn > +continuation +callcc( std::allocator_arg_t, StackAlloc salloc, Fn && fn) { + return continuation{ + detail::create_context< continuation >( + salloc, std::forward< Fn >( fn) ) }.resume(); +} + +template< typename StackAlloc, typename Fn > +continuation +callcc( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn) { + return continuation{ + detail::create_context< continuation >( + palloc, salloc, std::forward< Fn >( fn) ) }.resume(); +} + +inline +void swap( continuation & l, continuation & r) noexcept { + l.swap( r); +} + +}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_CONTEXT_CONTINUATION_H diff --git a/include/boost/context/detail/exception.hpp b/include/boost/context/detail/exception.hpp index 8ffff67..14b4ab5 100644 --- a/include/boost/context/detail/exception.hpp +++ b/include/boost/context/detail/exception.hpp @@ -20,7 +20,9 @@ namespace context { namespace detail { struct forced_unwind { - fcontext_t fctx; + fcontext_t fctx{ nullptr }; + + forced_unwind() = default; forced_unwind( fcontext_t fctx_) : fctx( fctx_) { diff --git a/include/boost/context/pooled_fixedsize_stack.hpp b/include/boost/context/pooled_fixedsize_stack.hpp index 3c0953c..9c417fd 100644 --- a/include/boost/context/pooled_fixedsize_stack.hpp +++ b/include/boost/context/pooled_fixedsize_stack.hpp @@ -14,6 +14,7 @@ #include #include +#include #include #include diff --git a/performance/callcc/Jamfile.v2 b/performance/callcc/Jamfile.v2 index ae2f564..b389487 100644 --- a/performance/callcc/Jamfile.v2 +++ b/performance/callcc/Jamfile.v2 @@ -26,7 +26,6 @@ project boost/context/performance/fcontext speed multi release - -DBOOST_DISABLE_ASSERTS ; alias sources diff --git a/src/execution_context.cpp b/src/execution_context.cpp index a4146a4..5e58d91 100644 --- a/src/execution_context.cpp +++ b/src/execution_context.cpp @@ -4,7 +4,11 @@ // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) +#if defined(BOOST_USE_UCONTEXT) +#include "boost/context/continuation_ucontext.hpp" +#else #include "boost/context/execution_context.hpp" +#endif #include @@ -12,14 +16,17 @@ # include BOOST_ABI_PREFIX #endif -#if ! defined(BOOST_CONTEXT_NO_CXX11) -# if (defined(BOOST_EXECUTION_CONTEXT) && (BOOST_EXECUTION_CONTEXT == 1)) +#if defined(BOOST_USE_UCONTEXT) || (defined(BOOST_EXECUTION_CONTEXT) && (BOOST_EXECUTION_CONTEXT == 1)) namespace boost { namespace context { namespace detail { thread_local +#if defined(BOOST_USE_UCONTEXT) +activation_record * +#elif defined(BOOST_EXECUTION_CONTEXT) && (BOOST_EXECUTION_CONTEXT == 1) activation_record::ptr_t +#endif activation_record::current_rec; // zero-initialization @@ -28,30 +35,50 @@ thread_local static std::size_t counter; // schwarz counter activation_record_initializer::activation_record_initializer() noexcept { if ( 0 == counter++) { +#if defined(BOOST_USE_UCONTEXT) + activation_record::current_rec = new activation_record(); +#elif defined(BOOST_EXECUTION_CONTEXT) && (BOOST_EXECUTION_CONTEXT == 1) activation_record::current_rec.reset( new activation_record() ); +#endif } } activation_record_initializer::~activation_record_initializer() { if ( 0 == --counter) { BOOST_ASSERT( activation_record::current_rec->is_main_context() ); +#if defined(BOOST_USE_UCONTEXT) + delete activation_record::current_rec; +#elif defined(BOOST_EXECUTION_CONTEXT) && (BOOST_EXECUTION_CONTEXT == 1) delete activation_record::current_rec.detach(); +#endif } } } +#if defined(BOOST_USE_UCONTEXT) +namespace detail { + +activation_record *& +activation_record::current() noexcept { + // initialized the first time control passes; per thread + thread_local static activation_record_initializer initializer; + return activation_record::current_rec; +} + +} +#elif defined(BOOST_EXECUTION_CONTEXT) && (BOOST_EXECUTION_CONTEXT == 1) execution_context execution_context::current() noexcept { // initialized the first time control passes; per thread thread_local static detail::activation_record_initializer initializer; return execution_context(); } - -}} -# endif #endif -# ifdef BOOST_HAS_ABI_HEADERS -# include BOOST_ABI_SUFFIX -# endif +}} +#endif + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif diff --git a/test/test_callcc.cpp b/test/test_callcc.cpp index d00fd93..702ca3e 100644 --- a/test/test_callcc.cpp +++ b/test/test_callcc.cpp @@ -634,7 +634,6 @@ boost::unit_test::test_suite * init_unit_test_suite( int, char* []) test->add( BOOST_TEST_CASE( & test_exception) ); test->add( BOOST_TEST_CASE( & test_fp) ); test->add( BOOST_TEST_CASE( & test_stacked) ); - test->add( BOOST_TEST_CASE( & test_stacked) ); test->add( BOOST_TEST_CASE( & test_prealloc) ); test->add( BOOST_TEST_CASE( & test_ontop) ); test->add( BOOST_TEST_CASE( & test_ontop_exception) );