https://github.com/mmha created https://github.com/llvm/llvm-project/pull/231469
The lifetime of temporaries used to initialize a loop condition variable needs
to be extended to the end of each iteration. Example:
```c++
ConvertibleToBool get_ctb();
while(ConvertibleToBool const& cond = get_ctb()) {
...
} // destroy temporary here, then reevaluate
```
Capture the destructor cleanups so temporary lifetimes get extended.
Assisted-By: Claude Opus 5.5 <[email protected]>
>From 6dbff5491848999ce0cbb844c137465816a0f9cb Mon Sep 17 00:00:00 2001
From: Morris Hafner <[email protected]>
Date: Tue, 13 Oct 2026 12:03:12 +0000
Subject: [PATCH] [CIR] Handle lifetime-extended temporaries in loop conditions
The lifetime of temporaries used to initialize a loop condition variable needs
to be extended to the end of each iteration. Example:
```c++
ConvertibleToBool get_ctb();
while(ConvertibleToBool const& cond = get_ctb()) {
...
} // destroy temporary here, then reevaluate
```
Capture the destructor cleanups so temporary lifetimes get extended.
Assisted-By: Claude Opus 5.5 <[email protected]>
---
clang/lib/CIR/CodeGen/CIRGenCleanup.cpp | 7 +-
clang/lib/CIR/CodeGen/CIRGenDecl.cpp | 139 ++++-
clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 3 +-
clang/lib/CIR/CodeGen/CIRGenFunction.h | 32 +-
clang/lib/CIR/CodeGen/CIRGenStmt.cpp | 26 +-
clang/test/CIR/CodeGen/lifetime-marker.cpp | 81 ++-
.../CIR/CodeGen/loop-cond-var-cleanup.cpp | 495 ++++++++++++++++++
7 files changed, 755 insertions(+), 28 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
index 7a703b84bb04f1..cc94983c52899a 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
@@ -833,10 +833,11 @@ void
CIRGenFunction::emitCapturedCleanups(EHScopeStack::stable_iterator depth,
if (scope.isEHCleanup())
cleanupFlags.setIsEHCleanupKind();
- // A captured cleanup may be guarded by an active flag. A loop condition
- // variable's destructor cleanup has one that is false while its
+ // A captured cleanup may be guarded by an active flag. The destructor
+ // cleanups of a loop condition variable and of the temporaries that its
+ // initializer lifetime-extends have one that is false while its
// initializer runs, so a throwing initializer does not destroy the
- // not-yet-constructed variable. The lifetime-end cleanup has no flag
+ // not-yet-constructed variables. The lifetime-end cleanup has no flag
// because its lifetime starts before initialization. For a loop, each
// emission serves both the normal per-iteration exit and the EH unwind
// path.
diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
index 01b9f810fbed4f..000792eba73589 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
@@ -22,10 +22,12 @@
#include "clang/AST/DeclOpenACC.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
+#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/Cuda.h"
#include "clang/CIR/Dialect/IR/CIRAttrs.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/MissingFeatures.h"
+#include "llvm/Support/SaveAndRestore.h"
using namespace clang;
using namespace clang::CIRGen;
@@ -387,13 +389,76 @@ void CIRGenFunction::emitAutoVarDecl(const VarDecl &d) {
emitAutoVarCleanups(emission);
}
-void CIRGenFunction::emitLoopConditionVariable(const VarDecl &d,
- CapturedCleanups &condCleanup) {
+namespace {
+/// Finds the temporaries with a destructor that the initializer of a variable
+/// with automatic storage duration lifetime-extends. Their cleanups are pushed
+/// onto the EH stack when the initializer's full-expression ends.
+class LifetimeExtendedTemporaryFinder
+ : public RecursiveASTVisitor<LifetimeExtendedTemporaryFinder> {
+ const VarDecl &var;
+ llvm::SmallPtrSet<const MaterializeTemporaryExpr *, 2> visited;
+ SmallVector<QualType, 2> types;
+
+public:
+ explicit LifetimeExtendedTemporaryFinder(const VarDecl &var) : var(var) {}
+
+ /// The types of the temporaries that were found.
+ ArrayRef<QualType> found() const { return types; }
+
+ // Look into the semantic form of initializer lists, which holds the backing
+ // array of a std::initializer_list, and into default member initializers.
+ // The array filler of an initializer list is not visited.
+ bool shouldVisitImplicitCode() const { return true; }
+
+ /// Records the type of each temporary requiring destruction whose lifetime
+ /// is extended by \c var.
+ bool VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *m) {
+ if (m->getExtendingDecl() != &var ||
+ m->getStorageDuration() != SD_Automatic || !visited.insert(m).second)
+ return true;
+ // Like pushTemporaryCleanup, look at the type of the whole temporary, not
+ // at the subobject that the reference binds to.
+ SmallVector<const Expr *, 2> commaLHSs;
+ SmallVector<SubobjectAdjustment, 2> adjustments;
+ const Expr *e =
+ m->getSubExpr()->skipRValueSubobjectAdjustments(commaLHSs,
adjustments);
+ if (e->getType().isDestructedType() != QualType::DK_none)
+ types.push_back(e->getType());
+ return true;
+ }
+};
+} // namespace
+
+static bool destructorMayThrow(QualType type) {
+ const CXXRecordDecl *rd =
+ type->getBaseElementTypeUnsafe()->getAsCXXRecordDecl();
+ if (!rd || rd->hasTrivialDestructor())
+ return false;
+ const auto *fpt =
rd->getDestructor()->getType()->castAs<FunctionProtoType>();
+ return isUnresolvedExceptionSpec(fpt->getExceptionSpecType()) ||
+ !fpt->isNothrow();
+}
+
+SmallVector<QualType, 2>
+CIRGenFunction::getLifetimeExtendedTemporariesWithDestructor(const VarDecl &d)
{
+ const Expr *init = d.getInit();
+ if (!init)
+ return {};
+ LifetimeExtendedTemporaryFinder finder(d);
+ finder.TraverseStmt(const_cast<Expr *>(init));
+ return SmallVector<QualType, 2>(finder.found());
+}
+
+void CIRGenFunction::emitLoopConditionVariable(
+ const VarDecl &d, CapturedCleanups &condCleanup,
+ ArrayRef<QualType> extendedTemporaries) {
// A condition variable always has automatic storage duration, so this
// mirrors the auto-var path of emitVarDecl/emitAutoVarDecl. Capture the
// lifetime-end cleanup pushed while emitting the alloca, but emit the
- // initializer with capturing disabled so its own cleanups get their normal
- // cir.cleanup.scope handling. The variable's destructor cleanup is captured
+ // initializer with capturing disabled so the cleanups of its temporaries get
+ // their normal cir.cleanup.scope handling. Only the cleanups of the
+ // temporaries that it lifetime-extends are captured, when its
+ // full-expression ends. The variable's destructor cleanup is captured
// separately after initialization.
assert(d.hasLocalStorage() && "loop condition variable is not local");
@@ -419,9 +484,27 @@ void CIRGenFunction::emitLoopConditionVariable(const
VarDecl &d,
// false while the initializer runs and set to true once construction
// completes. The flag is stored to on every iteration, so it also resets
// correctly across iterations.
+ //
+ // The temporaries that the initializer lifetime-extends are destroyed with
+ // the variable as well ([class.temporary]p6). Their cleanups are pushed when
+ // the initializer's full-expression ends, so capture them too and guard them
+ // with the same flag.
bool needsCleanup = d.needsDestruction(getContext()) != QualType::DK_none;
+ bool extendsTemporaries = !extendedTemporaries.empty();
+
+ // Destroy the variable first, then the temporaries in reverse order of
+ // construction. A throw would skip the remaining destructors because CFG
+ // flattening does not yet support EH paths in loop cleanup regions.
+ if (getLangOpts().Exceptions && extendsTemporaries &&
+ ((needsCleanup && destructorMayThrow(d.getType())) ||
+ (extendedTemporaries.size() > 1 &&
+ llvm::any_of(extendedTemporaries, destructorMayThrow))))
+ cgm.errorNYI(d.getSourceRange(),
+ "loop condition variable cleanup with a potentially-throwing "
+ "destructor before another destructor");
+
Address activeFlag = Address::invalid();
- if (needsCleanup) {
+ if (needsCleanup || extendsTemporaries) {
mlir::Location loc = getLoc(d.getSourceRange());
activeFlag = createTempAllocaWithoutCast(
builder.getBoolTy(), CharUnits::One(), loc, "cond.cleanup.isactive",
@@ -430,10 +513,31 @@ void CIRGenFunction::emitLoopConditionVariable(const
VarDecl &d,
builder.createFlagStore(loc, false, activeFlag.getPointer());
}
- emitAutoVarInit(emission);
+ EHScopeStack::stable_iterator initDepth = ehStack.stable_begin();
+ {
+ LifetimeExtensionCapture capture{&d, &condCleanup, activeFlag};
+ llvm::SaveAndRestore<const LifetimeExtensionCapture *> captureExtended(
+ lifetimeExtensionCapture, extendsTemporaries ? &capture : nullptr);
+ emitAutoVarInit(emission);
+ }
- if (needsCleanup) {
- // Construction has completed, so activate the destructor cleanup.
+ // Only lifetime-extended cleanups remain after initialization. If the AST
+ // walk found no extended temporaries, these cleanups are uncaptured and wrap
+ // cir.condition in cir.cleanup.scope (e.g. for initializer-list array
fillers).
+ if (!extendsTemporaries && ehStack.stable_begin() != initDepth) {
+ cgm.errorNYI(d.getSourceRange(),
+ "loop condition variable whose initializer leaves uncaptured "
+ "cleanups");
+ popCleanupBlocks(initDepth);
+ }
+#ifndef NDEBUG
+ for (auto it = ehStack.begin(), ie = ehStack.find(initDepth); it != ie; ++it)
+ assert(!cast<EHCleanupScope>(*it).getCleanupScopeOp() &&
+ "loop condition variable initializer left an uncaptured cleanup");
+#endif
+
+ if (activeFlag.isValid()) {
+ // Construction has completed, so activate the destructor cleanups.
mlir::Location loc = getLoc(d.getSourceRange());
builder.createFlagStore(loc, true, activeFlag.getPointer());
}
@@ -1222,10 +1326,9 @@ void CIRGenFunction::pushDestroyAndDeferDeactivation(
destroyer);
}
-void CIRGenFunction::pushLifetimeExtendedDestroy(CleanupKind cleanupKind,
- Address addr, QualType type,
- Destroyer *destroyer,
- bool useEHCleanupForArray) {
+void CIRGenFunction::pushLifetimeExtendedDestroy(
+ CleanupKind cleanupKind, Address addr, QualType type, Destroyer *destroyer,
+ bool useEHCleanupForArray, const ValueDecl *extendingDecl) {
if (isInConditionalBranch()) {
cgm.errorNYI("conditional lifetime-extended destroy");
return;
@@ -1239,10 +1342,22 @@ void
CIRGenFunction::pushLifetimeExtendedDestroy(CleanupKind cleanupKind,
assert(!cir::MissingFeatures::useEHCleanupForArray());
pushCleanupAfterFullExpr(cleanupKind, addr, type, destroyer);
+
+ // A temporary that a loop condition variable extends is destroyed with the
+ // variable at the end of each iteration, in the loop's cleanup region.
+ if (lifetimeExtensionCapture &&
+ extendingDecl == lifetimeExtensionCapture->extendingDecl) {
+ PendingCleanupEntry &entry = lifetimeExtendedCleanupStack.back();
+ entry.activeFlag = lifetimeExtensionCapture->activeFlag;
+ entry.capturedBy = lifetimeExtensionCapture->cleanups;
+ }
}
void CIRGenFunction::pushPendingCleanupToEHStack(
const PendingCleanupEntry &entry) {
+ std::optional<CapturedCleanups::CaptureScope> capture;
+ if (entry.capturedBy)
+ capture.emplace(*entry.capturedBy);
ehStack.pushCleanup<DestroyObject>(entry.kind, entry.addr, entry.type,
entry.destroyer);
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 619ad771a97c91..bc1364823f5144 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -2026,7 +2026,8 @@ static void pushTemporaryCleanup(CIRGenFunction &cgf,
case SD_Automatic:
cgf.pushLifetimeExtendedDestroy(
NormalAndEHCleanup, referenceTemporary, e->getType(),
- CIRGenFunction::destroyCXXObject, cgf.getLangOpts().Exceptions);
+ CIRGenFunction::destroyCXXObject, cgf.getLangOpts().Exceptions,
+ m->getExtendingDecl());
break;
case SD_Dynamic:
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h
b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 6ca9f27748b28c..32ce202b79279f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -129,6 +129,8 @@ class CIRGenFunction : public CIRGenTypeCache {
typedef void Destroyer(CIRGenFunction &cgf, Address addr, QualType ty);
+ class CapturedCleanups;
+
/// A cleanup whose destructor call is not emitted where the cleanup is
/// registered. Used by the lifetime-extended cleanup stack, whose entries
/// are later promoted onto the EH scope stack, and by the deferred
@@ -148,6 +150,9 @@ class CIRGenFunction : public CIRGenTypeCache {
QualType type;
Destroyer *destroyer;
Address activeFlag = Address::invalid();
+ /// If set, the cleanup is captured by this owner instead of opening a
+ /// cir.cleanup.scope when it is pushed onto the EH stack.
+ CapturedCleanups *capturedBy = nullptr;
};
llvm::SmallVector<PendingCleanupEntry> lifetimeExtendedCleanupStack;
@@ -200,6 +205,19 @@ class CIRGenFunction : public CIRGenTypeCache {
/// what closes those scopes.
cir::CleanupScopeOp currentFullExprCleanupScope = nullptr;
+ /// Set by emitLoopConditionVariable while it emits the initializer of a loop
+ /// condition variable that lifetime-extends temporaries with a destructor.
+ /// The cleanups of the temporaries that \c extendingDecl extends are
+ /// captured by \c cleanups for the loop's cleanup region and guarded by
+ /// \c activeFlag, instead of opening cir.cleanup.scope operations around the
+ /// cir.condition.
+ struct LifetimeExtensionCapture {
+ const clang::ValueDecl *extendingDecl;
+ CapturedCleanups *cleanups;
+ Address activeFlag;
+ };
+ const LifetimeExtensionCapture *lifetimeExtensionCapture = nullptr;
+
/// A cleanup that was pushed to the EH stack but whose deactivation is
/// deferred until the enclosing CleanupDeactivationScope exits. Used to
/// protect partially-constructed aggregates (e.g. lambda captures) so that
@@ -1730,7 +1748,8 @@ class CIRGenFunction : public CIRGenTypeCache {
void pushLifetimeExtendedDestroy(CleanupKind kind, Address addr,
QualType type, Destroyer *destroyer,
- bool useEHCleanupForArray);
+ bool useEHCleanupForArray,
+ const clang::ValueDecl *extendingDecl);
Destroyer *getDestroyer(clang::QualType::DestructionKind kind);
@@ -1907,8 +1926,17 @@ class CIRGenFunction : public CIRGenTypeCache {
/// Emit a loop's condition-variable declaration. This needs special handling
/// so that we can manage per-iteration cleanups for the loop condition.
+ /// \p extendedTemporaries are the types of the temporaries with a destructor
+ /// that its initializer lifetime-extends (see
+ /// getLifetimeExtendedTemporariesWithDestructor).
void emitLoopConditionVariable(const clang::VarDecl &d,
- CapturedCleanups &condCleanup);
+ CapturedCleanups &condCleanup,
+ llvm::ArrayRef<QualType> extendedTemporaries);
+
+ /// The types of the temporaries with a destructor that the initializer of
+ /// the variable \p d, which has automatic storage duration,
lifetime-extends.
+ static llvm::SmallVector<QualType, 2>
+ getLifetimeExtendedTemporariesWithDestructor(const clang::VarDecl &d);
/// Emit the initializer for an allocated variable. If this call is not
/// associated with the call to emitAutoVarAlloca (as the address of the
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index d06d32f5b33c06..94c2f70eb50468 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -1030,14 +1030,20 @@ mlir::LogicalResult CIRGenFunction::emitForStmt(const
ForStmt &s) {
checkIfLoopMustProgress(s.getCond(), CodeGenUtils::hasEmptyLoopBody(s));
// A condition variable's lifetime is a single iteration, so capture its
- // destructor and lifetime-end cleanups and emit them into the loop's
+ // destructor and lifetime-end cleanups, and those of the temporaries that
+ // its initializer lifetime-extends, and emit them into the loop's
// per-iteration cleanup region. This scope is constructed after the
// init-statement so the init-statement's cleanups are not captured.
const VarDecl *condVar = s.getConditionVariable();
+ SmallVector<QualType, 2> condVarExtendedTemporaries;
+ if (condVar)
+ condVarExtendedTemporaries =
+ getLifetimeExtendedTemporariesWithDestructor(*condVar);
bool needsCondCleanup =
condVar &&
(condVar->needsDestruction(getContext()) != QualType::DK_none ||
- shouldEmitLifetimeMarkersForAutoVar());
+ shouldEmitLifetimeMarkersForAutoVar() ||
+ !condVarExtendedTemporaries.empty());
CapturedCleanups loopCondScope(*this, needsCondCleanup);
auto condBuilder = [&](mlir::OpBuilder &b, mlir::Location loc) {
@@ -1047,7 +1053,8 @@ mlir::LogicalResult CIRGenFunction::emitForStmt(const
ForStmt &s) {
if (s.getCond()) {
// If the for statement declares a condition variable, emit that here.
if (condVar)
- emitLoopConditionVariable(*condVar, loopCondScope);
+ emitLoopConditionVariable(*condVar, loopCondScope,
+ condVarExtendedTemporaries);
// C99 6.8.5p2/p4: The first substatement is executed if the
// expression compares unequal to 0. The condition must be a
// scalar type.
@@ -1165,13 +1172,19 @@ mlir::LogicalResult CIRGenFunction::emitWhileStmt(const
WhileStmt &s) {
checkIfLoopMustProgress(s.getCond(), CodeGenUtils::hasEmptyLoopBody(s));
// A condition variable's lifetime is a single iteration, so capture its
- // destructor and lifetime-end cleanups and emit them into the loop's
+ // destructor and lifetime-end cleanups, and those of the temporaries that
+ // its initializer lifetime-extends, and emit them into the loop's
// per-iteration cleanup region.
const VarDecl *condVar = s.getConditionVariable();
+ SmallVector<QualType, 2> condVarExtendedTemporaries;
+ if (condVar)
+ condVarExtendedTemporaries =
+ getLifetimeExtendedTemporariesWithDestructor(*condVar);
bool needsCondCleanup =
condVar &&
(condVar->needsDestruction(getContext()) != QualType::DK_none ||
- shouldEmitLifetimeMarkersForAutoVar());
+ shouldEmitLifetimeMarkersForAutoVar() ||
+ !condVarExtendedTemporaries.empty());
CapturedCleanups loopCondScope(*this, needsCondCleanup);
auto condBuilder = [&](mlir::OpBuilder &b, mlir::Location loc) {
@@ -1179,7 +1192,8 @@ mlir::LogicalResult CIRGenFunction::emitWhileStmt(const
WhileStmt &s) {
assert(!cir::MissingFeatures::emitCondLikelihoodViaExpectIntrinsic());
// If the while statement declares a condition variable, emit that here.
if (condVar)
- emitLoopConditionVariable(*condVar, loopCondScope);
+ emitLoopConditionVariable(*condVar, loopCondScope,
+ condVarExtendedTemporaries);
// C99 6.8.5p2/p4: The first substatement is executed if the
// expression compares unequal to 0. The condition must be a
// scalar type.
diff --git a/clang/test/CIR/CodeGen/lifetime-marker.cpp
b/clang/test/CIR/CodeGen/lifetime-marker.cpp
index 12ac98b3d53437..b0615cd1831c5f 100644
--- a/clang/test/CIR/CodeGen/lifetime-marker.cpp
+++ b/clang/test/CIR/CodeGen/lifetime-marker.cpp
@@ -1,13 +1,13 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -fclangir -emit-cir %s
-o %t.cir
-// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR
+// RUN: FileCheck --input-file=%t.cir %s
--check-prefixes=CIR,EXT-CIR,EXT-CIR-NOEH
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -fclangir -emit-llvm
-disable-llvm-passes %s -o %t.ll
-// RUN: FileCheck --input-file=%t.ll %s --check-prefix=LLVM
+// RUN: FileCheck --input-file=%t.ll %s
--check-prefixes=LLVM,EXT-LLVM,EXT-LLVM-NOEH
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o
%t-o0.cir
// RUN: FileCheck --input-file=%t-o0.cir %s --implicit-check-not "cir.lifetime"
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -fcxx-exceptions
-fexceptions -fclangir -emit-cir %s -o %t-eh.cir
-// RUN: FileCheck --input-file=%t-eh.cir %s --check-prefix=CIR-EH
+// RUN: FileCheck --input-file=%t-eh.cir %s
--check-prefixes=CIR-EH,EXT-CIR,EXT-CIR-EH
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -fcxx-exceptions
-fexceptions -fclangir -emit-llvm -disable-llvm-passes %s -o %t-eh.ll
-// RUN: FileCheck --input-file=%t-eh.ll %s --check-prefixes=LLVM-EH
+// RUN: FileCheck --input-file=%t-eh.ll %s
--check-prefixes=LLVM-EH,EXT-LLVM,EXT-LLVM-EH
void use(int);
@@ -275,6 +275,79 @@ void while_record_condvar() {
// LLVM-EH: call void @_ZN8LoopCondD1Ev(ptr {{.*}} %[[C]])
// LLVM-EH: call void @llvm.lifetime.end.p0(ptr %[[C]])
+LoopCond makeLoopCond();
+
+// Destroy the extended temporary before ending the reference's lifetime.
+// CIR does not emit lifetime markers for the temporary yet and are
+// therefore not checked here.
+void while_extended_condvar() {
+ while (const LoopCond &c = makeLoopCond()) {}
+}
+
+// EXT-CIR-LABEL: cir.func{{.*}} @_Z22while_extended_condvarv
+// EXT-CIR: %[[TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} :
!cir.ptr<!rec_LoopCond>
+// EXT-CIR: %[[C:.*]] = cir.alloca "c" {{.*}} :
!cir.ptr<!cir.ptr<!rec_LoopCond>>
+// EXT-CIR: cir.while {
+// EXT-CIR-NEXT: cir.lifetime.start %[[C]] :
!cir.ptr<!cir.ptr<!rec_LoopCond>>
+// EXT-CIR: cir.call @_Z12makeLoopCondv(%[[TMP]])
+// EXT-CIR: } do {
+// EXT-CIR-NOEH: } cleanup normal {
+// EXT-CIR-EH: } cleanup all {
+// EXT-CIR: cir.if %{{.*}} {
+// EXT-CIR-NEXT: cir.call @_ZN8LoopCondD1Ev(%[[TMP]]){{.*}}
+// EXT-CIR-NEXT: }
+// EXT-CIR-NEXT: cir.lifetime.end %[[C]] :
!cir.ptr<!cir.ptr<!rec_LoopCond>>
+// EXT-CIR-NEXT: cir.yield
+
+// EXT-LLVM-LABEL: define{{.*}} void @_Z22while_extended_condvarv
+// EXT-LLVM: call void @llvm.lifetime.start.p0(ptr %[[C:.*]])
+// EXT-LLVM-NOEH: call void @_Z12makeLoopCondv(ptr {{.*}} %[[TMP:[^)]*]])
+// EXT-LLVM-EH: invoke void @_Z12makeLoopCondv(ptr {{.*}} %[[TMP:[^)]*]])
+// EXT-LLVM: call void @_ZN8LoopCondD1Ev(ptr {{.*}} %[[TMP]])
+// EXT-LLVM: call void @llvm.lifetime.end.p0(ptr %[[C]])
+// EXT-LLVM-EH: landingpad { ptr, i32 }
+// EXT-LLVM-EH-NEXT: cleanup
+// EXT-LLVM-EH: call void @_ZN8LoopCondD1Ev(ptr {{.*}} %[[TMP]])
+// EXT-LLVM-EH: call void @llvm.lifetime.end.p0(ptr %[[C]])
+
+struct LoopCondHolder {
+ const LoopCond &c;
+ ~LoopCondHolder();
+ operator bool() const;
+};
+
+// Destroy the record, then its extended temporary, then end its lifetime.
+void while_extended_record_condvar() {
+ while (LoopCondHolder h{makeLoopCond()}) {}
+}
+
+// EXT-CIR-LABEL: cir.func{{.*}} @_Z29while_extended_record_condvarv
+// EXT-CIR: %[[TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} :
!cir.ptr<!rec_LoopCond>
+// EXT-CIR: %[[H:.*]] = cir.alloca "h" {{.*}} :
!cir.ptr<!rec_LoopCondHolder>
+// EXT-CIR: cir.while {
+// EXT-CIR-NEXT: cir.lifetime.start %[[H]] :
!cir.ptr<!rec_LoopCondHolder>
+// EXT-CIR-NOEH: } cleanup normal {
+// EXT-CIR-EH: } cleanup all {
+// EXT-CIR: cir.if %{{.*}} {
+// EXT-CIR-NEXT: cir.call @_ZN14LoopCondHolderD1Ev(%[[H]]){{.*}}
+// EXT-CIR-NEXT: }
+// EXT-CIR: cir.if %{{.*}} {
+// EXT-CIR-NEXT: cir.call @_ZN8LoopCondD1Ev(%[[TMP]]){{.*}}
+// EXT-CIR-NEXT: }
+// EXT-CIR-NEXT: cir.lifetime.end %[[H]] : !cir.ptr<!rec_LoopCondHolder>
+// EXT-CIR-NEXT: cir.yield
+
+// EXT-LLVM-LABEL: define{{.*}} void @_Z29while_extended_record_condvarv
+// EXT-LLVM: call void @llvm.lifetime.start.p0(ptr %[[H:.*]])
+// EXT-LLVM: call void @_ZN14LoopCondHolderD1Ev(ptr {{.*}} %[[H]])
+// EXT-LLVM: call void @_ZN8LoopCondD1Ev(ptr {{.*}} %[[TMP:[^)]*]])
+// EXT-LLVM: call void @llvm.lifetime.end.p0(ptr %[[H]])
+// EXT-LLVM-EH: landingpad { ptr, i32 }
+// EXT-LLVM-EH-NEXT: cleanup
+// EXT-LLVM-EH: call void @_ZN14LoopCondHolderD1Ev(ptr {{.*}} %[[H]])
+// EXT-LLVM-EH: call void @_ZN8LoopCondD1Ev(ptr {{.*}} %[[TMP]])
+// EXT-LLVM-EH: call void @llvm.lifetime.end.p0(ptr %[[H]])
+
#ifdef __EXCEPTIONS
struct Ex {};
diff --git a/clang/test/CIR/CodeGen/loop-cond-var-cleanup.cpp
b/clang/test/CIR/CodeGen/loop-cond-var-cleanup.cpp
index 548047879421ff..0643d4fc7d10f5 100644
--- a/clang/test/CIR/CodeGen/loop-cond-var-cleanup.cpp
+++ b/clang/test/CIR/CodeGen/loop-cond-var-cleanup.cpp
@@ -1032,3 +1032,498 @@ void while_cond_var_nested(int n) {
// OGCG: ret void
// OGCG: [[EHRESUME]]:
// OGCG: resume { ptr, i32 }
+
+void use(S &);
+
+struct Holder {
+ S &&a;
+ S &&b;
+ ~Holder();
+ explicit operator bool();
+};
+
+// Destroy the variable first, then its extended temporaries in reverse order.
+// All three cleanups share the active flag.
+void for_cond_var_extended_member() {
+ for (; Holder h{makeS(), makeS()};)
+ use(h.a);
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z28for_cond_var_extended_memberv
+// CIR: %[[FLAG:.*]] = cir.alloca {{.*}}"cond.cleanup.isactive"{{.*}}
: !cir.ptr<!cir.bool>
+// CIR: %[[A:.*]] = cir.alloca {{.*}}"ref.tmp0"{{.*}} :
!cir.ptr<!rec_S>
+// CIR: %[[B:.*]] = cir.alloca {{.*}}"ref.tmp1"{{.*}} :
!cir.ptr<!rec_S>
+// CIR: %[[H:.*]] = cir.alloca {{.*}}"h"{{.*}} : !cir.ptr<!rec_Holder>
+// CIR: cir.for : cond {
+// CIR: %[[FALSE:.*]] = cir.const #false
+// CIR-NEXT: cir.store %[[FALSE]], %[[FLAG]]
+// CIR: cir.call @_Z5makeSv(%[[A]])
+// The first temporary is protected while the second is initialized.
+// CIR: cir.cleanup.scope {
+// CIR: cir.call @_Z5makeSv(%[[B]])
+// CIR: } cleanup eh {
+// CIR: cir.call @_ZN1SD1Ev(%[[B]]) nothrow
+// CIR: } cleanup eh {
+// CIR-NEXT: cir.call @_ZN1SD1Ev(%[[A]]) nothrow
+// CIR: %[[TRUE:.*]] = cir.const #true
+// CIR-NEXT: cir.store %[[TRUE]], %[[FLAG]]
+// CIR: cir.condition
+// CIR: } cleanup all {
+// CIR-NEXT: %[[ACTIVE:.*]] = cir.load {{.*}}%[[FLAG]]
+// CIR-NEXT: cir.if %[[ACTIVE]] {
+// CIR-NEXT: cir.call @_ZN6HolderD1Ev(%[[H]]) nothrow{{.*}}
+// CIR-NEXT: }
+// CIR-NEXT: %[[ACTIVE_B:.*]] = cir.load {{.*}}%[[FLAG]]
+// CIR-NEXT: cir.if %[[ACTIVE_B]] {
+// CIR-NEXT: cir.call @_ZN1SD1Ev(%[[B]]) nothrow{{.*}}
+// CIR-NEXT: }
+// CIR-NEXT: %[[ACTIVE_A:.*]] = cir.load {{.*}}%[[FLAG]]
+// CIR-NEXT: cir.if %[[ACTIVE_A]] {
+// CIR-NEXT: cir.call @_ZN1SD1Ev(%[[A]]) nothrow{{.*}}
+// CIR-NEXT: }
+// CIR-NEXT: cir.yield
+// CIR-NEXT: }
+
+// LLVM-LABEL: define dso_local void @_Z28for_cond_var_extended_memberv()
{{.*}} personality ptr @__gxx_personality_v0 {
+// LLVM: %[[A:.*]] = alloca %struct.S
+// LLVM: %[[B:.*]] = alloca %struct.S
+// LLVM: %[[H:.*]] = alloca %struct.Holder
+// LLVM: %[[FLAG:.*]] = alloca i8
+// LLVM: store i8 0, ptr %[[FLAG]]
+// LLVM: invoke void @_Z5makeSv(ptr {{.*}} %[[A]])
+// LLVM-NEXT: to label %{{.*}} unwind label %[[LPAD:.*]]
+// LLVM: invoke void @_Z5makeSv(ptr {{.*}} %[[B]])
+// LLVM: store i8 1, ptr %[[FLAG]]
+// LLVM: call void @_ZN6HolderD1Ev(ptr {{.*}} %[[H]])
+// LLVM: call void @_ZN1SD1Ev(ptr {{.*}} %[[B]])
+// LLVM: call void @_ZN1SD1Ev(ptr {{.*}} %[[A]])
+// LLVM: [[LPAD]]:
+// LLVM: landingpad { ptr, i32 }
+// LLVM-NEXT: cleanup
+// LLVM: load i8, ptr %[[FLAG]]
+// LLVM: call void @_ZN6HolderD1Ev(ptr {{.*}} %[[H]])
+// LLVM: load i8, ptr %[[FLAG]]
+// LLVM: call void @_ZN1SD1Ev(ptr {{.*}} %[[B]])
+// LLVM: load i8, ptr %[[FLAG]]
+// LLVM: call void @_ZN1SD1Ev(ptr {{.*}} %[[A]])
+// LLVM: resume { ptr, i32 }
+
+// OGCG-LABEL: define dso_local void @_Z28for_cond_var_extended_memberv()
{{.*}} personality ptr @__gxx_personality_v0 {
+// OGCG: call void @_Z5makeSv(ptr {{.*}} %[[A:[^)]*]])
+// OGCG: invoke void @_Z5makeSv(ptr {{.*}} %[[B:[^)]*]])
+// OGCG: {{^}}cleanup:
+// OGCG: call void @_ZN6HolderD1Ev(
+// OGCG-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[B]])
+// OGCG-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[A]])
+
+S pick(S &);
+
+// A temporary that a variable in a statement expression in the initializer
+// extends is destroyed at the end of the statement expression.
+void while_cond_var_extended_in_stmt_expr() {
+ while (S &&s = ({
+ S &&t = makeS();
+ pick(t);
+ }))
+ use(s);
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z36while_cond_var_extended_in_stmt_exprv
+// CIR: %[[FLAG:.*]] = cir.alloca {{.*}}"cond.cleanup.isactive"{{.*}}
: !cir.ptr<!cir.bool>
+// CIR: %[[OUTER_TMP:.*]] = cir.alloca {{.*}}"ref.tmp0"{{.*}} :
!cir.ptr<!rec_S>
+// CIR: %[[S:.*]] = cir.alloca {{.*}}"s"{{.*}} :
!cir.ptr<!cir.ptr<!rec_S>>
+// CIR: cir.while {
+// CIR: %[[FALSE:.*]] = cir.const #false
+// CIR-NEXT: cir.store %[[FALSE]], %[[FLAG]]
+// CIR-NEXT: cir.scope {
+// CIR: %[[INNER_TMP:.*]] = cir.alloca {{.*}}"ref.tmp1"{{.*}} :
!cir.ptr<!rec_S>
+// CIR: cir.call @_Z5makeSv(%[[INNER_TMP]])
+// CIR: cir.cleanup.scope {
+// CIR: cir.call @_Z4pickR1S(%[[OUTER_TMP]],
+// CIR: } cleanup all {
+// CIR-NEXT: cir.call @_ZN1SD1Ev(%[[INNER_TMP]]) nothrow{{.*}}
+// CIR-NEXT: cir.yield
+// CIR-NEXT: }
+// CIR-NEXT: }
+// CIR-NEXT: cir.store {{.*}}%[[OUTER_TMP]], %[[S]]
+// CIR-NEXT: %[[TRUE:.*]] = cir.const #true
+// CIR-NEXT: cir.store %[[TRUE]], %[[FLAG]]
+// CIR: cir.condition
+// CIR: } cleanup all {
+// CIR-NEXT: %[[ACTIVE:.*]] = cir.load {{.*}}%[[FLAG]]
+// CIR-NEXT: cir.if %[[ACTIVE]] {
+// CIR-NEXT: cir.call @_ZN1SD1Ev(%[[OUTER_TMP]]) nothrow{{.*}}
+// CIR-NEXT: }
+// CIR-NEXT: cir.yield
+// CIR-NEXT: }
+
+// OGCG-LABEL: define dso_local void
@_Z36while_cond_var_extended_in_stmt_exprv() {{.*}} personality ptr
@__gxx_personality_v0 {
+// OGCG: call void @_Z5makeSv(ptr {{.*}} %[[INNER_TMP:.*]])
+// OGCG: invoke void @_Z4pickR1S(ptr {{.*}} %[[OUTER_TMP:[^,]*]],
+// OGCG: call void @_ZN1SD1Ev(ptr {{.*}} %[[INNER_TMP]])
+// OGCG: invoke {{.*}} @_ZN1ScvbEv(
+// OGCG: {{^}}cleanup:
+// OGCG: call void @_ZN1SD1Ev(ptr {{.*}} %[[OUTER_TMP]])
+
+struct DefaultInit {
+ S &&s = makeS();
+ explicit operator bool();
+};
+
+// A temporary in a default member initializer is extended as well.
+void while_cond_var_extended_default_init() {
+ while (DefaultInit d{})
+ use(d.s);
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z36while_cond_var_extended_default_initv
+// CIR: %[[FLAG:.*]] = cir.alloca {{.*}}"cond.cleanup.isactive"{{.*}}
: !cir.ptr<!cir.bool>
+// CIR: %[[TMP:.*]] = cir.alloca {{.*}}"ref.tmp0"{{.*}} :
!cir.ptr<!rec_S>
+// CIR: cir.while {
+// CIR: %[[FALSE:.*]] = cir.const #false
+// CIR-NEXT: cir.store %[[FALSE]], %[[FLAG]]
+// CIR: cir.call @_Z5makeSv(%[[TMP]])
+// CIR: cir.cleanup.scope {
+// CIR: } cleanup eh {
+// CIR-NEXT: cir.call @_ZN1SD1Ev(%[[TMP]]) nothrow
+// CIR: %[[TRUE:.*]] = cir.const #true
+// CIR-NEXT: cir.store %[[TRUE]], %[[FLAG]]
+// CIR-NOT: @_ZN1SD1Ev
+// CIR: } cleanup all {
+// CIR-NEXT: %[[ACTIVE:.*]] = cir.load {{.*}}%[[FLAG]]
+// CIR-NEXT: cir.if %[[ACTIVE]] {
+// CIR-NEXT: cir.call @_ZN1SD1Ev(%[[TMP]]) nothrow{{.*}}
+// CIR-NEXT: }
+// CIR-NEXT: cir.yield
+// CIR-NEXT: }
+
+// OGCG-LABEL: define dso_local void
@_Z36while_cond_var_extended_default_initv() {{.*}} personality ptr
@__gxx_personality_v0 {
+// OGCG: call void @_Z5makeSv(ptr {{.*}} %[[TMP:.*]])
+// OGCG: {{^}}cleanup:
+// OGCG: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+
+namespace std {
+template <class E> class initializer_list {
+ const E *b;
+ decltype(sizeof 0) n;
+
+public:
+ constexpr initializer_list() : b(nullptr), n(0) {}
+};
+} // namespace std
+
+struct List {
+ std::initializer_list<S> l;
+ explicit operator bool();
+};
+
+void use(List &);
+
+// So does a std::initializer_list member for its backing array.
+void while_cond_var_extended_init_list() {
+ while (List l{{S{}, S{}}})
+ use(l);
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z33while_cond_var_extended_init_listv
+// CIR: %[[FLAG:.*]] = cir.alloca {{.*}}"cond.cleanup.isactive"{{.*}}
: !cir.ptr<!cir.bool>
+// CIR: %[[ARRAY:.*]] = cir.alloca {{.*}}"ref.tmp0"{{.*}} :
!cir.ptr<!cir.array<!rec_S x 2>>
+// CIR: cir.while {
+// CIR: %[[FALSE:.*]] = cir.const #false
+// CIR-NEXT: cir.store %[[FALSE]], %[[FLAG]]
+// The elements constructed so far are destroyed if a constructor throws.
+// CIR: cir.cleanup.scope {
+// CIR: cir.call @_ZN1SC1Ev(
+// CIR: cir.call @_ZN1SC1Ev(
+// CIR-NEXT: cir.yield
+// CIR-NEXT: } cleanup eh {
+// The whole array is destroyed if the rest of the initialization throws.
+// CIR: cir.cleanup.scope {
+// CIR: cir.get_member {{.*}} {name = "n"}
+// CIR: } cleanup eh {
+// CIR: %[[TRUE:.*]] = cir.const #true
+// CIR-NEXT: cir.store %[[TRUE]], %[[FLAG]]
+// CIR: } cleanup all {
+// CIR-NEXT: %[[ACTIVE:.*]] = cir.load {{.*}}%[[FLAG]]
+// CIR-NEXT: cir.if %[[ACTIVE]] {
+// CIR-NEXT: %[[SIZE:.*]] = cir.const #cir.int<2> : !u64i
+// CIR-NEXT: %[[BEGIN:.*]] = cir.cast array_to_ptrdecay %[[ARRAY]]
+// CIR-NEXT: cir.ptr_stride %[[BEGIN]], %[[SIZE]]
+// CIR: cir.do {
+// CIR: cir.call @_ZN1SD1Ev({{.*}}) nothrow{{.*}}
+// CIR: } while {
+
+// LLVM-LABEL: define dso_local void @_Z33while_cond_var_extended_init_listv()
{{.*}} personality ptr @__gxx_personality_v0 {
+// LLVM: %[[FLAG:.*]] = alloca i8
+// LLVM: store i8 0, ptr %[[FLAG]]
+// LLVM: invoke void @_ZN1SC1Ev(
+// LLVM-NEXT: to label %{{.*}} unwind label %[[PARTIAL_LPAD:.*]]
+// LLVM: invoke void @_ZN1SC1Ev(
+// LLVM-NEXT: to label %{{.*}} unwind label %[[PARTIAL_LPAD]]
+// LLVM: [[PARTIAL_LPAD]]:
+// LLVM: landingpad { ptr, i32 }
+// LLVM: call void @_ZN1SD1Ev(
+// LLVM: store i8 1, ptr %[[FLAG]]
+// LLVM: landingpad { ptr, i32 }
+// The partial-array cleanup and the loop's EH cleanup share this block, so the
+// whole array is destroyed only once the flag is set.
+// LLVM: %[[EH_ACTIVE:.*]] = load i8, ptr %[[FLAG]]
+// LLVM: %[[EH_ACTIVE_BOOL:.*]] = trunc i8 %[[EH_ACTIVE]] to i1
+// LLVM: br i1 %[[EH_ACTIVE_BOOL]], label %{{.*}}, label %{{.*}}
+// LLVM: call void @_ZN1SD1Ev(
+// LLVM: resume { ptr, i32 }
+
+// OGCG-LABEL: define dso_local void @_Z33while_cond_var_extended_init_listv()
{{.*}} personality ptr @__gxx_personality_v0 {
+// OGCG: {{^}}cleanup:
+// OGCG: {{^}}arraydestroy.body{{[0-9]*}}:
+// OGCG: call void @_ZN1SD1Ev(
+
+// The extended temporary is destroyed on iteration exits, early returns, and
+// unwinding. Its cleanup is inactive until initialization completes.
+int while_cond_var_extended_return(int n) {
+ while (S &&s = makeS()) {
+ if (n)
+ return n;
+ use(s);
+ }
+ return 0;
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z30while_cond_var_extended_returni
+// CIR: %[[FLAG:.*]] = cir.alloca {{.*}}"cond.cleanup.isactive"{{.*}}
: !cir.ptr<!cir.bool>
+// CIR: %[[TMP:.*]] = cir.alloca {{.*}}"ref.tmp0"{{.*}} :
!cir.ptr<!rec_S>
+// CIR: %[[S:.*]] = cir.alloca {{.*}}"s"{{.*}} :
!cir.ptr<!cir.ptr<!rec_S>>
+// CIR: cir.while {
+// CIR: %[[FALSE:.*]] = cir.const #false
+// CIR-NEXT: cir.store %[[FALSE]], %[[FLAG]]
+// CIR-NEXT: cir.call @_Z5makeSv(%[[TMP]])
+// CIR-NEXT: cir.store {{.*}}%[[TMP]], %[[S]]
+// CIR-NEXT: %[[TRUE:.*]] = cir.const #true
+// CIR-NEXT: cir.store %[[TRUE]], %[[FLAG]]
+// CIR-NOT: @_ZN1SD1Ev
+// CIR: cir.condition
+// CIR-NOT: @_ZN1SD1Ev
+// CIR: } do {
+// CIR-NOT: @_ZN1SD1Ev
+// CIR: cir.return
+// CIR-NOT: @_ZN1SD1Ev
+// CIR: cir.call @_Z3useR1S
+// CIR-NOT: @_ZN1SD1Ev
+// CIR: } cleanup all {
+// CIR-NEXT: %[[ACTIVE:.*]] = cir.load {{.*}}%[[FLAG]]
+// CIR-NEXT: cir.if %[[ACTIVE]] {
+// CIR-NEXT: cir.call @_ZN1SD1Ev(%[[TMP]]) nothrow{{.*}}
+// CIR-NEXT: }
+// CIR-NEXT: cir.yield
+// CIR-NEXT: }
+
+// LLVM-LABEL: define dso_local {{.*}}i32 @_Z30while_cond_var_extended_returni(
+// LLVM: %[[TMP:.*]] = alloca %struct.S
+// LLVM: %[[FLAG:.*]] = alloca i8
+// LLVM: store i8 0, ptr %[[FLAG]]
+// LLVM-NEXT: invoke void @_Z5makeSv(ptr {{.*}} %[[TMP]])
+// LLVM-NEXT: to label %[[MAKE_CONT:.*]] unwind label %[[LPAD:.*]]
+// LLVM: [[MAKE_CONT]]:
+// LLVM: store i8 1, ptr %[[FLAG]]
+// LLVM: store i32 %{{.*}}, ptr %[[RETVAL:.*]], align 4
+// LLVM-NEXT: store i32 [[RET_DEST:[0-9]+]], ptr %[[DEST:.*]], align 4
+// LLVM-NEXT: br label %[[CLEANUP:.*]]
+// LLVM: invoke void @_Z3useR1S(
+// LLVM: [[CLEANUP]]:
+// LLVM: %[[ACTIVE:.*]] = load i8, ptr %[[FLAG]]
+// LLVM: %[[ACTIVE_BOOL:.*]] = trunc i8 %[[ACTIVE]] to i1
+// LLVM: br i1 %[[ACTIVE_BOOL]], label %[[DTOR:.*]], label %{{.*}}
+// LLVM: [[DTOR]]:
+// LLVM-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// LLVM: %[[SEL:.*]] = load i32, ptr %[[DEST]]
+// LLVM: switch i32 %[[SEL]], label %{{.*}} [
+// LLVM: i32 [[RET_DEST]], label %[[RETURN:.*]]
+// LLVM: [[RETURN]]:
+// LLVM-NEXT: %[[RV:.*]] = load i32, ptr %[[RETVAL]]
+// LLVM-NEXT: ret i32 %[[RV]]
+// LLVM: [[LPAD]]:
+// LLVM: landingpad { ptr, i32 }
+// LLVM-NEXT: cleanup
+// LLVM: %[[EH_ACTIVE:.*]] = load i8, ptr %[[FLAG]]
+// LLVM: %[[EH_ACTIVE_BOOL:.*]] = trunc i8 %[[EH_ACTIVE]] to i1
+// LLVM: br i1 %[[EH_ACTIVE_BOOL]], label %[[EH_DTOR:.*]], label
%{{.*}}
+// LLVM: [[EH_DTOR]]:
+// LLVM: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// LLVM: resume { ptr, i32 }
+
+// OGCG-LABEL: define dso_local {{.*}}i32 @_Z30while_cond_var_extended_returni(
+// OGCG: call void @_Z5makeSv(ptr {{.*}} %[[TMP:.*]])
+// OGCG: {{^}}lpad:
+// OGCG: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// OGCG: store i32 %{{.*}}, ptr %[[RETVAL:.*]], align 4
+// OGCG-NEXT: store i32 [[RET_DEST:[0-9]+]], ptr %[[DEST:.*]], align 4
+// OGCG-NEXT: br label %[[CLEANUP:.*]]
+// OGCG: [[CLEANUP]]:
+// OGCG-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// OGCG: switch i32 %{{.*}}, label %{{.*}} [
+// OGCG: i32 [[RET_DEST]], label %[[RETURN:.*]]
+// OGCG: [[RETURN]]:
+// OGCG: ret i32
+
+// A for loop gets a cleanup region for the extended temporary even though the
+// reference itself needs no cleanup.
+void for_cond_var_extended() {
+ for (; S &&s = makeS();)
+ use(s);
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z21for_cond_var_extendedv
+// CIR: %[[FLAG:.*]] = cir.alloca {{.*}}"cond.cleanup.isactive"{{.*}}
: !cir.ptr<!cir.bool>
+// CIR: %[[TMP:.*]] = cir.alloca {{.*}}"ref.tmp0"{{.*}} :
!cir.ptr<!rec_S>
+// CIR: cir.for : cond {
+// CIR: %[[FALSE:.*]] = cir.const #false
+// CIR-NEXT: cir.store %[[FALSE]], %[[FLAG]]
+// CIR-NEXT: cir.call @_Z5makeSv(%[[TMP]])
+// CIR-NEXT: cir.store {{.*}}%[[TMP]], %{{.*}}
+// CIR-NEXT: %[[TRUE:.*]] = cir.const #true
+// CIR-NEXT: cir.store %[[TRUE]], %[[FLAG]]
+// CIR: cir.condition
+// CIR: } cleanup all {
+// CIR-NEXT: %[[ACTIVE:.*]] = cir.load {{.*}}%[[FLAG]]
+// CIR-NEXT: cir.if %[[ACTIVE]] {
+// CIR-NEXT: cir.call @_ZN1SD1Ev(%[[TMP]]) nothrow{{.*}}
+// CIR-NEXT: }
+// CIR-NEXT: cir.yield
+// CIR-NEXT: }
+
+// The temporary in one arm of the conditional operator is destroyed at the end
+// of the full-expression, in that full-expression's own cleanup scope. Only
the
+// result, which the variable extends, is destroyed with the variable.
+void while_cond_var_extended_conditional(int n) {
+ while (S &&s = n ? chainS(makeS()) : makeS())
+ use(s);
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z35while_cond_var_extended_conditionali
+// CIR: %[[FLAG:.*]] = cir.alloca {{.*}}"cond.cleanup.isactive"{{.*}}
: !cir.ptr<!cir.bool>
+// CIR: %[[ARG_TMP:.*]] = cir.alloca {{.*}}"ref.tmp1"{{.*}} :
!cir.ptr<!rec_S>
+// CIR: %[[TMP:.*]] = cir.alloca {{.*}}"ref.tmp0"{{.*}} :
!cir.ptr<!rec_S>
+// CIR: cir.while {
+// CIR: %[[FALSE:.*]] = cir.const #false
+// CIR-NEXT: cir.store %[[FALSE]], %[[FLAG]]
+// CIR: cir.cleanup.scope {
+// CIR: cir.if {{.*}} {
+// CIR: cir.call @_Z6chainSRK1S(%[[TMP]], %[[ARG_TMP]])
+// CIR: } else {
+// CIR: cir.call @_Z5makeSv(%[[TMP]])
+// CIR: }
+// CIR: } cleanup all {
+// CIR: cir.call @_ZN1SD1Ev(%[[ARG_TMP]]) nothrow
+// CIR: }
+// CIR-NOT: @_ZN1SD1Ev
+// CIR: %[[TRUE:.*]] = cir.const #true
+// CIR-NEXT: cir.store %[[TRUE]], %[[FLAG]]
+// CIR-NOT: @_ZN1SD1Ev
+// CIR: cir.condition
+// CIR: } do {
+// CIR: } cleanup all {
+// CIR-NEXT: %[[ACTIVE:.*]] = cir.load {{.*}}%[[FLAG]]
+// CIR-NEXT: cir.if %[[ACTIVE]] {
+// CIR-NEXT: cir.call @_ZN1SD1Ev(%[[TMP]]) nothrow{{.*}}
+// CIR-NEXT: }
+// CIR-NEXT: cir.yield
+// CIR-NEXT: }
+
+struct P {
+ int x;
+ S s;
+};
+P makeP();
+void use(const int &);
+
+// A reference to a member of a temporary extends the whole temporary, so its
+// destructor runs at the end of each iteration even though the variable's type
+// is trivially destructible.
+void while_cond_var_extended_subobject() {
+ while (const int &i = makeP().x)
+ use(i);
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z33while_cond_var_extended_subobjectv
+// CIR: %[[FLAG:.*]] = cir.alloca {{.*}}"cond.cleanup.isactive"{{.*}}
: !cir.ptr<!cir.bool>
+// CIR: %[[TMP:.*]] = cir.alloca {{.*}}"ref.tmp0"{{.*}} :
!cir.ptr<!rec_P>
+// CIR: cir.while {
+// CIR: %[[FALSE:.*]] = cir.const #false
+// CIR-NEXT: cir.store %[[FALSE]], %[[FLAG]]
+// CIR-NEXT: cir.call @_Z5makePv(%[[TMP]])
+// CIR-NEXT: cir.cleanup.scope {
+// CIR-NEXT: cir.get_member %[[TMP]][0] {name = "x"}
+// CIR: } cleanup eh {
+// CIR-NEXT: cir.call @_ZN1PD1Ev(%[[TMP]]) nothrow
+// CIR: %[[TRUE:.*]] = cir.const #true
+// CIR-NEXT: cir.store %[[TRUE]], %[[FLAG]]
+// CIR: cir.condition
+// CIR: } cleanup all {
+// CIR-NEXT: %[[ACTIVE:.*]] = cir.load {{.*}}%[[FLAG]]
+// CIR-NEXT: cir.if %[[ACTIVE]] {
+// CIR-NEXT: cir.call @_ZN1PD1Ev(%[[TMP]]) nothrow{{.*}}
+// CIR-NEXT: }
+// CIR-NEXT: cir.yield
+// CIR-NEXT: }
+
+struct ThrowingDtor {
+ ~ThrowingDtor() noexcept(false);
+ explicit operator bool() const;
+};
+ThrowingDtor makeThrowingDtor();
+
+struct ThrowingDtorHolder {
+ const ThrowingDtor &t;
+ ~ThrowingDtorHolder();
+ explicit operator bool() const;
+};
+void use(const ThrowingDtorHolder &);
+
+// The destructor of the temporary may throw, but it runs last, so an exception
+// from it skips no other destructor. On the EH path, it terminates.
+void while_cond_var_extended_throwing_last() {
+ while (ThrowingDtorHolder h{makeThrowingDtor()})
+ use(h);
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z37while_cond_var_extended_throwing_lastv
+// CIR: %[[FLAG:.*]] = cir.alloca {{.*}}"cond.cleanup.isactive"{{.*}}
: !cir.ptr<!cir.bool>
+// CIR: %[[TMP:.*]] = cir.alloca {{.*}}"ref.tmp0"{{.*}} :
!cir.ptr<!rec_ThrowingDtor>
+// CIR: %[[H:.*]] = cir.alloca {{.*}}"h"{{.*}} :
!cir.ptr<!rec_ThrowingDtorHolder>
+// CIR: cir.while {
+// CIR: } cleanup all {
+// CIR-NEXT: %[[ACTIVE:.*]] = cir.load {{.*}}%[[FLAG]]
+// CIR-NEXT: cir.if %[[ACTIVE]] {
+// CIR-NEXT: cir.call @_ZN18ThrowingDtorHolderD1Ev(%[[H]]) nothrow{{.*}}
+// CIR-NEXT: }
+// CIR-NEXT: %[[ACTIVE2:.*]] = cir.load {{.*}}%[[FLAG]]
+// CIR-NEXT: cir.if %[[ACTIVE2]] {
+// CIR-NEXT: cir.call @_ZN12ThrowingDtorD1Ev(%[[TMP]]) : {{.*}}
+// CIR-NEXT: }
+// CIR-NEXT: cir.yield
+// CIR-NEXT: }
+
+// LLVM-LABEL: define dso_local void
@_Z37while_cond_var_extended_throwing_lastv() {{.*}} personality ptr
@__gxx_personality_v0 {
+// LLVM: %[[TMP:.*]] = alloca %struct.ThrowingDtor
+// LLVM: %[[H:.*]] = alloca %struct.ThrowingDtorHolder
+// LLVM: call void @_ZN18ThrowingDtorHolderD1Ev(ptr {{.*}} %[[H]])
+// LLVM: call void @_ZN12ThrowingDtorD1Ev(ptr {{.*}} %[[TMP]])
+// LLVM: landingpad { ptr, i32 }
+// LLVM-NEXT: cleanup
+// LLVM: call void @_ZN18ThrowingDtorHolderD1Ev(ptr {{.*}} %[[H]])
+// LLVM: invoke void @_ZN12ThrowingDtorD1Ev(ptr {{.*}} %[[TMP]])
+// LLVM-NEXT: to label %{{.*}} unwind label %[[TERMINATE:.*]]
+// LLVM: resume { ptr, i32 }
+// LLVM: [[TERMINATE]]:
+// LLVM-NEXT: landingpad { ptr, i32 }
+// LLVM-NEXT: catch ptr null
+// LLVM: call void @__clang_call_terminate(
+
+// OGCG-LABEL: define dso_local void
@_Z37while_cond_var_extended_throwing_lastv() {{.*}} personality ptr
@__gxx_personality_v0 {
+// OGCG: lpad:
+// OGCG: call void @_ZN18ThrowingDtorHolderD1Ev(ptr {{.*}}
%[[H:[^)]*]])
+// OGCG-NEXT: invoke void @_ZN12ThrowingDtorD1Ev(ptr {{.*}} %[[TMP:[^)]*]])
+// OGCG-NEXT: to label %{{.*}} unwind label %terminate.lpad
+// OGCG: {{^}}cleanup:
+// OGCG-NEXT: call void @_ZN18ThrowingDtorHolderD1Ev(ptr {{.*}} %[[H]])
+// OGCG-NEXT: call void @_ZN12ThrowingDtorD1Ev(ptr {{.*}} %[[TMP]])
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits