Skip to content

Commit 4a294b5

Browse files
[Clang] CGCoroutines skip emitting try block for value returning noexcept init await_resume calls (#73160)
Previously we were not properly skipping the generation of the `try { }` block around the `init_suspend.await_resume()` if the `await_resume` is not returning void. The reason being that the resume expression was wrapped in a `CXXBindTemporaryExpr` and the first dyn_cast failed, silently ignoring the noexcept. This only mattered for `init_suspend` because it had its own try block. This patch changes to first extract the sub expression when we see a `CXXBindTemporaryExpr`. Then perform the same logic to check for `noexcept`. Another version of this patch also wanted to assert the second step by `cast<CXXMemberCallExpr>` and as far as I understand it should be a valid assumption. I can change to that if upstream prefers.
1 parent 5933589 commit 4a294b5

File tree

2 files changed

+107
-11
lines changed

2 files changed

+107
-11
lines changed

clang/lib/CodeGen/CGCoroutine.cpp

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,48 @@ static SmallString<32> buildSuspendPrefixStr(CGCoroData &Coro, AwaitKind Kind) {
129129
return Prefix;
130130
}
131131

132-
static bool memberCallExpressionCanThrow(const Expr *E) {
133-
if (const auto *CE = dyn_cast<CXXMemberCallExpr>(E))
134-
if (const auto *Proto =
135-
CE->getMethodDecl()->getType()->getAs<FunctionProtoType>())
136-
if (isNoexceptExceptionSpec(Proto->getExceptionSpecType()) &&
137-
Proto->canThrow() == CT_Cannot)
138-
return false;
139-
return true;
132+
// Check if function can throw based on prototype noexcept, also works for
133+
// destructors which are implicitly noexcept but can be marked noexcept(false).
134+
static bool FunctionCanThrow(const FunctionDecl *D) {
135+
const auto *Proto = D->getType()->getAs<FunctionProtoType>();
136+
if (!Proto) {
137+
// Function proto is not found, we conservatively assume throwing.
138+
return true;
139+
}
140+
return !isNoexceptExceptionSpec(Proto->getExceptionSpecType()) ||
141+
Proto->canThrow() != CT_Cannot;
142+
}
143+
144+
static bool ResumeStmtCanThrow(const Stmt *S) {
145+
if (const auto *CE = dyn_cast<CallExpr>(S)) {
146+
const auto *Callee = CE->getDirectCallee();
147+
if (!Callee)
148+
// We don't have direct callee. Conservatively assume throwing.
149+
return true;
150+
151+
if (FunctionCanThrow(Callee))
152+
return true;
153+
154+
// Fall through to visit the children.
155+
}
156+
157+
if (const auto *TE = dyn_cast<CXXBindTemporaryExpr>(S)) {
158+
// Special handling of CXXBindTemporaryExpr here as calling of Dtor of the
159+
// temporary is not part of `children()` as covered in the fall through.
160+
// We need to mark entire statement as throwing if the destructor of the
161+
// temporary throws.
162+
const auto *Dtor = TE->getTemporary()->getDestructor();
163+
if (FunctionCanThrow(Dtor))
164+
return true;
165+
166+
// Fall through to visit the children.
167+
}
168+
169+
for (const auto *child : S->children())
170+
if (ResumeStmtCanThrow(child))
171+
return true;
172+
173+
return false;
140174
}
141175

142176
// Emit suspend expression which roughly looks like:
@@ -233,7 +267,7 @@ static LValueOrRValue emitSuspendExpression(CodeGenFunction &CGF, CGCoroData &Co
233267
// is marked as 'noexcept', we avoid generating this additional IR.
234268
CXXTryStmt *TryStmt = nullptr;
235269
if (Coro.ExceptionHandler && Kind == AwaitKind::Init &&
236-
memberCallExpressionCanThrow(S.getResumeExpr())) {
270+
ResumeStmtCanThrow(S.getResumeExpr())) {
237271
Coro.ResumeEHVar =
238272
CGF.CreateTempAlloca(Builder.getInt1Ty(), Prefix + Twine("resume.eh"));
239273
Builder.CreateFlagStore(true, Coro.ResumeEHVar);

clang/test/CodeGenCoroutines/coro-init-await-nontrivial-return.cpp

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ struct NontrivialType {
77
~NontrivialType() {}
88
};
99

10+
struct NontrivialTypeWithThrowingDtor {
11+
~NontrivialTypeWithThrowingDtor() noexcept(false) {}
12+
};
13+
14+
namespace can_throw {
1015
struct Task {
1116
struct promise_type;
1217
using handle_type = std::coroutine_handle<promise_type>;
@@ -38,9 +43,66 @@ Task coro_create() {
3843
co_return;
3944
}
4045

41-
// CHECK-LABEL: define{{.*}} ptr @_Z11coro_createv(
46+
// CHECK-LABEL: define{{.*}} ptr @_ZN9can_throw11coro_createEv(
4247
// CHECK: init.ready:
4348
// CHECK-NEXT: store i1 true, ptr {{.*}}
44-
// CHECK-NEXT: call void @_ZN4Task23initial_suspend_awaiter12await_resumeEv(
49+
// CHECK-NEXT: call void @_ZN9can_throw4Task23initial_suspend_awaiter12await_resumeEv(
4550
// CHECK-NEXT: call void @_ZN14NontrivialTypeD1Ev(
4651
// CHECK-NEXT: store i1 false, ptr {{.*}}
52+
}
53+
54+
template <typename R>
55+
struct NoexceptResumeTask {
56+
struct promise_type;
57+
using handle_type = std::coroutine_handle<promise_type>;
58+
59+
struct initial_suspend_awaiter {
60+
bool await_ready() {
61+
return false;
62+
}
63+
64+
void await_suspend(handle_type h) {}
65+
66+
R await_resume() noexcept { return {}; }
67+
};
68+
69+
struct promise_type {
70+
void return_void() {}
71+
void unhandled_exception() {}
72+
initial_suspend_awaiter initial_suspend() { return {}; }
73+
std::suspend_never final_suspend() noexcept { return {}; }
74+
NoexceptResumeTask get_return_object() {
75+
return NoexceptResumeTask{handle_type::from_promise(*this)};
76+
}
77+
};
78+
79+
handle_type handler;
80+
};
81+
82+
namespace no_throw {
83+
using InitNoThrowTask = NoexceptResumeTask<NontrivialType>;
84+
85+
InitNoThrowTask coro_create() {
86+
co_return;
87+
}
88+
89+
// CHECK-LABEL: define{{.*}} ptr @_ZN8no_throw11coro_createEv(
90+
// CHECK: init.ready:
91+
// CHECK-NEXT: call void @_ZN18NoexceptResumeTaskI14NontrivialTypeE23initial_suspend_awaiter12await_resumeEv(
92+
// CHECK-NEXT: call void @_ZN14NontrivialTypeD1Ev(
93+
}
94+
95+
namespace throwing_dtor {
96+
using InitTaskWithThrowingDtor = NoexceptResumeTask<NontrivialTypeWithThrowingDtor>;
97+
98+
InitTaskWithThrowingDtor coro_create() {
99+
co_return;
100+
}
101+
102+
// CHECK-LABEL: define{{.*}} ptr @_ZN13throwing_dtor11coro_createEv(
103+
// CHECK: init.ready:
104+
// CHECK-NEXT: store i1 true, ptr {{.*}}
105+
// CHECK-NEXT: call void @_ZN18NoexceptResumeTaskI30NontrivialTypeWithThrowingDtorE23initial_suspend_awaiter12await_resumeEv(
106+
// CHECK-NEXT: call void @_ZN30NontrivialTypeWithThrowingDtorD1Ev(
107+
// CHECK-NEXT: store i1 false, ptr {{.*}}
108+
}

0 commit comments

Comments
 (0)