forked from OctaForge/libostd
add initial coroutine module (slow ucontext_t, POSIX only, WiP)
This commit is contained in:
parent
d33ca88d0a
commit
8ecaa338bc
201
ostd/coroutine.hh
Normal file
201
ostd/coroutine.hh
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
/* Coroutines for OctaSTD.
|
||||||
|
*
|
||||||
|
* This file is part of OctaSTD. See COPYING.md for further information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef OSTD_COROUTINE_HH
|
||||||
|
#define OSTD_COROUTINE_HH
|
||||||
|
|
||||||
|
/* currently there is only POSIX support using obsolete ucontext stuff...
|
||||||
|
* we will want to implement Windows support using its fibers and also
|
||||||
|
* lightweight custom context switching with handwritten asm where we
|
||||||
|
* want this to run, but ucontext will stay as a fallback
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
#include <ucontext.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <exception>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
#include "ostd/types.hh"
|
||||||
|
|
||||||
|
namespace ostd {
|
||||||
|
|
||||||
|
constexpr size_t COROUTINE_DEFAULT_STACK_SIZE = SIGSTKSZ;
|
||||||
|
|
||||||
|
struct coroutine_error: std::runtime_error {
|
||||||
|
using std::runtime_error::runtime_error;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct coroutine_context {
|
||||||
|
coroutine_context(size_t ss, void (*callp)(void *), void *data):
|
||||||
|
p_stack(new byte[ss])
|
||||||
|
{
|
||||||
|
getcontext(&p_coro);
|
||||||
|
p_coro.uc_link = &p_orig;
|
||||||
|
p_coro.uc_stack.ss_sp = p_stack.get();
|
||||||
|
p_coro.uc_stack.ss_size = ss;
|
||||||
|
using mcfp = void (*)();
|
||||||
|
using cpfp = void (*)(void *);
|
||||||
|
if constexpr(sizeof(void *) > sizeof(int)) {
|
||||||
|
union intu {
|
||||||
|
struct { int p1, p2; };
|
||||||
|
void *p;
|
||||||
|
cpfp fp;
|
||||||
|
};
|
||||||
|
intu ud, uf;
|
||||||
|
ud.p = data;
|
||||||
|
uf.fp = callp;
|
||||||
|
using amcfp = void (*)(int, int, int, int);
|
||||||
|
amcfp mcf = [](int f1, int f2, int d1, int d2) -> void {
|
||||||
|
intu ud2, uf2;
|
||||||
|
uf2.p1 = f1, uf2.p2 = f2;
|
||||||
|
ud2.p1 = d1, ud2.p2 = d2;
|
||||||
|
(uf2.fp)(ud2.p);
|
||||||
|
};
|
||||||
|
makecontext(
|
||||||
|
&p_coro, reinterpret_cast<mcfp>(mcf), 4,
|
||||||
|
uf.p1, uf.p2, ud.p1, ud.p2
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
using amcfp = void (*)(int, int);
|
||||||
|
amcfp mcf = [](int f1, int d1) {
|
||||||
|
reinterpret_cast<cpfp>(f1)(reinterpret_cast<void *>(d1));
|
||||||
|
};
|
||||||
|
makecontext(&p_coro, reinterpret_cast<mcfp>(mcf), 2, callp, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void call() {
|
||||||
|
if (p_finished) {
|
||||||
|
throw coroutine_error{"dead coroutine"};
|
||||||
|
}
|
||||||
|
swapcontext(&p_orig, &p_coro);
|
||||||
|
if (p_except) {
|
||||||
|
std::rethrow_exception(std::move(p_except));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void yield_swap() {
|
||||||
|
swapcontext(&p_coro, &p_orig);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_eh() {
|
||||||
|
p_except = std::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_done() {
|
||||||
|
p_finished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_done() const {
|
||||||
|
return p_finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/* TODO: new'ing the stack is sub-optimal */
|
||||||
|
std::unique_ptr<byte[]> p_stack;
|
||||||
|
ucontext_t p_coro;
|
||||||
|
ucontext_t p_orig;
|
||||||
|
std::exception_ptr p_except;
|
||||||
|
bool p_finished = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct coroutine;
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
/* we need this because yield is specialized based on result */
|
||||||
|
template<typename R, typename ...A>
|
||||||
|
struct coro_base {
|
||||||
|
coro_base(void (*callp)(void *), size_t ss):
|
||||||
|
p_ctx(ss, callp, this)
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::tuple<A &&...> yield(R &&ret) {
|
||||||
|
p_result = std::forward<R>(ret);
|
||||||
|
p_ctx.yield_swap();
|
||||||
|
return std::move(p_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
R ctx_call(A ...args) {
|
||||||
|
p_args = std::forward_as_tuple(std::forward<A>(args)...);
|
||||||
|
p_ctx.call();
|
||||||
|
return std::forward<R>(p_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<A...> p_args;
|
||||||
|
R p_result;
|
||||||
|
coroutine_context p_ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename ...A>
|
||||||
|
struct coro_base<void, A...> {
|
||||||
|
coro_base(void (*callp)(void *), size_t ss):
|
||||||
|
p_ctx(ss, callp, this)
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::tuple<A &&...> yield() {
|
||||||
|
p_ctx.yield_swap();
|
||||||
|
return std::move(p_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void ctx_call(A ...args) {
|
||||||
|
p_args = std::forward_as_tuple(std::forward<A>(args)...);
|
||||||
|
p_ctx.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<A...> p_args;
|
||||||
|
coroutine_context p_ctx;
|
||||||
|
};
|
||||||
|
} /* namespace detail */
|
||||||
|
|
||||||
|
template<typename R, typename ...A>
|
||||||
|
struct coroutine<R(A...)>: detail::coro_base<R, A...> {
|
||||||
|
coroutine(
|
||||||
|
std::function<R(coroutine<R(A...)> &, A...)> func,
|
||||||
|
size_t ss = COROUTINE_DEFAULT_STACK_SIZE
|
||||||
|
):
|
||||||
|
detail::coro_base<R, A...>(&ctx_func, ss), p_func(std::move(func))
|
||||||
|
{}
|
||||||
|
|
||||||
|
operator bool() const {
|
||||||
|
return this->p_ctx.is_done();
|
||||||
|
}
|
||||||
|
|
||||||
|
R operator()(A ...args) {
|
||||||
|
return this->ctx_call(std::forward<A>(args)...);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
template<size_t ...I>
|
||||||
|
R call(std::index_sequence<I...>) {
|
||||||
|
return p_func(*this, std::forward<A>(std::get<I>(this->p_args))...);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ctx_func(void *data) {
|
||||||
|
coroutine &self = *(static_cast<coroutine *>(data));
|
||||||
|
try {
|
||||||
|
using indices = std::index_sequence_for<A...>;
|
||||||
|
if constexpr(std::is_same_v<R, void>) {
|
||||||
|
self.call(indices{});
|
||||||
|
} else {
|
||||||
|
self.p_result = self.call(indices{});
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
self.p_ctx.set_eh();
|
||||||
|
}
|
||||||
|
self.p_ctx.set_done();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::function<R(coroutine<R(A...)> &, A...)> p_func;
|
||||||
|
};
|
||||||
|
|
||||||
|
} /* namespace ostd */
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in a new issue