https://github.com/mmha updated https://github.com/llvm/llvm-project/pull/231469
>From 3b96550c74fc570c7b5e659567aecb279bc8adec 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 | 140 ++++- 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, 756 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..badda004a8eeac 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,32 @@ 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 +1327,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 +1343,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
