Skip to content

Commit 256dc09

Browse files
committed
[coroutines] Add cleanup for compiler injected objects/allocations in coroutine body
Summary: * Use pushCleanup to emit freeing coroutine memory on normal and EH exits. * Surround emitted code with CodeGenFunction::RunCleanupsScope. Reviewers: rsmith, rnk, EricWF Reviewed By: rnk Subscribers: cfe-commits Differential Revision: https://reviews.llvm.org/D31460 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@299281 91177308-0d34-0410-b5e6-96231b3b80d8
1 parent 03c6593 commit 256dc09

File tree

3 files changed

+114
-28
lines changed

3 files changed

+114
-28
lines changed

lib/CodeGen/CGCoroutine.cpp

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,24 @@ void CodeGenFunction::EmitCoreturnStmt(CoreturnStmt const &S) {
215215
EmitBranchThroughCleanup(CurCoro.Data->FinalJD);
216216
}
217217

218+
namespace {
219+
// Make sure to call coro.delete on scope exit.
220+
struct CallCoroDelete final : public EHScopeStack::Cleanup {
221+
Stmt *Deallocate;
222+
223+
// TODO: Wrap deallocate in if(coro.free(...)) Deallocate.
224+
void Emit(CodeGenFunction &CGF, Flags) override {
225+
// Note: That deallocation will be emitted twice: once for a normal exit and
226+
// once for exceptional exit. This usage is safe because Deallocate does not
227+
// contain any declarations. The SubStmtBuilder::makeNewAndDeleteExpr()
228+
// builds a single call to a deallocation function which is safe to emit
229+
// multiple times.
230+
CGF.EmitStmt(Deallocate);
231+
}
232+
explicit CallCoroDelete(Stmt *DeallocStmt) : Deallocate(DeallocStmt) {}
233+
};
234+
}
235+
218236
void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
219237
auto *NullPtr = llvm::ConstantPointerNull::get(Builder.getInt8PtrTy());
220238
auto &TI = CGM.getContext().getTargetInfo();
@@ -248,26 +266,28 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
248266
EmitBlock(InitBB);
249267
}
250268

251-
// FIXME: Setup cleanup scopes.
269+
CurCoro.Data->CleanupJD = getJumpDestInCurrentScope(RetBB);
270+
{
271+
CodeGenFunction::RunCleanupsScope ResumeScope(*this);
272+
EHStack.pushCleanup<CallCoroDelete>(NormalAndEHCleanup, S.getDeallocate());
252273

253-
EmitStmt(S.getPromiseDeclStmt());
274+
EmitStmt(S.getPromiseDeclStmt());
254275

255-
CurCoro.Data->CleanupJD = getJumpDestInCurrentScope(RetBB);
256-
CurCoro.Data->FinalJD = getJumpDestInCurrentScope(FinalBB);
276+
CurCoro.Data->FinalJD = getJumpDestInCurrentScope(FinalBB);
257277

258-
// FIXME: Emit initial suspend and more before the body.
278+
// FIXME: Emit initial suspend and more before the body.
259279

260-
CurCoro.Data->CurrentAwaitKind = AwaitKind::Normal;
261-
EmitStmt(S.getBody());
280+
CurCoro.Data->CurrentAwaitKind = AwaitKind::Normal;
281+
EmitStmt(S.getBody());
262282

263-
// See if we need to generate final suspend.
264-
const bool CanFallthrough = Builder.GetInsertBlock();
265-
const bool HasCoreturns = CurCoro.Data->CoreturnCount > 0;
266-
if (CanFallthrough || HasCoreturns) {
267-
EmitBlock(FinalBB);
268-
// FIXME: Emit final suspend.
283+
// See if we need to generate final suspend.
284+
const bool CanFallthrough = Builder.GetInsertBlock();
285+
const bool HasCoreturns = CurCoro.Data->CoreturnCount > 0;
286+
if (CanFallthrough || HasCoreturns) {
287+
EmitBlock(FinalBB);
288+
// FIXME: Emit final suspend.
289+
}
269290
}
270-
EmitStmt(S.getDeallocate());
271291

272292
EmitBlock(RetBB);
273293

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Verify that coroutine promise and allocated memory are freed up on exception.
2+
// RUN: %clang_cc1 -std=c++1z -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s
3+
4+
namespace std::experimental {
5+
template <typename... T> struct coroutine_traits;
6+
7+
template <class Promise = void> struct coroutine_handle {
8+
coroutine_handle() = default;
9+
static coroutine_handle from_address(void *) { return {}; }
10+
};
11+
template <> struct coroutine_handle<void> {
12+
static coroutine_handle from_address(void *) { return {}; }
13+
coroutine_handle() = default;
14+
template <class PromiseType>
15+
coroutine_handle(coroutine_handle<PromiseType>) {}
16+
};
17+
}
18+
19+
struct suspend_always {
20+
bool await_ready();
21+
void await_suspend(std::experimental::coroutine_handle<>);
22+
void await_resume();
23+
};
24+
25+
template <> struct std::experimental::coroutine_traits<void> {
26+
struct promise_type {
27+
void get_return_object();
28+
suspend_always initial_suspend();
29+
suspend_always final_suspend();
30+
void return_void();
31+
promise_type();
32+
~promise_type();
33+
void unhandled_exception();
34+
};
35+
};
36+
37+
struct Cleanup { ~Cleanup(); };
38+
void may_throw();
39+
40+
// CHECK: define void @_Z1fv(
41+
void f() {
42+
// CHECK: call i8* @_Znwm(i64
43+
44+
// If promise constructor throws, check that we free the memory.
45+
46+
// CHECK: invoke void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_typeC1Ev(
47+
// CHECK-NEXT: to label %{{.+}} unwind label %[[DeallocPad:.+]]
48+
49+
Cleanup cleanup;
50+
may_throw();
51+
52+
// if may_throw throws, check that we destroy the promise and free the memory.
53+
54+
// CHECK: invoke void @_Z9may_throwv(
55+
// CHECK-NEXT: to label %{{.+}} unwind label %[[PromDtorPad:.+]]
56+
57+
// CHECK: [[DeallocPad]]:
58+
// CHECK-NEXT: landingpad
59+
// CHECK-NEXT: cleanup
60+
// CHECK: br label %[[Dealloc:.+]]
61+
62+
// CHECK: [[PromDtorPad]]:
63+
// CHECK-NEXT: landingpad
64+
// CHECK-NEXT: cleanup
65+
// CHECK: call void @_ZN7CleanupD1Ev(%struct.Cleanup*
66+
// CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_typeD1Ev(
67+
// CHECK: br label %[[Dealloc]]
68+
69+
// CHECK: [[Dealloc]]:
70+
// CHECK: %[[Mem:.+]] = call i8* @llvm.coro.free(
71+
// CHECK: call void @_ZdlPv(i8* %[[Mem]])
72+
73+
co_return;
74+
}

test/CodeGenCoroutines/coro-return.cpp

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
1-
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++1z -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s
22

3-
namespace std {
4-
namespace experimental {
5-
template <typename... T>
6-
struct coroutine_traits;
3+
namespace std::experimental {
4+
template <typename... T> struct coroutine_traits;
75

8-
template <class Promise = void>
9-
struct coroutine_handle {
6+
template <class Promise = void> struct coroutine_handle {
107
coroutine_handle() = default;
118
static coroutine_handle from_address(void *) { return {}; }
129
};
13-
14-
template <>
15-
struct coroutine_handle<void> {
10+
template <> struct coroutine_handle<void> {
1611
static coroutine_handle from_address(void *) { return {}; }
1712
coroutine_handle() = default;
1813
template <class PromiseType>
1914
coroutine_handle(coroutine_handle<PromiseType>) {}
2015
};
21-
22-
}
2316
}
2417

2518
struct suspend_always {
@@ -28,8 +21,7 @@ struct suspend_always {
2821
void await_resume();
2922
};
3023

31-
template<>
32-
struct std::experimental::coroutine_traits<void> {
24+
template <> struct std::experimental::coroutine_traits<void> {
3325
struct promise_type {
3426
void get_return_object();
3527
suspend_always initial_suspend();

0 commit comments

Comments
 (0)