Skip to content

Commit

Permalink
support for asio::yield_context
Browse files Browse the repository at this point in the history
  • Loading branch information
klemens-morgenstern committed Apr 23, 2024
1 parent 187781b commit d87eefc
Show file tree
Hide file tree
Showing 11 changed files with 439 additions and 57 deletions.
20 changes: 18 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,10 @@ jobs:
cp -r $GITHUB_WORKSPACE/* libs/$LIBRARY
git submodule update --init tools/boostdep
python3 tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY
git submodule update --init libs/test
# Temporary workaround
git submodule update --init libs/callable_traits
git submodule update --init libs/context
git submodule update --init libs/chrono
git submodule update --init libs/ratio
Expand Down Expand Up @@ -165,6 +167,8 @@ jobs:
python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" %LIBRARY%
git submodule update --init libs/test
# temporary workaround,
git submodule update --init libs/callable_traits
git submodule update --init libs/context
git submodule update --init libs/chrono
git submodule update --init libs/ratio
cmd /c bootstrap
Expand Down Expand Up @@ -221,6 +225,8 @@ jobs:
python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY
git submodule update --init libs/test
# Temporary workaround
git submodule update --init libs/callable_traits
git submodule update --init libs/context
git submodule update --init libs/chrono
git submodule update --init libs/ratio
Expand Down Expand Up @@ -277,6 +283,8 @@ jobs:
python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY
git submodule update --init libs/test
# Temporary workaround
git submodule update --init libs/callable_traits
git submodule update --init libs/context
git submodule update --init libs/chrono
git submodule update --init libs/ratio
Expand Down Expand Up @@ -350,6 +358,8 @@ jobs:
python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY
git submodule update --init libs/test
# Temporary workaround
git submodule update --init libs/callable_traits
git submodule update --init libs/context
git submodule update --init libs/chrono
git submodule update --init libs/ratio
Expand Down Expand Up @@ -403,6 +413,8 @@ jobs:
python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" %LIBRARY%
git submodule update --init libs/test
# Temporary workaround
git submodule update --init libs/callable_traits
git submodule update --init libs/context
git submodule update --init libs/chrono
git submodule update --init libs/ratio
Expand Down Expand Up @@ -458,6 +470,8 @@ jobs:
python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" %LIBRARY%
git submodule update --init libs/test
# Temporary workaround
git submodule update --init libs/callable_traits
git submodule update --init libs/context
git submodule update --init libs/chrono
git submodule update --init libs/ratio
Expand Down Expand Up @@ -531,8 +545,10 @@ jobs:
xcopy /s /e /q %GITHUB_WORKSPACE% libs\%LIBRARY%\
git submodule update --init tools/boostdep
python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" %LIBRARY%
git submodule update --init libs/test
# Temporary workaround
git submodule update --init libs/callable_traits
git submodule update --init libs/context
git submodule update --init libs/chrono
git submodule update --init libs/ratio
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ if (NOT BOOST_COBALT_IS_ROOT)
target_link_libraries(boost_cobalt
PUBLIC
Boost::asio
Boost::callable_traits
Boost::circular_buffer
Boost::config
Boost::container
Expand Down
3 changes: 3 additions & 0 deletions doc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ include::reference/error.adoc[]
include::reference/config.adoc[]
include::reference/leaf.adoc[]

include::reference/experimental/context.adoc[]


= In-Depth

include::background/custom_executors.adoc[]
Expand Down
69 changes: 69 additions & 0 deletions doc/reference/experimental/context.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
[#context]
== cobalt/experimental/context.hpp

WARNING: This is (most likely) undefined behaviour, since the violates a precondition in the standard. A paper to address this can be found here (https://isocpp.org/files/papers/P3203R0.html).

This header provides `experimental` support for using `boost.fiber` based stackful coroutines
as if they were C++20 coroutines. That is, they can use `awaitables` by being able to be put into a `coroutine_handle`.
Likewise the implementation uses a C++20 coroutine promise and runs is as if it was a C++20 coroutine.

[source,cpp]
----
//
void delay(experimental::context<promise<void>> h, std::chrono::milliseconds ms)
{
asio::steady_timer tim{co_await cobalt::this_coro::executor, ms};
h.await(tim.async_wait(cobalt::use_op)); // instead of co_await.
}
cobalt::main co_main(int argc, char *argv[])
{
cobalt::promise<void> dl = cobalt::experimental::make_context(&delay, 50);
co_await dl;
co_return 0;
}
----

=== Reference

[source,cpp]
----
// The internal coroutine context.
/// Args are the function arguments after the handle.
template<typename Return, typename ... Args>
struct context
{
// Get a handle to the promise
promise_type & promise();
const promise_type & promise() const;
// Convert it to any context if the underlying promise is the same
template<typename Return_, typename ... Args_>
constexpr operator context<Return_, Args_...>() const;
// Await something. Uses await_transform automatically.
template<typename Awaitable>
auto await(Awaitable && aw);
// Yield a value, if supported by the promise.
template<typename Yield>
auto yield(Yield && value);
};
// Create a fiber with a custom stack allocator (see boost.fiber for details) and explicit result (e.g. `promise<void>`)
template<typename Return, typename ... Args, std::invocable<context<Return, Args...>, Args...> Func, typename StackAlloc>
auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Args && ... args);
// Create a fiber with the default allocator and explicit result (e.g. `promise<void>`)
template<typename Return, typename ... Args, std::invocable<context<Return, Args...>, Args...> Func>
auto make_context(Func && func, Args && ... args);
// Create a fiber with a custom stack allocator and implicit result (deduced from the first argument to func).
template<typename ... Args, typename Func, typename StackAlloc>
auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Args && ... args);
// Create a fiber with the default stack allocator and implicit result (deduced from the first argument to func).
template<typename ... Args, typename Func>
auto make_context(Func && func, Args && ... args);
----

4 changes: 1 addition & 3 deletions include/boost/cobalt/detail/fork.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,7 @@ struct fork
return st.resource.allocate(size);
}

template<typename ... Rest>
void operator delete(void * raw, const std::size_t size, Rest && ...) noexcept;
void operator delete(void *, const std::size_t) noexcept {}
void operator delete(void *) noexcept {}

template<typename ... Rest>
promise_type(shared_state & st, Rest & ...)
Expand Down
121 changes: 72 additions & 49 deletions include/boost/cobalt/experimental/context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
//
// 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)
#ifndef BOOST_COBALT_CONTEXT_HPP
#define BOOST_COBALT_CONTEXT_HPP
#ifndef BOOST_COBALT_EXPERIMENTAL_CONTEXT_HPP
#define BOOST_COBALT_EXPERIMENTAL_CONTEXT_HPP

#include <boost/callable_traits/args.hpp>
#include <boost/context/fiber.hpp>
#include <boost/context/fixedsize_stack.hpp>

#include <boost/cobalt/concepts.hpp>
#include <boost/cobalt/experimental/frame.hpp>
#include <boost/cobalt/config.hpp>
#include <coroutine>
#include <new>
// this is all UB according to the standard. BUT it shouldn't be!

namespace boost::cobalt::experimental
{
Expand All @@ -25,25 +25,20 @@ namespace detail
{

template<typename Promise>
struct fiber_frame
struct context_frame : frame<context_frame<Promise>, Promise>
{
void (*resume_) (fiber_frame *) = +[](fiber_frame * ff) { ff->resume();};
void (*destroy_)(fiber_frame *) = +[](fiber_frame * ff) { ff->destroy();};

Promise promise;

boost::context::fiber caller, callee;

void (*after_resume)(fiber_frame *, void *) = nullptr;
void (*after_resume)(context_frame *, void *) = nullptr;
void * after_resume_p;

template<typename ... Args>
requires std::constructible_from<Promise, Args...>
fiber_frame(Args && ... args) : promise(args...) {}
context_frame(Args && ... args) : frame<context_frame, Promise>(args...) {}

template<typename ... Args>
requires (!std::constructible_from<Promise, Args...> && std::is_default_constructible_v<Promise>)
fiber_frame(Args && ...) {}
context_frame(Args && ...) {}

void resume()
{
Expand All @@ -54,32 +49,53 @@ struct fiber_frame
void destroy()
{
auto c = std::exchange(callee, {});
this->~fiber_frame();
this->~context_frame();
}

template<typename Awaitable>
auto do_resume(void * )
{
return +[](context_frame * this_, void * p)
{
auto aw_ = static_cast<Awaitable*>(p);
auto h = std::coroutine_handle<Promise>::from_address(this_) ;
aw_->await_suspend(h);
};
}

template<typename Awaitable>
auto do_resume(bool * )
{
return +[](context_frame * this_, void * p)
{
auto aw_ = static_cast<Awaitable*>(p);
auto h = std::coroutine_handle<Promise>::from_address(this_) ;
if (!aw_->await_suspend(h))
h.resume();
};
}

template<typename Awaitable, typename Promise_>
auto do_resume(std::coroutine_handle<Promise_> * )
{
return +[](context_frame * this_, void * p)
{
auto aw_ = static_cast<Awaitable*>(p);
auto h = std::coroutine_handle<Promise>::from_address(this_) ;
aw_->await_suspend(h).resume();
};
}


template<typename Awaitable>
auto do_await(Awaitable aw)
{
if (!aw.await_ready())
{
after_resume_p = & aw;
after_resume =
+[](fiber_frame * this_, void * p)
{
auto aw_ = static_cast<Awaitable*>(p);
auto h = std::coroutine_handle<Promise>::from_address(this_) ;
if constexpr (requires {{aw_->await_suspend(h)} -> std::same_as<void>;})
aw_->await_suspend(h);
else if constexpr (requires {{aw_->await_suspend(h)} -> std::same_as<bool>;})
{
if (!aw_->await_suspend(h))
h.resume();
}
else if constexpr (requires {{aw_->await_suspend(h)} -> std::convertible_to<std::coroutine_handle<void>>;})
aw_->await_suspend(h).resume();
else
static_assert(std::is_void_v<Awaitable>, "Invalid return from await_suspend()");
};
after_resume = do_resume<Awaitable>(
static_cast<decltype(aw.await_suspend(std::declval<std::coroutine_handle<Promise>>()))*>(nullptr)
);
caller = std::move(caller).resume();
}
return aw.await_resume();
Expand Down Expand Up @@ -162,6 +178,20 @@ struct await_transform_impl : await_transform_base, T
template<typename T>
concept has_await_transform = ! requires (await_transform_impl<T> & p) {p.await_transform(await_transform_base::dummy{});};

template<typename Promise, typename Context, typename Func, typename ... Args>
void do_return(std::true_type /* is_void */, Promise& promise, Context ctx, Func && func, Args && ... args)
{
std::forward<Func>(func)(ctx, std::forward<Args>(args)...);
promise.return_void();
}

template<typename Promise, typename Context, typename Func, typename ... Args>
void do_return(std::false_type /* is_void */, Promise& promise, Context ctx, Func && func, Args && ... args)
{
promise.return_value(std::forward<Func>(func)(ctx, std::forward<Args>(args)...));
}


}

template<typename Return, typename ... Args>
Expand Down Expand Up @@ -242,14 +272,14 @@ struct context

private:

context(detail::fiber_frame<promise_type> * frame) : frame_(frame) {}
context(detail::context_frame<promise_type> * frame) : frame_(frame) {}
template<typename, typename ...>
friend struct context;

//template<typename >
friend struct detail::fiber_frame<promise_type>;
friend struct detail::context_frame<promise_type>;

detail::fiber_frame<promise_type> * frame_;
detail::context_frame<promise_type> * frame_;
};

template<typename Return, typename ... Args, std::invocable<context<Return, Args...>, Args...> Func, typename StackAlloc>
Expand All @@ -258,17 +288,17 @@ auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Arg
auto sctx_ = salloc.allocate();

using promise_type = typename std::coroutine_traits<Return, Args...>::promise_type;
void * p = static_cast<char*>(sctx_.sp) - sizeof(detail::fiber_frame<promise_type>);
auto sz = sctx_.size - sizeof(detail::fiber_frame<promise_type>);
void * p = static_cast<char*>(sctx_.sp) - sizeof(detail::context_frame<promise_type>);
auto sz = sctx_.size - sizeof(detail::context_frame<promise_type>);

if (auto diff = reinterpret_cast<std::uintptr_t>(p) % alignof(detail::fiber_frame<promise_type>); diff != 0u)
if (auto diff = reinterpret_cast<std::uintptr_t>(p) % alignof(detail::context_frame<promise_type>); diff != 0u)
{
p = static_cast<char*>(p) - diff;
sz -= diff;
}

boost::context::preallocated psc{p, sz, sctx_};
auto f = new (p) detail::fiber_frame<promise_type>(args...);
auto f = new (p) detail::context_frame<promise_type>(args...);

auto res = f->promise.get_return_object();

Expand All @@ -278,11 +308,11 @@ auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Arg

struct invoker
{
detail::fiber_frame<promise_type> * frame;
detail::context_frame<promise_type> * frame;
mutable Func func;
mutable std::tuple<Args...> args;

invoker(detail::fiber_frame<promise_type> * frame, Func && func, Args && ... args)
invoker(detail::context_frame<promise_type> * frame, Func && func, Args && ... args)
: frame(frame), func(std::forward<Func>(func)), args(std::forward<Args>(args)...)
{
}
Expand All @@ -303,15 +333,8 @@ auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Arg
auto ctx = frame->template get_context<Return, Args...>();
using return_type = decltype(std::forward<Func>(func)(ctx, std::forward<Args>(args_)...));

if constexpr (std::is_void_v<return_type>)
{
std::forward<Func>(func)(ctx, std::forward<Args>(args_)...);
frame->promise.return_void();
}
else
frame->promise.return_value(std::forward<Func>(func)(ctx, std::forward<Args>(args_)...));


detail::do_return(std::is_void<return_type>{}, frame->promise, ctx,
std::forward<Func>(func), std::forward<Args>(args_)...);
},
std::move(args));
}
Expand Down Expand Up @@ -364,4 +387,4 @@ auto make_context(Func && func, Args && ... args)

}

#endif //BOOST_COBALT_CONTEXT_HPP
#endif //BOOST_COBALT_EXPERIMENTAL_CONTEXT_HPP
Loading

0 comments on commit d87eefc

Please sign in to comment.