2
0
mirror of https://github.com/boostorg/context.git synced 2026-02-21 15:02:26 +00:00
Files
context/doc/callcc.qbk
2017-01-08 09:38:37 +01:00

698 lines
22 KiB
Plaintext

[/
Copyright Oliver Kowalke 2016.
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
]
[#cc]
[section:cc call/cc]
__callcc__ (call with current continuation) is a universal control operator
(well-known from the programming language Scheme) that captures the current
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.
Continuations can be suspended and resumed later in order to change the control
flow of a program.
Modern mico-processors are registers machines; the content of processor
registers represent a continuation of the executed program at a given point in
time.
Operating systems simulate parallel execution of programs on a single processor
by switching between programs (context switch) by preserving and restoring the
continuation, e.g. the content of all registers.
[heading __cc__]
__cc__ is the C++ equivalent to Scheme's __callcc__ operator. It captures the
current continuation (the rest of the computation; code after __cc__) and
trickers a context switch. The context switch is achived by preserving certain
registers (including instruction and stack pointer), defined by the calling
convention of the ABI, of the current continuation and restoring those
registers of the resumed continuation. The control flow of the resumed
continuation continues.
The current continuation is suspended and passed as argument to the resumed
continuation.
__cc__ expects a __context_fn__ with signature
`'continuation(continuation && c, Arg ...arg)'`. The parameter `c`
represents the current continuation from which this continuation was resumed
(e.g. that has called __cc__) and `arg` are the data passed to __cc__.
On return the __context_fn__ of the current continuation has to specify an
__con__ to which the execution control is transferred after termination
of the current continuation.
If an instance with valid state goes out of scope and the __context_fn__ has
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__.]
[heading __con__]
__con__ represents a contiunation; it contains the content of preserved
registers and manages the associated stack (allocation/deallocation).
__con__ s a one-shot continuation - it can be used only once, after applied to
__cc__ it is invalidated.
__con__ is only move-constructible and move-assignable.
As a first-class object __con__ can be applied to and returned from a function,
assigned to a variable or stored in a container.
A contiunation is continued by appling to function `resume()`.
[heading Usage]
namespace ctx=boost::context;
ctx::continuation source=ctx::callcc(
[](ctx::continuation && sink){
int a=0;
int b=1;
for(;;){
sink=ctx::resume(std::move(sink),a);
auto next=a+b;
a=b;
b=next;
}
return std::move(sink);
});
for (int j=0;j<10;++j) {
std::cout << ctx::transfer_data<int>(source) << " ";
source=ctx::resume(std::move(source));
}
output:
0 1 1 2 3 5 8 13 21 34 55
This simple example demonstrates the basic usage of __callcc__ as a generator.
The continuation `sink` represents the ['main]-continuation (function `main()`).
`sink` is captured (current-contiunation) and passed as parameter to the lambda.
Because the state is invalidated (one-shot continuation) by each call of __cc__,
the new state of the __con__, returned by __cc__, needs to be assigned to `sink`
after each call.
The lambda that calculates the Fibonacci numbers is executed inside
the continuation represented by `source`. Calculated Fibonacci numbers are
transferred between the two continuations' as argument of __cc__ (and returned
by `ctx::transfer_data<int>(source)`). Note that this example represents a ['generator]
thus no value is transferred into the lambda via __cc__.
The locale variables `a`, `b` and ` next` remain their values during each
context switch. This is possible due `source` has its own stack
and the stack is exchanged by each context switch.
[heading Parameter passing]
Calling __cc__ without arguments means, that no data will be transferred, only
the context switch is executed.
The arguments passed to __cc__, in one continuation, are accessible via
`ctx::transfer_data<>(c)` in the other continuation.
namespace ctx=boost::context;
int i=1;
ctx::continuation c1=callcc([](ctx::continuation && c2){
int j=ctx::transfer_data<int>(c2);
std::printf("inside c1,j==%d\n",j);
return resume(std::move(c2),j+1);
},
i);
i=ctx::transfer_data<int>(c1);
std::printf("i==%d\n",i);
output:
inside c1,j==1
i==2
`callcc(<lambda>,i)` enters the lambda in continuation represented by `c1` with
argument `i=1`.
The expression `resume(std::move(c2),j+1)` resumes the continuation `c2` and
transfers back an integer of `j+1`. On return of `callcc(<lambda>, i)`, the
variable `i` gets the value of `j+1` assigned (`i=transfer_data<int>(c1)`).
More than one argument can be transferred too.
namespace ctx=boost::context;
int i=2,j=1;
ctx::continuation c1=callcc([](ctx::continuation && c2){
int i, j;
std::tie(i,j)=ctx::transfer_data<int,int>(c2);
std::printf("inside c1,i==%d,j==%d\n",i,j);
return resume(std::move(c2),i+j,i-j);
},
i, j);
std::tie(i,j)=ctx::transfer_data<int,int>(c1);
std::printf("i==%d,j==%d\n",i,j);
output:
inside c1,i==2,j==1
i==3,j==1
For use-cases, that require to transfer data of different type:
namespace ctx=boost::context;
ctx::continuation f1(ctx::continuation && c) {
int i=3;
c=ctx::resume(std::move(c),i);
std::string s{ "abc" };
c=ctx::resume(std::move(c),s);
i=7;s="xyz";
c=ctx::resume(std::move(c),i,s);
c=ctx::resume(std::move(c));
return std::move(c);
}
ctx::continuation c = ctx::callcc( f1);
int i = ctx::transfer_data< int >( c);
std::cout << "f1: returned : " << i << std::endl;
c = ctx::resume( std::move( c) );
std::string s = ctx::transfer_data< std::string >( c);
std::cout << "f1: returned : " << s << std::endl;
c = ctx::resume( std::move( c) );
std::tie(i,s)=ctx::transfer_data< int, std::string >( c);
std::cout << "f1: returned : " << i << ", " << s << std::endl;
c = ctx::resume( std::move( c) );
std::cout << std::boolalpha;
std::cout << "f1: returned data : " << ctx::data_available( c) << std::endl;
output:
f1: returned : 3
f1: returned : abc
f1: returned : 7, xyz
f1: returned data : false
[heading Exception handling]
If the function executed inside a __context_fn__ emits ans exception, the
application is terminated by calling `std::terminate()`. `std::exception_ptr`
can be used to transfer exceptions between different continuations.
[important Do not jump from inside a catch block and then re-throw the exception
in another continuation.]
[#cc_ontop]
[heading Executing function on top of a continuation]
Sometimes it is useful to execute a new function on top of a resumed
continuation. For this purpose __cc__ with second argument `exec_ontop_arg` has
to be used.
The function passed as argument must accept a reference to __con__ and return,
depending on functions argument list, a tuple of its argument types, a single
return type or void.
namespace ctx=boost::context;
ctx::continuation f1(ctx::continuation && c) {
int data=ctx::transfer_data<int>(c);
std::cout << "f1: entered first time: " << data << std::endl;
c=ctx::resume(std::move(c),data+1);
data=ctx::transfer_data<int>(c);
std::cout << "f1: entered second time: " << data << std::endl;
c=ctx::resume(std::move(c),data+1);
data=ctx::transfer_data<int>(c);
std::cout << "f1: entered third time: " << data << std::endl;
return std::move(c);
};
int f2(ctx::continuation & c){
int data=ctx::transfer_data<int>(c);
std::cout << "f2: entered: " << data << std::endl;
return data;
};
int data = 0;
ctx::continuation c=ctx::callcc(f1,data+1);
data=ctx::transfer_data<int>(c);
std::cout << "f1: returned first time: " << data << std::endl;
c=ctx::resume(std::move(c),data+1);
data=ctx::transfer_data<int>(c);
std::cout << "f1: returned second time: " << data << std::endl;
c=ctx::resume(std::move(c),ctx::exec_ontop_arg,f2,-1);
std::cout << "f1: returned third time" << std::endl;
output:
f1: entered first time: 1
f1: returned first time: 2
f1: entered second time: 3
f1: returned second time: 4
f2: entered: -1
f1: entered third time: -1
f1: returned third time
The expression `resume(std::move(c),ctx::exec_ontop_arg,f2,-1)` executes
`f2()` on top of contiunation `c`, e.g. an additional stack frame is allocated
on top of the stack (in front of `f1()`). `f2()` returns argument `-1` that will
returned by the second invocation of `resume(std::move(c),data+1)` in `f1()`.
Another option is to execute a function on top of the continuation that throws
an exception.
namespace ctx=boost::context;
struct my_exception : public std::runtime_error {
ctx::continuation c;
my_exception(ctx::continuation && c_,std::string const& what) :
std::runtime_error{ what },
c{ std::move( c_) } {
}
};
ctx::continuation c=ctx::callcc([](ctx::continuation && c) {
for (;;) {
try {
std::cout << "entered" << std::endl;
c=ctx::resume(std::move(c));
} catch (my_exception & ex) {
std::cerr << "my_exception: " << ex.what() << std::endl;
return std::move(ex.c);
}
}
return std::move(c);
});
c = ctx::resume(std::move( c),
ctx::exec_ontop_arg,
[](ctx::continuation & c){
throw my_exception(std::move(c),"abc");
});
output:
entered
my_exception: abc
In this exception `my_exception` is throw from a function invoked ontop of
continuation `c` and catched inside the `for`-loop.
[heading Stack unwinding]
On construction of __con__ a stack is allocated.
If the __context_fn__ returns the stack will be destructed.
If the __context_fn__ has not yet returned and the destructor of an valid
__con__ instance (e.g. ['continuation::operator bool()] returns
`true`) is called, the stack will be destructed too.
[important Code executed by __context_fn__ must not prevent the propagation of the
__forced_unwind__ exception. Absorbing that exception will cause stack
unwinding to fail. Thus, any code that catches all exceptions must re-throw any
pending __forced_unwind__ exception.]
[#cc_prealloc]
[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
__con__ is created.
[note The user is responsible for destructing the control structure at the top
of the stack.]
namespace ctx=boost::context;
// 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 {
// captured continuation
ctx::continuation c;
template< typename StackAllocator >
my_control_structure(void * sp,std::size_t size,stack_context sctx,StackAllocator salloc) :
// create captured continuation
c{} {
c=ctx::callcc(std::allocator_arg,preallocated(sp,size,sctx),salloc,entry_func);
}
...
};
[heading Inverting the control flow]
namespace ctx=boost::context;
/*
* grammar:
* P ---> E '\0'
* E ---> T {('+'|'-') T}
* T ---> S {('*'|'/') S}
* S ---> digit | '(' E ')'
*/
class Parser{
char next;
std::istream& is;
std::function<void(char)> cb;
char pull(){
return std::char_traits<char>::to_char_type(is.get());
}
void scan(){
do{
next=pull();
}
while(isspace(next));
}
public:
Parser(std::istream& is_,std::function<void(char)> cb_) :
next(), is(is_), cb(cb_)
{}
void run() {
scan();
E();
}
private:
void E(){
T();
while (next=='+'||next=='-'){
cb(next);
scan();
T();
}
}
void T(){
S();
while (next=='*'||next=='/'){
cb(next);
scan();
S();
}
}
void S(){
if (isdigit(next)){
cb(next);
scan();
}
else if(next=='('){
cb(next);
scan();
E();
if (next==')'){
cb(next);
scan();
}else{
throw std::runtime_error("parsing failed");
}
}
else{
throw std::runtime_error("parsing failed");
}
}
};
std::istringstream is("1+1");
// execute parser in new continuation
ctx::continuation source;
// user-code pulls parsed data from parser
// invert control flow
source=ctx::callcc(
[&is](ctx::continuation && sink){
// create parser with callback function
Parser p(is,
[&sink](char c){
// resume main continuation
sink=ctx::resume(std::move(sink),c);
});
// start recursive parsing
p.run();
// resume main continuation
return std::move(sink);
});
while(ctx::data_available(source)){
char c=ctx::transfer_data<char>(source);
printf("Parsed: %c\n",c);
source=ctx::resume(std::move(source) );
}
output:
Parsed: 1
Parsed: +
Parsed: 1
In this example a recursive descent parser uses a callback to emit a newly
passed symbol. Using __callcc__ 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 continuations.
[heading Class `continuation`]
#include <boost/context/continuation.hpp>
struct exec_ontop_arg_t {};
const exec_ontop_arg_t exec_ontop_arg{};
class continuation {
public:
continuation() noexcept = default;
~continuation();
continuation(continuation && other) noexcept;
continuation & operator=(continuation && other) noexcept;
continuation(continuation const& other) noexcept = delete;
continuation & operator=(continuation const& other) noexcept = delete;
explicit operator bool() const noexcept;
bool operator!() const noexcept;
bool operator==(continuation const& other) const noexcept;
bool operator!=(continuation const& other) const noexcept;
bool operator<(continuation const& other) const noexcept;
bool operator>(continuation const& other) const noexcept;
bool operator<=(continuation const& other) const noexcept;
bool operator>=(continuation const& other) const noexcept;
template<typename charT,class traitsT>
friend std::basic_ostream<charT,traitsT> &
operator<<(std::basic_ostream<charT,traitsT> & os,continuation const& other) {
void swap(continuation & other) noexcept;
};
[constructor_heading cc..constructor]
continuation() noexcept;
[variablelist
[[Effects:] [Creates a invalid continuation.]]
[[Throws:] [Nothing.]]
]
[destructor_heading cc..destructor destructor]
~continuation();
[variablelist
[[Effects:] [Destructs the associated stack if `*this` is a valid continuation,
e.g. ['continuation::operator bool()] returns `true`.]]
[[Throws:] [Nothing.]]
]
[move_constructor_heading cc..move constructor]
continuation(continuation && other) noexcept;
[variablelist
[[Effects:] [Moves underlying capture continuation to `*this`.]]
[[Throws:] [Nothing.]]
]
[move_assignment_heading cc..move assignment]
continuation & operator=(continuation && other) noexcept;
[variablelist
[[Effects:] [Moves the state of `other` to `*this` using move semantics.]]
[[Throws:] [Nothing.]]
]
[operator_heading cc..operator_bool..operator bool]
explicit operator bool() const noexcept;
[variablelist
[[Returns:] [`true` if `*this` points to a captured continuation.]]
[[Throws:] [Nothing.]]
]
[operator_heading cc..operator_not..operator!]
bool operator!() const noexcept;
[variablelist
[[Returns:] [`true` if `*this` does not point to a captured continuation.]]
[[Throws:] [Nothing.]]
]
[operator_heading cc..operator_equal..operator==]
bool operator==(continuation const& other) const noexcept;
[variablelist
[[Returns:] [`true` if `*this` and `other` represent the same continuation,
`false` otherwise.]]
[[Throws:] [Nothing.]]
]
[operator_heading cc..operator_notequal..operator!=]
bool operator!=(continuation const& other) const noexcept;
[variablelist
[[Returns:] [[`! (other == * this)]]]
[[Throws:] [Nothing.]]
]
[operator_heading cc..operator_less..operator<]
bool operator<(continuation const& other) const noexcept;
[variablelist
[[Returns:] [`true` if `*this != other` is true and the
implementation-defined total order of `continuation` values places `*this`
before `other`, false otherwise.]]
[[Throws:] [Nothing.]]
]
[operator_heading cc..operator_greater..operator>]
bool operator>(continuation const& other) const noexcept;
[variablelist
[[Returns:] [`other < * this`]]
[[Throws:] [Nothing.]]
]
[operator_heading cc..operator_lesseq..operator<=]
bool operator<=(continuation const& other) const noexcept;
[variablelist
[[Returns:] [`! (other < * this)`]]
[[Throws:] [Nothing.]]
]
[operator_heading cc..operator_greatereq..operator>=]
bool operator>=(continuation const& other) const noexcept;
[variablelist
[[Returns:] [`! (* this < other)`]]
[[Throws:] [Nothing.]]
]
[hding cc_..Non-member function [`operator<<()]]
template<typename charT,class traitsT>
std::basic_ostream<charT,traitsT> &
operator<<(std::basic_ostream<charT,traitsT> & os,continuation const& other);
[variablelist
[[Efects:] [Writes the representation of `other` to stream `os`.]]
[[Returns:] [`os`]]
]
[hding cc__..Non-member function [`data_available()]]
bool data_available(continuation const& c) noexcept;
[variablelist
[[Efects:] [Tests if `c` has data.]]
[[Returns:] [`true` if data have been transferred, otherwise `false`.]]
[[Throws:] [Nothing.]]
]
[hding cc___..Non-member tempalte function [`data()]]
template<typename ...Arg>
<unspecified> data(continuation & c);
[variablelist
[[Returns:] [Data that have been trasnferred via `callcc()` or `resume()` are
returned as `std::tuple<Arg...>` or `Arg` if single parameter.]]
]
[heading Call with current contiunation]
#include <boost/context/continuation.hpp>
template<typename Fn,typename ...Arg>
continuation callcc(Fn && fn,Arg ...arg);
template<typename StackAlloc,typename Fn,typename ...Arg>
continuation callcc(std::allocator_arg_t,StackAlloc salloc,Fn && fn,Arg ...arg);
template<typename StackAlloc,typename Fn,typename ...Arg>
continuation callcc(std::allocator_arg_t,preallocated palloc,StackAlloc salloc,Fn && fn,Arg ...arg);
[variablelist
[[Effects:] [Captures current continuation and creates a new continuation
prepared to execute `fn`. `fixedsize_stack` is used as default stack allocator
(stack size == fixedsize_stack::traits::default_size()).
The function with argument type `preallocated`, is used to create a user
defined data [link cc_prealloc (for instance additional control structures)] on
top of the stack.
The arguments, `... arg`, are passed to the current continuation to be
transferred by the call to `callcc()` in the same thread.]]
[[Returns:] [The continuation representing the contexcontinuation that has been suspended.]]
[[Note:] [The returned continuation indicates if the suspended continuation has
terminated (return from context-function) via `bool operator()`. If the returned
continuation has terminated no data are transferred.]]
]
[heading Resume continuation]
#include <boost/context/continuation.hpp>
template<typename ...Arg>
continuation resume(continuation && c,Arg ...arg);
template<typename Fn,typename ...Arg>
continuation resume(continuation && c,exec_ontop_arg_t,Fn && fn,Arg ...arg);
[variablelist
[[Effects:] [Captures current continuation and resumes continuation `c`.
The function with argument type `exec_ontop_arg`, is used to execute function
`fn` in continuation `c` (e.g. the stack frame of `fn` is allocated on stack of
`c`).]]
[[Returns:] [The continuation representing the continuation that has been
suspended.]]
[[Note:] [The returned arguments from `fn` are passed as arguments to the
context-function of resumed continuation.]]
[[Note:] [Function `fn` needs to return a tuple of arguments ([link cc_ontop see
description]).]]
[[Note:] [The returned continuation indicates if the suspended continuation has
terminated (return from context-function) via `bool operator()`. If the returned
continuation has terminated no data are transferred.]]
]
[endsect]