Await 2.0 - Stackless Resumable Functions - Gor Nishanov - CppCon 2014
Await 2.0 - Stackless Resumable Functions - Gor Nishanov - CppCon 2014
0
Stackless Resumable
Functions
M O S T S C AL ABLE , M O S T E F F I C IEN T, M O S T O P E N
C O R O U TI N E S O F AN Y P R O G R AM MIN G L AN G U AGE I N
E X I S TEN C E
subroutines coroutines
call Allocate frame, pass parameters Allocate frame, pass parameters
return Free frame, return result Free frame, return eventual result
suspend x yes
resume x yes
• Symmetric / Asymmetric
• Modula-2 / Win32 Fibers / Boost::context are symmetric (SwitchToFiber)
• C# asymmetric (distinct suspend and resume operations)
• First-class / Constrained
• Can coroutine be passed as a parameter, returned from a function, stored in a
data structure?
• Stackful / Stackless
• How much state coroutine has? Just the locals of the coroutine or entire stack?
• Can coroutine be suspended from nested stack frames
4k stacklet
1 meg of stack 1 meg
4k stacklet of stack
4k stacklet
auto conn =
<unhandled-exception> <Promise>.set_exception (
std::current_exception())
<get-return-object> <Promise>.get_return_object()
<cancel-check> if(<Promise>.cancellation_requested())
<goto end>
exception
generator<int> fib(int n)
{ generator<int>
int a = 0;
int b = 1;
while (n-- > 0) generator<int>::iterator
{
yield a;
auto next = a + b; {
a = b; auto && __range = fib(35);
b = next; for (auto __begin = __range.begin(),
} __end = __range.end()
} ;
__begin != __end
;
int main() {
++__begin)
for (auto v : fib(35))
{
{
if (v > 10) auto v = *__begin;
break; {
cout << v << ' '; if (v > 10) break;
} cout << v << ' ';
} }
}
CppCon 2014 • Stackless Resumable Functions } 13
Recursive Generators
if (n <= 0)
return; int main()
{
if (n == 1) auto r = range(0, 100);
{ copy(begin(r), end(r),
yield a; ostream_iterator<int>(cout, " "));
}
return;
}
auto mid = a + n / 2;
n
1 1 1 x
1 0 1
= y
int main() {
const int N = 100 * 1000 * 1000; c0-g0-c1
vector<channel<int>> c(N + 1); c1-g1-c2
…
for (int i = 0; i < N; ++i)
goroutine::go(pusher(c[i], c[i + 1])); cn-gn-cn+1
c.front().sync_push(0);
BE-DEVs
FE-DEVs
Library Designer Paradise
• Lib devs can design new coroutines types
• generator<T>
• goroutine
• spawnable<T>
• task<T>
• …
{
auto && __range = fib(35);
for (auto __begin = __range.begin(),
__end = __range.end()
;
__begin != __end
;
++__begin)
{
int main() { auto v = *__begin;
for (auto v : fib(35)) cout << v << endl;
cout << v << endl; }
} }
{
auto && __tmp = <expr>;
if (!__tmp.await_ready()) {
__tmp.await_suspend(<resumption-function-object>);
suspend
resume
}
<cancel-check>
return __tmp.await_resume();
}
{
auto && __tmp = <expr>;
if (! await_ready(__tmp)) {
await_suspend(__tmp, <resumption-function-object>);
suspend
resume
}
<cancel-check>
return await_resume(__tmp);
}
struct _____blank____ {
bool await_ready(){ return false; }
template <typename F>
void await_suspend(F const&){}
void await_resume(){}
};
struct suspend_always {
bool await_ready(){ return false; }
template <typename F>
void await_suspend(F const&){}
void await_resume(){}
};
struct suspend_never {
bool await_ready(){ return true; }
template <typename F>
void await_suspend(F const&){}
void await_resume(){}
};
struct lock_or_suspend {
std::unique_lock<std::mutex> lock;
lock_or_suspend(std::mutex & mut) : lock(mut, std::try_to_lock) {}
class sleep_for {
static void TimerCallback(PTP_CALLBACK_INSTANCE, void* Context, PTP_TIMER) {
std::resumable_handle<>::from_address(Context)();
}
PTP_TIMER timer = nullptr;
std::chrono::system_clock::duration duration;
public:
sleep_for(std::chrono::system_clock::duration d) : duration(d){}
void await_resume() {}
std::resumable_traits<generator<int>, int>
void set_exception(std::exception_ptr e) {
promise.set_exception(std::move(e));
}
suspend_never initial_suspend() { return{}; }
suspend_never final_suspend() { return{}; }
namespace boost {
namespace boost {
void set_exception(std::exception_ptr e) {
promise.set_exception(std::move(e));
}
bool cancellation_requested() { return false; }
};
}; CppCon 2014 • Stackless Resumable Functions 38
Exceptionless Error Propagation (Promise Part)
namespace std {
template <typename T, typename… anything>
struct resumable_traits<boost::unique_future<T>, anything…> {
struct promise_type {
boost::promise<T> promise;
bool cancelling = false;
auto get_return_object() { return promise.get_future(); }
void set_exception(std::exception_ptr e) {
promise.set_exception(std::move(e)); cancelling = true;
}
bool cancellation_requested() { return cancelling; }
};
}; CppCon 2014 • Stackless Resumable Functions 39
Simple Happy path and reasonable error
propagation
{
auto && __tmp = <expr>;
if (! await_ready(__tmp)) {
await_suspend(__tmp, <resumption-function-object>);
suspend
resume
}
if (<promise>.cancellation_requested()) goto <end-label>;
return await_resume(__tmp);
}
• https://github.com/GorNishanov/await/
• Draft snapshot: D4134 Resumable Functions v2.pdf
• In October 2014 look for
• N4134 at http://isocpp.org
• http://open-std.org/JTC1/SC22/WG21/
Coroutine Frame
ret-main savedRBP RSI = a Coroutine
RBX = b Promise
slot1 slot2
RBP = $fp saved
RDI slot
RDI
slot3 slot4
saved
RSI slot
RSI
saved RBX
RBX slot
RAX = &__range
saved
RIP slot
RIP
struct iterator {
for(…;…; ++__begin) iterator& operator ++() {
RSP Stack resume_cb(); return *this; }
ret-addr __range RCX = $fp …
slot1 slot2
savedRDI
resumable_handle<Promise> resume_cb;
};
slot3
savedRSI slot4
savedRBX Heap
RDI = n
Coroutine Frame
ret-main savedRBP RSI = a Coroutine
RBX = b Promise
slot1 slot2
slot3 slot4 RBP = $fp saved
RDI slot
RDI
saved
RSI slot
RSI
RBX slot
saved RBX
saved
RIP slot
RIP
{
auto && __tmp = <expr>;
if (! await_ready(__tmp) &&
await_suspend(__tmp, <resumption-function-object>) {
suspend
resume
}
if (<promise>.cancellation_requested()) goto <end-label>;
return await_resume(__tmp);
}
library:
suspend_now
generator<T>::promise_type::yield_value(T const& expr) {
this->current_value = &expr;
return{};
}
me->IoResult = IoResult;
me->NumberOfBytesTransferred = NumberOfBytesTransferred;
me->resume();
}
};
StartThreadpoolIo(conn.io);
auto error = detail::TcpSocket::Connect(conn.handle, remote, this);
if (error) { CancelThreadpoolIo(conn.io); throw_error(GetLastError());
}
Connection await_resume() {
if (conn.error) throw_error(error);
return std::move(conn);
}
};
std :: cout << “equal = “ << std::equal (begin (reader1), end( reader1), begin(reader2))
<< std :: endl ;
Recursive Tree Walk (Stackless)
generator<std::string> traverse(node_t* n)
{
if (p->left) yield traverse(p->left);
yield p->name;
if (p->right) yield traverse(p->right);
}