diff --git a/.drone.star b/.drone.star index aa7cd95f..251512a4 100644 --- a/.drone.star +++ b/.drone.star @@ -13,6 +13,7 @@ deps = [ 'libs/assert', 'libs/beast', 'libs/bind', + 'libs/callable_traits', 'libs/chrono', 'libs/circular_buffer', 'libs/concept_check', diff --git a/include/boost/cobalt/detail/detached.hpp b/include/boost/cobalt/detail/detached.hpp index 97c8b98c..97c8956c 100644 --- a/include/boost/cobalt/detail/detached.hpp +++ b/include/boost/cobalt/detail/detached.hpp @@ -70,7 +70,7 @@ struct detached_promise { } - std::suspend_never initial_suspend() {return {};} + std::suspend_never initial_suspend() noexcept {return {};} std::suspend_never final_suspend() noexcept {return {};} void return_void() {} diff --git a/include/boost/cobalt/detail/generator.hpp b/include/boost/cobalt/detail/generator.hpp index 2c2760cb..6f97fa89 100644 --- a/include/boost/cobalt/detail/generator.hpp +++ b/include/boost/cobalt/detail/generator.hpp @@ -345,7 +345,7 @@ struct generator_promise this->reset_cancellation_source(signal.slot()); } - std::suspend_never initial_suspend() {return {};} + std::suspend_never initial_suspend() noexcept {return {};} struct final_awaitable { diff --git a/include/boost/cobalt/detail/main.hpp b/include/boost/cobalt/detail/main.hpp index 6481e293..d25acb2e 100644 --- a/include/boost/cobalt/detail/main.hpp +++ b/include/boost/cobalt/detail/main.hpp @@ -61,7 +61,7 @@ struct main_promise : signal_helper, return my_resource->deallocate(raw, size); } #endif - std::suspend_always initial_suspend() {return {};} + std::suspend_always initial_suspend() noexcept {return {};} BOOST_COBALT_DECL auto final_suspend() noexcept -> std::suspend_never; diff --git a/include/boost/cobalt/detail/promise.hpp b/include/boost/cobalt/detail/promise.hpp index e9dc78ed..7bb48cff 100644 --- a/include/boost/cobalt/detail/promise.hpp +++ b/include/boost/cobalt/detail/promise.hpp @@ -317,7 +317,7 @@ struct cobalt_promise this->reset_cancellation_source(signal.slot()); } - std::suspend_never initial_suspend() {return {};} + std::suspend_never initial_suspend() noexcept {return {};} auto final_suspend() noexcept { return final_awaitable{this}; diff --git a/include/boost/cobalt/detail/task.hpp b/include/boost/cobalt/detail/task.hpp index d5afb39a..4d08b897 100644 --- a/include/boost/cobalt/detail/task.hpp +++ b/include/boost/cobalt/detail/task.hpp @@ -322,7 +322,7 @@ struct task_promise } }; - auto initial_suspend() + auto initial_suspend() noexcept { return initial_awaitable{this}; diff --git a/include/boost/cobalt/detail/with.hpp b/include/boost/cobalt/detail/with.hpp index cc47d27c..0a0fbf05 100644 --- a/include/boost/cobalt/detail/with.hpp +++ b/include/boost/cobalt/detail/with.hpp @@ -79,7 +79,7 @@ struct with_impl::promise_type e = std::current_exception(); } - std::suspend_always initial_suspend() {return {};} + std::suspend_always initial_suspend() noexcept {return {};} struct final_awaitable { diff --git a/include/boost/cobalt/experimental/context.hpp b/include/boost/cobalt/experimental/context.hpp new file mode 100644 index 00000000..608d4dcf --- /dev/null +++ b/include/boost/cobalt/experimental/context.hpp @@ -0,0 +1,367 @@ +// Copyright (c) 2024 Klemens D. Morgenstern +// +// 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 + +#include +#include +#include + +#include +#include +#include +#include +// this is all UB according to the standard. BUT it shouldn't be! + +namespace boost::cobalt::experimental +{ + +template +struct context; + +namespace detail +{ + +template +struct fiber_frame +{ + 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_p; + + template + requires std::constructible_from + fiber_frame(Args && ... args) : promise(args...) {} + + template + requires (!std::constructible_from && std::is_default_constructible_v) + fiber_frame(Args && ...) {} + + void resume() + { + callee = std::move(callee).resume(); + if (auto af = std::exchange(after_resume, nullptr)) + af(this, after_resume_p); + } + void destroy() + { + auto c = std::exchange(callee, {}); + this->~fiber_frame(); + } + + template + auto do_await(Awaitable aw) + { + if (!aw.await_ready()) + { + after_resume_p = & aw; + after_resume = + +[](fiber_frame * this_, void * p) + { + auto aw_ = static_cast(p); + auto h = std::coroutine_handle::from_address(this_) ; + if constexpr (requires {{aw_->await_suspend(h)} -> std::same_as;}) + aw_->await_suspend(h); + else if constexpr (requires {{aw_->await_suspend(h)} -> std::same_as;}) + { + if (!aw_->await_suspend(h)) + h.resume(); + } + else if constexpr (requires {{aw_->await_suspend(h)} -> std::convertible_to>;}) + aw_->await_suspend(h).resume(); + else + static_assert(std::is_void_v, "Invalid return from await_suspend()"); + }; + caller = std::move(caller).resume(); + } + return aw.await_resume(); + } + + template + context get_context() + { + return context{this}; + } + +}; + + +template +struct stack_allocator : boost::context::fixedsize_stack {}; + +template +requires requires {Promise::operator new(std::size_t{});} +struct stack_allocator +{ + boost::context::stack_context allocate() + { + const auto size = Traits::default_size(); + const auto p = Promise::operator new(size); + + boost::context::stack_context sctx; + sctx.size = size; + sctx.sp = static_cast< char * >( p) + sctx.size; + return sctx; + } + + void deallocate( boost::context::stack_context & sctx) noexcept + { + void * vp = static_cast< char * >( sctx.sp) - sctx.size; + Promise::operator delete(vp, sctx.size); + } +}; + +template + requires requires {Promise::operator new(std::size_t{}, std::decay_t()...);} +struct stack_allocator +{ + std::tuple args; + + boost::context::stack_context allocate() + { + const auto size = Traits::default_size(); + const auto p = std::apply( + [size](auto & ... args_) + { + return Promise::operator new(size, args_...); + }, args); + + boost::context::stack_context sctx; + sctx.size = size; + sctx.sp = static_cast< char * >( p) + sctx.size; + return sctx; + } + + void deallocate( boost::context::stack_context & sctx) noexcept + { + void * vp = static_cast< char * >( sctx.sp) - sctx.size; + Promise::operator delete(vp, sctx.size); + } +}; + + +struct await_transform_base +{ + struct dummy {}; + void await_transform(dummy); +}; + +template +struct await_transform_impl : await_transform_base, T +{ +}; + +template +concept has_await_transform = ! requires (await_transform_impl & p) {p.await_transform(await_transform_base::dummy{});}; + +} + +template +struct context +{ + using return_type = Return; + using promise_type = typename std::coroutine_traits::promise_type; + + promise_type & promise() {return frame_->promise;} + const promise_type & promise() const {return frame_->promise;} + template + requires std::same_as::promise_type> + constexpr operator context() const + { + return {frame_}; + } + + template + requires (detail::has_await_transform && + requires (promise_type & pro, Awaitable && aw) + { + {pro.await_transform(std::forward(aw))} -> awaitable_type; + }) + auto await(Awaitable && aw) + { + return frame_->do_await(frame_->promise.await_transform(std::forward(aw))); + } + + template + requires (detail::has_await_transform && + requires (promise_type & pro, Awaitable && aw) + { + {pro.await_transform(std::forward(aw).operator co_await())} -> awaitable_type; + }) + auto await(Awaitable && aw) + { + return frame_->do_await(frame_->promise.await_transform(std::forward(aw)).operator co_await()); + } + template + requires (detail::has_await_transform && + requires (promise_type & pro, Awaitable && aw) + { + {operator co_await(pro.await_transform(std::forward(aw)))} -> awaitable_type; + }) + auto await(Awaitable && aw) + { + return frame_->do_await(operator co_await(frame_->promise.await_transform(std::forward(aw)))); + } + template Awaitable> + requires (!detail::has_await_transform ) + auto await(Awaitable && aw) + { + return frame_->do_await(std::forward(aw)); + } + + template + requires (!detail::has_await_transform + && requires (Awaitable && aw) {{operator co_await(std::forward(aw))} -> awaitable_type;}) + auto await(Awaitable && aw) + { + return frame_->do_await(operator co_await(std::forward(aw))); + } + + template + requires (!detail::has_await_transform + && requires (Awaitable && aw) {{std::forward(aw).operator co_await()} -> awaitable_type;}) + auto await(Awaitable && aw) + { + return frame_->do_await(std::forward(aw).operator co_await()); + } + + template + requires requires (promise_type & pro, Yield && value) {{pro.yield_value(std::forward(value))} -> awaitable_type;} + auto yield(Yield && value) + { + frame_->do_await(frame_->promise.yield_value(std::forward(value))); + } + + private: + + context(detail::fiber_frame * frame) : frame_(frame) {} + template + friend struct context; + + //template + friend struct detail::fiber_frame; + + detail::fiber_frame * frame_; +}; + +template, Args...> Func, typename StackAlloc> +auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Args && ... args) +{ + auto sctx_ = salloc.allocate(); + + using promise_type = typename std::coroutine_traits::promise_type; + void * p = static_cast(sctx_.sp) - sizeof(detail::fiber_frame); + auto sz = sctx_.size - sizeof(detail::fiber_frame); + + if (auto diff = reinterpret_cast(p) % alignof(detail::fiber_frame); diff != 0u) + { + p = static_cast(p) - diff; + sz -= diff; + } + + boost::context::preallocated psc{p, sz, sctx_}; + auto f = new (p) detail::fiber_frame(args...); + + auto res = f->promise.get_return_object(); + + constexpr auto is_always_lazy = + requires (promise_type & pro) {{pro.initial_suspend()} -> std::same_as;} + && noexcept(f->promise.initial_suspend()); + + struct invoker + { + detail::fiber_frame * frame; + mutable Func func; + mutable std::tuple args; + + invoker(detail::fiber_frame * frame, Func && func, Args && ... args) + : frame(frame), func(std::forward(func)), args(std::forward(args)...) + { + } + + boost::context::fiber operator()(boost::context::fiber && f) const + { + auto & promise = frame->promise; + frame->caller = std::move(f); + + try + { + if (!is_always_lazy) + frame->do_await(promise.initial_suspend()); + + std::apply( + [&](auto && ... args_) + { + auto ctx = frame->template get_context(); + using return_type = decltype(std::forward(func)(ctx, std::forward(args_)...)); + + if constexpr (std::is_void_v) + { + std::forward(func)(ctx, std::forward(args_)...); + frame->promise.return_void(); + } + else + frame->promise.return_value(std::forward(func)(ctx, std::forward(args_)...)); + + + }, + std::move(args)); + } + catch (boost::context::detail::forced_unwind &) { throw; } + catch (...) {promise.unhandled_exception();} + + static_assert(noexcept(promise.final_suspend())); + frame->do_await(promise.final_suspend()); + return std::move(frame->caller); + } + + }; + + f->callee = boost::context::fiber{ + std::allocator_arg, psc, std::forward(salloc), + invoker(f, std::forward(func), std::forward(args)...)}; + + if constexpr (is_always_lazy) + f->promise.initial_suspend(); + else + f->resume(); + + return res; +} + +template, Args...> Func> +auto make_context(Func && func, Args && ... args) +{ + return make_context(std::forward(func), std::allocator_arg, + boost::context::fixedsize_stack(), std::forward(args)...); +} + + +template +auto make_context(Func && func, std::allocator_arg_t, StackAlloc && salloc, Args && ... args) +{ + return make_context>::return_type>( + std::forward(func), std::allocator_arg, std::forward(salloc), std::forward(args)... + ); +} + + +template +auto make_context(Func && func, Args && ... args) +{ + return make_context>::return_type>( + std::forward(func), std::forward(args)... + ); +} + +} + +#endif //BOOST_COBALT_CONTEXT_HPP diff --git a/include/boost/cobalt/experimental/fiber.hpp b/include/boost/cobalt/experimental/fiber.hpp deleted file mode 100644 index 0605ec40..00000000 --- a/include/boost/cobalt/experimental/fiber.hpp +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2023 Klemens D. Morgenstern -// -// 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_FIBER_HPP -#define BOOST_COBALT_FIBER_HPP - -#include - -// this is all UB according to the standard. BUT it shouldn't be! - -namespace boost::cobalt::experimental -{ - -namespace detail -{ - -struct fiber_promise -{ - -}; - -struct fiber_frame -{ - void (*resume_) (fiber_frame *) = +[](fiber_frame * ff) { ff->resume();}; - void (*destroy_)(fiber_frame *) = +[](fiber_frame * ff) { ff->destroy();}; - - fiber_promise promise; - - void resume() {} - void destroy() {} -}; - -} - -} - -#endif //BOOST_COBALT_FIBER_HPP diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 928a65da..d03ea243 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -22,7 +22,7 @@ add_test(NAME boost_cobalt_basic_tests COMMAND boost_cobalt_basic_tests) target_compile_features(boost_cobalt PUBLIC cxx_std_20) -add_executable(boost_cobalt_experimental test_main.cpp experimental/fiber.cpp) +add_executable(boost_cobalt_experimental test_main.cpp experimental/context.cpp) target_link_libraries(boost_cobalt_experimental Boost::cobalt Boost::unit_test_framework Boost::context) add_test(NAME boost_cobalt_experimental COMMAND boost_cobalt_experimental) diff --git a/test/Jamfile.jam b/test/Jamfile.jam index 34b96dd6..a8681618 100644 --- a/test/Jamfile.jam +++ b/test/Jamfile.jam @@ -32,4 +32,4 @@ for local src in [ glob *.cpp : main.cpp main_compile.cpp test_main.cpp concepts run $(src) test_impl ; } -run experimental/fiber.cpp test_impl ; \ No newline at end of file +run experimental/context.cpp test_impl ; \ No newline at end of file diff --git a/test/experimental/context.cpp b/test/experimental/context.cpp new file mode 100644 index 00000000..1c73e564 --- /dev/null +++ b/test/experimental/context.cpp @@ -0,0 +1,86 @@ +// Copyright (c) 2024 Klemens D. Morgenstern +// +// 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) + +#include +#include +#include +#include + +#include "../test.hpp" + +using namespace boost::cobalt; + +BOOST_AUTO_TEST_SUITE(fiber); + +BOOST_AUTO_TEST_CASE(basics) +{ + boost::cobalt::experimental::detail::fiber_frame ff; + + auto hh = std::coroutine_handle::from_address(&ff); + BOOST_CHECK(!hh.done()); + ff.resume_ = nullptr; + BOOST_CHECK(hh.done()); + + BOOST_CHECK(&ff.promise == &hh.promise()); + BOOST_CHECK(std::coroutine_handle::from_promise(ff.promise).address() == &ff); +} + +int stackful_task(experimental::context> c, int init) +{ + static_assert(experimental::detail::has_await_transform); + BOOST_CHECK(c.await(this_coro::executor) == c.promise().get_executor()); + + bool done = false; + boost::asio::post(c.promise().get_executor(), + [&]{done = true;}); + + BOOST_CHECK(!done); + c.await(boost::asio::post(use_op)); + BOOST_CHECK(done); + return init * 2; +} + +CO_TEST_CASE(task_) +{ + task t = experimental::make_context(&stackful_task, 12); + + BOOST_CHECK(24 == co_await t); +} + +void stackful_task_void(experimental::context> c) +{ + static_assert(experimental::detail::has_await_transform); + BOOST_CHECK(c.await(this_coro::executor) == c.promise().get_executor()); +} + +CO_TEST_CASE(task_void) +{ + co_await experimental::make_context(&stackful_task_void); +} + +int stackful_generator(experimental::context> c) +{ + static_assert(experimental::detail::has_await_transform); + BOOST_CHECK(c.await(this_coro::executor) == c.promise().get_executor()); + + c.yield(1); + c.yield(2); + c.yield(3); + c.yield(4); + + return 5; +} + +CO_TEST_CASE(generator_) +{ + generator g = experimental::make_context(&stackful_generator); + BOOST_CHECK(1 == co_await g); + BOOST_CHECK(2 == co_await g); + BOOST_CHECK(3 == co_await g); + BOOST_CHECK(4 == co_await g); + BOOST_CHECK(5 == co_await g); +} + +BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/experimental/fiber.cpp b/test/experimental/fiber.cpp deleted file mode 100644 index e43c4d69..00000000 --- a/test/experimental/fiber.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2023 Klemens D. Morgenstern -// -// 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) - -#include - -#include "../test.hpp" - -BOOST_AUTO_TEST_SUITE(fiber); - -BOOST_AUTO_TEST_CASE(basics) -{ - boost::cobalt::experimental::detail::fiber_frame ff; - - using pro = boost::cobalt::experimental::detail::fiber_promise; - auto hh = std::coroutine_handle::from_address(&ff); - BOOST_CHECK(!hh.done()); - ff.resume_ = nullptr; - BOOST_CHECK(hh.done()); - - BOOST_CHECK(&ff.promise == &hh.promise()); - BOOST_CHECK(std::coroutine_handle::from_promise(ff.promise).address() == &ff); -} - - -BOOST_AUTO_TEST_SUITE_END(); \ No newline at end of file