From c56bad9b0833773884b07ed9de7c13051d98d1f4 Mon Sep 17 00:00:00 2001 From: Klemens Morgenstern Date: Thu, 24 Oct 2024 21:55:20 +0800 Subject: [PATCH] added experimental/composition. --- include/boost/cobalt/detail/handler.hpp | 5 + include/boost/cobalt/detail/sbo_resource.hpp | 17 ++ .../boost/cobalt/experimental/composition.hpp | 238 ++++++++++++++++++ include/boost/cobalt/op.hpp | 54 +++- test/CMakeLists.txt | 2 +- test/Jamfile.jam | 2 +- test/experimental/composition.cpp | 30 +++ test/experimental/yield_context.cpp | 2 +- 8 files changed, 335 insertions(+), 15 deletions(-) create mode 100644 include/boost/cobalt/experimental/composition.hpp create mode 100644 test/experimental/composition.cpp diff --git a/include/boost/cobalt/detail/handler.hpp b/include/boost/cobalt/detail/handler.hpp index 3091c6a6..8376c6b1 100644 --- a/include/boost/cobalt/detail/handler.hpp +++ b/include/boost/cobalt/detail/handler.hpp @@ -23,6 +23,8 @@ namespace boost::cobalt namespace detail { +template +struct composition_promise; enum class completed_immediately_t { no, maybe, yes, initiating @@ -279,6 +281,9 @@ struct completion_handler : detail::completion_handler_base #if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING) boost::source_location loc_; #endif + + template + friend struct detail::composition_promise; }; }; diff --git a/include/boost/cobalt/detail/sbo_resource.hpp b/include/boost/cobalt/detail/sbo_resource.hpp index 2b72efea..ae37315e 100644 --- a/include/boost/cobalt/detail/sbo_resource.hpp +++ b/include/boost/cobalt/detail/sbo_resource.hpp @@ -136,9 +136,24 @@ struct sbo_resource operator delete(p, std::align_val_t(align)); #endif #endif + } } +#if defined(BOOST_COBALT_NO_PMR) + [[nodiscard]] + void* + allocate(size_t bytes, size_t alignment = alignof(std::max_align_t)) + { + return ::operator new(bytes, do_allocate(bytes, alignment)); + } + + void + deallocate(void* p, size_t bytes, size_t alignment = alignof(std::max_align_t)) + { + return do_deallocate(p, bytes, alignment); + } +#endif #if !defined(BOOST_COBALT_NO_PMR) constexpr bool do_is_equal(memory_resource const& other) const noexcept override @@ -179,6 +194,8 @@ struct sbo_allocator resource_->do_deallocate(p, sizeof(T) * n, alignof(T)); } sbo_allocator(sbo_resource * resource) : resource_(resource) {} + + sbo_resource * resource() const {return resource_;} private: template friend struct sbo_allocator; diff --git a/include/boost/cobalt/experimental/composition.hpp b/include/boost/cobalt/experimental/composition.hpp new file mode 100644 index 00000000..d916409b --- /dev/null +++ b/include/boost/cobalt/experimental/composition.hpp @@ -0,0 +1,238 @@ +// +// Copyright (c) 2024 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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_EXPERIMENTAL_COMPOSITION_HPP +#define BOOST_COBALT_EXPERIMENTAL_COMPOSITION_HPP + +#include +#include + + +namespace boost::cobalt::detail +{ + +template +struct composition_promise + : + promise_cancellation_base, + enable_await_allocator>, + enable_await_executor> +{ + void get_return_object() {} + + using promise_cancellation_base::await_transform; + using enable_await_allocator>::await_transform; + using enable_await_executor>::await_transform; + + using handler_type = completion_handler; + + using allocator_type = typename handler_type::allocator_type; + allocator_type get_allocator() const {return handler.get_allocator();} +#if !defined(BOOST_COBALT_NO_PMR) + using resource_type = pmr::memory_resource; +#else + using resource_type = cobalt::detail::sbo_resource; +#endif + + template + BOOST_NOINLINE + auto await_transform(asio::deferred_async_operation op_) + { + struct deferred_op : op + { + asio::deferred_async_operation op_; + deferred_op(asio::deferred_async_operation op_, + resource_type * resource) + : op_(std::move(op_)), resource(resource) {} + + void initiate(cobalt::completion_handler complete) override + { + std::move(op_)(std::move(complete)); + } + + resource_type * resource; + + typename op::awaitable_base operator co_await() + { + return static_cast&&>(*this).operator co_await().replace_resource(this->resource); + } + }; + + return cobalt::as_tuple(deferred_op{std::move(op_), handler.get_allocator().resource()}); + } + + template + requires requires (Op && op, resource_type* res) + { + {static_cast(op).operator co_await().replace_resource(res)} -> awaitable_type; + } + BOOST_NOINLINE + auto await_transform(Op && op_) + { + struct replacing_op + { + Op op; + + resource_type * resource; + + auto operator co_await() + { + return std::forward(op).operator co_await().replace_resource(this->resource); + } + }; + + return cobalt::as_tuple(replacing_op{std::forward(op_), handler.get_allocator().resource()}); + } + + + using executor_type = typename handler_type::executor_type ; + const executor & get_executor() const {return handler.get_executor();} + + template + static void * operator new(const std::size_t size, Ts & ... args) + { + using tt = std::pair; + + // | memory_resource | size_t | | coroutine. + constexpr auto block_size = sizeof(tt) / sizeof(std::max_align_t) + + (sizeof(tt) % sizeof(std::max_align_t) ? 1 : 0); + + + auto res = std::get(std::tie(args...)).get_allocator().resource(); + const auto p = res->allocate(size + (block_size * sizeof(std::max_align_t))); + new (p) tt(res, size); + return static_cast(p) + block_size; + } + + static void operator delete(void * raw) noexcept + { + using tt = std::pair; + + // | memory_resource | size_t | | coroutine. + constexpr auto block_size = sizeof(tt) / sizeof(std::max_align_t) + + (sizeof(tt) % sizeof(std::max_align_t) ? 1 : 0); + + const auto p = static_cast(raw) - block_size; + + const auto tp = *reinterpret_cast(p); + const auto res = tp.first; + const auto size = tp.second; + + res->deallocate(p, size + (block_size * sizeof(std::max_align_t))); + } + + completion_handler handler; + + template + composition_promise(Ts && ... args) : handler(std::move(std::get(std::tie(args...)))) + { + + } + + void unhandled_exception() { throw ; } + constexpr static std::suspend_never initial_suspend() {return {};} + + void return_value(std::tuple args) + { + handler.result.emplace(std::move(args)); + } + + + struct final_awaitable + { + constexpr bool await_ready() noexcept {return false;} + completion_handler handler; + + BOOST_NOINLINE + std::coroutine_handle await_suspend(std::coroutine_handle h) noexcept + { + auto exec = handler.get_executor(); + auto ho = handler.self.release(); + detail::self_destroy(h, exec); + return ho; + } + + constexpr void await_resume() noexcept {} + }; + + BOOST_NOINLINE + auto final_suspend() noexcept + { + return final_awaitable{std::move(handler)}; + } +}; + +} + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + + +template +struct std::coroutine_traits> +{ + using promise_type = ::boost::cobalt::detail::composition_promise; +}; + +#endif //BOOST_COBALT_EXPERIMENTAL_COMPOSITION_HPP diff --git a/include/boost/cobalt/op.hpp b/include/boost/cobalt/op.hpp index a2e16958..edc24fa6 100644 --- a/include/boost/cobalt/op.hpp +++ b/include/boost/cobalt/op.hpp @@ -27,17 +27,23 @@ struct op virtual void initiate(cobalt::completion_handler complete) = 0 ; virtual ~op() = default; - struct awaitable + struct awaitable_base { op &op_; std::optional> result; - awaitable(op * op_) : op_(*op_) {} - awaitable(awaitable && lhs) - : op_(lhs.op_) - , result(std::move(lhs.result)) - { - } +#if !defined(BOOST_COBALT_NO_PMR) + using resource_type = pmr::memory_resource; +#else + using resource_type = detail::sbo_resource; +#endif + + awaitable_base(op * op_, resource_type *resource) : op_(*op_), resource(resource) {} + awaitable_base(awaitable_base && lhs) noexcept = default; + +#if defined(_MSC_VER) + BOOST_NOINLINE ~awaitable_base() {} +#endif bool await_ready() { @@ -45,12 +51,11 @@ struct op return result.has_value(); } - char buffer[BOOST_COBALT_SBO_BUFFER_SIZE]; - detail::sbo_resource resource{buffer, sizeof(buffer)}; - detail::completed_immediately_t completed_immediately = detail::completed_immediately_t::no; std::exception_ptr init_ep; + resource_type *resource; + template bool await_suspend(std::coroutine_handle h #if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING) @@ -63,9 +68,9 @@ struct op completed_immediately = detail::completed_immediately_t::initiating; #if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING) - op_.initiate(completion_handler{h, result, &resource, &completed_immediately, loc}); + op_.initiate(completion_handler{h, result, resource, &completed_immediately, loc}); #else - op_.initiate(completion_handler{h, result, &resource, &completed_immediately}); + op_.initiate(completion_handler{h, result, resource, &completed_immediately}); #endif if (completed_immediately == detail::completed_immediately_t::initiating) completed_immediately = detail::completed_immediately_t::no; @@ -86,6 +91,9 @@ struct op return await_resume(as_result_tag{}).value(loc); } +#if defined(_MSC_VER) + BOOST_NOINLINE +#endif auto await_resume(const struct as_tuple_tag &) { if (init_ep) @@ -93,6 +101,9 @@ struct op return *std::move(result); } +#if defined(_MSC_VER) + BOOST_NOINLINE +#endif auto await_resume(const struct as_result_tag &) { if (init_ep) @@ -101,6 +112,25 @@ struct op } }; + struct awaitable : awaitable_base + { + char buffer[BOOST_COBALT_SBO_BUFFER_SIZE]; + detail::sbo_resource resource{buffer, sizeof(buffer)}; + + awaitable(op * op_) : awaitable_base(op_, &resource) {} + awaitable(awaitable && rhs) : awaitable_base(std::move(rhs)) + { + this->awaitable_base::resource = &resource; + } + + awaitable_base replace_resource(typename awaitable_base::resource_type * resource) && + { + awaitable_base nw = std::move(*this); + nw.resource = resource; + return nw; + } + }; + awaitable operator co_await() && { return awaitable{this}; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c86699ce..132f9df5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -20,7 +20,7 @@ target_link_libraries(boost_cobalt_basic_tests Boost::cobalt Boost::unit_test_f add_test(NAME boost_cobalt_main COMMAND boost_cobalt_main) add_test(NAME boost_cobalt_basic_tests COMMAND boost_cobalt_basic_tests) -add_executable(boost_cobalt_experimental EXCLUDE_FROM_ALL test_main.cpp experimental/context.cpp experimental/yield_context.cpp) +add_executable(boost_cobalt_experimental EXCLUDE_FROM_ALL test_main.cpp experimental/context.cpp experimental/yield_context.cpp experimental/composition.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 25b1e7de..596927bf 100644 --- a/test/Jamfile.jam +++ b/test/Jamfile.jam @@ -34,4 +34,4 @@ for local src in [ glob *.cpp : main.cpp main_compile.cpp test_main.cpp concepts run $(src) test_impl ; } -run experimental/context.cpp test_impl //boost/context ; \ No newline at end of file +run experimental/context.cpp experimental/composition.cpp test_impl //boost/context ; \ No newline at end of file diff --git a/test/experimental/composition.cpp b/test/experimental/composition.cpp new file mode 100644 index 00000000..b7291cad --- /dev/null +++ b/test/experimental/composition.cpp @@ -0,0 +1,30 @@ +// +// Copyright (c) 2024 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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" + +using namespace boost::cobalt; + +struct dummy_op final : op +{ + void initiate(completion_handler) + { + std::tuple<> t = co_await boost::asio::post(co_await this_coro::executor, boost::asio::deferred); + t = co_await boost::asio::post(co_await this_coro::executor, use_op); + co_return {boost::asio::error::no_such_device, 42}; + } +}; + +CO_TEST_CASE(composition) +{ + auto [ec, n] = co_await boost::cobalt::as_tuple(dummy_op{}); + BOOST_CHECK(ec == boost::asio::error::no_such_device); + BOOST_CHECK(n == 42); + co_return ; +} diff --git a/test/experimental/yield_context.cpp b/test/experimental/yield_context.cpp index 2c4919c2..2b930316 100644 --- a/test/experimental/yield_context.cpp +++ b/test/experimental/yield_context.cpp @@ -94,7 +94,7 @@ BOOST_AUTO_TEST_CASE(await) boost::asio::io_context ioc; boost::asio::spawn(ioc, - [&](boost::asio::yield_context ctx) + [&](boost::asio::basic_yield_context ctx) { experimental::await(dummy_aw(), ctx); experimental::await(t(), ctx);