merge coroutine context into coroutine.hh
parent
ca40fb1e0c
commit
94915ee5fc
|
@ -6,16 +6,19 @@
|
|||
#ifndef OSTD_COROUTINE_HH
|
||||
#define OSTD_COROUTINE_HH
|
||||
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <typeinfo>
|
||||
#include <utility>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <optional>
|
||||
|
||||
#include "ostd/platform.hh"
|
||||
#include "ostd/types.hh"
|
||||
#include "ostd/range.hh"
|
||||
|
||||
#include "ostd/internal/context.hh"
|
||||
#include "ostd/context_stack.hh"
|
||||
|
||||
namespace ostd {
|
||||
|
||||
|
@ -23,6 +26,228 @@ struct coroutine_error: std::runtime_error {
|
|||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
struct coroutine_context;
|
||||
|
||||
namespace detail {
|
||||
/* from boost.fcontext */
|
||||
using fcontext_t = void *;
|
||||
|
||||
struct transfer_t {
|
||||
fcontext_t ctx;
|
||||
void *data;
|
||||
};
|
||||
|
||||
extern "C" OSTD_EXPORT
|
||||
transfer_t OSTD_CDECL ostd_jump_fcontext(
|
||||
fcontext_t const to, void *vp
|
||||
);
|
||||
|
||||
extern "C" OSTD_EXPORT
|
||||
fcontext_t OSTD_CDECL ostd_make_fcontext(
|
||||
void *sp, size_t size, void (*fn)(transfer_t)
|
||||
);
|
||||
|
||||
extern "C" OSTD_EXPORT
|
||||
transfer_t OSTD_CDECL ostd_ontop_fcontext(
|
||||
fcontext_t const to, void *vp, transfer_t (*fn)(transfer_t)
|
||||
);
|
||||
|
||||
OSTD_EXPORT extern thread_local coroutine_context *coro_current;
|
||||
} /* namespace detail */
|
||||
|
||||
struct coroutine_context {
|
||||
static coroutine_context *current() {
|
||||
return detail::coro_current;
|
||||
}
|
||||
|
||||
protected:
|
||||
coroutine_context() {}
|
||||
|
||||
/* coroutine context must be polymorphic */
|
||||
virtual ~coroutine_context() {
|
||||
unwind();
|
||||
free_stack();
|
||||
}
|
||||
|
||||
coroutine_context(coroutine_context const &) = delete;
|
||||
coroutine_context(coroutine_context &&c):
|
||||
p_stack(std::move(c.p_stack)), p_coro(c.p_coro), p_orig(c.p_orig),
|
||||
p_except(std::move(c.p_except)), p_state(c.p_state), p_free(c.p_free)
|
||||
{
|
||||
c.p_coro = c.p_orig = nullptr;
|
||||
c.p_stack = { nullptr, 0 };
|
||||
c.p_free = nullptr;
|
||||
c.set_dead();
|
||||
}
|
||||
|
||||
coroutine_context &operator=(coroutine_context const &) = delete;
|
||||
coroutine_context &operator=(coroutine_context &&c) {
|
||||
swap(c);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void call() {
|
||||
set_exec();
|
||||
/* switch to new coroutine */
|
||||
coroutine_context *curr = std::exchange(detail::coro_current, this);
|
||||
coro_jump();
|
||||
/* switch back to previous */
|
||||
detail::coro_current = curr;
|
||||
rethrow();
|
||||
}
|
||||
|
||||
void coro_jump() {
|
||||
p_coro = detail::ostd_jump_fcontext(p_coro, this).ctx;
|
||||
}
|
||||
|
||||
void yield_jump() {
|
||||
p_state = state::HOLD;
|
||||
p_orig = detail::ostd_jump_fcontext(p_orig, nullptr).ctx;
|
||||
}
|
||||
|
||||
void yield_done() {
|
||||
set_dead();
|
||||
p_orig = detail::ostd_jump_fcontext(p_orig, nullptr).ctx;
|
||||
}
|
||||
|
||||
bool is_hold() const {
|
||||
return (p_state == state::HOLD);
|
||||
}
|
||||
|
||||
bool is_dead() const {
|
||||
return (p_state == state::TERM);
|
||||
}
|
||||
|
||||
void set_dead() {
|
||||
p_state = state::TERM;
|
||||
}
|
||||
|
||||
void set_exec() {
|
||||
p_state = state::EXEC;
|
||||
}
|
||||
|
||||
void rethrow() {
|
||||
if (p_except) {
|
||||
std::rethrow_exception(std::move(p_except));
|
||||
}
|
||||
}
|
||||
|
||||
void swap(coroutine_context &other) noexcept {
|
||||
using std::swap;
|
||||
swap(p_stack, other.p_stack);
|
||||
swap(p_coro, other.p_coro);
|
||||
swap(p_orig, other.p_orig);
|
||||
swap(p_except, other.p_except);
|
||||
swap(p_state, other.p_state);
|
||||
swap(p_free, other.p_free);
|
||||
}
|
||||
|
||||
template<typename C, typename SA>
|
||||
void make_context(SA &sa) {
|
||||
p_stack = sa.allocate();
|
||||
|
||||
void *sp = get_stack_ptr<SA>();
|
||||
size_t asize = p_stack.size -
|
||||
(static_cast<byte *>(p_stack.ptr) - static_cast<byte *>(sp));
|
||||
|
||||
p_coro = detail::ostd_make_fcontext(sp, asize, &context_call<C, SA>);
|
||||
new (sp) SA(std::move(sa));
|
||||
p_free = &free_stack_call<SA>;
|
||||
}
|
||||
|
||||
private:
|
||||
/* we also store the stack allocator at the end of the stack */
|
||||
template<typename SA>
|
||||
void *get_stack_ptr() {
|
||||
/* 16 byte stack pointer alignment */
|
||||
constexpr size_t salign = 16;
|
||||
/* makes enough space so that we can store the allocator at
|
||||
* stack pointer location (we'll need it for dealloc later)
|
||||
*/
|
||||
constexpr size_t sasize = sizeof(SA);
|
||||
|
||||
void *sp = static_cast<byte *>(p_stack.ptr) - sasize - salign;
|
||||
size_t space = sasize + salign;
|
||||
sp = std::align(salign, sasize, sp, space);
|
||||
|
||||
return sp;
|
||||
}
|
||||
|
||||
struct forced_unwind {
|
||||
detail::fcontext_t ctx;
|
||||
forced_unwind(detail::fcontext_t c): ctx(c) {}
|
||||
};
|
||||
|
||||
enum class state {
|
||||
HOLD = 0, EXEC, TERM
|
||||
};
|
||||
|
||||
void unwind() {
|
||||
if (is_dead()) {
|
||||
/* this coroutine was either initialized with a null function or
|
||||
* it's already terminated and thus its stack has already unwound
|
||||
*/
|
||||
return;
|
||||
}
|
||||
if (!p_orig) {
|
||||
/* this coroutine never got to live :( */
|
||||
return;
|
||||
}
|
||||
detail::ostd_ontop_fcontext(
|
||||
std::exchange(p_coro, nullptr), nullptr,
|
||||
[](detail::transfer_t t) -> detail::transfer_t {
|
||||
throw forced_unwind{t.ctx};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
template<typename SA>
|
||||
static void free_stack_call(void *data) {
|
||||
auto &self = *(static_cast<coroutine_context *>(data));
|
||||
auto &sa = *(static_cast<SA *>(self.get_stack_ptr<SA>()));
|
||||
SA dsa{std::move(sa)};
|
||||
sa.~SA();
|
||||
dsa.deallocate(self.p_stack);
|
||||
}
|
||||
|
||||
void free_stack() {
|
||||
if (p_free) {
|
||||
p_free(this);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename C, typename SA>
|
||||
static void context_call(detail::transfer_t t) {
|
||||
auto &self = *(static_cast<C *>(t.data));
|
||||
self.p_orig = t.ctx;
|
||||
if (self.is_hold()) {
|
||||
/* we never got to execute properly, we're HOLD because we
|
||||
* jumped here without setting the state to EXEC before that
|
||||
*/
|
||||
goto release;
|
||||
}
|
||||
try {
|
||||
self.resume_call();
|
||||
} catch (coroutine_context::forced_unwind v) {
|
||||
/* forced_unwind is unique */
|
||||
self.p_orig = v.ctx;
|
||||
} catch (...) {
|
||||
/* some other exception, will be rethrown later */
|
||||
self.p_except = std::current_exception();
|
||||
}
|
||||
/* switch back, release stack */
|
||||
release:
|
||||
self.yield_done();
|
||||
}
|
||||
|
||||
stack_context p_stack;
|
||||
detail::fcontext_t p_coro = nullptr;
|
||||
detail::fcontext_t p_orig = nullptr;
|
||||
std::exception_ptr p_except;
|
||||
state p_state = state::HOLD;
|
||||
void (*p_free)(void *) = nullptr;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct coroutine;
|
||||
|
||||
|
|
|
@ -1,243 +0,0 @@
|
|||
/* Context switching for coroutines.
|
||||
*
|
||||
* This file is part of OctaSTD. See COPYING.md for futher information.
|
||||
*/
|
||||
|
||||
#ifndef OSTD_INTERNAL_CONTEXT_HH
|
||||
#define OSTD_INTERNAL_CONTEXT_HH
|
||||
|
||||
#include <exception>
|
||||
#include <utility>
|
||||
#include <typeinfo>
|
||||
|
||||
#include "ostd/types.hh"
|
||||
#include "ostd/platform.hh"
|
||||
#include "ostd/context_stack.hh"
|
||||
|
||||
namespace ostd {
|
||||
|
||||
struct coroutine_context;
|
||||
|
||||
namespace detail {
|
||||
/* from boost.fcontext */
|
||||
using fcontext_t = void *;
|
||||
|
||||
struct transfer_t {
|
||||
fcontext_t ctx;
|
||||
void *data;
|
||||
};
|
||||
|
||||
extern "C" OSTD_EXPORT
|
||||
transfer_t OSTD_CDECL ostd_jump_fcontext(
|
||||
fcontext_t const to, void *vp
|
||||
);
|
||||
|
||||
extern "C" OSTD_EXPORT
|
||||
fcontext_t OSTD_CDECL ostd_make_fcontext(
|
||||
void *sp, size_t size, void (*fn)(transfer_t)
|
||||
);
|
||||
|
||||
extern "C" OSTD_EXPORT
|
||||
transfer_t OSTD_CDECL ostd_ontop_fcontext(
|
||||
fcontext_t const to, void *vp, transfer_t (*fn)(transfer_t)
|
||||
);
|
||||
|
||||
OSTD_EXPORT extern thread_local coroutine_context *coro_current;
|
||||
} /* namespace detail */
|
||||
|
||||
struct coroutine_context {
|
||||
static coroutine_context *current() {
|
||||
return detail::coro_current;
|
||||
}
|
||||
|
||||
protected:
|
||||
coroutine_context() {}
|
||||
|
||||
/* coroutine context must be polymorphic */
|
||||
virtual ~coroutine_context() {
|
||||
unwind();
|
||||
free_stack();
|
||||
}
|
||||
|
||||
coroutine_context(coroutine_context const &) = delete;
|
||||
coroutine_context(coroutine_context &&c):
|
||||
p_stack(std::move(c.p_stack)), p_coro(c.p_coro), p_orig(c.p_orig),
|
||||
p_except(std::move(c.p_except)), p_state(c.p_state), p_free(c.p_free)
|
||||
{
|
||||
c.p_coro = c.p_orig = nullptr;
|
||||
c.p_stack = { nullptr, 0 };
|
||||
c.p_free = nullptr;
|
||||
c.set_dead();
|
||||
}
|
||||
|
||||
coroutine_context &operator=(coroutine_context const &) = delete;
|
||||
coroutine_context &operator=(coroutine_context &&c) {
|
||||
swap(c);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void call() {
|
||||
set_exec();
|
||||
/* switch to new coroutine */
|
||||
coroutine_context *curr = std::exchange(detail::coro_current, this);
|
||||
coro_jump();
|
||||
/* switch back to previous */
|
||||
detail::coro_current = curr;
|
||||
rethrow();
|
||||
}
|
||||
|
||||
void coro_jump() {
|
||||
p_coro = detail::ostd_jump_fcontext(p_coro, this).ctx;
|
||||
}
|
||||
|
||||
void yield_jump() {
|
||||
p_state = state::HOLD;
|
||||
p_orig = detail::ostd_jump_fcontext(p_orig, nullptr).ctx;
|
||||
}
|
||||
|
||||
void yield_done() {
|
||||
set_dead();
|
||||
p_orig = detail::ostd_jump_fcontext(p_orig, nullptr).ctx;
|
||||
}
|
||||
|
||||
bool is_hold() const {
|
||||
return (p_state == state::HOLD);
|
||||
}
|
||||
|
||||
bool is_dead() const {
|
||||
return (p_state == state::TERM);
|
||||
}
|
||||
|
||||
void set_dead() {
|
||||
p_state = state::TERM;
|
||||
}
|
||||
|
||||
void set_exec() {
|
||||
p_state = state::EXEC;
|
||||
}
|
||||
|
||||
void rethrow() {
|
||||
if (p_except) {
|
||||
std::rethrow_exception(std::move(p_except));
|
||||
}
|
||||
}
|
||||
|
||||
void swap(coroutine_context &other) noexcept {
|
||||
using std::swap;
|
||||
swap(p_stack, other.p_stack);
|
||||
swap(p_coro, other.p_coro);
|
||||
swap(p_orig, other.p_orig);
|
||||
swap(p_except, other.p_except);
|
||||
swap(p_state, other.p_state);
|
||||
swap(p_free, other.p_free);
|
||||
}
|
||||
|
||||
template<typename C, typename SA>
|
||||
void make_context(SA &sa) {
|
||||
p_stack = sa.allocate();
|
||||
|
||||
void *sp = get_stack_ptr<SA>();
|
||||
size_t asize = p_stack.size -
|
||||
(static_cast<byte *>(p_stack.ptr) - static_cast<byte *>(sp));
|
||||
|
||||
p_coro = detail::ostd_make_fcontext(sp, asize, &context_call<C, SA>);
|
||||
new (sp) SA(std::move(sa));
|
||||
p_free = &free_stack_call<SA>;
|
||||
}
|
||||
|
||||
private:
|
||||
/* we also store the stack allocator at the end of the stack */
|
||||
template<typename SA>
|
||||
void *get_stack_ptr() {
|
||||
/* 16 byte stack pointer alignment */
|
||||
constexpr size_t salign = 16;
|
||||
/* makes enough space so that we can store the allocator at
|
||||
* stack pointer location (we'll need it for dealloc later)
|
||||
*/
|
||||
constexpr size_t sasize = sizeof(SA);
|
||||
|
||||
void *sp = static_cast<byte *>(p_stack.ptr) - sasize - salign;
|
||||
size_t space = sasize + salign;
|
||||
sp = std::align(salign, sasize, sp, space);
|
||||
|
||||
return sp;
|
||||
}
|
||||
|
||||
struct forced_unwind {
|
||||
detail::fcontext_t ctx;
|
||||
forced_unwind(detail::fcontext_t c): ctx(c) {}
|
||||
};
|
||||
|
||||
enum class state {
|
||||
HOLD = 0, EXEC, TERM
|
||||
};
|
||||
|
||||
void unwind() {
|
||||
if (is_dead()) {
|
||||
/* this coroutine was either initialized with a null function or
|
||||
* it's already terminated and thus its stack has already unwound
|
||||
*/
|
||||
return;
|
||||
}
|
||||
if (!p_orig) {
|
||||
/* this coroutine never got to live :( */
|
||||
return;
|
||||
}
|
||||
detail::ostd_ontop_fcontext(
|
||||
std::exchange(p_coro, nullptr), nullptr,
|
||||
[](detail::transfer_t t) -> detail::transfer_t {
|
||||
throw forced_unwind{t.ctx};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
template<typename SA>
|
||||
static void free_stack_call(void *data) {
|
||||
auto &self = *(static_cast<coroutine_context *>(data));
|
||||
auto &sa = *(static_cast<SA *>(self.get_stack_ptr<SA>()));
|
||||
SA dsa{std::move(sa)};
|
||||
sa.~SA();
|
||||
dsa.deallocate(self.p_stack);
|
||||
}
|
||||
|
||||
void free_stack() {
|
||||
if (p_free) {
|
||||
p_free(this);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename C, typename SA>
|
||||
static void context_call(detail::transfer_t t) {
|
||||
auto &self = *(static_cast<C *>(t.data));
|
||||
self.p_orig = t.ctx;
|
||||
if (self.is_hold()) {
|
||||
/* we never got to execute properly, we're HOLD because we
|
||||
* jumped here without setting the state to EXEC before that
|
||||
*/
|
||||
goto release;
|
||||
}
|
||||
try {
|
||||
self.resume_call();
|
||||
} catch (coroutine_context::forced_unwind v) {
|
||||
/* forced_unwind is unique */
|
||||
self.p_orig = v.ctx;
|
||||
} catch (...) {
|
||||
/* some other exception, will be rethrown later */
|
||||
self.p_except = std::current_exception();
|
||||
}
|
||||
/* switch back, release stack */
|
||||
release:
|
||||
self.yield_done();
|
||||
}
|
||||
|
||||
stack_context p_stack;
|
||||
detail::fcontext_t p_coro = nullptr;
|
||||
detail::fcontext_t p_orig = nullptr;
|
||||
std::exception_ptr p_except;
|
||||
state p_state = state::HOLD;
|
||||
void (*p_free)(void *) = nullptr;
|
||||
};
|
||||
|
||||
} /* namespace ostd */
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue