Skip to content

Commit

Permalink
generator and channale are same thing
Browse files Browse the repository at this point in the history
  • Loading branch information
kelbon committed Aug 6, 2023
1 parent a792cc3 commit 07a4fb1
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 58 deletions.
4 changes: 2 additions & 2 deletions include/channel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ struct channel_promise : enable_memory_resource_support {
return leaf.done();
}

public:
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;
Expand Down Expand Up @@ -181,6 +180,7 @@ struct channel {
using value_type = Yield;

private:
// invariant: != nullptr
std::coroutine_handle<> handle = always_done_coroutine();

public:
Expand All @@ -191,7 +191,7 @@ struct channel {
assume_not_null(handle);
}

constexpr channel(channel&& other) noexcept : handle(std::exchange(other.handle, nullptr)) {
constexpr channel(channel&& other) noexcept : handle(other.release()) {
}
constexpr channel& operator=(channel&& other) noexcept {
std::swap(handle, other.handle);
Expand Down
34 changes: 18 additions & 16 deletions include/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,15 +291,17 @@ 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) {
assert(frame_size < 128 && "hack dont works(");
// there are only one always done coroutine.
// Its always without state, only compiler-inner things
alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) static char hack[128];
return hack;
// will be deleted by single always_done_coroutine instance
addr() = new char[frame_size];
return addr();
}

static void operator delete(void* ptr, std::size_t frame_size) noexcept {
static void operator delete(void*, std::size_t) noexcept {
// noop
}
static constexpr std::suspend_never initial_suspend() noexcept {
Expand Down Expand Up @@ -334,26 +336,26 @@ struct coroutine_traits<::dd::always_done_coroutine_handle, Args...> {

namespace dd::noexport {

static inline const auto always_done_coro = []() -> always_done_coroutine_handle { co_return; }();
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{
[]() -> always_done_coroutine_handle { co_return; }()};

} // namespace dd::noexport

namespace dd {
// TODO тип для обмена сообщениями между каналом/генератором и потребителем
// что то типа упаковки над указателем, желательно ещё НЕ назвать его socket
// мб connection<T> + send/recieve сообщений
// или просто send/reseive обёртки на lvalue ссылкой... Тоже неплохо, а в генераторе
// сделать например перегрузку yield
// важно то, что это не должно зависеть от возвращаемого типа генератора/канала!
// хм, recieve должен на вход генератор чтоли получать... И иметь оператор co_await для канала
// recieve(gen) co_await recieve(channel)

// returns handle for which
// .done() == true
// .destroy() is noop
// .resume() produces undefined behavior
inline always_done_coroutine_handle always_done_coroutine() noexcept {
return noexport::always_done_coro;
return noexport::always_done_coro.handle;
}

template <typename R>
Expand Down
73 changes: 33 additions & 40 deletions include/generator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ template <typename>
struct generator;

// TODO usage .begin as output iterator hmm что то типа .out хммм
// TODO send(x) yield overload

template <typename Yield>
struct generator_promise : enable_memory_resource_support {
static_assert(!std::is_reference_v<Yield>);
Expand Down Expand Up @@ -61,14 +61,16 @@ struct generator_promise : enable_memory_resource_support {
};

struct attach_leaf {
const handle_type leaf;
// precondition: leaf != nullptr
std::coroutine_handle<> leaf;

bool await_ready() const noexcept {
return !leaf || leaf.done();
assume_not_null(leaf);
return leaf.done();
}

std::coroutine_handle<> await_suspend(handle_type owner) const noexcept {
generator_promise& leaf_p = leaf.promise();
generator_promise& leaf_p = handle_type::from_address(leaf.address()).promise();
generator_promise& root = *owner.promise().root;
leaf_p.root = &root;
leaf_p.owner = owner;
Expand All @@ -78,8 +80,7 @@ struct generator_promise : enable_memory_resource_support {
static constexpr void await_resume() noexcept {
}
~attach_leaf() {
if (leaf)
leaf.destroy();
leaf.destroy();
}
};

Expand Down Expand Up @@ -111,7 +112,7 @@ struct generator_promise : enable_memory_resource_support {
co_yield Yield(x);
}
};
handle_type h = make_gen(e.rng).release();
auto h = make_gen(e.rng).release();
assume_not_null(h);
return attach_leaf{h};
}
Expand Down Expand Up @@ -146,21 +147,18 @@ struct generator_promise : enable_memory_resource_support {
template <typename Yield>
struct generator_iterator {
private:
// but.. why?
// Its required for guarantee that default constructed generator is an empty range
// so i store erased handle when iterator is default constructed and 'handle' otherwise
// P.S. i know about formal UB here
union {
// invariants: always != nullptr, if !erased_handle.done(), then 'handle' stored
std::coroutine_handle<> erased_handle;
std::coroutine_handle<generator_promise<Yield>> handle;
};
using handle_type = std::coroutine_handle<generator_promise<Yield>>;
// invariant: != nullptr
std::coroutine_handle<> _handle = always_done_coroutine();

public:
generator_iterator() noexcept : erased_handle(always_done_coroutine()) {
handle_type handle() const noexcept {
return handle_type::from_address(_handle.address());
}

public:
generator_iterator() noexcept = default;
// precondition: h != nullptr
generator_iterator(std::coroutine_handle<generator_promise<Yield>> h) noexcept : handle(h) {
generator_iterator(std::coroutine_handle<> h) noexcept : _handle(h) {
assume_not_null(h);
}

Expand All @@ -171,20 +169,17 @@ struct generator_iterator {
using difference_type = ptrdiff_t;

bool operator==(std::default_sentinel_t) const noexcept {
assert(erased_handle != nullptr); // invariant
return erased_handle.done();
return _handle.done();
}
// * may be invoked > 1 times, but be carefull with moving out
// * returns rvalue ref
reference operator*() const noexcept {
assert(!handle.done());
// returns && because yield guarantees that generator will not observe changes
// and i want effective for(std::string s : generator)
return static_cast<reference>(*handle.promise().current_result);
assert(*this != std::default_sentinel);
return static_cast<reference>(*handle().promise().current_result);
}
// * after invoking references to value from operator* are invalidated
generator_iterator& operator++() KELCORO_LIFETIMEBOUND {
assert(!handle.done());
handle.promise().produce_next();
assert(*this != std::default_sentinel);
handle().promise().produce_next();
return *this;
}
void operator++(int) {
Expand All @@ -205,32 +200,31 @@ struct generator {
using iterator = generator_iterator<Yield>;

private:
handle_type handle = nullptr;
// invariant: != nullptr
std::coroutine_handle<> handle = always_done_coroutine();

public:
// postcondition: empty(), 'for' loop produces 0 values
constexpr generator() noexcept = default;
// precondition: 'handle' != nullptr && !handle.done()
// precondition: 'handle' != nullptr
constexpr generator(handle_type handle) noexcept : handle(handle) {
assume_not_null(handle);
assume(!handle.done());
}
// postcondition: other.empty()
constexpr generator(generator&& other) noexcept : handle(std::exchange(other.handle, nullptr)) {
constexpr generator(generator&& other) noexcept : handle(other.release()) {
}
constexpr generator& operator=(generator&& other) noexcept {
std::swap(handle, other.handle);
return *this;
}
// postcondition: .empty()
// after this method its caller responsibility to correctly destroy 'handle'
[[nodiscard]] constexpr handle_type release() noexcept {
return std::exchange(handle, nullptr);
[[nodiscard]] constexpr std::coroutine_handle<> release() noexcept {
return std::exchange(handle, always_done_coroutine());
}
// postcondition: .empty()
constexpr void clear() noexcept {
if (handle)
release().destroy();
release().destroy();
}
constexpr ~generator() {
clear();
Expand All @@ -239,7 +233,7 @@ struct generator {
// observers

constexpr bool empty() const noexcept {
return !handle || handle.done();
return handle.done();
}
constexpr explicit operator bool() const noexcept {
return !empty();
Expand All @@ -248,10 +242,9 @@ struct generator {
// * if .empty(), then begin() == end()
// produces next value(often first)
iterator begin() KELCORO_LIFETIMEBOUND {
if (empty()) [[unlikely]]
return iterator{};
iterator result(handle);
++result;
if (!empty()) [[likely]]
++result;
return result;
}
static constexpr std::default_sentinel_t end() noexcept {
Expand Down

0 comments on commit 07a4fb1

Please sign in to comment.