xref: /llvm-project/clang/test/CodeGenCoroutines/pr59723.cpp (revision 7037331a2f05990cd59f35a7c0f6ce87c0f3cb5f)
1 // This is reduced test case from https://github.com/llvm/llvm-project/issues/59723.
2 // This is not a minimal reproducer intentionally to check the compiler's ability.
3 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fcxx-exceptions\
4 // RUN:     -fexceptions -O2 -emit-llvm %s -o - | FileCheck %s
5 
6 #include "Inputs/coroutine.h"
7 
8 // executor and operation base
9 
10 class bug_any_executor;
11 
12 struct bug_async_op_base
13 {
14 	void invoke();
15 
16 protected:
17 
18 	~bug_async_op_base() = default;
19 };
20 
21 class bug_any_executor
22 {
23 	using op_type = bug_async_op_base;
24 
25 public:
26 
27 	virtual ~bug_any_executor() = default;
28 
29 	// removing noexcept enables clang to find that the pointer has escaped
30 	virtual void post(op_type& op) noexcept = 0;
31 
32 	virtual void wait() noexcept = 0;
33 };
34 
35 class bug_thread_executor : public bug_any_executor
36 {
37 
38 public:
39 
start()40 	void start()
41 	{
42 
43 	}
44 
~bug_thread_executor()45 	~bug_thread_executor()
46 	{
47 	}
48 
49 	// although this implementation is not realy noexcept due to allocation but I have a real one that is and required to be noexcept
50 	virtual void post(bug_async_op_base& op) noexcept override;
51 
wait()52 	virtual void wait() noexcept override
53 	{
54 
55 	}
56 };
57 
58 // task and promise
59 
60 struct bug_final_suspend_notification
61 {
62 	virtual std::coroutine_handle<> get_waiter() = 0;
63 };
64 
65 class bug_task;
66 
67 class bug_task_promise
68 {
69 	friend bug_task;
70 public:
71 
72 	bug_task get_return_object() noexcept;
73 
initial_suspend()74 	constexpr std::suspend_always initial_suspend() noexcept { return {}; }
75 
final_suspend()76 	std::suspend_always final_suspend() noexcept
77 	{
78 		return {};
79 	}
80 
81 	void unhandled_exception() noexcept;
82 
return_void() const83 	constexpr void return_void() const noexcept {}
84 
get_result() const85 	void get_result() const
86 	{
87 
88 	}
89 };
90 
91 template <class T, class U>
exchange(T && t,U && u)92 T exchange(T &&t, U &&u) {
93     T ret = t;
94     t = u;
95     return ret;
96 }
97 
98 class bug_task
99 {
100 	friend bug_task_promise;
101 	using handle = std::coroutine_handle<>;
102 	using promise_t = bug_task_promise;
103 
bug_task(handle coro,promise_t * p)104 	bug_task(handle coro, promise_t* p) noexcept : this_coro{ coro }, this_promise{ p }
105 	{
106 
107 	}
108 
109 public:
110 	using promise_type = bug_task_promise;
111 
bug_task(bug_task && other)112     bug_task(bug_task&& other) noexcept
113 		: this_coro{ exchange(other.this_coro, nullptr) }, this_promise{ exchange(other.this_promise, nullptr) } {
114 
115 	}
116 
~bug_task()117 	~bug_task()
118 	{
119 		if (this_coro)
120 			this_coro.destroy();
121 	}
122 
await_ready() const123 	constexpr bool await_ready() const noexcept
124 	{
125 		return false;
126 	}
127 
await_suspend(handle waiter)128 	handle await_suspend(handle waiter) noexcept
129 	{
130 		return this_coro;
131 	}
132 
await_resume()133 	void await_resume()
134 	{
135 		return this_promise->get_result();
136 	}
137 
138 	handle this_coro;
139 	promise_t* this_promise;
140 };
141 
get_return_object()142 bug_task bug_task_promise::get_return_object() noexcept
143 {
144 	return { std::coroutine_handle<bug_task_promise>::from_promise(*this), this };
145 }
146 
147 // spawn operation and spawner
148 
149 template<class Handler>
150 class bug_spawn_op final : public bug_async_op_base, bug_final_suspend_notification
151 {
152 	Handler handler;
153 	bug_task task_;
154 
155 public:
156 
bug_spawn_op(Handler handler,bug_task && t)157 	bug_spawn_op(Handler handler, bug_task&& t)
158 		: handler { handler }, task_{ static_cast<bug_task&&>(t) } {}
159 
get_waiter()160 	virtual std::coroutine_handle<> get_waiter() override
161 	{
162 		handler();
163 		return std::noop_coroutine();
164 	}
165 };
166 
167 class bug_spawner;
168 
169 struct bug_spawner_awaiter
170 {
171 	bug_spawner& s;
172 	std::coroutine_handle<> waiter;
173 
bug_spawner_awaiterbug_spawner_awaiter174 	bug_spawner_awaiter(bug_spawner& s) : s{ s } {}
175 
176 	bool await_ready() const noexcept;
177 
178 	void await_suspend(std::coroutine_handle<> coro);
179 
await_resumebug_spawner_awaiter180 	void await_resume() {}
181 };
182 
183 class bug_spawner
184 {
185 	friend bug_spawner_awaiter;
186 
187 	struct final_handler_t
188 	{
189 		bug_spawner& s;
190 
operator ()bug_spawner::final_handler_t191 		void operator()()
192 		{
193 			s.awaiter_->waiter.resume();
194 		}
195 	};
196 
197 public:
198 
bug_spawner(bug_any_executor & ex)199 	bug_spawner(bug_any_executor& ex) : ex_{ ex } {}
200 
spawn(bug_task && t)201 	void spawn(bug_task&& t) {
202 		using op_t = bug_spawn_op<final_handler_t>;
203 		// move task into ptr
204 		op_t* ptr = new op_t(final_handler_t{ *this }, static_cast<bug_task&&>(t));
205 		++count_;
206 		ex_.post(*ptr); // ptr escapes here thus task escapes but clang can't deduce that unless post() is not noexcept
207 	}
208 
wait()209 	bug_spawner_awaiter wait() noexcept { return { *this }; }
210 
211 private:
212 	bug_any_executor& ex_; // if bug_thread_executor& is used instead enables clang to detect the escape of the promise
213 	bug_spawner_awaiter* awaiter_ = nullptr;
214 	unsigned count_ = 0;
215 };
216 
217 // test case
218 
bug_spawned_task(int id,int inc)219 bug_task bug_spawned_task(int id, int inc)
220 {
221 	co_return;
222 }
223 
224 struct A {
225     A();
226 };
227 
throwing_fn(bug_spawner & s)228 void throwing_fn(bug_spawner& s) {
229 	s.spawn(bug_spawned_task(1, 2));
230     throw A{};
231 }
232 
233 // Check that the coroutine frame of bug_spawned_task are allocated from operator new.
234 // CHECK: define{{.*}}@_Z11throwing_fnR11bug_spawner
235 // CHECK-NOT: alloc
236 // CHECK: %[[CALL:.+]] = {{.*}}@_Znwm(i64{{.*}} 24)
237 // CHECK: store ptr @_Z16bug_spawned_taskii.resume, ptr %[[CALL]]
238