serenity/AK/Coroutine.h
Dan Klishch 500ff2a7ed AK: Declare AK::Coroutine<T>::CoroutinePromiseBase::unhandled_exception
For some reason, Clang wants AK to work with exceptions enabled so much
that it produces a very annoying warning complaining about the absence
of (completely useless in our setup) unhandled_exception method in
promise type for every coroutine declared. We work around this during
build by suppressing -Wcoroutine-missing-unhandled-exception in
Meta/CMake/common_compile_options.cmake. However, the flag is only added
if build is using Clang. If one builds coroutine code with GCC but still
uses clangd, clangd, obviously, doesn't pick up warning suppression and
annoys user with a squiggly yellow line under each coroutine function
declaration.
2024-08-29 13:28:42 +02:00

285 lines
8 KiB
C++

/*
* Copyright (c) 2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Concepts.h>
#include <AK/Noncopyable.h>
#include <coroutine>
namespace AK {
namespace Detail {
// FIXME: GCC ICEs when an implementation of CO_TRY_OR_FAIL with statement expressions is used. See also LibTest/AsyncTestCase.h.
#if defined(AK_COROUTINE_STATEMENT_EXPRS_BROKEN) && !defined(AK_COROUTINE_DESTRUCTION_BROKEN)
# define AK_USE_TRY_OR_FAIL_AWAITER
#endif
#ifdef AK_USE_TRY_OR_FAIL_AWAITER
namespace Test {
template<typename T>
struct TryOrFailAwaiter;
}
#endif
struct SuspendNever {
// Even though we set -fno-exceptions, Clang really wants these to be noexcept.
bool await_ready() const noexcept { return true; }
void await_suspend(std::coroutine_handle<>) const noexcept { }
void await_resume() const noexcept { }
};
struct SuspendAlways {
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<>) const noexcept { }
void await_resume() const noexcept { }
};
struct SymmetricControlTransfer {
SymmetricControlTransfer(std::coroutine_handle<> handle)
: m_handle(handle ? handle : std::noop_coroutine())
{
}
bool await_ready() const noexcept { return false; }
auto await_suspend(std::coroutine_handle<>) const noexcept { return m_handle; }
void await_resume() const noexcept { }
std::coroutine_handle<> m_handle;
};
template<typename T>
struct TryAwaiter;
template<typename T>
struct ValueHolder {
alignas(T) u8 m_return_value[sizeof(T)];
};
template<>
struct ValueHolder<void> { };
}
template<typename T>
class [[nodiscard]] Coroutine : private Detail::ValueHolder<T> {
struct CoroutinePromiseVoid;
struct CoroutinePromiseValue;
AK_MAKE_NONCOPYABLE(Coroutine);
public:
using ReturnType = T;
using promise_type = Conditional<SameAs<T, void>, CoroutinePromiseVoid, CoroutinePromiseValue>;
~Coroutine()
{
VERIFY(await_ready());
if constexpr (!SameAs<T, void>)
return_value()->~T();
if (m_handle)
m_handle.destroy();
}
Coroutine(Coroutine&& other)
{
m_handle = AK::exchange(other.m_handle, {});
if (!await_ready())
m_handle.promise().m_coroutine = this;
else if constexpr (!IsVoid<T>)
new (return_value()) T(move(*other.return_value()));
}
Coroutine& operator=(Coroutine&& other)
{
if (this != &other) {
this->~Coroutine();
new (this) Coroutine(move(other));
}
return *this;
}
bool await_ready() const
{
return !m_handle || m_handle.done();
}
void await_suspend(std::coroutine_handle<> awaiter)
{
m_handle.promise().m_awaiter = awaiter;
}
// Do NOT bind the result of await_resume() on a temporary coroutine (or the result of CO_TRY) to auto&&!
[[nodiscard]] decltype(auto) await_resume()
{
if constexpr (SameAs<T, void>)
return;
else
return static_cast<T&&>(*return_value());
}
private:
template<typename U>
friend struct Detail::TryAwaiter;
#ifdef AK_USE_TRY_OR_FAIL_AWAITER
template<typename U>
friend struct AK::Detail::Test::TryOrFailAwaiter;
#endif
// You cannot just have return_value and return_void defined in the same promise type because C++.
struct CoroutinePromiseBase {
CoroutinePromiseBase() = default;
Coroutine get_return_object()
{
return { std::coroutine_handle<promise_type>::from_promise(*static_cast<promise_type*>(this)) };
}
Detail::SuspendNever initial_suspend() { return {}; }
Detail::SymmetricControlTransfer final_suspend() noexcept
{
return { m_awaiter };
}
void unhandled_exception() = delete;
std::coroutine_handle<> m_awaiter;
Coroutine* m_coroutine { nullptr };
};
struct CoroutinePromiseValue : CoroutinePromiseBase {
template<typename U>
requires requires { { T(forward<U>(declval<U>())) }; }
void return_value(U&& returned_object)
{
new (this->m_coroutine->return_value()) T(forward<U>(returned_object));
}
void return_value(T&& returned_object)
{
new (this->m_coroutine->return_value()) T(move(returned_object));
}
};
struct CoroutinePromiseVoid : CoroutinePromiseBase {
void return_void() { }
};
Coroutine(std::coroutine_handle<promise_type>&& handle)
: m_handle(move(handle))
{
m_handle.promise().m_coroutine = this;
}
T* return_value()
{
return reinterpret_cast<T*>(this->m_return_value);
}
std::coroutine_handle<promise_type> m_handle;
};
template<typename T>
T must_sync(Coroutine<ErrorOr<T>>&& coroutine)
{
VERIFY(coroutine.await_ready());
auto&& object = coroutine.await_resume();
VERIFY(!object.is_error());
if constexpr (!IsSame<T, void>)
return object.release_value();
}
#ifndef AK_COROUTINE_DESTRUCTION_BROKEN
namespace Detail {
template<typename T>
struct TryAwaiter {
TryAwaiter(T& expression)
requires(!IsSpecializationOf<T, Coroutine>)
: m_expression(&expression)
{
}
TryAwaiter(T&& expression)
requires(!IsSpecializationOf<T, Coroutine>)
: m_expression(&expression)
{
}
bool await_ready() { return false; }
template<typename U>
requires IsSpecializationOf<T, ErrorOr>
std::coroutine_handle<> await_suspend(std::coroutine_handle<U> handle)
{
if (!m_expression->is_error()) {
return handle;
} else {
auto awaiter = handle.promise().m_awaiter;
auto* coroutine = handle.promise().m_coroutine;
using ReturnType = RemoveReference<decltype(*coroutine)>::ReturnType;
static_assert(IsSpecializationOf<ReturnType, ErrorOr>,
"CO_TRY can only be used inside functions returning a specialization of ErrorOr");
// Move error to the user-visible AK::Coroutine
new (coroutine->return_value()) ReturnType(m_expression->release_error());
// ... and tell it that there's a result available.
coroutine->m_handle = {};
// Run destructors for locals in the coroutine that failed.
handle.destroy();
// Lastly, transfer control to the parent (or nothing, if parent is not yet suspended).
if (awaiter)
return awaiter;
return std::noop_coroutine();
}
}
decltype(auto) await_resume()
{
return m_expression->release_value();
}
T* m_expression { nullptr };
};
}
# ifndef AK_COROUTINE_TYPE_DEDUCTION_BROKEN
# define CO_TRY(expression) (co_await ::AK::Detail::TryAwaiter { (expression) })
# else
namespace Detail {
template<typename T>
auto declval_coro_result(Coroutine<T>&&) -> T;
template<typename T>
auto declval_coro_result(T&&) -> T;
}
// GCC cannot handle CO_TRY(...CO_TRY(...)...), this hack ensures that it always has the right type information available.
// FIXME: Remove this once GCC can correctly infer the result type of `co_await TryAwaiter { ... }`.
# define CO_TRY(expression) static_cast<decltype(AK::Detail::declval_coro_result(expression).release_value())>(co_await ::AK::Detail::TryAwaiter { (expression) })
# endif
#elifndef AK_COROUTINE_STATEMENT_EXPRS_BROKEN
# define CO_TRY(expression) \
({ \
AK_IGNORE_DIAGNOSTIC("-Wshadow", \
auto _temporary_result = (expression)); \
if (_temporary_result.is_error()) [[unlikely]] \
co_return _temporary_result.release_error(); \
_temporary_result.release_value(); \
})
#else
# error Unable to work around compiler bugs in definiton of CO_TRY.
#endif
}
#ifdef USING_AK_GLOBALLY
using AK::Coroutine;
#endif