Skip to content

Commit

Permalink
added experimental/composition.
Browse files Browse the repository at this point in the history
  • Loading branch information
klemens-morgenstern committed Oct 30, 2024
1 parent be2b334 commit c56bad9
Show file tree
Hide file tree
Showing 8 changed files with 335 additions and 15 deletions.
5 changes: 5 additions & 0 deletions include/boost/cobalt/detail/handler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ namespace boost::cobalt
namespace detail
{

template<typename ... Args>
struct composition_promise;
enum class completed_immediately_t
{
no, maybe, yes, initiating
Expand Down Expand Up @@ -279,6 +281,9 @@ struct completion_handler : detail::completion_handler_base
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
boost::source_location loc_;
#endif

template<typename ... Args_>
friend struct detail::composition_promise;
};

};
Expand Down
17 changes: 17 additions & 0 deletions include/boost/cobalt/detail/sbo_resource.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<typename>
friend struct sbo_allocator;
Expand Down
238 changes: 238 additions & 0 deletions include/boost/cobalt/experimental/composition.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
//
// Copyright (c) 2024 Klemens Morgenstern ([email protected])
//
// 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 <boost/cobalt/op.hpp>
#include <boost/cobalt/detail/sbo_resource.hpp>


namespace boost::cobalt::detail
{

template<typename ... Args>
struct composition_promise
:
promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>,
enable_await_allocator<composition_promise<Args...>>,
enable_await_executor<composition_promise<Args...>>
{
void get_return_object() {}

using promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>::await_transform;
using enable_await_allocator<composition_promise<Args...>>::await_transform;
using enable_await_executor<composition_promise<Args...>>::await_transform;

using handler_type = completion_handler<Args...>;

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<typename ... Args_, typename Initiation, typename ... InitArgs>
BOOST_NOINLINE
auto await_transform(asio::deferred_async_operation<void(Args_...), Initiation, InitArgs...> op_)
{
struct deferred_op : op<Args_...>
{
asio::deferred_async_operation<void(Args_...), Initiation, InitArgs...> op_;
deferred_op(asio::deferred_async_operation<void(Args_...), Initiation, InitArgs...> op_,
resource_type * resource)
: op_(std::move(op_)), resource(resource) {}

void initiate(cobalt::completion_handler<Args_...> complete) override
{
std::move(op_)(std::move(complete));
}

resource_type * resource;

typename op<Args_...>::awaitable_base operator co_await()
{
return static_cast<op<Args_...>&&>(*this).operator co_await().replace_resource(this->resource);
}
};

return cobalt::as_tuple(deferred_op{std::move(op_), handler.get_allocator().resource()});
}

template<typename Op>
requires requires (Op && op, resource_type* res)
{
{static_cast<Op>(op).operator co_await().replace_resource(res)} -> awaitable_type<composition_promise>;
}
BOOST_NOINLINE
auto await_transform(Op && op_)
{
struct replacing_op
{
Op op;

resource_type * resource;

auto operator co_await()
{
return std::forward<Op>(op).operator co_await().replace_resource(this->resource);
}
};

return cobalt::as_tuple(replacing_op{std::forward<Op>(op_), handler.get_allocator().resource()});
}


using executor_type = typename handler_type::executor_type ;
const executor & get_executor() const {return handler.get_executor();}

template<typename ... Ts>
static void * operator new(const std::size_t size, Ts & ... args)
{
using tt = std::pair<resource_type *, std::size_t>;

// | memory_resource | size_t | <padding> | 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<sizeof... (Ts) - 1>(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<std::max_align_t*>(p) + block_size;
}

static void operator delete(void * raw) noexcept
{
using tt = std::pair<resource_type *, std::size_t>;

// | memory_resource | size_t | <padding> | 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<std::max_align_t*>(raw) - block_size;

const auto tp = *reinterpret_cast<tt*>(p);
const auto res = tp.first;
const auto size = tp.second;

res->deallocate(p, size + (block_size * sizeof(std::max_align_t)));
}

completion_handler<Args...> handler;

template<typename ... Ts>
composition_promise(Ts && ... args) : handler(std::move(std::get<sizeof... (Ts) - 1>(std::tie(args...))))
{

}

void unhandled_exception() { throw ; }
constexpr static std::suspend_never initial_suspend() {return {};}

void return_value(std::tuple<Args ...> args)
{
handler.result.emplace(std::move(args));
}


struct final_awaitable
{
constexpr bool await_ready() noexcept {return false;}
completion_handler<Args...> handler;

BOOST_NOINLINE
std::coroutine_handle<void> await_suspend(std::coroutine_handle<composition_promise> 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<typename ... Args>
struct std::coroutine_traits<void, boost::cobalt::completion_handler<Args...>>
{
using promise_type = ::boost::cobalt::detail::composition_promise<Args...>;
};

template<typename T0, typename ... Args>
struct std::coroutine_traits<void, T0, boost::cobalt::completion_handler<Args...>>
{
using promise_type = ::boost::cobalt::detail::composition_promise<Args...>;
};

template<typename T0, typename T1, typename ... Args>
struct std::coroutine_traits<void, T0, T1, boost::cobalt::completion_handler<Args...>>
{
using promise_type = ::boost::cobalt::detail::composition_promise<Args...>;
};


template<typename T0, typename T1, typename T2, typename ... Args>
struct std::coroutine_traits<void, T0, T1, T2, boost::cobalt::completion_handler<Args...>>
{
using promise_type = ::boost::cobalt::detail::composition_promise<Args...>;
};


template<typename T0, typename T1, typename T2, typename T3, typename ... Args>
struct std::coroutine_traits<void, T0, T1, T2, T3, boost::cobalt::completion_handler<Args...>>
{
using promise_type = ::boost::cobalt::detail::composition_promise<Args...>;
};


template<typename T0, typename T1, typename T2, typename T3, typename T4, typename ... Args>
struct std::coroutine_traits<void, T0, T1, T2, T3, T4, boost::cobalt::completion_handler<Args...>>
{
using promise_type = ::boost::cobalt::detail::composition_promise<Args...>;
};


template<typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename ... Args>
struct std::coroutine_traits<void, T0, T1, T2, T3, T4, T5, boost::cobalt::completion_handler<Args...>>
{
using promise_type = ::boost::cobalt::detail::composition_promise<Args...>;
};


template<typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename ... Args>
struct std::coroutine_traits<void, T0, T1, T2, T3, T4, T5, T6, boost::cobalt::completion_handler<Args...>>
{
using promise_type = ::boost::cobalt::detail::composition_promise<Args...>;
};


template<typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename ... Args>
struct std::coroutine_traits<void, T0, T1, T2, T3, T4, T5, T6, T7, boost::cobalt::completion_handler<Args...>>
{
using promise_type = ::boost::cobalt::detail::composition_promise<Args...>;
};


template<typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename ... Args>
struct std::coroutine_traits<void, T0, T1, T2, T3, T4, T5, T6, T7, T8, boost::cobalt::completion_handler<Args...>>
{
using promise_type = ::boost::cobalt::detail::composition_promise<Args...>;
};

#endif //BOOST_COBALT_EXPERIMENTAL_COMPOSITION_HPP
54 changes: 42 additions & 12 deletions include/boost/cobalt/op.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,35 @@ struct op
virtual void initiate(cobalt::completion_handler<Args...> complete) = 0 ;
virtual ~op() = default;

struct awaitable
struct awaitable_base
{
op<Args...> &op_;
std::optional<std::tuple<Args...>> result;

awaitable(op<Args...> * 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<Args...> * 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()
{
op_.ready(handler<Args...>(result));
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<typename Promise>
bool await_suspend(std::coroutine_handle<Promise> h
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
Expand All @@ -63,9 +68,9 @@ struct op
completed_immediately = detail::completed_immediately_t::initiating;

#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
op_.initiate(completion_handler<Args...>{h, result, &resource, &completed_immediately, loc});
op_.initiate(completion_handler<Args...>{h, result, resource, &completed_immediately, loc});
#else
op_.initiate(completion_handler<Args...>{h, result, &resource, &completed_immediately});
op_.initiate(completion_handler<Args...>{h, result, resource, &completed_immediately});
#endif
if (completed_immediately == detail::completed_immediately_t::initiating)
completed_immediately = detail::completed_immediately_t::no;
Expand All @@ -86,13 +91,19 @@ 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)
std::rethrow_exception(init_ep);
return *std::move(result);
}

#if defined(_MSC_VER)
BOOST_NOINLINE
#endif
auto await_resume(const struct as_result_tag &)
{
if (init_ep)
Expand All @@ -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<Args...> * 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};
Expand Down
Loading

0 comments on commit c56bad9

Please sign in to comment.