| Home | Libraries | People | FAQ | More

### Class execution_context

| Important
execution_context requires C++11!

Class execution_context encapsulates fcontext_t and manages the context' stack (allocation/deallocation).

execution_context allocates the context stack (using its StackAllocator argument) and creates a control structure on top of it. This structure controls the life time of the stack. Instances of execution_context, 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 StackAllocator. execution_context is copy-constructible, move-constructible, copy-assignable and move-assignable. execution_context maintains a static, thread-local pointer (smart pointer), accessed by execution_context::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 execution_context and thus the stack deallocation is faster compared to captured_context.

execution_context expects a function/functor with signature `void( void * vp)` ( `vp` is the data passed at the first invocation of execution_context::operator()).

##### usage of execution_context

    /*
     * 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 execution_context 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 execution_context.

If the code executed by execution_context 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.

##### 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 execution_context 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) {
        }
        ...
    };

##### exception handling

If the function executed inside a execution_context emits ans exception, the application is terminated by calling ['std::terminate(). std::exception_ptr can be used to transfer exceptions between different execution contexts.

##### parameter passing

The void pointer argument passed to execution_context::operator(), in one context, is passed as the last argument of the context-function if the context is started for the first time. In all following invocations of execution_context::operator() the void pointer passed to execution_context::operator(), in one context, is returned by execution_context::operator() 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);
        }
    };

    int main() {
        X x;
        std::cout << x( 7) << std::endl;
        std::cout << "done" << std::endl;
    }

##### 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 execution_context current()`

Returns:

  Returns an instance of excution_context pointing to the active execution context.

Throws:

  Nothing.

##### `template< typname Fn, typename ... Args > execution_context( Fn && fn, Args && ... args)`

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().

##### `template< typename StackAlloc, typname Fn, typename ... Args > execution_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Args && ... args)`

Effects:

  Creates a new execution context and prepares the context to execute `fn`.

##### `template< typename StackAlloc, typname Fn, typename ... Args > execution_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Args && ... args)`

Effects:

  Creates a new execution context and prepares the context to execute `fn`. Used to store control structures on top of the stack.

##### `execution_context( execution_context const& other)`

Effects:

  Copies `other`, e.g. underlying capture record is shared with `*this`.

Throws:

  Nothing.

##### `execution_context( execution_context && other)`

Effects:

  Moves underlying capture record to `*this`.

Throws:

  Nothing.

##### `execution_context & operator=( execution_context const& other)`

Effects:

  Copies the state of `other` to `*this`, state (capture record) is shared.

Throws:

  Nothing.

##### `execution_context & operator=( execution_context && other)`

Effects:

  Moves the state of `other` to `*this` using move semantics.

Throws:

  Nothing.

##### `explicit operator bool() const noexcept`

Returns:

  `true` if `*this` points to a capture record.

Throws:

  Nothing.

##### `bool operator!() const noexcept`

Returns:

  `true` if `*this` does not point to a capture record.

Throws:

  Nothing.

##### `void * operator()( void * vp)`

    void * operator()( void * vp = nullptr) noexcept;

    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);

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==`

    bool operator==( execution_context const& other) const noexcept;

Returns:

  `true` if `*this` and `other` represent the same execution context, `false` otherwise.

Throws:

  Nothing.

##### `operator!=`

    bool operator!=( execution_context const& other) const noexcept;

Returns:

  [`! (other == * this)

[[Throws:] [Nothing.]] ]

##### `operator<`

    bool operator<( execution_context const& other) const noexcept;

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>`

    bool operator>( execution_context const& other) const noexcept;

Returns:

  `other < * this`

Throws:

  Nothing.

##### `operator<=`

    bool operator<=( execution_context const& other) const noexcept;

Returns:

  `! (other < * this)`

Throws:

  Nothing.

##### `operator>=`

    bool operator>=( execution_context const& other) const noexcept;

Returns:

  `! (* this < other)`

Throws:

  Nothing.

##### `operator<<`

    template< typename charT, class traitsT >
    std::basic_ostream< charT, traitsT > &
    operator<<( std::basic_ostream< charT, traitsT > & os, execution_context const& other);

Efects:

  Writes the representation of `other` to stream `os`.

Returns:

  `os`

##### Struct `preallocated`

    struct preallocated {
        void        *   sp;
        std::size_t     size;
        stack_context   sctx;

        preallocated( void * sp, std:size_t size, stack_allocator sctx) noexcept;
    };

##### `preallocated( void * sp, std:size_t size, stack_allocator sctx)`

Effects:

  Creates an object of preallocated.