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

Reply via email to