mirror of
https://github.com/boostorg/context.git
synced 2026-01-19 04:02:17 +00:00
469 lines
16 KiB
Plaintext
469 lines
16 KiB
Plaintext
[/
|
|
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
|
|
]
|
|
|
|
[#econtext]
|
|
[section:econtext Class execution_context]
|
|
|
|
Class __econtext__ encapsulates __fcontext__ and manages the context' stack
|
|
(allocation/deallocation).
|
|
|
|
__econtext__ allocates the context stack (using its [link stack __stack_allocator__]
|
|
argument) and creates a control structure on top of it. This structure
|
|
controls the life time of the stack. Instances of __econtext__, associated
|
|
with a specific context, share the ownership of the control structure.
|
|
If the last reference goes out of scope, the control structure is destroyed and
|
|
the stack gets deallocated via the __stack_allocator__.
|
|
|
|
__econtext__ is copy-constructible, move-constructible, copy-assignable and
|
|
move-assignable.
|
|
|
|
__econtext__ maintains a static, thread-local pointer (smart pointer),
|
|
accessed by __ec_current__, pointing to the active context.
|
|
On each context switch the static thread-local pointer is updated.
|
|
The usage of this global pointer makes the context switch a little bit slower
|
|
(due access of thread local storage) but has some advantages. It allows to access
|
|
the control structure of the current active context from arbitrary code paths
|
|
required in order to support segmented stacks, which need to call certain
|
|
maintenance functions (__splitstack_getcontext() etc.) before each context switch
|
|
(each context switch exchanges the stack).
|
|
Additionally the destruction of __econtext__ and thus the stack deallocation is
|
|
faster compared to [link ccontext __ccontext__].
|
|
|
|
__econtext__ expects a function/functor with signature `void(void* vp)` (
|
|
`vp` is the data passed at the first invocation of
|
|
[operator_link execution_context operator_call operator()]).
|
|
|
|
|
|
[heading usage of __econtext__]
|
|
|
|
int n=35;
|
|
int p=0;
|
|
boost::context::execution_context mctx( boost::context::execution_context::current() );
|
|
boost::context::execution_context ctx(
|
|
[n,&p,&mctx](void*)mutable{
|
|
int a=0;
|
|
int b=1;
|
|
while(n-->0){
|
|
yield(a);
|
|
auto next=a+b;
|
|
a=b;
|
|
b=next;
|
|
}
|
|
});
|
|
for(int i=0;i<10;++i){
|
|
ctx();
|
|
std::cout<<p<<" ";
|
|
}
|
|
|
|
output:
|
|
0 1 1 2 3 5 8 13 21 34
|
|
|
|
This simple example demonstrates the basic usage of __econtext__.
|
|
|
|
[heading inverting the control flow]
|
|
|
|
/*
|
|
* grammar:
|
|
* P ---> E '\0'
|
|
* E ---> T {('+'|'-') T}
|
|
* T ---> S {('*'|'/') S}
|
|
* S ---> digit | '(' E ')'
|
|
*/
|
|
class Parser{
|
|
// implementation omitted; see examples directory
|
|
};
|
|
|
|
int main() {
|
|
std::istringstream is("1+1");
|
|
bool done=false;
|
|
std::exception_ptr except;
|
|
|
|
// create handle to main execution context
|
|
auto main_ctx( boost::context::execution_context::current() );
|
|
|
|
// execute parser in new execution context
|
|
boost::context::execution_context parser_ctx(
|
|
std::allocator_arg,
|
|
boost::context::fixedsize_stack(4096),
|
|
[&main_ctx,&is,&done,&except](void*){
|
|
// create parser with callback function
|
|
Parser p( is,
|
|
[&main_ctx,&c](char ch){
|
|
// resume main execution context
|
|
main_ctx( & ch);
|
|
});
|
|
try {
|
|
// start recursive parsing
|
|
p.run();
|
|
} catch ( ... ) {
|
|
// store other exceptions in exception-pointer
|
|
except = std::current_exception();
|
|
}
|
|
// set termination flag
|
|
done=true;
|
|
// resume main execution context
|
|
main_ctx();
|
|
});
|
|
|
|
// user-code pulls parsed data from parser
|
|
// invert control flow
|
|
void * vp = parser_ctx();
|
|
if ( except) {
|
|
std::rethrow_exception( except);
|
|
}
|
|
while( ! done) {
|
|
printf("Parsed: %c\n",* static_cast< char* >( vp) );
|
|
parser_ctx();
|
|
if ( except) {
|
|
std::rethrow_exception( except);
|
|
}
|
|
}
|
|
|
|
std::cout << "main: done" << std::endl;
|
|
}
|
|
|
|
output:
|
|
Parsed: 1
|
|
Parsed: +
|
|
Parsed: 1
|
|
|
|
|
|
In this example a recursive descent parser uses a callback to emit a newly
|
|
passed symbol. Using __econtext__ the control flow can be inverted, e.g. the
|
|
user-code pulls parsed symbols from the parser - instead to get pushed from the
|
|
parser (via callback).
|
|
|
|
The data (character) is transferred between the two __econtext__.
|
|
|
|
If the code executed by __econtext__ emits an exception, the application is
|
|
terminated. ['std::exception_ptr] can be used to transfer exceptions between
|
|
different execution contexts.
|
|
|
|
Sometimes it is necessary to unwind the stack of an unfinished context to
|
|
destroy local stack variables so they can release allocated resources (RAII
|
|
pattern). The user is responsible for this task.
|
|
|
|
[heading allocating control structures on top of stack]
|
|
Allocating control structures on top of the stack requires to allocated the
|
|
__stack_context__ and create the control structure with placement new before
|
|
__econtext__ is created.
|
|
[note The user is responsible for destructing the control structure at the top
|
|
of the stack.]
|
|
|
|
// stack-allocator used for (de-)allocating stack
|
|
fixedsize_stack salloc( 4048);
|
|
// allocate stack space
|
|
stack_context sctx( salloc.allocate() );
|
|
// reserve space for control structure on top of the stack
|
|
void * sp = static_cast< char * >( sctx.sp) - sizeof( my_control_structure);
|
|
std::size_t size = sctx.size - sizeof( my_control_structure);
|
|
// placement new creates control structure on reserved space
|
|
my_control_structure * cs = new ( sp) my_control_structure( sp, size, sctx, salloc);
|
|
...
|
|
// destructing the control structure
|
|
cs->~my_control_structure();
|
|
...
|
|
struct my_control_structure {
|
|
// execution context
|
|
execution_context ectx;
|
|
|
|
template< typename StackAllocator >
|
|
my_control_structure( void * sp, std::size_t size, stack_context sctx, StackAllocator salloc) :
|
|
// create execution context
|
|
ectx( std::allocator_arg, preallocated( sp, size, sctx), salloc, entry_func) {
|
|
}
|
|
...
|
|
};
|
|
|
|
[heading exception handling]
|
|
If the function executed inside a __econtext__ emits ans exception, the
|
|
application is terminated by calling ['std::terminate(). ['std::exception_ptr]
|
|
can be used to transfer exceptions between different execution contexts.
|
|
|
|
[heading parameter passing]
|
|
The void pointer argument passed to __ec_op__, in one context, is passed as
|
|
the last argument of the __context_fn__ if the context is started for the
|
|
first time.
|
|
In all following invocations of __ec_op__ the void pointer passed to
|
|
__ec_op__, in one context, is returned by __ec_op__ in the other context.
|
|
|
|
class X {
|
|
private:
|
|
std::exception_ptr excptr_;
|
|
boost::context::execution_context caller_;
|
|
boost::context::execution_context callee_;
|
|
|
|
public:
|
|
X() :
|
|
excptr_(),
|
|
caller_( boost::context::execution_context::current() ),
|
|
callee_( [=] (void * vp) {
|
|
try {
|
|
int i = * static_cast< int * >( vp);
|
|
std::string str = boost::lexical_cast<std::string>(i);
|
|
caller_( & str);
|
|
} catch (...) {
|
|
excptr_=std::current_exception();
|
|
}
|
|
})
|
|
{}
|
|
|
|
std::string operator()( int i) {
|
|
void * ret = callee_( & i);
|
|
if(excptr_){
|
|
std::rethrow_exception(excptr_);
|
|
}
|
|
return * static_cast< std::string * >( ret);
|
|
}
|
|
};
|
|
|
|
X x;
|
|
std::cout << x( 7) << std::endl;
|
|
|
|
output:
|
|
7
|
|
|
|
|
|
[heading Class `execution_context`]
|
|
|
|
class execution_context {
|
|
public:
|
|
static execution_context current() noexcept;
|
|
|
|
template< typename Fn, typename ... Args >
|
|
execution_context( Fn && fn, Args && ... args);
|
|
|
|
template< typename StackAlloc, typename Fn, typename ... Args >
|
|
execution_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Args && ... args);
|
|
|
|
template< typename StackAlloc, typename Fn, typename ... Args >
|
|
execution_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Args && ... args);
|
|
|
|
execution_context( execution_context const& other) noexcept;
|
|
execution_context( execution_context && other) noexcept;
|
|
|
|
execution_context & operator=( execution_context const& other) noexcept;
|
|
execution_context & operator=( execution_context && other) noexcept;
|
|
|
|
explicit operator bool() const noexcept;
|
|
bool operator!() const noexcept;
|
|
|
|
void * operator()( void * vp = nullptr);
|
|
|
|
template< typename Fn, typename ... Args >
|
|
void * operator()( exec_ontop_arg_t, Fn && fn, Args && ... args);
|
|
|
|
template< typename Fn, typename ... Args >
|
|
void * operator()( void * vp, exec_ontop_arg_t, Fn && fn, Args && ... args);
|
|
|
|
bool operator==( execution_context const& other) const noexcept;
|
|
|
|
bool operator!=( execution_context const& other) const noexcept;
|
|
|
|
bool operator<( execution_context const& other) const noexcept;
|
|
|
|
bool operator>( execution_context const& other) const noexcept;
|
|
|
|
bool operator<=( execution_context const& other) const noexcept;
|
|
|
|
bool operator>=( execution_context const& other) const noexcept;
|
|
|
|
template< typename charT, class traitsT >
|
|
friend std::basic_ostream< charT, traitsT > &
|
|
operator<<( std::basic_ostream< charT, traitsT > & os, execution_context const& other);
|
|
};
|
|
|
|
[static_member_heading execution_context..current]
|
|
|
|
static execution_context current() noexcept;
|
|
|
|
[variablelist
|
|
[[Returns:] [Returns an instance of excution_context pointing to the active
|
|
execution context.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[constructor_heading execution_context..constructor]
|
|
|
|
template< typename Fn, typename ... Args >
|
|
execution_context( Fn && fn, Args && ... args);
|
|
|
|
template< typename StackAlloc, typename Fn, typename ... Args >
|
|
execution_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Args && ... args);
|
|
|
|
template< typename StackAlloc, typename Fn, typename ... Args >
|
|
execution_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Args && ... args);
|
|
|
|
[variablelist
|
|
[[Effects:] [Creates a new execution context and prepares the context to execute
|
|
`fn`. `fixedsize_stack` is used as default stack allocator
|
|
(stack size == fixedsize_stack::traits::default_size()).
|
|
The constructor with argument type `preallocated`, is used to store control
|
|
structures on top of the stack.]]
|
|
]
|
|
|
|
[copy_constructor_heading execution_context..copy constructor]
|
|
|
|
execution_context( execution_context const& other) noexcept;
|
|
|
|
[variablelist
|
|
[[Effects:] [Copies `other`, e.g. underlying capture record is shared
|
|
with `*this`.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[move_constructor_heading execution_context..move constructor]
|
|
|
|
execution_context( execution_context && other) noexcept;
|
|
|
|
[variablelist
|
|
[[Effects:] [Moves underlying capture record to `*this`.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[copy_assignment_heading execution_context..copy assignment]
|
|
|
|
execution_context & operator=( execution_context const& other) noexcept;
|
|
|
|
[variablelist
|
|
[[Effects:] [Copies the state of `other` to `*this`, state (capture record) is shared.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[move_assignment_heading execution_context..move assignment]
|
|
|
|
execution_context & operator=( execution_context && other) noexcept;
|
|
|
|
[variablelist
|
|
[[Effects:] [Moves the state of `other` to `*this` using move semantics.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[operator_heading execution_context..operator_bool..operator bool]
|
|
|
|
explicit operator bool() const noexcept;
|
|
|
|
[variablelist
|
|
[[Returns:] [`true` if `*this` points to a capture record.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[operator_heading execution_context..operator_not..operator!]
|
|
|
|
bool operator!() const noexcept;
|
|
|
|
[variablelist
|
|
[[Returns:] [`true` if `*this` does not point to a capture record.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[operator_heading execution_context..operator_call..operator()]
|
|
|
|
void * operator()( void * vp = nullptr) noexcept;
|
|
|
|
[variablelist
|
|
[[Effects:] [Stores internally the current context data (stack pointer,
|
|
instruction pointer, and CPU registers) of the current active context and
|
|
restores the context data from `*this`, which implies jumping to `*this`'s
|
|
context.
|
|
The void pointer argument, `vp`, is passed to the current context to be returned
|
|
by the most recent call to `execution_context::operator()` in the same thread.
|
|
`fn` is executed with arguments `args` on top of the stack of `this`.
|
|
[[Note:] [The behaviour is undefined if `operator()()` is called while `execution_context::current()`
|
|
returns `*this` (e.g. resuming an already running context). If the top-level context
|
|
function returns, `std::exit()` is called.]]
|
|
[[Returns:] [The void pointer argument passed to the most recent call to
|
|
`execution_context::operator()`, if any.]]
|
|
]
|
|
|
|
[operator_heading execution_context..operator_call..operator(exec_ontop_arg_t)]
|
|
|
|
template< typename Fn, typename ... Args >
|
|
void * operator()( exec_ontop_arg_t, Fn && fn, Args && ... args);
|
|
|
|
template< typename Fn, typename ... Args >
|
|
void * operator()( void * data, exec_ontop_arg_t, Fn && fn, Args && ... args);
|
|
|
|
[variablelist
|
|
[[Effects:] [Same as `operator()(void*)`, additionally function `fn` is executed
|
|
with arguments `args` in the context of `*this` (e.g. the stack frame of `fn` is
|
|
allocated on stack of `*this`.]]
|
|
[[Returns:] [The void pointer argument passed to the most recent call to
|
|
`execution_context::operator()`, if any.]]
|
|
]
|
|
|
|
[operator_heading execution_context..operator_equal..operator==]
|
|
|
|
bool operator==( execution_context const& other) const noexcept;
|
|
|
|
[variablelist
|
|
[[Returns:] [`true` if `*this` and `other` represent the same execution context,
|
|
`false` otherwise.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[operator_heading execution_context..operator_notequal..operator!=]
|
|
|
|
bool operator!=( execution_context const& other) const noexcept;
|
|
|
|
[variablelist
|
|
[[Returns:] [[`! (other == * this)]]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[operator_heading execution_context..operator_less..operator<]
|
|
|
|
bool operator<( execution_context const& other) const noexcept;
|
|
|
|
[variablelist
|
|
[[Returns:] [`true` if `*this != other` is true and the
|
|
implementation-defined total order of `execution_context` values places `*this` before
|
|
`other`, false otherwise.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[operator_heading execution_context..operator_greater..operator>]
|
|
|
|
bool operator>( execution_context const& other) const noexcept;
|
|
|
|
[variablelist
|
|
[[Returns:] [`other < * this`]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[operator_heading execution_context..operator_lesseq..operator<=]
|
|
|
|
bool operator<=( execution_context const& other) const noexcept;
|
|
|
|
[variablelist
|
|
[[Returns:] [`! (other < * this)`]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[operator_heading execution_context..operator_greatereq..operator>=]
|
|
|
|
bool operator>=( execution_context const& other) const noexcept;
|
|
|
|
[variablelist
|
|
[[Returns:] [`! (* this < other)`]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[hding execution_context..Non-member function [`operator<<()]]
|
|
|
|
template< typename charT, class traitsT >
|
|
std::basic_ostream< charT, traitsT > &
|
|
operator<<( std::basic_ostream< charT, traitsT > & os, execution_context const& other);
|
|
|
|
[variablelist
|
|
[[Efects:] [Writes the representation of `other` to stream `os`.]]
|
|
[[Returns:] [`os`]]
|
|
]
|
|
|
|
|
|
[endsect]
|