mirror of
https://github.com/boostorg/context.git
synced 2026-01-19 04:02:17 +00:00
support ucontext_t in callcc()
This commit is contained in:
@@ -25,6 +25,9 @@ feature.compose <valgrind>on : <define>BOOST_USE_VALGRIND ;
|
||||
feature.feature context-switch : cc ec : optional propagated composite ;
|
||||
feature.compose <context-switch>ec : <define>BOOST_USE_EXECUTION_CONTEXT ;
|
||||
|
||||
feature.feature context : ucontext : optional propagated composite ;
|
||||
feature.compose <context>ucontext : <define>BOOST_USE_UCONTEXT ;
|
||||
|
||||
project boost/context
|
||||
: requirements
|
||||
<toolset>gcc,<segmented-stacks>on:<cxxflags>-fsplit-stack
|
||||
|
||||
@@ -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 (<ABI|binary format>)
|
||||
[[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
|
||||
|
||||
@@ -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 <boost/context/continuation.hpp>
|
||||
|
||||
@@ -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()]]
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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.]
|
||||
|
||||
|
||||
@@ -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 <boost/context/segmented_stack.hpp>
|
||||
|
||||
|
||||
@@ -70,6 +70,10 @@ exe endless_loop
|
||||
: endless_loop.cpp
|
||||
;
|
||||
|
||||
exe segmented
|
||||
: segmented.cpp
|
||||
;
|
||||
|
||||
#exe backtrace
|
||||
# : backtrace.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;
|
||||
}
|
||||
|
||||
51
example/callcc/segmented.cpp
Normal file
51
example/callcc/segmented.cpp
Normal file
@@ -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 <cstdlib>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/context/all.hpp>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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 <boost/context/detail/config.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/intrusive_ptr.hpp>
|
||||
|
||||
#if defined(BOOST_NO_CXX17_STD_APPLY)
|
||||
#include <boost/context/detail/apply.hpp>
|
||||
#endif
|
||||
#if defined(BOOST_NO_CXX14_STD_EXCHANGE)
|
||||
#include <boost/context/detail/exchange.hpp>
|
||||
#endif
|
||||
#if defined(BOOST_NO_CXX17_STD_INVOKE)
|
||||
#include <boost/context/detail/invoke.hpp>
|
||||
#endif
|
||||
#include <boost/context/detail/disable_overload.hpp>
|
||||
#include <boost/context/detail/exception.hpp>
|
||||
#include <boost/context/detail/fcontext.hpp>
|
||||
#include <boost/context/detail/tuple.hpp>
|
||||
#include <boost/context/fixedsize_stack.hpp>
|
||||
#include <boost/context/flags.hpp>
|
||||
#include <boost/context/preallocated.hpp>
|
||||
#include <boost/context/segmented_stack.hpp>
|
||||
#include <boost/context/stack_context.hpp>
|
||||
|
||||
#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 <boost/context/continuation_ucontext.hpp>
|
||||
#else
|
||||
Ctx cc = std::invoke( fn_, std::move( from) );
|
||||
#include <boost/context/continuation_fcontext.hpp>
|
||||
#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
|
||||
|
||||
556
include/boost/context/continuation_fcontext.hpp
Normal file
556
include/boost/context/continuation_fcontext.hpp
Normal file
@@ -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 <boost/context/detail/config.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/intrusive_ptr.hpp>
|
||||
|
||||
#if defined(BOOST_NO_CXX17_STD_APPLY)
|
||||
#include <boost/context/detail/apply.hpp>
|
||||
#endif
|
||||
#if defined(BOOST_NO_CXX14_STD_EXCHANGE)
|
||||
#include <boost/context/detail/exchange.hpp>
|
||||
#endif
|
||||
#if defined(BOOST_NO_CXX17_STD_INVOKE)
|
||||
#include <boost/context/detail/invoke.hpp>
|
||||
#endif
|
||||
#include <boost/context/detail/disable_overload.hpp>
|
||||
#include <boost/context/detail/exception.hpp>
|
||||
#include <boost/context/detail/fcontext.hpp>
|
||||
#include <boost/context/detail/tuple.hpp>
|
||||
#include <boost/context/fixedsize_stack.hpp>
|
||||
#include <boost/context/flags.hpp>
|
||||
#include <boost/context/preallocated.hpp>
|
||||
#include <boost/context/segmented_stack.hpp>
|
||||
#include <boost/context/stack_context.hpp>
|
||||
|
||||
#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
|
||||
696
include/boost/context/continuation_ucontext.hpp
Normal file
696
include/boost/context/continuation_ucontext.hpp
Normal file
@@ -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 <ucontext.h>
|
||||
}
|
||||
|
||||
#include <boost/context/detail/config.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <system_error>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/config.hpp>
|
||||
|
||||
#if defined(BOOST_NO_CXX17_STD_APPLY)
|
||||
#include <boost/context/detail/apply.hpp>
|
||||
#endif
|
||||
#include <boost/context/detail/disable_overload.hpp>
|
||||
#if defined(BOOST_NO_CXX14_STD_EXCHANGE)
|
||||
#include <boost/context/detail/exchange.hpp>
|
||||
#endif
|
||||
#include <boost/context/fixedsize_stack.hpp>
|
||||
#include <boost/context/flags.hpp>
|
||||
#include <boost/context/preallocated.hpp>
|
||||
#if defined(BOOST_USE_SEGMENTED_STACKS)
|
||||
#include <boost/context/segmented_stack.hpp>
|
||||
#endif
|
||||
#include <boost/context/stack_context.hpp>
|
||||
|
||||
#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>(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>(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
|
||||
@@ -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_) {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/intrusive_ptr.hpp>
|
||||
#include <boost/pool/pool.hpp>
|
||||
|
||||
#include <boost/context/detail/config.hpp>
|
||||
|
||||
@@ -26,7 +26,6 @@ project boost/context/performance/fcontext
|
||||
<optimization>speed
|
||||
<threading>multi
|
||||
<variant>release
|
||||
<cxxflags>-DBOOST_DISABLE_ASSERTS
|
||||
;
|
||||
|
||||
alias sources
|
||||
|
||||
@@ -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 <boost/config.hpp>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) );
|
||||
|
||||
Reference in New Issue
Block a user