[clang] [codegen] Emit missing cleanups for stmt-expr and coro suspensions [take-2] (PR #85398)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Mon Mar 25 07:15:28 PDT 2024
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/85398
>From 0654f624b3b60026398e8b0168623a85e3129630 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Fri, 15 Mar 2024 13:19:36 +0000
Subject: [PATCH 1/5] [codegen] Emit cleanups for branch in stmt-expr and coro
suspensions
---
clang/lib/CodeGen/CGCleanup.cpp | 7 +++++--
clang/lib/CodeGen/CGExprAgg.cpp | 13 +++++++------
2 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/clang/lib/CodeGen/CGCleanup.cpp b/clang/lib/CodeGen/CGCleanup.cpp
index f87caf050eeaa7..c22d4da0fefc3c 100644
--- a/clang/lib/CodeGen/CGCleanup.cpp
+++ b/clang/lib/CodeGen/CGCleanup.cpp
@@ -694,7 +694,8 @@ void CodeGenFunction::PopCleanupBlock(bool FallthroughIsBranchThrough) {
// - whether there's a fallthrough
llvm::BasicBlock *FallthroughSource = Builder.GetInsertBlock();
- bool HasFallthrough = (FallthroughSource != nullptr && IsActive);
+ bool HasFallthrough =
+ FallthroughSource != nullptr && (IsActive || HasExistingBranches);
// Branch-through fall-throughs leave the insertion point set to the
// end of the last cleanup, which points to the current scope. The
@@ -719,7 +720,9 @@ void CodeGenFunction::PopCleanupBlock(bool FallthroughIsBranchThrough) {
// If we have a prebranched fallthrough into an inactive normal
// cleanup, rewrite it so that it leads to the appropriate place.
- if (Scope.isNormalCleanup() && HasPrebranchedFallthrough && !IsActive) {
+ if (Scope.isNormalCleanup() && HasPrebranchedFallthrough &&
+ !RequiresNormalCleanup) {
+ assert(!IsActive);
llvm::BasicBlock *prebranchDest;
// If the prebranch is semantically branching through the next
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index 5190b22bcc1622..7e62599089b8df 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -15,6 +15,7 @@
#include "CodeGenFunction.h"
#include "CodeGenModule.h"
#include "ConstantEmitter.h"
+#include "EHScopeStack.h"
#include "TargetInfo.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h"
@@ -1389,15 +1390,15 @@ AggExprEmitter::VisitLambdaExpr(LambdaExpr *E) {
if (QualType::DestructionKind DtorKind =
CurField->getType().isDestructedType()) {
assert(LV.isSimple());
- if (CGF.needsEHCleanup(DtorKind)) {
+ if (DtorKind) {
if (!CleanupDominator)
CleanupDominator = CGF.Builder.CreateAlignedLoad(
CGF.Int8Ty,
llvm::Constant::getNullValue(CGF.Int8PtrTy),
CharUnits::One()); // placeholder
- CGF.pushDestroy(EHCleanup, LV.getAddress(CGF), CurField->getType(),
- CGF.getDestroyer(DtorKind), false);
+ CGF.pushDestroy(NormalAndEHCleanup, LV.getAddress(CGF),
+ CurField->getType(), CGF.getDestroyer(DtorKind), false);
Cleanups.push_back(CGF.EHStack.stable_begin());
}
}
@@ -1806,9 +1807,9 @@ void AggExprEmitter::VisitCXXParenListOrInitListExpr(
if (QualType::DestructionKind dtorKind
= field->getType().isDestructedType()) {
assert(LV.isSimple());
- if (CGF.needsEHCleanup(dtorKind)) {
- CGF.pushDestroy(EHCleanup, LV.getAddress(CGF), field->getType(),
- CGF.getDestroyer(dtorKind), false);
+ if (dtorKind) {
+ CGF.pushDestroy(NormalAndEHCleanup, LV.getAddress(CGF),
+ field->getType(), CGF.getDestroyer(dtorKind), false);
addCleanup(CGF.EHStack.stable_begin());
pushedCleanup = true;
}
>From 1b7c160b030f3a765d40723bbbf9a7daa4a1e090 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Fri, 15 Mar 2024 15:22:07 +0000
Subject: [PATCH 2/5] add tests
---
.../control-flow-in-expr-cleanup.cpp | 172 ++++++++++++++++++
1 file changed, 172 insertions(+)
create mode 100644 clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp
diff --git a/clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp b/clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp
new file mode 100644
index 00000000000000..d703532b5a10b9
--- /dev/null
+++ b/clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp
@@ -0,0 +1,172 @@
+// RUN: %clang_cc1 --std=c++20 -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s
+
+#include "Inputs/coroutine.h"
+
+struct Printy {
+ Printy(const char *name) : name(name) {}
+ ~Printy() {}
+ const char *name;
+};
+
+struct coroutine {
+ struct promise_type;
+ std::coroutine_handle<promise_type> handle;
+ ~coroutine() {
+ if (handle) handle.destroy();
+ }
+};
+
+struct coroutine::promise_type {
+ coroutine get_return_object() {
+ return {std::coroutine_handle<promise_type>::from_promise(*this)};
+ }
+ std::suspend_never initial_suspend() noexcept { return {}; }
+ std::suspend_always final_suspend() noexcept { return {}; }
+ void return_void() {}
+ void unhandled_exception() {}
+};
+
+struct Awaiter : std::suspend_always {
+ Printy await_resume() { return {"awaited"}; }
+};
+
+int foo() { return 2; }
+
+struct PrintiesCopy {
+ Printy a;
+ Printy b;
+ Printy c;
+};
+
+void ParenInit() {
+ // CHECK: define dso_local void @_Z9ParenInitv()
+ // CHECK: [[CLEANUP_DEST:%.+]] = alloca i32, align 4
+ PrintiesCopy ps(Printy("a"),
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ ({
+ if (foo()) return;
+ // CHECK: if.then:
+ // CHECK-NEXT: store i32 1, ptr [[CLEANUP_DEST]], align 4
+ // CHECK-NEXT: br label %cleanup
+ Printy("b");
+ // CHECK: if.end:
+ // CHECK-NEXT: call void @_ZN6PrintyC1EPKc
+ }),
+ ({
+ if (foo()) return;
+ // CHECK: if.then{{.*}}:
+ // CHECK-NEXT: store i32 1, ptr [[CLEANUP_DEST]], align 4
+ // CHECK-NEXT: call void @_ZN6PrintyD1Ev
+ // CHECK-NEXT: br label %cleanup
+ Printy("c");
+ // CHECK: if.end{{.*}}:
+ // CHECK-NEXT: call void @_ZN6PrintyC1EPKc
+ // CHECK-NEXT: call void @_ZN12PrintiesCopyD1Ev
+ // CHECK-NEXT: br label %return
+ }));
+ // CHECK: cleanup:
+ // CHECK-NEXT: call void @_ZN6PrintyD1Ev
+ // CHECK-NEXT: br label %return
+}
+
+coroutine ParenInitCoro() {
+ // CHECK: define dso_local void @_Z13ParenInitCorov
+ // CHECK: [[ACTIVE1:%.+]] = alloca i1, align 1
+ // CHECK: [[ACTIVE2:%.+]] = alloca i1, align 1
+ PrintiesCopy ps(Printy("a"), Printy("b"),
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ // CHECK-NEXT: store i1 true, ptr [[ACTIVE1]].reload.addr, align 1
+ // CHECK-NEXT: store i1 true, ptr [[ACTIVE2]].reload.addr, align 1
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ co_await Awaiter{}
+
+ // CHECK: await.cleanup:
+ // CHECK-NEXT: br label %[[CLEANUP:.+]].from.await.cleanup
+
+ // CHECK: [[CLEANUP]].from.await.cleanup:
+ // CHECK: br label %[[CLEANUP]]
+
+ // CHECK: await.ready:
+ // CHECK: store i1 false, ptr [[ACTIVE1]].reload.addr, align 1
+ // CHECK-NEXT: store i1 false, ptr [[ACTIVE2]].reload.addr, align 1
+ // CHECK-NEXT: br label %[[CLEANUP]].from.await.ready
+
+ // CHECK: [[CLEANUP]].from.await.ready:
+ // CHECK: br label %[[CLEANUP]]
+
+ // CHECK: [[CLEANUP]]:
+ // CHECK: [[IS_ACTIVE1:%.+]] = load i1, ptr [[ACTIVE1]].reload.addr, align 1
+ // CHECK-NEXT: br i1 [[IS_ACTIVE1]], label %[[ACTION1:.+]], label %[[DONE1:.+]]
+
+ // CHECK: [[ACTION1]]:
+ // CHECK: call void @_ZN6PrintyD1Ev
+ // CHECK: br label %[[DONE1]]
+
+ // CHECK: [[DONE1]]:
+ // CHECK: [[IS_ACTIVE2:%.+]] = load i1, ptr [[ACTIVE2]].reload.addr, align 1
+ // CHECK-NEXT: br i1 [[IS_ACTIVE2]], label %[[ACTION2:.+]], label %[[DONE2:.+]]
+
+ // CHECK: [[ACTION2]]:
+ // CHECK: call void @_ZN6PrintyD1Ev
+ // CHECK: br label %[[DONE2]]
+ );
+}
+
+// TODO: Add more assertions after preliminary review.
+// struct S {
+// Printy arr1[2];
+// Printy arr2[2];
+// Printy p;
+// };
+
+// void ArraySubobjects() {
+// S s{{Printy("a"), Printy("b")},
+// {Printy("a"), ({
+// if (foo() == 1) {
+// return;
+// }
+// Printy("b");
+// })},
+// ({
+// if (foo() == 2) {
+// return;
+// }
+// Printy("b");
+// })};
+// }
+
+// coroutine ArraySubobjectsCoro() {
+// S s{{Printy("a"), Printy("b")},
+// {Printy("a"), co_await Awaiter{}},
+// co_await Awaiter{}};
+// }
+
+// struct A {
+// Printy a;
+// };
+// struct B : A {
+// Printy b;
+// Printy c;
+// };
+
+// void BaseClassCtors() {
+// auto S = B({Printy("a")}, Printy("b"), ({
+// return;
+// Printy("c");
+// }));
+// }
+
+// coroutine BaseClassCtorsCoro() {
+// auto S = B({Printy("a")}, Printy("b"), co_await Awaiter{});
+// }
+
+// void LambdaInit() {
+// auto S = [a = Printy("a"), b = ({
+// return;
+// Printy("b");
+// })]() { return a; };
+// }
+
+// coroutine LambdaInitCoro() {
+// auto S = [a = Printy("a"), b = co_await Awaiter{}]() { return a; };
+// }
>From b5c5fe685df999668890aab0209427bea6eca2f3 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Mon, 18 Mar 2024 12:23:57 +0000
Subject: [PATCH 3/5] Handle array init cleanups and lifetime extended dtors
---
clang/include/clang/AST/Expr.h | 2 ++
clang/lib/AST/Expr.cpp | 17 +++++++++++++++++
clang/lib/CodeGen/CGCleanup.cpp | 8 ++++++++
clang/lib/CodeGen/CGDecl.cpp | 22 ++++++++++++++--------
clang/lib/CodeGen/CGExprAgg.cpp | 3 ++-
clang/lib/CodeGen/CGExprCXX.cpp | 2 +-
clang/lib/CodeGen/CodeGenFunction.h | 12 +++++++++++-
7 files changed, 55 insertions(+), 11 deletions(-)
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 446bec4081e869..26d0d589cd2550 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -246,6 +246,8 @@ class Expr : public ValueStmt {
return static_cast<bool>(getDependence() & ExprDependence::Error);
}
+ bool containsControlFlow() const;
+
/// getExprLoc - Return the preferred location for the arrow when diagnosing
/// a problem with a generic expression.
SourceLocation getExprLoc() const LLVM_READONLY;
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index b4de2155adcebd..40cf06847528b8 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -24,6 +24,7 @@
#include "clang/AST/IgnoreExpr.h"
#include "clang/AST/Mangle.h"
#include "clang/AST/RecordLayout.h"
+#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/CharInfo.h"
@@ -2503,6 +2504,22 @@ Stmt *BlockExpr::getBody() {
// Generic Expression Routines
//===----------------------------------------------------------------------===//
+bool Expr::containsControlFlow() const {
+ struct BranchDetector : public RecursiveASTVisitor<BranchDetector> {
+ bool HasBranch = false;
+ bool activate() {
+ HasBranch = true;
+ return false;
+ }
+ bool VisitCoawaitExpr(CoawaitExpr *) { return activate(); }
+ bool VisitCoyieldExpr(CoyieldExpr *) { return activate(); }
+ bool VisitStmtExpr(StmtExpr *) { return activate(); }
+ };
+ BranchDetector detector;
+ detector.TraverseStmt(const_cast<Expr *>(this));
+ return detector.HasBranch;
+}
+
bool Expr::isReadIfDiscardedInCPlusPlus11() const {
// In C++11, discarded-value expressions of a certain form are special,
// according to [expr]p10:
diff --git a/clang/lib/CodeGen/CGCleanup.cpp b/clang/lib/CodeGen/CGCleanup.cpp
index c22d4da0fefc3c..5e51f263930848 100644
--- a/clang/lib/CodeGen/CGCleanup.cpp
+++ b/clang/lib/CodeGen/CGCleanup.cpp
@@ -492,7 +492,15 @@ void CodeGenFunction::PopCleanupBlocks(
/// cleanups from the given savepoint in the lifetime-extended cleanups stack.
void CodeGenFunction::PopCleanupBlocks(
EHScopeStack::stable_iterator Old, size_t OldLifetimeExtendedSize,
+ size_t OldDeactivateAfterFullExprStackSize,
std::initializer_list<llvm::Value **> ValuesToReload) {
+ for (size_t I = DeactivateAfterFullExprStack.size();
+ I > OldDeactivateAfterFullExprStackSize; I--) {
+ DeactivateCleanupBlock(DeactivateAfterFullExprStack[I - 1].Cleanup,
+ DeactivateAfterFullExprStack[I - 1].DominatingIP);
+ DeactivateAfterFullExprStack[I - 1].DominatingIP->eraseFromParent();
+ }
+ DeactivateAfterFullExprStack.resize(OldDeactivateAfterFullExprStackSize);
PopCleanupBlocks(Old, ValuesToReload);
// Move our deferred cleanups onto the EH stack.
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index bbe14ef4c17244..24760727d966aa 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -19,6 +19,7 @@
#include "CodeGenFunction.h"
#include "CodeGenModule.h"
#include "ConstantEmitter.h"
+#include "EHScopeStack.h"
#include "PatternInit.h"
#include "TargetInfo.h"
#include "clang/AST/ASTContext.h"
@@ -2204,10 +2205,16 @@ void CodeGenFunction::pushLifetimeExtendedDestroy(CleanupKind cleanupKind,
// Push an EH-only cleanup for the object now.
// FIXME: When popping normal cleanups, we need to keep this EH cleanup
// around in case a temporary's destructor throws an exception.
- if (cleanupKind & EHCleanup)
- EHStack.pushCleanup<DestroyObject>(
- static_cast<CleanupKind>(cleanupKind & ~NormalCleanup), addr, type,
- destroyer, useEHCleanupForArray);
+ if (cleanupKind) {
+ // Placeholder dominating IP for this cleanup.
+ llvm::Instruction *CleanupDominator = Builder.CreateAlignedLoad(
+ Int8Ty, llvm::Constant::getNullValue(Int8PtrTy), CharUnits::One());
+ EHStack.pushCleanup<DestroyObject>(static_cast<CleanupKind>(cleanupKind),
+ addr, type, destroyer,
+ useEHCleanupForArray);
+ DeactivateAfterFullExprStack.push_back(
+ {EHStack.stable_begin(), CleanupDominator});
+ }
return pushCleanupAfterFullExprWithActiveFlag<DestroyObject>(
cleanupKind, Address::invalid(), addr, type, destroyer, useEHCleanupForArray);
@@ -2437,10 +2444,9 @@ void CodeGenFunction::pushIrregularPartialArrayCleanup(llvm::Value *arrayBegin,
QualType elementType,
CharUnits elementAlign,
Destroyer *destroyer) {
- pushFullExprCleanup<IrregularPartialArrayDestroy>(EHCleanup,
- arrayBegin, arrayEndPointer,
- elementType, elementAlign,
- destroyer);
+ pushFullExprCleanup<IrregularPartialArrayDestroy>(
+ NormalAndEHCleanup, arrayBegin, arrayEndPointer, elementType,
+ elementAlign, destroyer);
}
/// pushRegularPartialArrayCleanup - Push an EH cleanup to destroy
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index 7e62599089b8df..8b502596a215c2 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -558,7 +558,8 @@ void AggExprEmitter::EmitArrayInit(Address DestPtr, llvm::ArrayType *AType,
Address endOfInit = Address::invalid();
EHScopeStack::stable_iterator cleanup;
llvm::Instruction *cleanupDominator = nullptr;
- if (CGF.needsEHCleanup(dtorKind)) {
+ if (CGF.needsEHCleanup(dtorKind) ||
+ (dtorKind && ExprToVisit->containsControlFlow())) {
// In principle we could tell the cleanup where we are more
// directly, but the control flow can get so varied here that it
// would actually be quite complex. Therefore we go through an
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index 2adbef6d55122c..fd413dcfc236c6 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -1103,7 +1103,7 @@ void CodeGenFunction::EmitNewArrayInitializer(
}
// Enter a partial-destruction Cleanup if necessary.
- if (needsEHCleanup(DtorKind)) {
+ if (needsEHCleanup(DtorKind) || (DtorKind && E->containsControlFlow())) {
// In principle we could tell the Cleanup where we are more
// directly, but the control flow can get so varied here that it
// would actually be quite complex. Therefore we go through an
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 6c825a302913df..8f2d09ba017738 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -648,6 +648,12 @@ class CodeGenFunction : public CodeGenTypeCache {
EHScopeStack EHStack;
llvm::SmallVector<char, 256> LifetimeExtendedCleanupStack;
+ struct DeactivateAfterFullExprCleanup {
+ EHScopeStack::stable_iterator Cleanup;
+ llvm::Instruction *DominatingIP;
+ };
+ llvm::SmallVector<DeactivateAfterFullExprCleanup>
+ DeactivateAfterFullExprStack;
llvm::SmallVector<const JumpDest *, 2> SEHTryEpilogueStack;
llvm::Instruction *CurrentFuncletPad = nullptr;
@@ -904,6 +910,7 @@ class CodeGenFunction : public CodeGenTypeCache {
class RunCleanupsScope {
EHScopeStack::stable_iterator CleanupStackDepth, OldCleanupScopeDepth;
size_t LifetimeExtendedCleanupStackSize;
+ size_t DeactivateAfterFullExprStackSize;
bool OldDidCallStackSave;
protected:
bool PerformCleanup;
@@ -923,6 +930,8 @@ class CodeGenFunction : public CodeGenTypeCache {
CleanupStackDepth = CGF.EHStack.stable_begin();
LifetimeExtendedCleanupStackSize =
CGF.LifetimeExtendedCleanupStack.size();
+ DeactivateAfterFullExprStackSize =
+ CGF.DeactivateAfterFullExprStack.size();
OldDidCallStackSave = CGF.DidCallStackSave;
CGF.DidCallStackSave = false;
OldCleanupScopeDepth = CGF.CurrentCleanupScopeDepth;
@@ -950,7 +959,7 @@ class CodeGenFunction : public CodeGenTypeCache {
assert(PerformCleanup && "Already forced cleanup");
CGF.DidCallStackSave = OldDidCallStackSave;
CGF.PopCleanupBlocks(CleanupStackDepth, LifetimeExtendedCleanupStackSize,
- ValuesToReload);
+ DeactivateAfterFullExprStackSize, ValuesToReload);
PerformCleanup = false;
CGF.CurrentCleanupScopeDepth = OldCleanupScopeDepth;
}
@@ -1172,6 +1181,7 @@ class CodeGenFunction : public CodeGenTypeCache {
void
PopCleanupBlocks(EHScopeStack::stable_iterator OldCleanupStackSize,
size_t OldLifetimeExtendedStackSize,
+ size_t OldDeactivateAfterFullExprStackSize,
std::initializer_list<llvm::Value **> ValuesToReload = {});
void ResolveBranchFixups(llvm::BasicBlock *Target);
>From 801feaaffe5a0375160e81b48b08694f99ec813a Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Wed, 20 Mar 2024 14:28:41 +0000
Subject: [PATCH 4/5] Add more tests. Reuse CleanupDeactivationScope tosimplify
previous deferred deactivations
---
clang/include/clang/AST/Expr.h | 8 +-
clang/lib/AST/Expr.cpp | 9 +-
clang/lib/CodeGen/CGCleanup.cpp | 10 +-
clang/lib/CodeGen/CGDecl.cpp | 56 ++-
clang/lib/CodeGen/CGExprAgg.cpp | 82 +--
clang/lib/CodeGen/CGExprCXX.cpp | 29 +-
clang/lib/CodeGen/CodeGenFunction.cpp | 6 +
clang/lib/CodeGen/CodeGenFunction.h | 74 ++-
.../control-flow-in-expr-cleanup.cpp | 465 ++++++++++++++----
9 files changed, 535 insertions(+), 204 deletions(-)
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 26d0d589cd2550..36e4c8acdc6736 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -246,7 +246,13 @@ class Expr : public ValueStmt {
return static_cast<bool>(getDependence() & ExprDependence::Error);
}
- bool containsControlFlow() const;
+ /// Determines whether the expression contains primitives which may branch out
+ /// of the expr through a non-exceptional normal control flow. This is
+ /// possible if the expression contains:
+ /// - a statement expression having a control flow out of the expr (break,
+ /// goto, return).
+ /// - a coroutine suspension.
+ bool mayBranchOut() const;
/// getExprLoc - Return the preferred location for the arrow when diagnosing
/// a problem with a generic expression.
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 40cf06847528b8..b2d5df116ca9e8 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -25,6 +25,7 @@
#include "clang/AST/Mangle.h"
#include "clang/AST/RecordLayout.h"
#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/AST/Stmt.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/CharInfo.h"
@@ -2504,16 +2505,20 @@ Stmt *BlockExpr::getBody() {
// Generic Expression Routines
//===----------------------------------------------------------------------===//
-bool Expr::containsControlFlow() const {
+bool Expr::mayBranchOut() const {
struct BranchDetector : public RecursiveASTVisitor<BranchDetector> {
bool HasBranch = false;
bool activate() {
HasBranch = true;
return false;
}
+ // Coroutine suspensions.
bool VisitCoawaitExpr(CoawaitExpr *) { return activate(); }
bool VisitCoyieldExpr(CoyieldExpr *) { return activate(); }
- bool VisitStmtExpr(StmtExpr *) { return activate(); }
+ // Control flow in stmt-expressions.
+ bool VisitBreakStmt(BreakStmt *) { return activate(); }
+ bool VisitReturnStmt(ReturnStmt *) { return activate(); }
+ bool VisitGotoStmt(GotoStmt *) { return activate(); }
};
BranchDetector detector;
detector.TraverseStmt(const_cast<Expr *>(this));
diff --git a/clang/lib/CodeGen/CGCleanup.cpp b/clang/lib/CodeGen/CGCleanup.cpp
index 5e51f263930848..8f74a06e4f96d4 100644
--- a/clang/lib/CodeGen/CGCleanup.cpp
+++ b/clang/lib/CodeGen/CGCleanup.cpp
@@ -492,15 +492,7 @@ void CodeGenFunction::PopCleanupBlocks(
/// cleanups from the given savepoint in the lifetime-extended cleanups stack.
void CodeGenFunction::PopCleanupBlocks(
EHScopeStack::stable_iterator Old, size_t OldLifetimeExtendedSize,
- size_t OldDeactivateAfterFullExprStackSize,
std::initializer_list<llvm::Value **> ValuesToReload) {
- for (size_t I = DeactivateAfterFullExprStack.size();
- I > OldDeactivateAfterFullExprStackSize; I--) {
- DeactivateCleanupBlock(DeactivateAfterFullExprStack[I - 1].Cleanup,
- DeactivateAfterFullExprStack[I - 1].DominatingIP);
- DeactivateAfterFullExprStack[I - 1].DominatingIP->eraseFromParent();
- }
- DeactivateAfterFullExprStack.resize(OldDeactivateAfterFullExprStackSize);
PopCleanupBlocks(Old, ValuesToReload);
// Move our deferred cleanups onto the EH stack.
@@ -730,6 +722,8 @@ void CodeGenFunction::PopCleanupBlock(bool FallthroughIsBranchThrough) {
// cleanup, rewrite it so that it leads to the appropriate place.
if (Scope.isNormalCleanup() && HasPrebranchedFallthrough &&
!RequiresNormalCleanup) {
+ // FIXME: Come up with a program which would need forwarding prebranched
+ // fallthrough and add tests. Otherwise delete this and assert against it.
assert(!IsActive);
llvm::BasicBlock *prebranchDest;
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 24760727d966aa..02c597bd999dc5 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -2186,6 +2186,24 @@ void CodeGenFunction::pushDestroy(CleanupKind cleanupKind, Address addr,
destroyer, useEHCleanupForArray);
}
+// Pushes a destroy and defers its deactivation until its
+// DeferredDeactivationCleanupScope is exited.
+void CodeGenFunction::pushDestroyAndDeferDeactivation(
+ QualType::DestructionKind dtorKind, Address addr, QualType type) {
+ assert(dtorKind && "cannot push destructor for trivial type");
+
+ CleanupKind cleanupKind = getCleanupKind(dtorKind);
+ pushDestroyAndDeferDeactivation(
+ cleanupKind, addr, type, getDestroyer(dtorKind), cleanupKind & EHCleanup);
+}
+
+void CodeGenFunction::pushDestroyAndDeferDeactivation(
+ CleanupKind cleanupKind, Address addr, QualType type, Destroyer *destroyer,
+ bool useEHCleanupForArray) {
+ pushCleanupAndDeferDeactivation<DestroyObject>(
+ cleanupKind, addr, type, destroyer, useEHCleanupForArray);
+}
+
void CodeGenFunction::pushStackRestore(CleanupKind Kind, Address SPMem) {
EHStack.pushCleanup<CallStackRestore>(Kind, SPMem);
}
@@ -2202,22 +2220,19 @@ void CodeGenFunction::pushLifetimeExtendedDestroy(CleanupKind cleanupKind,
// If we're not in a conditional branch, we don't need to bother generating a
// conditional cleanup.
if (!isInConditionalBranch()) {
- // Push an EH-only cleanup for the object now.
// FIXME: When popping normal cleanups, we need to keep this EH cleanup
// around in case a temporary's destructor throws an exception.
- if (cleanupKind) {
- // Placeholder dominating IP for this cleanup.
- llvm::Instruction *CleanupDominator = Builder.CreateAlignedLoad(
- Int8Ty, llvm::Constant::getNullValue(Int8PtrTy), CharUnits::One());
- EHStack.pushCleanup<DestroyObject>(static_cast<CleanupKind>(cleanupKind),
- addr, type, destroyer,
- useEHCleanupForArray);
- DeactivateAfterFullExprStack.push_back(
- {EHStack.stable_begin(), CleanupDominator});
- }
+ // Add the cleanup to the EHStack. After the full-expr, this would be
+ // deactivated before being popped from the stack.
+ pushDestroyAndDeferDeactivation(cleanupKind, addr, type, destroyer,
+ useEHCleanupForArray);
+
+ // Since this is lifetime-extended, push it once again to the EHStack after
+ // the full expression.
return pushCleanupAfterFullExprWithActiveFlag<DestroyObject>(
- cleanupKind, Address::invalid(), addr, type, destroyer, useEHCleanupForArray);
+ cleanupKind, Address::invalid(), addr, type, destroyer,
+ useEHCleanupForArray);
}
// Otherwise, we should only destroy the object if it's been initialized.
@@ -2232,13 +2247,12 @@ void CodeGenFunction::pushLifetimeExtendedDestroy(CleanupKind cleanupKind,
Address ActiveFlag = createCleanupActiveFlag();
SavedType SavedAddr = saveValueInCond(addr);
- if (cleanupKind & EHCleanup) {
- EHStack.pushCleanup<ConditionalCleanupType>(
- static_cast<CleanupKind>(cleanupKind & ~NormalCleanup), SavedAddr, type,
- destroyer, useEHCleanupForArray);
- initFullExprCleanupWithFlag(ActiveFlag);
- }
+ pushCleanupAndDeferDeactivation<ConditionalCleanupType>(
+ cleanupKind, SavedAddr, type, destroyer, useEHCleanupForArray);
+ initFullExprCleanupWithFlag(ActiveFlag);
+ // Since this is lifetime-extended, push it once again to the EHStack after
+ // the full expression.
pushCleanupAfterFullExprWithActiveFlag<ConditionalCleanupType>(
cleanupKind, ActiveFlag, SavedAddr, type, destroyer,
useEHCleanupForArray);
@@ -2433,9 +2447,9 @@ namespace {
};
} // end anonymous namespace
-/// pushIrregularPartialArrayCleanup - Push an EH cleanup to destroy
-/// already-constructed elements of the given array. The cleanup
-/// may be popped with DeactivateCleanupBlock or PopCleanupBlock.
+/// pushIrregularPartialArrayCleanup - Push a NormalAndEHCleanup to
+/// destroy already-constructed elements of the given array. The cleanup may be
+/// popped with DeactivateCleanupBlock or PopCleanupBlock.
///
/// \param elementType - the immediate element type of the array;
/// possibly still an array type
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index 8b502596a215c2..bedd83e1b99cea 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -25,6 +25,7 @@
#include "llvm/IR/Constants.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalVariable.h"
+#include "llvm/IR/Instruction.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Intrinsics.h"
using namespace clang;
@@ -556,25 +557,24 @@ void AggExprEmitter::EmitArrayInit(Address DestPtr, llvm::ArrayType *AType,
// For that, we'll need an EH cleanup.
QualType::DestructionKind dtorKind = elementType.isDestructedType();
Address endOfInit = Address::invalid();
- EHScopeStack::stable_iterator cleanup;
- llvm::Instruction *cleanupDominator = nullptr;
+ CodeGenFunction::CleanupDeactivationScope deactivation(CGF);
+
if (CGF.needsEHCleanup(dtorKind) ||
- (dtorKind && ExprToVisit->containsControlFlow())) {
+ (dtorKind && ExprToVisit->mayBranchOut())) {
// In principle we could tell the cleanup where we are more
// directly, but the control flow can get so varied here that it
// would actually be quite complex. Therefore we go through an
// alloca.
+ llvm::Instruction *DominatingIP =
+ Builder.CreateFlagLoad(llvm::ConstantInt::getNullValue(CGF.Int8PtrTy));
endOfInit = CGF.CreateTempAlloca(begin->getType(), CGF.getPointerAlign(),
"arrayinit.endOfInit");
- cleanupDominator = Builder.CreateStore(begin, endOfInit);
+ Builder.CreateStore(begin, endOfInit);
CGF.pushIrregularPartialArrayCleanup(begin, endOfInit, elementType,
elementAlign,
CGF.getDestroyer(dtorKind));
- cleanup = CGF.EHStack.stable_begin();
-
- // Otherwise, remember that we didn't need a cleanup.
- } else {
- dtorKind = QualType::DK_none;
+ CGF.DeferredDeactivationCleanupStack.push_back(
+ {CGF.EHStack.stable_begin(), DominatingIP});
}
llvm::Value *one = llvm::ConstantInt::get(CGF.SizeTy, 1);
@@ -670,9 +670,6 @@ void AggExprEmitter::EmitArrayInit(Address DestPtr, llvm::ArrayType *AType,
CGF.EmitBlock(endBB);
}
-
- // Leave the partial-array cleanup if we entered one.
- if (dtorKind) CGF.DeactivateCleanupBlock(cleanup, cleanupDominator);
}
//===----------------------------------------------------------------------===//
@@ -1370,9 +1367,8 @@ AggExprEmitter::VisitLambdaExpr(LambdaExpr *E) {
LValue SlotLV = CGF.MakeAddrLValue(Slot.getAddress(), E->getType());
// We'll need to enter cleanup scopes in case any of the element
- // initializers throws an exception.
- SmallVector<EHScopeStack::stable_iterator, 16> Cleanups;
- llvm::Instruction *CleanupDominator = nullptr;
+ // initializers throws an exception or contains branch out of the expressions.
+ CodeGenFunction::CleanupDeactivationScope scope(CGF);
CXXRecordDecl::field_iterator CurField = E->getLambdaClass()->field_begin();
for (LambdaExpr::const_capture_init_iterator i = E->capture_init_begin(),
@@ -1391,28 +1387,12 @@ AggExprEmitter::VisitLambdaExpr(LambdaExpr *E) {
if (QualType::DestructionKind DtorKind =
CurField->getType().isDestructedType()) {
assert(LV.isSimple());
- if (DtorKind) {
- if (!CleanupDominator)
- CleanupDominator = CGF.Builder.CreateAlignedLoad(
- CGF.Int8Ty,
- llvm::Constant::getNullValue(CGF.Int8PtrTy),
- CharUnits::One()); // placeholder
-
- CGF.pushDestroy(NormalAndEHCleanup, LV.getAddress(CGF),
- CurField->getType(), CGF.getDestroyer(DtorKind), false);
- Cleanups.push_back(CGF.EHStack.stable_begin());
- }
+ if (DtorKind)
+ CGF.pushDestroyAndDeferDeactivation(
+ NormalAndEHCleanup, LV.getAddress(CGF), CurField->getType(),
+ CGF.getDestroyer(DtorKind), false);
}
}
-
- // Deactivate all the partial cleanups in reverse order, which
- // generally means popping them.
- for (unsigned i = Cleanups.size(); i != 0; --i)
- CGF.DeactivateCleanupBlock(Cleanups[i-1], CleanupDominator);
-
- // Destroy the placeholder if we made one.
- if (CleanupDominator)
- CleanupDominator->eraseFromParent();
}
void AggExprEmitter::VisitExprWithCleanups(ExprWithCleanups *E) {
@@ -1700,14 +1680,7 @@ void AggExprEmitter::VisitCXXParenListOrInitListExpr(
// We'll need to enter cleanup scopes in case any of the element
// initializers throws an exception.
SmallVector<EHScopeStack::stable_iterator, 16> cleanups;
- llvm::Instruction *cleanupDominator = nullptr;
- auto addCleanup = [&](const EHScopeStack::stable_iterator &cleanup) {
- cleanups.push_back(cleanup);
- if (!cleanupDominator) // create placeholder once needed
- cleanupDominator = CGF.Builder.CreateAlignedLoad(
- CGF.Int8Ty, llvm::Constant::getNullValue(CGF.Int8PtrTy),
- CharUnits::One());
- };
+ CodeGenFunction::CleanupDeactivationScope DeactivateCleanups(CGF);
unsigned curInitIndex = 0;
@@ -1730,10 +1703,8 @@ void AggExprEmitter::VisitCXXParenListOrInitListExpr(
CGF.EmitAggExpr(InitExprs[curInitIndex++], AggSlot);
if (QualType::DestructionKind dtorKind =
- Base.getType().isDestructedType()) {
- CGF.pushDestroy(dtorKind, V, Base.getType());
- addCleanup(CGF.EHStack.stable_begin());
- }
+ Base.getType().isDestructedType())
+ CGF.pushDestroyAndDeferDeactivation(dtorKind, V, Base.getType());
}
}
@@ -1809,9 +1780,9 @@ void AggExprEmitter::VisitCXXParenListOrInitListExpr(
= field->getType().isDestructedType()) {
assert(LV.isSimple());
if (dtorKind) {
- CGF.pushDestroy(NormalAndEHCleanup, LV.getAddress(CGF),
- field->getType(), CGF.getDestroyer(dtorKind), false);
- addCleanup(CGF.EHStack.stable_begin());
+ CGF.pushDestroyAndDeferDeactivation(
+ NormalAndEHCleanup, LV.getAddress(CGF), field->getType(),
+ CGF.getDestroyer(dtorKind), false);
pushedCleanup = true;
}
}
@@ -1824,17 +1795,6 @@ void AggExprEmitter::VisitCXXParenListOrInitListExpr(
if (GEP->use_empty())
GEP->eraseFromParent();
}
-
- // Deactivate all the partial cleanups in reverse order, which
- // generally means popping them.
- assert((cleanupDominator || cleanups.empty()) &&
- "Missing cleanupDominator before deactivating cleanup blocks");
- for (unsigned i = cleanups.size(); i != 0; --i)
- CGF.DeactivateCleanupBlock(cleanups[i-1], cleanupDominator);
-
- // Destroy the placeholder if we made one.
- if (cleanupDominator)
- cleanupDominator->eraseFromParent();
}
void AggExprEmitter::VisitArrayInitLoopExpr(const ArrayInitLoopExpr *E,
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index fd413dcfc236c6..f10437d6592421 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -1005,8 +1005,8 @@ void CodeGenFunction::EmitNewArrayInitializer(
const Expr *Init = E->getInitializer();
Address EndOfInit = Address::invalid();
QualType::DestructionKind DtorKind = ElementType.isDestructedType();
- EHScopeStack::stable_iterator Cleanup;
- llvm::Instruction *CleanupDominator = nullptr;
+ CleanupDeactivationScope deactivation(*this);
+ bool pushedCleanup = false;
CharUnits ElementSize = getContext().getTypeSizeInChars(ElementType);
CharUnits ElementAlign =
@@ -1103,18 +1103,21 @@ void CodeGenFunction::EmitNewArrayInitializer(
}
// Enter a partial-destruction Cleanup if necessary.
- if (needsEHCleanup(DtorKind) || (DtorKind && E->containsControlFlow())) {
+ if (needsEHCleanup(DtorKind) || (DtorKind && E->mayBranchOut())) {
// In principle we could tell the Cleanup where we are more
// directly, but the control flow can get so varied here that it
// would actually be quite complex. Therefore we go through an
// alloca.
+ llvm::Instruction *DominatingIP =
+ Builder.CreateFlagLoad(llvm::ConstantInt::getNullValue(Int8PtrTy));
EndOfInit = CreateTempAlloca(BeginPtr.getType(), getPointerAlign(),
"array.init.end");
- CleanupDominator = Builder.CreateStore(BeginPtr.getPointer(), EndOfInit);
pushIrregularPartialArrayCleanup(BeginPtr.getPointer(), EndOfInit,
ElementType, ElementAlign,
getDestroyer(DtorKind));
- Cleanup = EHStack.stable_begin();
+ DeferredDeactivationCleanupStack.push_back(
+ {EHStack.stable_begin(), DominatingIP});
+ pushedCleanup = true;
}
CharUnits StartAlign = CurPtr.getAlignment();
@@ -1160,9 +1163,6 @@ void CodeGenFunction::EmitNewArrayInitializer(
// initialization.
llvm::ConstantInt *ConstNum = dyn_cast<llvm::ConstantInt>(NumElements);
if (ConstNum && ConstNum->getZExtValue() <= InitListElements) {
- // If there was a Cleanup, deactivate it.
- if (CleanupDominator)
- DeactivateCleanupBlock(Cleanup, CleanupDominator);
return;
}
@@ -1277,12 +1277,14 @@ void CodeGenFunction::EmitNewArrayInitializer(
Builder.CreateStore(CurPtr.getPointer(), EndOfInit);
// Enter a partial-destruction Cleanup if necessary.
- if (!CleanupDominator && needsEHCleanup(DtorKind)) {
+ if (!pushedCleanup && needsEHCleanup(DtorKind)) {
+ llvm::Instruction *DominatingIP =
+ Builder.CreateFlagLoad(llvm::ConstantInt::getNullValue(Int8PtrTy));
pushRegularPartialArrayCleanup(BeginPtr.getPointer(), CurPtr.getPointer(),
ElementType, ElementAlign,
getDestroyer(DtorKind));
- Cleanup = EHStack.stable_begin();
- CleanupDominator = Builder.CreateUnreachable();
+ DeferredDeactivationCleanupStack.push_back(
+ {EHStack.stable_begin(), DominatingIP});
}
// Emit the initializer into this element.
@@ -1290,10 +1292,7 @@ void CodeGenFunction::EmitNewArrayInitializer(
AggValueSlot::DoesNotOverlap);
// Leave the Cleanup if we entered one.
- if (CleanupDominator) {
- DeactivateCleanupBlock(Cleanup, CleanupDominator);
- CleanupDominator->eraseFromParent();
- }
+ deactivation.ForceDeactivate();
// Advance to the next element by adjusting the pointer type as necessary.
llvm::Value *NextPtr =
diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp
index 4a3ff49b0007a3..ae0acc6e0a3e56 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -91,6 +91,8 @@ CodeGenFunction::CodeGenFunction(CodeGenModule &cgm, bool suppressNewContext)
CodeGenFunction::~CodeGenFunction() {
assert(LifetimeExtendedCleanupStack.empty() && "failed to emit a cleanup");
+ assert(DeferredDeactivationCleanupStack.empty() &&
+ "missed to deactivate a cleanup");
if (getLangOpts().OpenMP && CurFn)
CGM.getOpenMPRuntime().functionFinished(*this);
@@ -336,6 +338,10 @@ static void EmitIfUsed(CodeGenFunction &CGF, llvm::BasicBlock *BB) {
void CodeGenFunction::FinishFunction(SourceLocation EndLoc) {
assert(BreakContinueStack.empty() &&
"mismatched push/pop in break/continue stack!");
+ assert(LifetimeExtendedCleanupStack.empty() &&
+ "mismatched push/pop of cleanups in EHStack!");
+ assert(DeferredDeactivationCleanupStack.empty() &&
+ "mismatched activate/deactivate of cleanups!");
bool OnlySimpleReturnStmts = NumSimpleReturnExprs > 0
&& NumSimpleReturnExprs == NumReturnExprs
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 8f2d09ba017738..c848d7ae777ccf 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -648,12 +648,51 @@ class CodeGenFunction : public CodeGenTypeCache {
EHScopeStack EHStack;
llvm::SmallVector<char, 256> LifetimeExtendedCleanupStack;
- struct DeactivateAfterFullExprCleanup {
+
+ // A stack of cleanups which were added to EHStack but have to be deactivated
+ // later before being popped or emitted. These are usually deactivated on
+ // exiting a `CleanupDeactivationScope` scope. For instance, after a
+ // full-expr.
+ //
+ // These are specially useful for correctly emitting cleanups while
+ // encountering branches out of expression (through stmt-expr or coroutine
+ // suspensions).
+ struct DeferredDeactivateCleanup {
EHScopeStack::stable_iterator Cleanup;
llvm::Instruction *DominatingIP;
};
- llvm::SmallVector<DeactivateAfterFullExprCleanup>
- DeactivateAfterFullExprStack;
+ llvm::SmallVector<DeferredDeactivateCleanup> DeferredDeactivationCleanupStack;
+
+ // Enters a new scope for capturing cleanups which are deferred to be
+ // deactivated, all of which will be deactivated once the scope is exited.
+ struct CleanupDeactivationScope {
+ CodeGenFunction &CGF;
+ size_t OldDeactivateCleanupStackSize;
+ bool Deactivated;
+ CleanupDeactivationScope(CodeGenFunction &CGF)
+ : CGF(CGF), OldDeactivateCleanupStackSize(
+ CGF.DeferredDeactivationCleanupStack.size()),
+ Deactivated(false) {}
+
+ void ForceDeactivate() {
+ assert(!Deactivated && "Deactivating already deactivated scope");
+ auto &Stack = CGF.DeferredDeactivationCleanupStack;
+ for (size_t I = Stack.size(); I > OldDeactivateCleanupStackSize; I--) {
+ CGF.DeactivateCleanupBlock(Stack[I - 1].Cleanup,
+ Stack[I - 1].DominatingIP);
+ Stack[I - 1].DominatingIP->eraseFromParent();
+ }
+ Stack.resize(OldDeactivateCleanupStackSize);
+ Deactivated = true;
+ }
+
+ ~CleanupDeactivationScope() {
+ if (Deactivated)
+ return;
+ ForceDeactivate();
+ }
+ };
+
llvm::SmallVector<const JumpDest *, 2> SEHTryEpilogueStack;
llvm::Instruction *CurrentFuncletPad = nullptr;
@@ -859,6 +898,19 @@ class CodeGenFunction : public CodeGenTypeCache {
new (Buffer + sizeof(Header) + sizeof(T)) Address(ActiveFlag);
}
+ // Push a cleanup onto EHStack and deactivate it later. It is usually
+ // deactivated when exiting a `CleanupDeactivationScope` (for example: after a
+ // full expression).
+ template <class T, class... As>
+ void pushCleanupAndDeferDeactivation(CleanupKind Kind, As... A) {
+ // Placeholder dominating IP for this cleanup.
+ llvm::Instruction *DominatingIP =
+ Builder.CreateFlagLoad(llvm::Constant::getNullValue(Int8PtrTy));
+ EHStack.pushCleanup<T>(Kind, A...);
+ DeferredDeactivationCleanupStack.push_back(
+ {EHStack.stable_begin(), DominatingIP});
+ }
+
/// Set up the last cleanup that was pushed as a conditional
/// full-expression cleanup.
void initFullExprCleanup() {
@@ -910,7 +962,7 @@ class CodeGenFunction : public CodeGenTypeCache {
class RunCleanupsScope {
EHScopeStack::stable_iterator CleanupStackDepth, OldCleanupScopeDepth;
size_t LifetimeExtendedCleanupStackSize;
- size_t DeactivateAfterFullExprStackSize;
+ CleanupDeactivationScope DeactivateCleanups;
bool OldDidCallStackSave;
protected:
bool PerformCleanup;
@@ -925,13 +977,10 @@ class CodeGenFunction : public CodeGenTypeCache {
public:
/// Enter a new cleanup scope.
explicit RunCleanupsScope(CodeGenFunction &CGF)
- : PerformCleanup(true), CGF(CGF)
- {
+ : DeactivateCleanups(CGF), PerformCleanup(true), CGF(CGF) {
CleanupStackDepth = CGF.EHStack.stable_begin();
LifetimeExtendedCleanupStackSize =
CGF.LifetimeExtendedCleanupStack.size();
- DeactivateAfterFullExprStackSize =
- CGF.DeactivateAfterFullExprStack.size();
OldDidCallStackSave = CGF.DidCallStackSave;
CGF.DidCallStackSave = false;
OldCleanupScopeDepth = CGF.CurrentCleanupScopeDepth;
@@ -958,8 +1007,9 @@ class CodeGenFunction : public CodeGenTypeCache {
void ForceCleanup(std::initializer_list<llvm::Value**> ValuesToReload = {}) {
assert(PerformCleanup && "Already forced cleanup");
CGF.DidCallStackSave = OldDidCallStackSave;
+ DeactivateCleanups.ForceDeactivate();
CGF.PopCleanupBlocks(CleanupStackDepth, LifetimeExtendedCleanupStackSize,
- DeactivateAfterFullExprStackSize, ValuesToReload);
+ ValuesToReload);
PerformCleanup = false;
CGF.CurrentCleanupScopeDepth = OldCleanupScopeDepth;
}
@@ -1181,7 +1231,6 @@ class CodeGenFunction : public CodeGenTypeCache {
void
PopCleanupBlocks(EHScopeStack::stable_iterator OldCleanupStackSize,
size_t OldLifetimeExtendedStackSize,
- size_t OldDeactivateAfterFullExprStackSize,
std::initializer_list<llvm::Value **> ValuesToReload = {});
void ResolveBranchFixups(llvm::BasicBlock *Target);
@@ -2146,6 +2195,11 @@ class CodeGenFunction : public CodeGenTypeCache {
Address addr, QualType type);
void pushDestroy(CleanupKind kind, Address addr, QualType type,
Destroyer *destroyer, bool useEHCleanupForArray);
+ void pushDestroyAndDeferDeactivation(QualType::DestructionKind dtorKind,
+ Address addr, QualType type);
+ void pushDestroyAndDeferDeactivation(CleanupKind cleanupKind, Address addr,
+ QualType type, Destroyer *destroyer,
+ bool useEHCleanupForArray);
void pushLifetimeExtendedDestroy(CleanupKind kind, Address addr,
QualType type, Destroyer *destroyer,
bool useEHCleanupForArray);
diff --git a/clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp b/clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp
index d703532b5a10b9..b3c6d8f65a4fba 100644
--- a/clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp
+++ b/clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp
@@ -32,52 +32,52 @@ struct Awaiter : std::suspend_always {
int foo() { return 2; }
-struct PrintiesCopy {
+struct Printies {
Printy a;
Printy b;
Printy c;
};
void ParenInit() {
- // CHECK: define dso_local void @_Z9ParenInitv()
+ // CHECK-LABEL: define dso_local void @_Z9ParenInitv()
// CHECK: [[CLEANUP_DEST:%.+]] = alloca i32, align 4
- PrintiesCopy ps(Printy("a"),
- // CHECK: call void @_ZN6PrintyC1EPKc
- ({
- if (foo()) return;
- // CHECK: if.then:
- // CHECK-NEXT: store i32 1, ptr [[CLEANUP_DEST]], align 4
- // CHECK-NEXT: br label %cleanup
- Printy("b");
- // CHECK: if.end:
- // CHECK-NEXT: call void @_ZN6PrintyC1EPKc
- }),
- ({
- if (foo()) return;
- // CHECK: if.then{{.*}}:
- // CHECK-NEXT: store i32 1, ptr [[CLEANUP_DEST]], align 4
- // CHECK-NEXT: call void @_ZN6PrintyD1Ev
- // CHECK-NEXT: br label %cleanup
- Printy("c");
- // CHECK: if.end{{.*}}:
- // CHECK-NEXT: call void @_ZN6PrintyC1EPKc
- // CHECK-NEXT: call void @_ZN12PrintiesCopyD1Ev
- // CHECK-NEXT: br label %return
- }));
+ Printies ps(Printy("a"),
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ ({
+ if (foo()) return;
+ // CHECK: if.then:
+ // CHECK-NEXT: store i32 1, ptr [[CLEANUP_DEST]], align 4
+ // CHECK-NEXT: br label %cleanup
+ Printy("b");
+ // CHECK: if.end:
+ // CHECK-NEXT: call void @_ZN6PrintyC1EPKc
+ }),
+ ({
+ if (foo()) return;
+ // CHECK: if.then{{.*}}:
+ // CHECK-NEXT: store i32 1, ptr [[CLEANUP_DEST]], align 4
+ // CHECK-NEXT: call void @_ZN6PrintyD1Ev
+ // CHECK-NEXT: br label %cleanup
+ Printy("c");
+ // CHECK: if.end{{.*}}:
+ // CHECK-NEXT: call void @_ZN6PrintyC1EPKc
+ // CHECK-NEXT: call void @_ZN8PrintiesD1Ev
+ // CHECK-NEXT: br label %return
+ }));
// CHECK: cleanup:
// CHECK-NEXT: call void @_ZN6PrintyD1Ev
// CHECK-NEXT: br label %return
}
coroutine ParenInitCoro() {
- // CHECK: define dso_local void @_Z13ParenInitCorov
+ // CHECK-LABEL: define dso_local void @_Z13ParenInitCorov
// CHECK: [[ACTIVE1:%.+]] = alloca i1, align 1
// CHECK: [[ACTIVE2:%.+]] = alloca i1, align 1
- PrintiesCopy ps(Printy("a"), Printy("b"),
+ Printies ps(Printy("a"), Printy("b"),
// CHECK: call void @_ZN6PrintyC1EPKc
- // CHECK-NEXT: store i1 true, ptr [[ACTIVE1]].reload.addr, align 1
// CHECK-NEXT: store i1 true, ptr [[ACTIVE2]].reload.addr, align 1
// CHECK: call void @_ZN6PrintyC1EPKc
+ // CHECK-NEXT: store i1 true, ptr [[ACTIVE1]].reload.addr, align 1
co_await Awaiter{}
// CHECK: await.cleanup:
@@ -112,61 +112,354 @@ coroutine ParenInitCoro() {
);
}
-// TODO: Add more assertions after preliminary review.
-// struct S {
-// Printy arr1[2];
-// Printy arr2[2];
-// Printy p;
-// };
-
-// void ArraySubobjects() {
-// S s{{Printy("a"), Printy("b")},
-// {Printy("a"), ({
-// if (foo() == 1) {
-// return;
-// }
-// Printy("b");
-// })},
-// ({
-// if (foo() == 2) {
-// return;
-// }
-// Printy("b");
-// })};
-// }
-
-// coroutine ArraySubobjectsCoro() {
-// S s{{Printy("a"), Printy("b")},
-// {Printy("a"), co_await Awaiter{}},
-// co_await Awaiter{}};
-// }
-
-// struct A {
-// Printy a;
-// };
-// struct B : A {
-// Printy b;
-// Printy c;
-// };
-
-// void BaseClassCtors() {
-// auto S = B({Printy("a")}, Printy("b"), ({
-// return;
-// Printy("c");
-// }));
-// }
-
-// coroutine BaseClassCtorsCoro() {
-// auto S = B({Printy("a")}, Printy("b"), co_await Awaiter{});
-// }
-
-// void LambdaInit() {
-// auto S = [a = Printy("a"), b = ({
-// return;
-// Printy("b");
-// })]() { return a; };
-// }
-
-// coroutine LambdaInitCoro() {
-// auto S = [a = Printy("a"), b = co_await Awaiter{}]() { return a; };
-// }
+void break_in_stmt_expr() {
+ // Verify that the "break" in "if.then".calls dtor before jumping to "for.end".
+
+ // CHECK-LABEL: define dso_local void @_Z18break_in_stmt_exprv()
+ Printies p{Printy("a"),
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ ({
+ for (;;) {
+ Printies ps{
+ Printy("b"),
+ // CHECK: for.cond:
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ ({
+ if (foo()) {
+ break;
+ // CHECK: if.then:
+ // CHECK-NEXT: call void @_ZN6PrintyD1Ev
+ // CHECK-NEXT: br label %for.end
+ }
+ Printy("c");
+ // CHECK: if.end:
+ // CHECK-NEXT: call void @_ZN6PrintyC1EPKc
+ }),
+ Printy("d")};
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ // CHECK-NEXT: call void @_ZN8PrintiesD1Ev
+ // CHECK-NEXT: br label %for.cond
+ }
+ Printy("e");
+ // CHECK: for.end:
+ // CHECK-NEXT: call void @_ZN6PrintyC1EPKc
+ }),
+ Printy("f")};
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ // CHECK-NEXT: call void @_ZN8PrintiesD1Ev
+}
+
+void goto_in_stmt_expr() {
+ // Verify that:
+ // - correct branch fixups for deactivated normal cleanups are generated correctly.
+
+ // CHECK-LABEL: define dso_local void @_Z17goto_in_stmt_exprv()
+ // CHECK: [[CLEANUP_DEST_SLOT:%cleanup.dest.slot.*]] = alloca i32, align 4
+ {
+ Printies p1{Printy("a"), // CHECK: call void @_ZN6PrintyC1EPKc
+ ({
+ {
+ Printies p2{Printy("b"),
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ ({
+ if (foo() == 1) {
+ goto in;
+ // CHECK: if.then:
+ // CHECK-NEXT: store i32 2, ptr [[CLEANUP_DEST_SLOT]], align 4
+ // CHECK-NEXT: br label %[[CLEANUP1:.+]]
+ }
+ if (foo() == 2) {
+ goto out;
+ // CHECK: if.then{{.*}}:
+ // CHECK-NEXT: store i32 3, ptr [[CLEANUP_DEST_SLOT]], align 4
+ // CHECK-NEXT: br label %[[CLEANUP1]]
+ }
+ Printy("c");
+ // CHECK: if.end{{.*}}:
+ // CHECK-NEXT: call void @_ZN6PrintyC1EPKc
+ }),
+ Printy("d")};
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ // CHECK-NEXT: call void @_ZN8PrintiesD1Ev
+ // CHECK-NEXT: br label %in
+
+ }
+ in:
+ Printy("e");
+ // CHECK: in: ; preds = %if.end{{.*}}, %[[CLEANUP1]]
+ // CHECK-NEXT: call void @_ZN6PrintyC1EPKc
+ }),
+ Printy("f")};
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ // CHECK-NEXT: call void @_ZN8PrintiesD1Ev
+ // CHECK-NEXT: br label %out
+ }
+out:
+ return;
+ // CHECK: out:
+ // CHECK-NEXT: ret void
+
+ // CHECK: [[CLEANUP1]]: ; preds = %if.then{{.*}}, %if.then
+ // CHECK-NEXT: call void @_ZN6PrintyD1Ev
+ // CHECK-NEXT: %cleanup.dest = load i32, ptr [[CLEANUP_DEST_SLOT]], align 4
+ // CHECK-NEXT: switch i32 %cleanup.dest, label %[[CLEANUP2:.+]] [
+ // CHECK-NEXT: i32 2, label %in
+ // CHECK-NEXT: ]
+
+ // CHECK: [[CLEANUP2]]: ; preds = %[[CLEANUP1]]
+ // CHECK-NEXT: call void @_ZN6PrintyD1Ev
+ // CHECK-NEXT: %cleanup.dest{{.*}} = load i32, ptr [[CLEANUP_DEST_SLOT]], align 4
+ // CHECK-NEXT: switch i32 %cleanup.dest{{.*}}, label %unreachable [
+ // CHECK-NEXT: i32 3, label %out
+ // CHECK-NEXT: ]
+}
+
+void ArrayInit() {
+ // Printy arr[4] = {ctorA, ctorB, stmt-exprC, stmt-exprD};
+ // Verify that:
+ // - We do the necessary stores for array cleanups (endOfInit and last constructed element).
+ // - We update the array init element correctly for ctorA, ctorB and stmt-exprC.
+ // - stmt-exprC and stmt-exprD share the array body dtor code (see %cleanup).
+
+ // CHECK-LABEL: define dso_local void @_Z9ArrayInitv()
+ // CHECK: %arrayinit.endOfInit = alloca ptr, align 8
+ // CHECK: %cleanup.dest.slot = alloca i32, align 4
+ // CHECK: %arrayinit.begin = getelementptr inbounds [4 x %struct.Printy], ptr %arr, i64 0, i64 0
+ // CHECK: store ptr %arrayinit.begin, ptr %arrayinit.endOfInit, align 8
+ Printy arr[4] = {
+ Printy("a"),
+ // CHECK: call void @_ZN6PrintyC1EPKc(ptr noundef nonnull align 8 dereferenceable(8) %arrayinit.begin, ptr noundef @.str)
+ // CHECK: [[ARRAYINIT_ELEMENT1:%.+]] = getelementptr inbounds %struct.Printy, ptr %arrayinit.begin, i64 1
+ // CHECK: store ptr [[ARRAYINIT_ELEMENT1]], ptr %arrayinit.endOfInit, align 8
+ Printy("b"),
+ // CHECK: call void @_ZN6PrintyC1EPKc(ptr noundef nonnull align 8 dereferenceable(8) [[ARRAYINIT_ELEMENT1]], ptr noundef @.str.1)
+ // CHECK: [[ARRAYINIT_ELEMENT2:%.+]] = getelementptr inbounds %struct.Printy, ptr [[ARRAYINIT_ELEMENT1]], i64 1
+ // CHECK: store ptr [[ARRAYINIT_ELEMENT2]], ptr %arrayinit.endOfInit, align 8
+ ({
+ // CHECK: br i1 {{.*}}, label %if.then, label %if.end
+ if (foo()) {
+ return;
+ // CHECK: if.then:
+ // CHECK-NEXT: store i32 1, ptr %cleanup.dest.slot, align 4
+ // CHECK-NEXT: br label %cleanup
+ }
+ // CHECK: if.end:
+ Printy("c");
+ // CHECK-NEXT: call void @_ZN6PrintyC1EPKc
+ // CHECK-NEXT: %arrayinit.element2 = getelementptr inbounds %struct.Printy, ptr %arrayinit.element1, i64 1
+ // CHECK-NEXT: store ptr %arrayinit.element2, ptr %arrayinit.endOfInit, align 8
+ }),
+ ({
+ // CHECK: br i1 {{%.+}} label %[[IF_THEN2:.+]], label %[[IF_END2:.+]]
+ if (foo()) {
+ return;
+ // CHECK: [[IF_THEN2]]:
+ // CHECK-NEXT: store i32 1, ptr %cleanup.dest.slot, align 4
+ // CHECK-NEXT: br label %cleanup
+ }
+ // CHECK: [[IF_END2]]:
+ Printy("d");
+ // CHECK-NEXT: call void @_ZN6PrintyC1EPKc
+ // CHECK-NEXT: %array.begin = getelementptr inbounds [4 x %struct.Printy], ptr %arr, i32 0, i32 0
+ // CHECK-NEXT: %0 = getelementptr inbounds %struct.Printy, ptr %array.begin, i64 4
+ // CHECK-NEXT: br label %[[ARRAY_DESTROY_BODY1:.+]]
+ }),
+ };
+
+ // CHECK: [[ARRAY_DESTROY_BODY1]]:
+ // CHECK-NEXT: %arraydestroy.elementPast{{.*}} = phi ptr [ %0, %[[IF_END2]] ], [ %arraydestroy.element{{.*}}, %[[ARRAY_DESTROY_BODY1]] ]
+ // CHECK-NEXT: %arraydestroy.element{{.*}} = getelementptr inbounds %struct.Printy, ptr %arraydestroy.elementPast{{.*}}, i64 -1
+ // CHECK-NEXT: call void @_ZN6PrintyD1Ev
+ // CHECK-NEXT: %arraydestroy.done{{.*}} = icmp eq ptr %arraydestroy.element{{.*}}, %array.begin
+ // CHECK-NEXT: br i1 %arraydestroy.done{{.*}}, label %[[ARRAY_DESTROY_DONE1:.+]], label %[[ARRAY_DESTROY_BODY1]]
+
+ // CHECK: [[ARRAY_DESTROY_DONE1]]:
+ // CHECK-NEXT: ret void
+
+ // CHECK: cleanup:
+ // CHECK-NEXT: %1 = load ptr, ptr %arrayinit.endOfInit, align 8
+ // CHECK-NEXT: %arraydestroy.isempty = icmp eq ptr %arrayinit.begin, %1
+ // CHECK-NEXT: br i1 %arraydestroy.isempty, label %[[ARRAY_DESTROY_DONE2:.+]], label %[[ARRAY_DESTROY_BODY2:.+]]
+
+ // CHECK: [[ARRAY_DESTROY_BODY2]]:
+ // CHECK-NEXT: %arraydestroy.elementPast = phi ptr [ %1, %cleanup ], [ %arraydestroy.element, %[[ARRAY_DESTROY_BODY2]] ]
+ // CHECK-NEXT: %arraydestroy.element = getelementptr inbounds %struct.Printy, ptr %arraydestroy.elementPast, i64 -1
+ // CHECK-NEXT: call void @_ZN6PrintyD1Ev(ptr noundef nonnull align 8 dereferenceable(8) %arraydestroy.element)
+ // CHECK-NEXT: %arraydestroy.done = icmp eq ptr %arraydestroy.element, %arrayinit.begin
+ // CHECK-NEXT: br i1 %arraydestroy.done, label %[[ARRAY_DESTROY_DONE2]], label %[[ARRAY_DESTROY_BODY2]]
+
+ // CHECK: [[ARRAY_DESTROY_DONE2]]:
+ // CHECK-NEXT: br label %[[ARRAY_DESTROY_DONE1]]
+}
+
+coroutine ArrayInitCoro() {
+ // Verify that:
+ // - We do the necessary stores for array cleanups.
+ // - Array cleanups are called by await.cleanup.
+ // - We activate the cleanup after the first element and deactivate it in await.ready (see cleanup.isactive).
+
+ // CHECK-LABEL: define dso_local void @_Z13ArrayInitCorov
+ // CHECK: %arrayinit.endOfInit = alloca ptr, align 8
+ // CHECK: %cleanup.isactive = alloca i1, align 1
+ Printy arr[2] = {
+ Printy("a"),
+ // CHECK: %arrayinit.begin = getelementptr inbounds [2 x %struct.Printy], ptr %arr.reload.addr, i64 0, i64 0
+ // CHECK-NEXT: %arrayinit.begin.spill.addr = getelementptr inbounds %_Z13ArrayInitCorov.Frame, ptr %0, i32 0, i32 10
+ // CHECK-NEXT: store ptr %arrayinit.begin, ptr %arrayinit.begin.spill.addr, align 8
+ // CHECK-NEXT: store i1 true, ptr %cleanup.isactive.reload.addr, align 1
+ // CHECK-NEXT: store ptr %arrayinit.begin, ptr %arrayinit.endOfInit.reload.addr, align 8
+ // CHECK-NEXT: call void @_ZN6PrintyC1EPKc(ptr noundef nonnull align 8 dereferenceable(8) %arrayinit.begin, ptr noundef @.str)
+ // CHECK-NEXT: %arrayinit.element = getelementptr inbounds %struct.Printy, ptr %arrayinit.begin, i64 1
+ // CHECK-NEXT: %arrayinit.element.spill.addr = getelementptr inbounds %_Z13ArrayInitCorov.Frame, ptr %0, i32 0, i32 11
+ // CHECK-NEXT: store ptr %arrayinit.element, ptr %arrayinit.element.spill.addr, align 8
+ // CHECK-NEXT: store ptr %arrayinit.element, ptr %arrayinit.endOfInit.reload.addr, align 8
+ co_await Awaiter{}
+ // CHECK-NEXT: @_ZNSt14suspend_always11await_readyEv
+ // CHECK-NEXT: br i1 %{{.+}}, label %await.ready, label %CoroSave30
+ };
+ // CHECK: await.cleanup: ; preds = %AfterCoroSuspend{{.*}}
+ // CHECK-NEXT: br label %cleanup{{.*}}.from.await.cleanup
+
+ // CHECK: cleanup{{.*}}.from.await.cleanup: ; preds = %await.cleanup
+ // CHECK: br label %cleanup{{.*}}
+
+ // CHECK: await.ready:
+ // CHECK-NEXT: %arrayinit.element.reload.addr = getelementptr inbounds %_Z13ArrayInitCorov.Frame, ptr %0, i32 0, i32 11
+ // CHECK-NEXT: %arrayinit.element.reload = load ptr, ptr %arrayinit.element.reload.addr, align 8
+ // CHECK-NEXT: call void @_ZN7Awaiter12await_resumeEv
+ // CHECK-NEXT: store i1 false, ptr %cleanup.isactive.reload.addr, align 1
+ // CHECK-NEXT: br label %cleanup{{.*}}.from.await.ready
+
+ // CHECK: cleanup{{.*}}: ; preds = %cleanup{{.*}}.from.await.ready, %cleanup{{.*}}.from.await.cleanup
+ // CHECK: %cleanup.is_active = load i1, ptr %cleanup.isactive.reload.addr, align 1
+ // CHECK-NEXT: br i1 %cleanup.is_active, label %cleanup.action, label %cleanup.done
+
+ // CHECK: cleanup.action:
+ // CHECK: %arraydestroy.isempty = icmp eq ptr %arrayinit.begin.reload{{.*}}, %{{.*}}
+ // CHECK-NEXT: br i1 %arraydestroy.isempty, label %arraydestroy.done{{.*}}, label %arraydestroy.body.from.cleanup.action
+ // Ignore rest of the array cleanup.
+}
+
+void ArraySubobjects() {
+ struct S {
+ Printy arr1[2];
+ Printy arr2[2];
+ Printy p;
+ };
+ // CHECK-LABEL: define dso_local void @_Z15ArraySubobjectsv()
+ // CHECK: %arrayinit.endOfInit = alloca ptr, align 8
+ S s{{Printy("a"), Printy("b")},
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ {Printy("a"),
+ // CHECK: [[ARRAYINIT_BEGIN:%.+]] = getelementptr inbounds [2 x %struct.Printy]
+ // CHECK: store ptr [[ARRAYINIT_BEGIN]], ptr %arrayinit.endOfInit, align 8
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ // CHECK: [[ARRAYINIT_ELEMENT:%.+]] = getelementptr inbounds %struct.Printy
+ // CHECK: store ptr [[ARRAYINIT_ELEMENT]], ptr %arrayinit.endOfInit, align 8
+ ({
+ if (foo()) {
+ return;
+ // CHECK: if.then:
+ // CHECK-NEXT: [[V0:%.+]] = load ptr, ptr %arrayinit.endOfInit, align 8
+ // CHECK-NEXT: %arraydestroy.isempty = icmp eq ptr [[ARRAYINIT_BEGIN]], [[V0]]
+ // CHECK-NEXT: br i1 %arraydestroy.isempty, label %[[ARRAY_DESTROY_DONE:.+]], label %[[ARRAY_DESTROY_BODY:.+]]
+ }
+ Printy("b");
+ })
+ },
+ Printy("c")
+ // CHECK: if.end:
+ // CHECK-NEXT: call void @_ZN6PrintyC1EPKc
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ // CHECK-NEXT: call void @_ZZ15ArraySubobjectsvEN1SD1Ev
+ // CHECK-NEXT: br label %return
+ };
+ // CHECK: return:
+ // CHECK-NEXT: ret void
+
+ // CHECK: [[ARRAY_DESTROY_BODY]]:
+ // CHECK-NEXT: %arraydestroy.elementPast = phi ptr [ %0, %if.then ], [ %arraydestroy.element, %[[ARRAY_DESTROY_BODY]] ]
+ // CHECK-NEXT: %arraydestroy.element = getelementptr inbounds %struct.Printy, ptr %arraydestroy.elementPast, i64 -1
+ // CHECK-NEXT: call void @_ZN6PrintyD1Ev(ptr noundef nonnull align 8 dereferenceable(8) %arraydestroy.element)
+ // CHECK-NEXT: %arraydestroy.done = icmp eq ptr %arraydestroy.element, [[ARRAYINIT_BEGIN]]
+ // CHECK-NEXT: br i1 %arraydestroy.done, label %[[ARRAY_DESTROY_DONE]], label %[[ARRAY_DESTROY_BODY]]
+
+ // CHECK: [[ARRAY_DESTROY_DONE]]
+ // CHECK-NEXT: [[ARRAY_BEGIN:%.+]] = getelementptr inbounds [2 x %struct.Printy], ptr %arr1, i32 0, i32 0
+ // CHECK-NEXT: [[V1:%.+]] = getelementptr inbounds %struct.Printy, ptr [[ARRAY_BEGIN]], i64 2
+ // CHECK-NEXT: br label %[[ARRAY_DESTROY_BODY2:.+]]
+
+ // CHECK: [[ARRAY_DESTROY_BODY2]]:
+ // CHECK-NEXT: %arraydestroy.elementPast5 = phi ptr [ %1, %[[ARRAY_DESTROY_DONE]] ], [ %arraydestroy.element6, %[[ARRAY_DESTROY_BODY2]] ]
+ // CHECK-NEXT: %arraydestroy.element6 = getelementptr inbounds %struct.Printy, ptr %arraydestroy.elementPast5, i64 -1
+ // CHECK-NEXT: call void @_ZN6PrintyD1Ev(ptr noundef nonnull align 8 dereferenceable(8) %arraydestroy.element6)
+ // CHECK-NEXT: %arraydestroy.done7 = icmp eq ptr %arraydestroy.element6, [[ARRAY_BEGIN]]
+ // CHECK-NEXT: br i1 %arraydestroy.done7, label %[[ARRAY_DESTROY_DONE2:.+]], label %[[ARRAY_DESTROY_BODY2]]
+
+
+ // CHECK: [[ARRAY_DESTROY_DONE2]]:
+ // CHECK-NEXT: br label %return
+}
+
+void LambdaInit() {
+ // CHECK-LABEL: define dso_local void @_Z10LambdaInitv()
+ auto S = [a = Printy("a"), b = ({
+ if (foo()) {
+ return;
+ // CHECK: if.then:
+ // CHECK-NEXT: call void @_ZN6PrintyD1Ev
+ // CHECK-NEXT: br label %return
+ }
+ Printy("b");
+ })]() { return a; };
+}
+
+void LifetimeExtended() {
+ // CHECK-LABEL: define dso_local void @_Z16LifetimeExtendedv
+ struct PrintyRefBind {
+ const Printy &a;
+ const Printy &b;
+ };
+ PrintyRefBind ps = {Printy("a"), ({
+ if (foo()) {
+ return;
+ // CHECK: if.then:
+ // CHECK-NEXT: call void @_ZN6PrintyD1Ev
+ // CHECK-NEXT: br label %return
+ }
+ Printy("b");
+ })};
+}
+
+void NewArrayInit() {
+ // CHECK-LABEL: define dso_local void @_Z12NewArrayInitv()
+ // CHECK: %array.init.end = alloca ptr, align 8
+ // CHECK: store ptr %0, ptr %array.init.end, align 8
+ Printy *array = new Printy[3]{
+ "a",
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ // CHECK: store ptr %array.exp.next, ptr %array.init.end, align 8
+ "b",
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ // CHECK: store ptr %array.exp.next1, ptr %array.init.end, align 8
+ ({
+ if (foo()) {
+ return;
+ // CHECK: if.then:
+ // CHECK: br i1 %arraydestroy.isempty, label %arraydestroy.done{{.*}}, label %arraydestroy.body
+ }
+ "b";
+ // CHECK: if.end:
+ // CHECK: call void @_ZN6PrintyC1EPKc
+ })};
+ // CHECK: arraydestroy.body:
+ // CHECK-NEXT: %arraydestroy.elementPast = phi ptr [ %{{.*}}, %if.then ], [ %arraydestroy.element, %arraydestroy.body ]
+ // CHECK-NEXT: %arraydestroy.element = getelementptr inbounds %struct.Printy, ptr %arraydestroy.elementPast, i64 -1
+ // CHECK-NEXT: call void @_ZN6PrintyD1Ev(ptr noundef nonnull align 8 dereferenceable(8) %arraydestroy.element)
+ // CHECK-NEXT: %arraydestroy.done = icmp eq ptr %arraydestroy.element, %0
+ // CHECK-NEXT: br i1 %arraydestroy.done, label %arraydestroy.done{{.*}}, label %arraydestroy.body
+
+ // CHECK: arraydestroy.done{{.*}}: ; preds = %arraydestroy.body, %if.then
+ // CHECK-NEXT: br label %return
+}
\ No newline at end of file
>From 0702c966bbe526c3aff5e5db6c6d82470de5c22b Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Mon, 25 Mar 2024 14:15:14 +0000
Subject: [PATCH 5/5] Added co_return, continue as branches out of expr. Added
tests
---
clang/lib/AST/Expr.cpp | 4 +++
clang/lib/CodeGen/CGDecl.cpp | 2 +-
.../control-flow-in-expr-cleanup.cpp | 28 ++++++++++++++++++-
3 files changed, 32 insertions(+), 2 deletions(-)
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index b2d5df116ca9e8..9b94da06b511e2 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -26,6 +26,7 @@
#include "clang/AST/RecordLayout.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Stmt.h"
+#include "clang/AST/StmtCXX.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/CharInfo.h"
@@ -2515,10 +2516,13 @@ bool Expr::mayBranchOut() const {
// Coroutine suspensions.
bool VisitCoawaitExpr(CoawaitExpr *) { return activate(); }
bool VisitCoyieldExpr(CoyieldExpr *) { return activate(); }
+ bool VisitCoreturnStmt(CoreturnStmt *) { return activate(); }
// Control flow in stmt-expressions.
bool VisitBreakStmt(BreakStmt *) { return activate(); }
bool VisitReturnStmt(ReturnStmt *) { return activate(); }
bool VisitGotoStmt(GotoStmt *) { return activate(); }
+ bool VisitIndirectGotoStmt(IndirectGotoStmt *) { return activate(); }
+ bool VisitContinueStmt(ContinueStmt *) { return activate(); }
};
BranchDetector detector;
detector.TraverseStmt(const_cast<Expr *>(this));
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 02c597bd999dc5..4dce1f53841547 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -2187,7 +2187,7 @@ void CodeGenFunction::pushDestroy(CleanupKind cleanupKind, Address addr,
}
// Pushes a destroy and defers its deactivation until its
-// DeferredDeactivationCleanupScope is exited.
+// CleanupDeactivationScope is exited.
void CodeGenFunction::pushDestroyAndDeferDeactivation(
QualType::DestructionKind dtorKind, Address addr, QualType type) {
assert(dtorKind && "cannot push destructor for trivial type");
diff --git a/clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp b/clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp
index b3c6d8f65a4fba..5673c30eb40d06 100644
--- a/clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp
+++ b/clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp
@@ -462,4 +462,30 @@ void NewArrayInit() {
// CHECK: arraydestroy.done{{.*}}: ; preds = %arraydestroy.body, %if.then
// CHECK-NEXT: br label %return
-}
\ No newline at end of file
+}
+
+void ArrayInitWithContinue() {
+ // CHECK-LABEL: @_Z21ArrayInitWithContinuev
+ // Verify that we start to emit the array destructor.
+ // CHECK: %arrayinit.endOfInit = alloca ptr, align 8
+ for (int i = 0; i < 1; ++i) {
+ Printy arr[2] = {"a", ({
+ if (foo()) {
+ continue;
+ }
+ "b";
+ })};
+ }
+}
+
+coroutine ArrayInitWithCoReturn() {
+ // CHECK-LABEL: define dso_local void @_Z21ArrayInitWithCoReturnv
+ // Verify that we start to emit the array destructor.
+ // CHECK: %arrayinit.endOfInit = alloca ptr, align 8
+ Printy arr[2] = {"a", ({
+ if (foo()) {
+ co_return;
+ }
+ "b";
+ })};
+}
More information about the cfe-commits
mailing list