Skip to content

Commit

Permalink
step to same promise generator/channel
Browse files Browse the repository at this point in the history
  • Loading branch information
kelbon committed Aug 8, 2023
1 parent 07a4fb1 commit 8c7b6d6
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 68 deletions.
92 changes: 52 additions & 40 deletions include/channel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ namespace dd {

// logic and behavior is very similar to generator, but its async(may suspend before co_yield)

// TODO создать generator просто одним ифом в channel, это же одно и то же. Различие в обработке исключений,
// запрете co_await и наличии итератора(т.к. существует гарантия, что синхронно)

template <typename>
struct channel;

Expand All @@ -24,15 +21,13 @@ template <typename Yield>
struct channel_promise : enable_memory_resource_support {
static_assert(!std::is_reference_v<Yield>);
using handle_type = std::coroutine_handle<channel_promise>;

// TODO remove(make it iterator)
struct consumer_t {
private:
friend channel_promise;
friend channel<Yield>;

Yield* current_result = nullptr;
std::coroutine_handle<> handle;
std::coroutine_handle<> _top; // maybe always done coro or handle_type
std::coroutine_handle<> _top; // may be always done coro or handle_type

constexpr consumer_t(std::coroutine_handle<> top) noexcept : _top(top) {
assume_not_null(top);
Expand All @@ -46,20 +41,25 @@ struct channel_promise : enable_memory_resource_support {
return _top.done();
}
std::coroutine_handle<void> await_suspend(std::coroutine_handle<void> consumer) noexcept {
handle = consumer;
auto& p = top().promise();
p.consumer = this;
return p.current_worker;
auto& root = top().promise();
// root.owner = consumer;
root.consumer = consumer;
return root.current_worker;
}
[[nodiscard]] Yield* await_resume() const noexcept {
return current_result;
if (_top.done())
return nullptr; // TODO hmm
return top().promise().current_result;
}
};
// invariant: if running, then consumer != nullptr, setted when channel co_awaited
consumer_t* consumer;
// invariant: root != nullptr
channel_promise* root = this;
Yield* current_result = nullptr;
handle_type current_worker = get_return_object();
// nullptr means top-level
handle_type owner = nullptr;
// invariant: never nullptr, stores owner for leafs and consumer for top-level channel
// TODO for generator do same transformation
handle_type owner;
std::coroutine_handle<> consumer = nullptr; // TODO совместить с owner

channel_promise() = default;

Expand All @@ -77,10 +77,11 @@ struct channel_promise : enable_memory_resource_support {
static constexpr bool await_ready() noexcept {
return false;
}
std::coroutine_handle<> await_suspend(handle_type current_leaf) noexcept {
consumer_t& c = *current_leaf.promise().consumer;
c.current_result = std::addressof(value);
return c.handle;
std::coroutine_handle<> await_suspend(handle_type handle) noexcept {
channel_promise& root = *handle.promise().root;
root.current_result = std::addressof(value);
// return root.owner;
return root.consumer;
}
static constexpr void await_resume() noexcept {
}
Expand All @@ -97,43 +98,45 @@ struct channel_promise : enable_memory_resource_support {

std::coroutine_handle<> await_suspend(handle_type owner) const noexcept {
channel_promise& leaf_p = handle_type::from_address(leaf.address()).promise();
consumer_t& consumer = *owner.promise().consumer;
leaf_p.consumer = &consumer;
channel_promise& root = *owner.promise().root;
leaf_p.current_worker.promise().root = &root;
leaf_p.owner = owner;
consumer.top().promise().current_worker = leaf_p.current_worker;
leaf_p.current_worker.promise().consumer = owner.promise().consumer; //
root.current_worker = leaf_p.current_worker;
return leaf_p.current_worker;
}
constexpr void await_resume() const {
}
~attach_leaf() {
// make sure compiler, that its destroyed here (end of yield expression)
leaf.destroy();
}
};

public:
transfer_control_to yield_value(Yield&& rvalue) noexcept {
consumer->current_result = std::addressof(rvalue);
return transfer_control_to{consumer->handle};
root->current_result = std::addressof(rvalue);
// return transfer_control_to{root->owner};
return transfer_control_to{root->consumer};
}
hold_value_until_resume yield_value(const Yield& clvalue) noexcept(
std::is_nothrow_copy_constructible_v<Yield>) {
return hold_value_until_resume{Yield(clvalue)};
}
transfer_control_to yield_value(nothing_t) noexcept {
consumer->current_result = nullptr;
return transfer_control_to{consumer->handle};
root->current_result = nullptr;
// return transfer_control_to{root->owner};
return transfer_control_to{root->consumer};
}
transfer_control_to yield_value(by_ref<Yield> r) noexcept {
consumer->current_result = std::addressof(r.value);
return transfer_control_to{consumer->handle};
root->current_result = std::addressof(r.value);
// return transfer_control_to{root->owner};
return transfer_control_to{root->consumer};
}
// attaches leaf channel
// TODO think about generator-leaf
// postcondition: e.rng.empty()
template <typename X>
attach_leaf yield_value(elements_of<X> e) noexcept {
using rng_t = std::decay_t<X>;
using rng_t = std::remove_cvref_t<X>;
if constexpr (!std::is_same_v<rng_t, channel<Yield>>)
static_assert(![] {});
if constexpr (std::is_same_v<typename rng_t::value_type, Yield>) {
Expand All @@ -142,7 +145,7 @@ struct channel_promise : enable_memory_resource_support {
auto make_channel = [](auto& r) -> channel<Yield> {
auto next = r.next();
while (auto* x = co_await next)
co_yield Yield(*x);
co_yield Yield(std::move(*x));
};
return attach_leaf{make_channel(e.rng).release()};
}
Expand All @@ -151,15 +154,22 @@ struct channel_promise : enable_memory_resource_support {
static constexpr std::suspend_always initial_suspend() noexcept {
return {};
}
// transfer_control_to final_suspend() noexcept {
// if (&current_worker.promise() == this) { // top-level
// current_result = nullptr;
// } else {
// root->current_worker = handle_type::from_address(owner.address());
// handle_type::from_address(owner.address()).promise().root = root;
// }
// return transfer_control_to{owner};
// }
transfer_control_to final_suspend() const noexcept {
consumer->top().promise().current_worker = owner;
consumer->current_result = nullptr; // may be im last
// i dont have value here now, so i ask 'owner' to create it
if (owner) {
owner.promise().consumer = consumer;
root->current_worker = owner;
owner.promise().root = root;
return transfer_control_to{owner};
}
return transfer_control_to{consumer->handle};
return transfer_control_to{consumer};
}
static constexpr void return_void() noexcept {
}
Expand All @@ -186,7 +196,7 @@ struct channel {
public:
// postcondition: empty()
constexpr channel() noexcept = default;
// precondition: 'handle' != nullptr
// precondition: 'handle' != nullptr, handle does not have other owners
constexpr channel(handle_type handle) noexcept : handle(handle) {
assume_not_null(handle);
}
Expand Down Expand Up @@ -221,8 +231,10 @@ struct channel {
return !empty();
}

// co_await on result will return pointer to
// next value(or nullptr if dd::nothing yielded or .empty())
// usage:
// while(auto* x = co_await channel.next()) TODO doc
// while(auto* x = co_await channel.next())
[[nodiscard]] auto next() & noexcept KELCORO_LIFETIMEBOUND {
assume_not_null(handle);
return typename promise_type::consumer_t(handle);
Expand Down
28 changes: 11 additions & 17 deletions include/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,18 +291,19 @@ constexpr decltype(auto) build_awaiter(T&& value) {
}

struct always_done_coroutine_promise {
static auto& addr() {
constinit static char* a = nullptr;
return a;
}
static void* operator new(std::size_t frame_size) {
// will be deleted by single always_done_coroutine instance
addr() = new char[frame_size];
return addr();
// worst part - i have no guarantees about frame size, even when compiler exactly knows
// how much it will allocoate (if he will)
alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) static char bytes[50];
if (frame_size <= 50)
return bytes;
// this memory can not be deallocated in dctor of global object,
// because i have no guarantee, that this memory even will be allocated.
// so its memory leak. Ok
return new char[frame_size];
}

static void operator delete(void*, std::size_t) noexcept {
// noop
}
static constexpr std::suspend_never initial_suspend() noexcept {
return {};
Expand Down Expand Up @@ -336,14 +337,7 @@ struct coroutine_traits<::dd::always_done_coroutine_handle, Args...> {

namespace dd::noexport {

struct always_done_coro_holder {
always_done_coroutine_handle handle;

~always_done_coro_holder() {
delete[] always_done_coroutine_promise::addr();
}
};
static inline const always_done_coro_holder always_done_coro{
static inline const always_done_coroutine_handle always_done_coro{
[]() -> always_done_coroutine_handle { co_return; }()};

} // namespace dd::noexport
Expand All @@ -355,7 +349,7 @@ namespace dd {
// .destroy() is noop
// .resume() produces undefined behavior
inline always_done_coroutine_handle always_done_coroutine() noexcept {
return noexport::always_done_coro.handle;
return noexport::always_done_coro;
}

template <typename R>
Expand Down
18 changes: 8 additions & 10 deletions include/generator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ struct generator_promise : enable_memory_resource_support {

// invariant: root != nullptr
generator_promise* root = this;
// invariant: never nullptr, initialized in first co_yield
Yield* current_result = nullptr;
std::coroutine_handle<> current_worker = get_return_object();
// nullptr means top-level
handle_type current_worker = get_return_object();
// nullptr means top-level TODO better?
handle_type owner = nullptr;

generator_promise() = default;
Expand All @@ -42,7 +41,7 @@ struct generator_promise : enable_memory_resource_support {
// there are no correct things which you can do with co_await
// in generator
void await_transform(auto&&) = delete;
auto await_trasform(get_handle_t) const noexcept {
auto await_transform(get_handle_t) noexcept {
return this_coro::handle.operator co_await();
}

Expand Down Expand Up @@ -72,10 +71,10 @@ struct generator_promise : enable_memory_resource_support {
std::coroutine_handle<> await_suspend(handle_type owner) const noexcept {
generator_promise& leaf_p = handle_type::from_address(leaf.address()).promise();
generator_promise& root = *owner.promise().root;
leaf_p.root = &root;
leaf_p.current_worker.promise().root = &root;
leaf_p.owner = owner;
root.current_worker = leaf_p.current_worker;
return root.current_worker;
return leaf_p.current_worker;
}
static constexpr void await_resume() noexcept {
}
Expand Down Expand Up @@ -112,7 +111,7 @@ struct generator_promise : enable_memory_resource_support {
co_yield Yield(x);
}
};
auto h = make_gen(e.rng).release();
auto h = make_gen(e.rng).release(); // TODO better
assume_not_null(h);
return attach_leaf{h};
}
Expand All @@ -122,9 +121,8 @@ struct generator_promise : enable_memory_resource_support {
return {};
}
transfer_control_to final_suspend() const noexcept {
root->current_worker = owner;
root->current_result = nullptr;
if (owner) {
root->current_worker = owner;
owner.promise().root = root;
return transfer_control_to{owner};
}
Expand Down Expand Up @@ -206,7 +204,7 @@ struct generator {
public:
// postcondition: empty(), 'for' loop produces 0 values
constexpr generator() noexcept = default;
// precondition: 'handle' != nullptr
// precondition: 'handle' != nullptr, handle does not have other owners
constexpr generator(handle_type handle) noexcept : handle(handle) {
assume_not_null(handle);
}
Expand Down
6 changes: 5 additions & 1 deletion tests/tests_generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ TEST(empty) {
}
CHAN_OR_GEN
G<int> base_case() {
(void)co_await dd::this_coro::handle;
for (int i = 0; i < 100; ++i) {
RANDOM_CONTROL_FLOW;
co_yield i;
Expand Down Expand Up @@ -396,7 +397,10 @@ CHANNEL_TEST(null_terminated_channel) {
co_return error_count;
}
CO_TEST(null_terminated_channel);

// TODO tests когда начал генерировать, приостановился, скинул все остальные элементы как elements_of
// и для генератора и для канала
// TODO тесты с исключениями(бросок из рекурсии) и обработку исключений всё таки
// TODO генератор и канал должны использовать один и тот же промис абсолютно
struct log_resource : std::pmr::memory_resource {
size_t allocated = 0;
// sizeof of this thing affects frame size with 2 multiplier bcs its saved in frame + saved for coroutine
Expand Down

0 comments on commit 8c7b6d6

Please sign in to comment.