[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 Apr 1 03:26:48 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 01/20] [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 02/20] 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 03/20] 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 04/20] 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 05/20] 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";
+ })};
+}
>From cb31381a712114ad223b51aa45584092a7d55598 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Mon, 25 Mar 2024 14:41:26 +0000
Subject: [PATCH 06/20] Move non-coroutine tests to CodegenCXX
---
.../control-flow-in-stmt-expr.cpp} | 127 ------------------
.../coro-suspend-cleanups.cpp | 93 +++++++++++++
2 files changed, 93 insertions(+), 127 deletions(-)
rename clang/test/{CodeGenCoroutines/control-flow-in-expr-cleanup.cpp => CodeGenCXX/control-flow-in-stmt-expr.cpp} (74%)
create mode 100644 clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp
diff --git a/clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp b/clang/test/CodeGenCXX/control-flow-in-stmt-expr.cpp
similarity index 74%
rename from clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp
rename to clang/test/CodeGenCXX/control-flow-in-stmt-expr.cpp
index 5673c30eb40d06..ffde1bd6a724d8 100644
--- a/clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp
+++ b/clang/test/CodeGenCXX/control-flow-in-stmt-expr.cpp
@@ -1,35 +1,11 @@
// 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 Printies {
@@ -69,49 +45,6 @@ void ParenInit() {
// CHECK-NEXT: br label %return
}
-coroutine ParenInitCoro() {
- // CHECK-LABEL: define dso_local void @_Z13ParenInitCorov
- // CHECK: [[ACTIVE1:%.+]] = alloca i1, align 1
- // CHECK: [[ACTIVE2:%.+]] = alloca i1, align 1
- Printies ps(Printy("a"), Printy("b"),
- // CHECK: call void @_ZN6PrintyC1EPKc
- // 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:
- // 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]]
- );
-}
-
void break_in_stmt_expr() {
// Verify that the "break" in "if.then".calls dtor before jumping to "for.end".
@@ -292,54 +225,6 @@ void ArrayInit() {
// 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];
@@ -477,15 +362,3 @@ void ArrayInitWithContinue() {
})};
}
}
-
-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";
- })};
-}
diff --git a/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp b/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp
new file mode 100644
index 00000000000000..06cc2069dbe9ae
--- /dev/null
+++ b/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp
@@ -0,0 +1,93 @@
+// 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; }
+
+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.
+}
+
+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";
+ })};
+}
>From ab03ff7253e2fa544a200bd75bf7fbfd6c8ce2eb Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Wed, 27 Mar 2024 15:49:28 +0000
Subject: [PATCH 07/20] Add ASM goto stmt as a branch out of an expression
---
clang/lib/AST/Expr.cpp | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 9b94da06b511e2..c93ec798eb6977 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -2516,13 +2516,16 @@ 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 VisitCoreturnStmt(CoreturnStmt *) { return activate(); }
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(); }
+ bool VisitGCCAsmStmt(GCCAsmStmt *S) {
+ return S->isAsmGoto() ? activate() : true;
+ }
};
BranchDetector detector;
detector.TraverseStmt(const_cast<Expr *>(this));
>From fb30378c60a8b9b6fd134deeb5dd051c3a512863 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sun, 31 Mar 2024 20:36:14 +0000
Subject: [PATCH 08/20] Remove unneeded allocas for unemitted cleanups
---
clang/include/clang/AST/Expr.h | 8 ----
clang/lib/AST/Expr.cpp | 26 -------------
clang/lib/CodeGen/CGCleanup.cpp | 3 ++
clang/lib/CodeGen/CGCleanup.h | 59 ++++++++++++++++++++++++++++-
clang/lib/CodeGen/CGExpr.cpp | 12 ++++--
clang/lib/CodeGen/CGExprAgg.cpp | 7 +++-
clang/lib/CodeGen/CGExprCXX.cpp | 5 ++-
clang/lib/CodeGen/CodeGenFunction.h | 28 ++++++++++++++
8 files changed, 107 insertions(+), 41 deletions(-)
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 36e4c8acdc6736..446bec4081e869 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -246,14 +246,6 @@ class Expr : public ValueStmt {
return static_cast<bool>(getDependence() & ExprDependence::Error);
}
- /// 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.
SourceLocation getExprLoc() const LLVM_READONLY;
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index c93ec798eb6977..58925b4384704e 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -2506,32 +2506,6 @@ Stmt *BlockExpr::getBody() {
// Generic Expression Routines
//===----------------------------------------------------------------------===//
-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(); }
- // Control flow in stmt-expressions.
- bool VisitCoreturnStmt(CoreturnStmt *) { return activate(); }
- 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(); }
- bool VisitGCCAsmStmt(GCCAsmStmt *S) {
- return S->isAsmGoto() ? activate() : true;
- }
- };
- 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 8f74a06e4f96d4..2fee6d3ebe4f7e 100644
--- a/clang/lib/CodeGen/CGCleanup.cpp
+++ b/clang/lib/CodeGen/CGCleanup.cpp
@@ -797,6 +797,7 @@ void CodeGenFunction::PopCleanupBlock(bool FallthroughIsBranchThrough) {
EmitSehCppScopeEnd();
}
destroyOptimisticNormalEntry(*this, Scope);
+ Scope.MarkEmitted();
EHStack.popCleanup();
} else {
// If we have a fallthrough and no other need for the cleanup,
@@ -813,6 +814,7 @@ void CodeGenFunction::PopCleanupBlock(bool FallthroughIsBranchThrough) {
}
destroyOptimisticNormalEntry(*this, Scope);
+ Scope.MarkEmitted();
EHStack.popCleanup();
EmitCleanup(*this, Fn, cleanupFlags, NormalActiveFlag);
@@ -949,6 +951,7 @@ void CodeGenFunction::PopCleanupBlock(bool FallthroughIsBranchThrough) {
}
// IV. Pop the cleanup and emit it.
+ Scope.MarkEmitted();
EHStack.popCleanup();
assert(EHStack.hasNormalCleanups() == HasEnclosingCleanups);
diff --git a/clang/lib/CodeGen/CGCleanup.h b/clang/lib/CodeGen/CGCleanup.h
index 7a7344c07160db..93a79aa19bd5c9 100644
--- a/clang/lib/CodeGen/CGCleanup.h
+++ b/clang/lib/CodeGen/CGCleanup.h
@@ -16,8 +16,10 @@
#include "EHScopeStack.h"
#include "Address.h"
+#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
+#include "llvm/IR/Instruction.h"
namespace llvm {
class BasicBlock;
@@ -266,6 +268,54 @@ class alignas(8) EHCleanupScope : public EHScope {
};
mutable struct ExtInfo *ExtInfo;
+ /// Erases auxillary allocas and their usages for an unused cleanup.
+ /// Cleanups should mark these allocas as 'used' if the cleanup is
+ /// emitted, otherwise these instructions would be erased.
+ struct AuxillaryAllocas {
+ SmallVector<llvm::Instruction *, 1> AuxAllocas;
+ bool used = false;
+
+ // Records a potentially unused instruction to be erased later.
+ void Add(llvm::AllocaInst *Alloca) { AuxAllocas.push_back(Alloca); }
+
+ // Mark all recorded instructions as used. These will not be erased later.
+ void MarkUsed() {
+ used = true;
+ AuxAllocas.clear();
+ }
+ ~AuxillaryAllocas() {
+ if (used)
+ return;
+ llvm::SetVector<llvm::Instruction *> Uses;
+ for (auto *Inst : llvm::reverse(AuxAllocas))
+ CollectUses(Inst, Uses);
+ for (auto *I : Uses)
+ I->eraseFromParent();
+ }
+
+ private:
+ void CollectUses(llvm::Instruction *I,
+ llvm::SetVector<llvm::Instruction *> &Uses) {
+ if (!I)
+ return;
+
+ for (auto *User : I->users()) {
+ if (auto *UserI = dyn_cast<llvm::Instruction>(User)) {
+ CollectUses(UserI, Uses);
+ }
+ }
+ Uses.insert(I);
+ }
+ };
+ mutable struct AuxillaryAllocas *AuxAllocas;
+
+ AuxillaryAllocas &getAuxillaryAllocas() {
+ if (!AuxAllocas) {
+ AuxAllocas = new struct AuxillaryAllocas();
+ }
+ return *AuxAllocas;
+ }
+
/// The number of fixups required by enclosing scopes (not including
/// this one). If this is the top cleanup scope, all the fixups
/// from this index onwards belong to this scope.
@@ -298,7 +348,7 @@ class alignas(8) EHCleanupScope : public EHScope {
EHScopeStack::stable_iterator enclosingEH)
: EHScope(EHScope::Cleanup, enclosingEH),
EnclosingNormal(enclosingNormal), NormalBlock(nullptr),
- ActiveFlag(Address::invalid()), ExtInfo(nullptr),
+ ActiveFlag(Address::invalid()), ExtInfo(nullptr), AuxAllocas(nullptr),
FixupDepth(fixupDepth) {
CleanupBits.IsNormalCleanup = isNormal;
CleanupBits.IsEHCleanup = isEH;
@@ -312,8 +362,15 @@ class alignas(8) EHCleanupScope : public EHScope {
}
void Destroy() {
+ if (AuxAllocas)
+ delete AuxAllocas;
delete ExtInfo;
}
+ void AddAuxAllocas(llvm::SmallVector<llvm::AllocaInst *> Allocas) {
+ for (auto *Alloca : Allocas)
+ getAuxillaryAllocas().Add(Alloca);
+ }
+ void MarkEmitted() { getAuxillaryAllocas().MarkUsed(); }
// Objects of EHCleanupScope are not destructed. Use Destroy().
~EHCleanupScope() = delete;
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 59a7fe8925001c..37c8f308c65847 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -110,10 +110,16 @@ Address CodeGenFunction::CreateTempAlloca(llvm::Type *Ty, CharUnits Align,
llvm::AllocaInst *CodeGenFunction::CreateTempAlloca(llvm::Type *Ty,
const Twine &Name,
llvm::Value *ArraySize) {
+ llvm::AllocaInst *Alloca;
if (ArraySize)
- return Builder.CreateAlloca(Ty, ArraySize, Name);
- return new llvm::AllocaInst(Ty, CGM.getDataLayout().getAllocaAddrSpace(),
- ArraySize, Name, AllocaInsertPt);
+ Alloca = Builder.CreateAlloca(Ty, ArraySize, Name);
+ else
+ Alloca = new llvm::AllocaInst(Ty, CGM.getDataLayout().getAllocaAddrSpace(),
+ ArraySize, Name, AllocaInsertPt);
+ if (Allocas) {
+ Allocas->Add(Alloca);
+ }
+ return Alloca;
}
/// CreateDefaultAlignTempAlloca - This creates an alloca with the
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index bedd83e1b99cea..0e0492891f6a11 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -559,8 +559,8 @@ void AggExprEmitter::EmitArrayInit(Address DestPtr, llvm::ArrayType *AType,
Address endOfInit = Address::invalid();
CodeGenFunction::CleanupDeactivationScope deactivation(CGF);
- if (CGF.needsEHCleanup(dtorKind) ||
- (dtorKind && ExprToVisit->mayBranchOut())) {
+ if (dtorKind) {
+ CodeGenFunction::AllocaTrackerRAII AllocaTracker(CGF);
// 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
@@ -573,6 +573,9 @@ void AggExprEmitter::EmitArrayInit(Address DestPtr, llvm::ArrayType *AType,
CGF.pushIrregularPartialArrayCleanup(begin, endOfInit, elementType,
elementAlign,
CGF.getDestroyer(dtorKind));
+ cast<EHCleanupScope>(*CGF.EHStack.find(CGF.EHStack.stable_begin()))
+ .AddAuxAllocas(AllocaTracker.Take());
+
CGF.DeferredDeactivationCleanupStack.push_back(
{CGF.EHStack.stable_begin(), DominatingIP});
}
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index f10437d6592421..6161535a755558 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -1103,7 +1103,8 @@ void CodeGenFunction::EmitNewArrayInitializer(
}
// Enter a partial-destruction Cleanup if necessary.
- if (needsEHCleanup(DtorKind) || (DtorKind && E->mayBranchOut())) {
+ if (DtorKind) {
+ AllocaTrackerRAII AllocaTracker(*this);
// 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
@@ -1115,6 +1116,8 @@ void CodeGenFunction::EmitNewArrayInitializer(
pushIrregularPartialArrayCleanup(BeginPtr.getPointer(), EndOfInit,
ElementType, ElementAlign,
getDestroyer(DtorKind));
+ cast<EHCleanupScope>(*EHStack.find(EHStack.stable_begin()))
+ .AddAuxAllocas(AllocaTracker.Take());
DeferredDeactivationCleanupStack.push_back(
{EHStack.stable_begin(), DominatingIP});
pushedCleanup = true;
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index c848d7ae777ccf..67dbc6967b246c 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -39,6 +39,7 @@
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Frontend/OpenMP/OMPIRBuilder.h"
+#include "llvm/IR/Instructions.h"
#include "llvm/IR/ValueHandle.h"
#include "llvm/Support/Debug.h"
#include "llvm/Transforms/Utils/SanitizerStats.h"
@@ -2691,6 +2692,33 @@ class CodeGenFunction : public CodeGenTypeCache {
TBAAAccessInfo *TBAAInfo = nullptr);
LValue EmitLoadOfPointerLValue(Address Ptr, const PointerType *PtrTy);
+private:
+ struct AllocaTracker {
+ void Add(llvm::AllocaInst *I) { Allocas.push_back(I); }
+ llvm::SmallVector<llvm::AllocaInst *> Take() { return std::move(Allocas); }
+
+ private:
+ llvm::SmallVector<llvm::AllocaInst *> Allocas;
+ };
+ AllocaTracker *Allocas = nullptr;
+
+public:
+ // Captures all the allocas created during the scope of its RAII object.
+ struct AllocaTrackerRAII {
+ AllocaTrackerRAII(CodeGenFunction &CGF)
+ : CGF(CGF), OldTracker(CGF.Allocas) {
+ CGF.Allocas = &Tracker;
+ }
+ ~AllocaTrackerRAII() { CGF.Allocas = OldTracker; }
+
+ llvm::SmallVector<llvm::AllocaInst *> Take() { return Tracker.Take(); }
+
+ private:
+ CodeGenFunction &CGF;
+ AllocaTracker *OldTracker;
+ AllocaTracker Tracker;
+ };
+
/// CreateTempAlloca - This creates an alloca and inserts it into the entry
/// block if \p ArraySize is nullptr, otherwise inserts it at the current
/// insertion point of the builder. The caller is responsible for setting an
>From ed88da40217a4d5d3878bc8e1a3aeb434aee34f6 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 09/20] [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 e6f8e6873004f2..62266d793e747c 100644
--- a/clang/lib/CodeGen/CGCleanup.cpp
+++ b/clang/lib/CodeGen/CGCleanup.cpp
@@ -667,7 +667,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
@@ -692,7 +693,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 143855aa84ca3f..2e98a1fd5dcf1d 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"
@@ -1392,15 +1393,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());
}
}
@@ -1809,9 +1810,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 40448570dc6e4bce83fc6123948f79c999947b35 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 10/20] 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 3f31a9320277e820d7180cc6f8610a69c3152b8f 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 11/20] 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 6e153ebe024b42..12a5904bc31d30 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 6221ebd5c9b4e9..6fb93a390935fc 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"
@@ -2526,6 +2527,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 62266d793e747c..f252e0ede43a09 100644
--- a/clang/lib/CodeGen/CGCleanup.cpp
+++ b/clang/lib/CodeGen/CGCleanup.cpp
@@ -465,7 +465,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 267f2e40a7bbaa..db97c9a3af7c88 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"
@@ -2220,10 +2221,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);
@@ -2453,10 +2460,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 2e98a1fd5dcf1d..70b773d64b8d00 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -561,7 +561,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 a4fb673284ceca..1219d2df15f596 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -1105,7 +1105,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 e2a7e28c8211ea..d86e5ea075fe35 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -670,6 +670,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;
@@ -926,6 +932,7 @@ class CodeGenFunction : public CodeGenTypeCache {
class RunCleanupsScope {
EHScopeStack::stable_iterator CleanupStackDepth, OldCleanupScopeDepth;
size_t LifetimeExtendedCleanupStackSize;
+ size_t DeactivateAfterFullExprStackSize;
bool OldDidCallStackSave;
protected:
bool PerformCleanup;
@@ -945,6 +952,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;
@@ -972,7 +981,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;
}
@@ -1194,6 +1203,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 f9740b87ea46edc2335e261e0facc2d2b5b66323 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 12/20] 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 | 35 +-
clang/lib/CodeGen/CodeGenFunction.cpp | 6 +
clang/lib/CodeGen/CodeGenFunction.h | 74 ++-
.../control-flow-in-expr-cleanup.cpp | 465 ++++++++++++++----
9 files changed, 537 insertions(+), 208 deletions(-)
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 12a5904bc31d30..0aacb50a8bd174 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 6fb93a390935fc..6996702f26c8d6 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"
@@ -2527,16 +2528,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 f252e0ede43a09..3e664bf301c46a 100644
--- a/clang/lib/CodeGen/CGCleanup.cpp
+++ b/clang/lib/CodeGen/CGCleanup.cpp
@@ -465,15 +465,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.
@@ -703,6 +695,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 db97c9a3af7c88..4c16c3b47405ef 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -2202,6 +2202,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);
}
@@ -2218,22 +2236,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.
@@ -2248,13 +2263,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);
@@ -2449,9 +2463,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 70b773d64b8d00..a23cffbf9095db 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;
@@ -559,25 +560,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);
@@ -673,9 +673,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);
}
//===----------------------------------------------------------------------===//
@@ -1373,9 +1370,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(),
@@ -1394,28 +1390,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) {
@@ -1703,14 +1683,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;
@@ -1733,10 +1706,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());
}
}
@@ -1812,9 +1783,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;
}
}
@@ -1827,17 +1798,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 1219d2df15f596..750bcfc3eb4fd0 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -1008,8 +1008,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 =
@@ -1105,19 +1105,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.emitRawPointer(*this), EndOfInit);
pushIrregularPartialArrayCleanup(BeginPtr.emitRawPointer(*this),
EndOfInit, ElementType, ElementAlign,
getDestroyer(DtorKind));
- Cleanup = EHStack.stable_begin();
+ DeferredDeactivationCleanupStack.push_back(
+ {EHStack.stable_begin(), DominatingIP});
+ pushedCleanup = true;
}
CharUnits StartAlign = CurPtr.getAlignment();
@@ -1164,9 +1166,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;
}
@@ -1281,13 +1280,14 @@ void CodeGenFunction::EmitNewArrayInitializer(
Builder.CreateStore(CurPtr.emitRawPointer(*this), EndOfInit);
// Enter a partial-destruction Cleanup if necessary.
- if (!CleanupDominator && needsEHCleanup(DtorKind)) {
- llvm::Value *BeginPtrRaw = BeginPtr.emitRawPointer(*this);
- llvm::Value *CurPtrRaw = CurPtr.emitRawPointer(*this);
- pushRegularPartialArrayCleanup(BeginPtrRaw, CurPtrRaw, ElementType,
+ if (!pushedCleanup && needsEHCleanup(DtorKind)) {
+ llvm::Instruction *DominatingIP =
+ Builder.CreateFlagLoad(llvm::ConstantInt::getNullValue(Int8PtrTy));
+ pushRegularPartialArrayCleanup(BeginPtr.emitRawPointer(),
+ CurPtr.emitRawPointer(), ElementType,
ElementAlign, getDestroyer(DtorKind));
- Cleanup = EHStack.stable_begin();
- CleanupDominator = Builder.CreateUnreachable();
+ DeferredDeactivationCleanupStack.push_back(
+ {EHStack.stable_begin(), DominatingIP});
}
// Emit the initializer into this element.
@@ -1295,10 +1295,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 = Builder.CreateConstInBoundsGEP1_32(
diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp
index 44103884940fd9..75c7343e38adb3 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);
@@ -345,6 +347,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 d86e5ea075fe35..6b1d5bd541f8b3 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -670,12 +670,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;
@@ -881,6 +920,19 @@ class CodeGenFunction : public CodeGenTypeCache {
new (Buffer + sizeof(Header) + sizeof(T)) RawAddress(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() {
@@ -932,7 +984,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;
@@ -947,13 +999,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;
@@ -980,8 +1029,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;
}
@@ -1203,7 +1253,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);
@@ -2170,6 +2219,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 3ad7608cfcba1983860f8d3ab00d02ffc8b9fc23 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 13/20] 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 6996702f26c8d6..a21375fadbe3ec 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"
@@ -2538,10 +2539,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 4c16c3b47405ef..4f4013292b1fc0 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -2203,7 +2203,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";
+ })};
+}
>From 0a9b0cae28b983efcd659ab3e125bac9f2848579 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Mon, 25 Mar 2024 14:41:26 +0000
Subject: [PATCH 14/20] Move non-coroutine tests to CodegenCXX
---
.../control-flow-in-stmt-expr.cpp} | 127 ------------------
.../coro-suspend-cleanups.cpp | 93 +++++++++++++
2 files changed, 93 insertions(+), 127 deletions(-)
rename clang/test/{CodeGenCoroutines/control-flow-in-expr-cleanup.cpp => CodeGenCXX/control-flow-in-stmt-expr.cpp} (74%)
create mode 100644 clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp
diff --git a/clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp b/clang/test/CodeGenCXX/control-flow-in-stmt-expr.cpp
similarity index 74%
rename from clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp
rename to clang/test/CodeGenCXX/control-flow-in-stmt-expr.cpp
index 5673c30eb40d06..ffde1bd6a724d8 100644
--- a/clang/test/CodeGenCoroutines/control-flow-in-expr-cleanup.cpp
+++ b/clang/test/CodeGenCXX/control-flow-in-stmt-expr.cpp
@@ -1,35 +1,11 @@
// 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 Printies {
@@ -69,49 +45,6 @@ void ParenInit() {
// CHECK-NEXT: br label %return
}
-coroutine ParenInitCoro() {
- // CHECK-LABEL: define dso_local void @_Z13ParenInitCorov
- // CHECK: [[ACTIVE1:%.+]] = alloca i1, align 1
- // CHECK: [[ACTIVE2:%.+]] = alloca i1, align 1
- Printies ps(Printy("a"), Printy("b"),
- // CHECK: call void @_ZN6PrintyC1EPKc
- // 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:
- // 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]]
- );
-}
-
void break_in_stmt_expr() {
// Verify that the "break" in "if.then".calls dtor before jumping to "for.end".
@@ -292,54 +225,6 @@ void ArrayInit() {
// 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];
@@ -477,15 +362,3 @@ void ArrayInitWithContinue() {
})};
}
}
-
-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";
- })};
-}
diff --git a/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp b/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp
new file mode 100644
index 00000000000000..06cc2069dbe9ae
--- /dev/null
+++ b/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp
@@ -0,0 +1,93 @@
+// 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; }
+
+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.
+}
+
+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";
+ })};
+}
>From fa2b30afc5e830cf85edaca08356c6ab507bc9fd Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Wed, 27 Mar 2024 15:49:28 +0000
Subject: [PATCH 15/20] Add ASM goto stmt as a branch out of an expression
---
clang/lib/AST/Expr.cpp | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index a21375fadbe3ec..d7a5a16e119fc0 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -2539,13 +2539,16 @@ 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 VisitCoreturnStmt(CoreturnStmt *) { return activate(); }
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(); }
+ bool VisitGCCAsmStmt(GCCAsmStmt *S) {
+ return S->isAsmGoto() ? activate() : true;
+ }
};
BranchDetector detector;
detector.TraverseStmt(const_cast<Expr *>(this));
>From e73befd7322376a4f0c13daf9095c7da5d969fd9 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sun, 31 Mar 2024 20:36:14 +0000
Subject: [PATCH 16/20] Remove unneeded allocas for unemitted cleanups
---
clang/include/clang/AST/Expr.h | 8 ----
clang/lib/AST/Expr.cpp | 26 -------------
clang/lib/CodeGen/CGCleanup.cpp | 3 ++
clang/lib/CodeGen/CGCleanup.h | 59 ++++++++++++++++++++++++++++-
clang/lib/CodeGen/CGExpr.cpp | 12 ++++--
clang/lib/CodeGen/CGExprAgg.cpp | 7 +++-
clang/lib/CodeGen/CGExprCXX.cpp | 5 ++-
clang/lib/CodeGen/CodeGenFunction.h | 28 ++++++++++++++
8 files changed, 107 insertions(+), 41 deletions(-)
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 0aacb50a8bd174..6e153ebe024b42 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -246,14 +246,6 @@ class Expr : public ValueStmt {
return static_cast<bool>(getDependence() & ExprDependence::Error);
}
- /// 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.
SourceLocation getExprLoc() const LLVM_READONLY;
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index d7a5a16e119fc0..9cb20e7aaeb029 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -2529,32 +2529,6 @@ Stmt *BlockExpr::getBody() {
// Generic Expression Routines
//===----------------------------------------------------------------------===//
-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(); }
- // Control flow in stmt-expressions.
- bool VisitCoreturnStmt(CoreturnStmt *) { return activate(); }
- 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(); }
- bool VisitGCCAsmStmt(GCCAsmStmt *S) {
- return S->isAsmGoto() ? activate() : true;
- }
- };
- 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 3e664bf301c46a..5bf48bc22a5495 100644
--- a/clang/lib/CodeGen/CGCleanup.cpp
+++ b/clang/lib/CodeGen/CGCleanup.cpp
@@ -770,6 +770,7 @@ void CodeGenFunction::PopCleanupBlock(bool FallthroughIsBranchThrough) {
EmitSehCppScopeEnd();
}
destroyOptimisticNormalEntry(*this, Scope);
+ Scope.MarkEmitted();
EHStack.popCleanup();
} else {
// If we have a fallthrough and no other need for the cleanup,
@@ -786,6 +787,7 @@ void CodeGenFunction::PopCleanupBlock(bool FallthroughIsBranchThrough) {
}
destroyOptimisticNormalEntry(*this, Scope);
+ Scope.MarkEmitted();
EHStack.popCleanup();
EmitCleanup(*this, Fn, cleanupFlags, NormalActiveFlag);
@@ -921,6 +923,7 @@ void CodeGenFunction::PopCleanupBlock(bool FallthroughIsBranchThrough) {
}
// IV. Pop the cleanup and emit it.
+ Scope.MarkEmitted();
EHStack.popCleanup();
assert(EHStack.hasNormalCleanups() == HasEnclosingCleanups);
diff --git a/clang/lib/CodeGen/CGCleanup.h b/clang/lib/CodeGen/CGCleanup.h
index 03e4a29d7b3dbf..da6af7bd212cea 100644
--- a/clang/lib/CodeGen/CGCleanup.h
+++ b/clang/lib/CodeGen/CGCleanup.h
@@ -16,8 +16,10 @@
#include "EHScopeStack.h"
#include "Address.h"
+#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
+#include "llvm/IR/Instruction.h"
namespace llvm {
class BasicBlock;
@@ -266,6 +268,54 @@ class alignas(8) EHCleanupScope : public EHScope {
};
mutable struct ExtInfo *ExtInfo;
+ /// Erases auxillary allocas and their usages for an unused cleanup.
+ /// Cleanups should mark these allocas as 'used' if the cleanup is
+ /// emitted, otherwise these instructions would be erased.
+ struct AuxillaryAllocas {
+ SmallVector<llvm::Instruction *, 1> AuxAllocas;
+ bool used = false;
+
+ // Records a potentially unused instruction to be erased later.
+ void Add(llvm::AllocaInst *Alloca) { AuxAllocas.push_back(Alloca); }
+
+ // Mark all recorded instructions as used. These will not be erased later.
+ void MarkUsed() {
+ used = true;
+ AuxAllocas.clear();
+ }
+ ~AuxillaryAllocas() {
+ if (used)
+ return;
+ llvm::SetVector<llvm::Instruction *> Uses;
+ for (auto *Inst : llvm::reverse(AuxAllocas))
+ CollectUses(Inst, Uses);
+ for (auto *I : Uses)
+ I->eraseFromParent();
+ }
+
+ private:
+ void CollectUses(llvm::Instruction *I,
+ llvm::SetVector<llvm::Instruction *> &Uses) {
+ if (!I)
+ return;
+
+ for (auto *User : I->users()) {
+ if (auto *UserI = dyn_cast<llvm::Instruction>(User)) {
+ CollectUses(UserI, Uses);
+ }
+ }
+ Uses.insert(I);
+ }
+ };
+ mutable struct AuxillaryAllocas *AuxAllocas;
+
+ AuxillaryAllocas &getAuxillaryAllocas() {
+ if (!AuxAllocas) {
+ AuxAllocas = new struct AuxillaryAllocas();
+ }
+ return *AuxAllocas;
+ }
+
/// The number of fixups required by enclosing scopes (not including
/// this one). If this is the top cleanup scope, all the fixups
/// from this index onwards belong to this scope.
@@ -298,7 +348,7 @@ class alignas(8) EHCleanupScope : public EHScope {
EHScopeStack::stable_iterator enclosingEH)
: EHScope(EHScope::Cleanup, enclosingEH),
EnclosingNormal(enclosingNormal), NormalBlock(nullptr),
- ActiveFlag(Address::invalid()), ExtInfo(nullptr),
+ ActiveFlag(Address::invalid()), ExtInfo(nullptr), AuxAllocas(nullptr),
FixupDepth(fixupDepth) {
CleanupBits.IsNormalCleanup = isNormal;
CleanupBits.IsEHCleanup = isEH;
@@ -312,8 +362,15 @@ class alignas(8) EHCleanupScope : public EHScope {
}
void Destroy() {
+ if (AuxAllocas)
+ delete AuxAllocas;
delete ExtInfo;
}
+ void AddAuxAllocas(llvm::SmallVector<llvm::AllocaInst *> Allocas) {
+ for (auto *Alloca : Allocas)
+ getAuxillaryAllocas().Add(Alloca);
+ }
+ void MarkEmitted() { getAuxillaryAllocas().MarkUsed(); }
// Objects of EHCleanupScope are not destructed. Use Destroy().
~EHCleanupScope() = delete;
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 36872c0fedb76e..96b5cc77f43465 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -110,10 +110,16 @@ RawAddress CodeGenFunction::CreateTempAlloca(llvm::Type *Ty, CharUnits Align,
llvm::AllocaInst *CodeGenFunction::CreateTempAlloca(llvm::Type *Ty,
const Twine &Name,
llvm::Value *ArraySize) {
+ llvm::AllocaInst *Alloca;
if (ArraySize)
- return Builder.CreateAlloca(Ty, ArraySize, Name);
- return new llvm::AllocaInst(Ty, CGM.getDataLayout().getAllocaAddrSpace(),
- ArraySize, Name, AllocaInsertPt);
+ Alloca = Builder.CreateAlloca(Ty, ArraySize, Name);
+ else
+ Alloca = new llvm::AllocaInst(Ty, CGM.getDataLayout().getAllocaAddrSpace(),
+ ArraySize, Name, AllocaInsertPt);
+ if (Allocas) {
+ Allocas->Add(Alloca);
+ }
+ return Alloca;
}
/// CreateDefaultAlignTempAlloca - This creates an alloca with the
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index a23cffbf9095db..cc90434323f139 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -562,8 +562,8 @@ void AggExprEmitter::EmitArrayInit(Address DestPtr, llvm::ArrayType *AType,
Address endOfInit = Address::invalid();
CodeGenFunction::CleanupDeactivationScope deactivation(CGF);
- if (CGF.needsEHCleanup(dtorKind) ||
- (dtorKind && ExprToVisit->mayBranchOut())) {
+ if (dtorKind) {
+ CodeGenFunction::AllocaTrackerRAII AllocaTracker(CGF);
// 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
@@ -576,6 +576,9 @@ void AggExprEmitter::EmitArrayInit(Address DestPtr, llvm::ArrayType *AType,
CGF.pushIrregularPartialArrayCleanup(begin, endOfInit, elementType,
elementAlign,
CGF.getDestroyer(dtorKind));
+ cast<EHCleanupScope>(*CGF.EHStack.find(CGF.EHStack.stable_begin()))
+ .AddAuxAllocas(AllocaTracker.Take());
+
CGF.DeferredDeactivationCleanupStack.push_back(
{CGF.EHStack.stable_begin(), DominatingIP});
}
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index 750bcfc3eb4fd0..e2bbe8816d3258 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -1105,7 +1105,8 @@ void CodeGenFunction::EmitNewArrayInitializer(
}
// Enter a partial-destruction Cleanup if necessary.
- if (needsEHCleanup(DtorKind) || (DtorKind && E->mayBranchOut())) {
+ if (DtorKind) {
+ AllocaTrackerRAII AllocaTracker(*this);
// 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
@@ -1117,6 +1118,8 @@ void CodeGenFunction::EmitNewArrayInitializer(
pushIrregularPartialArrayCleanup(BeginPtr.emitRawPointer(*this),
EndOfInit, ElementType, ElementAlign,
getDestroyer(DtorKind));
+ cast<EHCleanupScope>(*EHStack.find(EHStack.stable_begin()))
+ .AddAuxAllocas(AllocaTracker.Take());
DeferredDeactivationCleanupStack.push_back(
{EHStack.stable_begin(), DominatingIP});
pushedCleanup = true;
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 6b1d5bd541f8b3..b137569643d615 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -39,6 +39,7 @@
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Frontend/OpenMP/OMPIRBuilder.h"
+#include "llvm/IR/Instructions.h"
#include "llvm/IR/ValueHandle.h"
#include "llvm/Support/Debug.h"
#include "llvm/Transforms/Utils/SanitizerStats.h"
@@ -2762,6 +2763,33 @@ class CodeGenFunction : public CodeGenTypeCache {
TBAAAccessInfo *TBAAInfo = nullptr);
LValue EmitLoadOfPointerLValue(Address Ptr, const PointerType *PtrTy);
+private:
+ struct AllocaTracker {
+ void Add(llvm::AllocaInst *I) { Allocas.push_back(I); }
+ llvm::SmallVector<llvm::AllocaInst *> Take() { return std::move(Allocas); }
+
+ private:
+ llvm::SmallVector<llvm::AllocaInst *> Allocas;
+ };
+ AllocaTracker *Allocas = nullptr;
+
+public:
+ // Captures all the allocas created during the scope of its RAII object.
+ struct AllocaTrackerRAII {
+ AllocaTrackerRAII(CodeGenFunction &CGF)
+ : CGF(CGF), OldTracker(CGF.Allocas) {
+ CGF.Allocas = &Tracker;
+ }
+ ~AllocaTrackerRAII() { CGF.Allocas = OldTracker; }
+
+ llvm::SmallVector<llvm::AllocaInst *> Take() { return Tracker.Take(); }
+
+ private:
+ CodeGenFunction &CGF;
+ AllocaTracker *OldTracker;
+ AllocaTracker Tracker;
+ };
+
/// CreateTempAlloca - This creates an alloca and inserts it into the entry
/// block if \p ArraySize is nullptr, otherwise inserts it at the current
/// insertion point of the builder. The caller is responsible for setting an
>From 4833db82e96eaa9b72b2e81b585c80e8c7b3f361 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sun, 31 Mar 2024 20:56:23 +0000
Subject: [PATCH 17/20] rebase
---
clang/lib/CodeGen/CGExprCXX.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index e2bbe8816d3258..a88b29b326bb92 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -1286,8 +1286,8 @@ void CodeGenFunction::EmitNewArrayInitializer(
if (!pushedCleanup && needsEHCleanup(DtorKind)) {
llvm::Instruction *DominatingIP =
Builder.CreateFlagLoad(llvm::ConstantInt::getNullValue(Int8PtrTy));
- pushRegularPartialArrayCleanup(BeginPtr.emitRawPointer(),
- CurPtr.emitRawPointer(), ElementType,
+ pushRegularPartialArrayCleanup(BeginPtr.emitRawPointer(*this),
+ CurPtr.emitRawPointer(*this), ElementType,
ElementAlign, getDestroyer(DtorKind));
DeferredDeactivationCleanupStack.push_back(
{EHStack.stable_begin(), DominatingIP});
>From 1eddcbea3551f000af0f941463ecc9624777c7b0 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sun, 31 Mar 2024 21:38:56 +0000
Subject: [PATCH 18/20] fix spaces
---
clang/lib/CodeGen/CGCleanup.h | 1 -
clang/lib/CodeGen/CodeGenFunction.h | 13 -------------
2 files changed, 14 deletions(-)
diff --git a/clang/lib/CodeGen/CGCleanup.h b/clang/lib/CodeGen/CGCleanup.h
index da6af7bd212cea..9841f7de554a5a 100644
--- a/clang/lib/CodeGen/CGCleanup.h
+++ b/clang/lib/CodeGen/CGCleanup.h
@@ -298,7 +298,6 @@ class alignas(8) EHCleanupScope : public EHScope {
llvm::SetVector<llvm::Instruction *> &Uses) {
if (!I)
return;
-
for (auto *User : I->users()) {
if (auto *UserI = dyn_cast<llvm::Instruction>(User)) {
CollectUses(UserI, Uses);
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 9efd25cb2d98da..b137569643d615 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -934,19 +934,6 @@ class CodeGenFunction : public CodeGenTypeCache {
{EHStack.stable_begin(), DominatingIP});
}
- // 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() {
>From da3d37ddf94df5e00be928a9813c95ceff70c534 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sun, 31 Mar 2024 22:10:08 +0000
Subject: [PATCH 19/20] remove old includes
---
clang/lib/AST/Expr.cpp | 3 ---
1 file changed, 3 deletions(-)
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 9cb20e7aaeb029..6221ebd5c9b4e9 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -24,9 +24,6 @@
#include "clang/AST/IgnoreExpr.h"
#include "clang/AST/Mangle.h"
#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"
>From 6780f1692046e8f6e3271c937001a8d7652168d2 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Mon, 1 Apr 2024 10:26:28 +0000
Subject: [PATCH 20/20] Handle repetition in CollectUses
---
clang/lib/CodeGen/CGCleanup.h | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/clang/lib/CodeGen/CGCleanup.h b/clang/lib/CodeGen/CGCleanup.h
index 9841f7de554a5a..8e866b60b73048 100644
--- a/clang/lib/CodeGen/CGCleanup.h
+++ b/clang/lib/CodeGen/CGCleanup.h
@@ -16,6 +16,7 @@
#include "EHScopeStack.h"
#include "Address.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
@@ -283,27 +284,28 @@ class alignas(8) EHCleanupScope : public EHScope {
used = true;
AuxAllocas.clear();
}
+
~AuxillaryAllocas() {
if (used)
return;
llvm::SetVector<llvm::Instruction *> Uses;
for (auto *Inst : llvm::reverse(AuxAllocas))
CollectUses(Inst, Uses);
- for (auto *I : Uses)
+ // Delete uses in the reverse order of insertion.
+ for (auto *I : llvm::reverse(Uses))
I->eraseFromParent();
}
private:
void CollectUses(llvm::Instruction *I,
llvm::SetVector<llvm::Instruction *> &Uses) {
- if (!I)
+ if (!I || !Uses.insert(I))
return;
- for (auto *User : I->users()) {
+ for (auto *User : I->materialized_users()) {
if (auto *UserI = dyn_cast<llvm::Instruction>(User)) {
CollectUses(UserI, Uses);
}
}
- Uses.insert(I);
}
};
mutable struct AuxillaryAllocas *AuxAllocas;
More information about the cfe-commits
mailing list