238 lines
4.4 KiB
C++
238 lines
4.4 KiB
C++
|
// This is reduced test case from https://github.com/llvm/llvm-project/issues/59723.
|
||
|
// This is not a minimal reproducer intentionally to check the compiler's ability.
|
||
|
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fcxx-exceptions\
|
||
|
// RUN: -fexceptions -O2 -emit-llvm %s -o - | FileCheck %s
|
||
|
|
||
|
#include "Inputs/coroutine.h"
|
||
|
|
||
|
// executor and operation base
|
||
|
|
||
|
class bug_any_executor;
|
||
|
|
||
|
struct bug_async_op_base
|
||
|
{
|
||
|
void invoke();
|
||
|
|
||
|
protected:
|
||
|
|
||
|
~bug_async_op_base() = default;
|
||
|
};
|
||
|
|
||
|
class bug_any_executor
|
||
|
{
|
||
|
using op_type = bug_async_op_base;
|
||
|
|
||
|
public:
|
||
|
|
||
|
virtual ~bug_any_executor() = default;
|
||
|
|
||
|
// removing noexcept enables clang to find that the pointer has escaped
|
||
|
virtual void post(op_type& op) noexcept = 0;
|
||
|
|
||
|
virtual void wait() noexcept = 0;
|
||
|
};
|
||
|
|
||
|
class bug_thread_executor : public bug_any_executor
|
||
|
{
|
||
|
|
||
|
public:
|
||
|
|
||
|
void start()
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
~bug_thread_executor()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
// although this implementation is not realy noexcept due to allocation but I have a real one that is and required to be noexcept
|
||
|
virtual void post(bug_async_op_base& op) noexcept override;
|
||
|
|
||
|
virtual void wait() noexcept override
|
||
|
{
|
||
|
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// task and promise
|
||
|
|
||
|
struct bug_final_suspend_notification
|
||
|
{
|
||
|
virtual std::coroutine_handle<> get_waiter() = 0;
|
||
|
};
|
||
|
|
||
|
class bug_task;
|
||
|
|
||
|
class bug_task_promise
|
||
|
{
|
||
|
friend bug_task;
|
||
|
public:
|
||
|
|
||
|
bug_task get_return_object() noexcept;
|
||
|
|
||
|
constexpr std::suspend_always initial_suspend() noexcept { return {}; }
|
||
|
|
||
|
std::suspend_always final_suspend() noexcept
|
||
|
{
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
void unhandled_exception() noexcept;
|
||
|
|
||
|
constexpr void return_void() const noexcept {}
|
||
|
|
||
|
void get_result() const
|
||
|
{
|
||
|
|
||
|
}
|
||
|
};
|
||
|
|
||
|
template <class T, class U>
|
||
|
T exchange(T &&t, U &&u) {
|
||
|
T ret = t;
|
||
|
t = u;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
class bug_task
|
||
|
{
|
||
|
friend bug_task_promise;
|
||
|
using handle = std::coroutine_handle<>;
|
||
|
using promise_t = bug_task_promise;
|
||
|
|
||
|
bug_task(handle coro, promise_t* p) noexcept : this_coro{ coro }, this_promise{ p }
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
public:
|
||
|
using promise_type = bug_task_promise;
|
||
|
|
||
|
bug_task(bug_task&& other) noexcept
|
||
|
: this_coro{ exchange(other.this_coro, nullptr) }, this_promise{ exchange(other.this_promise, nullptr) } {
|
||
|
|
||
|
}
|
||
|
|
||
|
~bug_task()
|
||
|
{
|
||
|
if (this_coro)
|
||
|
this_coro.destroy();
|
||
|
}
|
||
|
|
||
|
constexpr bool await_ready() const noexcept
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
handle await_suspend(handle waiter) noexcept
|
||
|
{
|
||
|
return this_coro;
|
||
|
}
|
||
|
|
||
|
void await_resume()
|
||
|
{
|
||
|
return this_promise->get_result();
|
||
|
}
|
||
|
|
||
|
handle this_coro;
|
||
|
promise_t* this_promise;
|
||
|
};
|
||
|
|
||
|
bug_task bug_task_promise::get_return_object() noexcept
|
||
|
{
|
||
|
return { std::coroutine_handle<bug_task_promise>::from_promise(*this), this };
|
||
|
}
|
||
|
|
||
|
// spawn operation and spawner
|
||
|
|
||
|
template<class Handler>
|
||
|
class bug_spawn_op final : public bug_async_op_base, bug_final_suspend_notification
|
||
|
{
|
||
|
Handler handler;
|
||
|
bug_task task_;
|
||
|
|
||
|
public:
|
||
|
|
||
|
bug_spawn_op(Handler handler, bug_task&& t)
|
||
|
: handler { handler }, task_{ static_cast<bug_task&&>(t) } {}
|
||
|
|
||
|
virtual std::coroutine_handle<> get_waiter() override
|
||
|
{
|
||
|
handler();
|
||
|
return std::noop_coroutine();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
class bug_spawner;
|
||
|
|
||
|
struct bug_spawner_awaiter
|
||
|
{
|
||
|
bug_spawner& s;
|
||
|
std::coroutine_handle<> waiter;
|
||
|
|
||
|
bug_spawner_awaiter(bug_spawner& s) : s{ s } {}
|
||
|
|
||
|
bool await_ready() const noexcept;
|
||
|
|
||
|
void await_suspend(std::coroutine_handle<> coro);
|
||
|
|
||
|
void await_resume() {}
|
||
|
};
|
||
|
|
||
|
class bug_spawner
|
||
|
{
|
||
|
friend bug_spawner_awaiter;
|
||
|
|
||
|
struct final_handler_t
|
||
|
{
|
||
|
bug_spawner& s;
|
||
|
|
||
|
void operator()()
|
||
|
{
|
||
|
s.awaiter_->waiter.resume();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
public:
|
||
|
|
||
|
bug_spawner(bug_any_executor& ex) : ex_{ ex } {}
|
||
|
|
||
|
void spawn(bug_task&& t) {
|
||
|
using op_t = bug_spawn_op<final_handler_t>;
|
||
|
// move task into ptr
|
||
|
op_t* ptr = new op_t(final_handler_t{ *this }, static_cast<bug_task&&>(t));
|
||
|
++count_;
|
||
|
ex_.post(*ptr); // ptr escapes here thus task escapes but clang can't deduce that unless post() is not noexcept
|
||
|
}
|
||
|
|
||
|
bug_spawner_awaiter wait() noexcept { return { *this }; }
|
||
|
|
||
|
private:
|
||
|
bug_any_executor& ex_; // if bug_thread_executor& is used instead enables clang to detect the escape of the promise
|
||
|
bug_spawner_awaiter* awaiter_ = nullptr;
|
||
|
unsigned count_ = 0;
|
||
|
};
|
||
|
|
||
|
// test case
|
||
|
|
||
|
bug_task bug_spawned_task(int id, int inc)
|
||
|
{
|
||
|
co_return;
|
||
|
}
|
||
|
|
||
|
struct A {
|
||
|
A();
|
||
|
};
|
||
|
|
||
|
void throwing_fn(bug_spawner& s) {
|
||
|
s.spawn(bug_spawned_task(1, 2));
|
||
|
throw A{};
|
||
|
}
|
||
|
|
||
|
// Check that the coroutine frame of bug_spawned_task are allocated from operator new.
|
||
|
// CHECK: define{{.*}}@_Z11throwing_fnR11bug_spawner
|
||
|
// CHECK-NOT: alloc
|
||
|
// CHECK: %[[CALL:.+]] = {{.*}}@_Znwm(i64{{.*}} 24)
|
||
|
// CHECK: store ptr @_Z16bug_spawned_taskii.resume, ptr %[[CALL]]
|