Files
revng-revng/include/revng/ADT/RecursiveCoroutine-coroutine.h
2024-06-27 11:05:51 +02:00

258 lines
8.2 KiB
C++

#pragma once
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include <coroutine>
#include <optional>
#include <type_traits>
#include <utility>
#include "revng/Support/Assert.h"
template<typename>
struct RecursiveCoroutine;
namespace revng::detail {
template<typename RetT>
struct ReturnBase {
void return_value(RetT R) {
revng_assert(not CurrValue.has_value());
CurrValue = std::move(R);
return;
}
RetT &&take() {
revng_assert(CurrValue.has_value());
return std::move(CurrValue.value());
}
protected:
std::optional<RetT> CurrValue = std::nullopt;
};
template<>
struct ReturnBase<void> {
void return_void() const { return; }
void take() const {}
};
template<typename ReturnT>
struct RecursivePromise : public ReturnBase<ReturnT> {
using promise_type = RecursivePromise<ReturnT>;
using coro_handle = std::coroutine_handle<promise_type>;
template<typename>
friend struct RecursivePromise;
RecursiveCoroutine<ReturnT> get_return_object() {
return RecursiveCoroutine<ReturnT>(coro_handle::from_promise(*this));
}
[[noreturn]] static RecursiveCoroutine<ReturnT>
get_return_object_on_allocation_failure() {
std::terminate();
// TODO: if we need this not to be a hard crash we could do the following
// return RecursiveCoroutine<void>();
}
[[noreturn]] void unhandled_exception() const { std::terminate(); }
auto initial_suspend() const { return std::suspend_always(); }
auto final_suspend() noexcept {
// In principle, we want our RecursiveCoroutine to always suspend at the
// end of execution. Cleanup of coroutine resources is done in the
// destructor of RecursiveCoroutine. However, we cannot simply return
// suspend_always, because if have an associated AwaiterContinuation, it
// means that the coroutine that is at this final suspension point was
// being co_awaited by an awaiter, that must be resumed when the awaitee
// reaches the final suspension point.
//
// So our final awaitable object should:
// - always suspend (await_ready should always return false)
// - return the handle to the awaiter continuation, so that if it's a
// valid handle it will be resumed.
// - never be resumed, because it's the final suspension point
// (await_resume actually aborts)
struct ResumeAwaiter {
ResumeAwaiter(std::coroutine_handle<> AwaiterHandle) :
Awaiter(AwaiterHandle) {}
bool await_ready() const noexcept { return false; }
std::coroutine_handle<> await_suspend(std::coroutine_handle<>) noexcept {
std::coroutine_handle<> ToResume = std::noop_coroutine();
if (Awaiter and not Awaiter.done()) {
ToResume = Awaiter;
Awaiter = {};
}
return ToResume;
}
void await_resume() const noexcept { revng_abort(); }
private:
std::coroutine_handle<> Awaiter;
};
auto ToResume = AwaiterContinuation;
AwaiterContinuation = {};
return ResumeAwaiter(ToResume);
}
RecursivePromise() : AwaiterContinuation(std::coroutine_handle<>{}) {}
~RecursivePromise() { revng_assert(not AwaiterContinuation); }
// Not copyable, otherwise AwaiterContinuation could be resumed twice.
RecursivePromise &operator=(const RecursivePromise &) = delete;
RecursivePromise(const RecursivePromise &) = delete;
RecursivePromise &operator=(RecursivePromise &&Other) {
if (this != &Other) {
this->AwaiterContinuation = Other.AwaiterContinuation;
Other.AwaiterContinuation = {};
}
return *this;
}
RecursivePromise(RecursivePromise &&Other) { *this = std::move(Other); }
template<typename AwaiteeReturnT>
auto await_transform(RecursiveCoroutine<AwaiteeReturnT> &&Awaitee) {
// This await_transform is called when the RecursiveCoroutine associated
// with this promise object (the awaiter) calls co_await on another
// RecursiveCoroutine (the awaitee).
//
// We want to inject the handle of the awaiter into the awaitee, so that
// when the awaitee ends it can resume the awaiter.
//
// We also have to return an awaitable that will dictate the suspension
// behavior of the awaiter.
// We want it to always suspend (await_ready should return false); we want
// it to resume the awaitee straight away after suspending (await_suspend
// should return a coroutine_handle to the awaitee), and we want the
// awaiter to get the result of the awaitee when resuming (await_resume
// should get the result of the awaitee and return it to the awaiter).
using AwaiteePromiseT = RecursivePromise<AwaiteeReturnT>;
using AwaiteeCoroHandle = typename AwaiteePromiseT::coro_handle;
AwaiteeCoroHandle AwaiteeHandle = Awaitee.OwnedHandle;
revng_assert(AwaiteeHandle and not AwaiteeHandle.done());
auto &AwaiteePromise = AwaiteeHandle.promise();
AwaiteePromise.AwaiterContinuation = coro_handle::from_promise(*this);
struct ResumeAwaitee {
ResumeAwaitee(AwaiteeCoroHandle AwaiteeHandle) : Awaitee(AwaiteeHandle) {}
bool await_ready() const { return false; }
auto await_suspend(std::coroutine_handle<>) {
// In principle we could clear Awaitee here, before suspending, so
// that if for some reason `await_suspend` is called twice we don't
// end up suspending the same coroutine twice (which is a bug).
// However, we still need access to Awaitee in `await_resume` to take
// the result of the Awaitee and propagate it to the awaiter.
revng_assert(Awaitee and not Awaitee.done());
return Awaitee;
}
auto await_resume() {
revng_assert(Awaitee and Awaitee.done());
if constexpr (std::is_void_v<AwaiteeReturnT>)
return;
else
return Awaitee.promise().take();
}
private:
AwaiteeCoroHandle Awaitee;
};
return ResumeAwaitee{ AwaiteeHandle };
}
protected:
std::coroutine_handle<> AwaiterContinuation;
};
} // namespace revng::detail
template<typename ReturnT = void>
struct RecursiveCoroutine {
public:
using promise_type = revng::detail::RecursivePromise<ReturnT>;
using coro_handle = std::coroutine_handle<promise_type>;
template<typename T>
friend struct revng::detail::RecursivePromise;
public:
RecursiveCoroutine() = delete;
RecursiveCoroutine(coro_handle H) : OwnedHandle(H) {}
// Not copyable, because we don't want OwnedHandle to be destroyed twice
RecursiveCoroutine &operator=(const RecursiveCoroutine &) = delete;
RecursiveCoroutine(const RecursiveCoroutine &) = delete;
// Movable, but we clean up the OwnedHandle, because we don't want it to be
// destroyed twice in the destructor
RecursiveCoroutine &operator=(RecursiveCoroutine &&Other) {
this->OwnedHandle = Other.OwnedHandle;
Other.OwnedHandle = {};
return *this;
}
RecursiveCoroutine(RecursiveCoroutine &&Other) { *this = std::move(Other); }
~RecursiveCoroutine() {
revng_assert(OwnedHandle);
// If we reach this point, the coroutine associated to the OwnedHandle was
// either awaited upon (and it is now suspended at its final suspension
// point), or it was not awaited upon at all (simply called as a regular
// function); in this last case its result has also not been taken with
// operator ReturnT and is going to be discarded.
// We need to run the coroutine anyway, to be consistent with function calls
// and to trigger potential side-effects.
if (not OwnedHandle.done())
OwnedHandle.resume();
revng_assert(OwnedHandle.done());
// After we are done we can clean up
OwnedHandle.destroy();
OwnedHandle = {};
}
// This is necessary to enable calling a recursive coroutine that returns a
// value in the same way you would call a regular function.
operator ReturnT() {
revng_assert(OwnedHandle);
if (not OwnedHandle.done())
OwnedHandle.resume();
revng_assert(OwnedHandle.done());
if constexpr (std::is_void_v<ReturnT>) {
return;
} else {
return OwnedHandle.promise().take();
}
}
auto operator*() { return *this->operator ReturnT(); }
auto operator!() { return !this->operator ReturnT(); }
protected:
coro_handle OwnedHandle;
};
#define rc_return co_return
#define rc_recur co_await