https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/225560
>From 8f08a8dd97a0ffe7bc20b0a061ebf1fc9d3df78a Mon Sep 17 00:00:00 2001 From: Andy Kaylor <[email protected]> Date: Tue, 22 Sep 2026 15:56:20 -0700 Subject: [PATCH 1/4] [CIR] Fix the order of temporary object cleanup We had a problem in CIR where we were performing cleanups in the wrong order when conditional and unconditional cleanups were needed for temporary objects created in a single expression. The unconditional cleanups were being emitted as they occur, while the conditional cleanups were deferred until the entire expression had been emitted. The result was that conditional cleanups were performed after unconditional cleanups, even if the unconditionally destroyed object was created first. This change fixes that problem by reorganizing the way conditional cleanups are handled. A cir.cleanup.scope operation is now created eagerly where a conditional begins rather than at the start of the full expression, so that it nests inside the cleanup scopes of temporaries created before the conditional and outside those created after it. Scope nesting determines the order in which the cleanup regions run, so this yields reverse-of-construction order. A ConditionalCleanupScope object tracks the scope and the conditional cleanups waiting to be emitted into it. If the eagerly created cleanup scope operation ends up not having been needed, the canonicalization pass will remove it. Assisted-by: Cursor / claude-opus-5 --- clang/lib/CIR/CodeGen/CIRGenCleanup.cpp | 149 ++++++++++++------ clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 8 + clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp | 11 +- clang/lib/CIR/CodeGen/CIRGenFunction.h | 98 ++++++++++-- .../cleanup-conditional-with-wrapper.cpp | 59 +++---- clang/test/CIR/CodeGen/dtors.cpp | 59 +++---- clang/test/CIR/CodeGen/new-null.cpp | 12 +- 7 files changed, 254 insertions(+), 142 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp index e5edbf62f0f788..a84531fe908d8f 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp @@ -120,7 +120,9 @@ void CIRGenFunction::initFullExprCleanupWithFlag(Address activeFlag) { CIRGenFunction::FullExprCleanupScope::FullExprCleanupScope(CIRGenFunction &cgf, const Expr *subExpr) : cgf(cgf), cleanups(cgf), scope(nullptr), - deferredCleanupStackSize(cgf.deferredConditionalCleanupStack.size()) { + oldFullExprCleanupScope(cgf.currentFullExprCleanupScope), + deferredCleanupStackSize(cgf.deferredConditionalCleanupStack.size()), + conditionalScopeDepth(cgf.conditionalCleanupScopes.size()) { assert(subExpr && "ExprWithCleanups always has a sub-expression"); ConditionalEvaluationFinder finder; @@ -138,6 +140,10 @@ CIRGenFunction::FullExprCleanupScope::FullExprCleanupScope(CIRGenFunction &cgf, [&](mlir::OpBuilder &b, mlir::Location loc) {}); cgf.builder.setInsertionPointToEnd(&scope.getBodyRegion().front()); } + + // Conditional cleanup scopes are only opened while a full-expression scope + // is active to close them. + cgf.currentFullExprCleanupScope = scope; } /// If the alloca that backs \p addr is currently nested inside the body @@ -187,16 +193,97 @@ static void hoistAllocaOutOfCleanupScope(CIRGenFunction &cgf, Address addr, } } +/// Close a cleanup scope opened by a ConditionalEvaluation. Terminate its body +/// region and emit this scope's slice of deferredConditionalCleanupStack into +/// its cleanup region, in reverse of push order. An insertion point that was +/// inside the scope's body is moved to immediately after the scope op. +static void closeConditionalCleanupScope( + CIRGenFunction &cgf, + const CIRGenFunction::ConditionalCleanupScope &condScope) { + CIRGenBuilderTy &builder = cgf.getBuilder(); + cir::CleanupScopeOp scope = condScope.scope; + size_t base = condScope.deferredCleanupStackSize; + auto &stack = cgf.deferredConditionalCleanupStack; + bool hasDeferredCleanups = stack.size() > base; + + // Make sure the body region has a terminator. + { + mlir::OpBuilder::InsertionGuard guard(builder); + mlir::Block &lastBodyBlock = scope.getBodyRegion().back(); + builder.setInsertionPointToEnd(&lastBodyBlock); + if (lastBodyBlock.empty() || + !lastBodyBlock.back().hasTrait<mlir::OpTrait::IsTerminator>()) + builder.createYield(scope.getLoc()); + } + + // Each deferred cleanup references its addr from the sibling cleanup region + // we are about to fill. If the alloca that backs that addr was created + // inside this scope's body region, hoist it out so it dominates the cleanup + // region. + if (hasDeferredCleanups) { + for (const CIRGenFunction::PendingCleanupEntry &entry : + llvm::make_range(stack.begin() + base, stack.end())) + hoistAllocaOutOfCleanupScope(cgf, entry.addr, scope); + } + + { + mlir::OpBuilder::InsertionGuard guard(builder); + mlir::Block &cleanupBlock = scope.getCleanupRegion().front(); + builder.setInsertionPointToEnd(&cleanupBlock); + + for (const CIRGenFunction::PendingCleanupEntry &entry : + llvm::reverse(llvm::make_range(stack.begin() + base, stack.end()))) { + if (entry.activeFlag.isValid()) { + // We may have hoisted this alloca out of the cleanup scope. If so, we + // will have also hoisted any casts between it and the address that we + // stored in the deferredConditionalCleanupStack. While I can't find a + // case where this actually happens, there is a theoretical + // possibility that we could have a second address that uses an alloca + // that has already been hoisted but a different cast chain. This + // assert guards against that possibility. + assert(entry.addr.getUnderlyingAllocaOp() && + (entry.addr.getUnderlyingAllocaOp()->getBlock() == + entry.addr.getPointer().getDefiningOp()->getBlock()) && + "alloca and cast are in different blocks"); + mlir::Value flag = builder.createLoad(scope.getLoc(), entry.activeFlag); + cir::IfOp::create(builder, scope.getLoc(), flag, + /*withElseRegion=*/false, + [&](mlir::OpBuilder &b, mlir::Location loc) { + cgf.emitDestroy(entry.addr, entry.type, + entry.destroyer); + builder.createYield(loc); + }); + } else { + cgf.emitDestroy(entry.addr, entry.type, entry.destroyer); + } + } + builder.createYield(scope.getLoc()); + } + + stack.truncate(base); + + // Move the insertion point out of the scope just closed, but only if it was + // inside it. Moving it unconditionally can strand it outside the region it + // belonged to, leaving an enclosing scope unterminated. popCleanup guards + // this the same way. + mlir::Block *insertBlock = builder.getInsertionBlock(); + if (insertBlock && + scope.getBodyRegion().findAncestorBlockInRegion(*insertBlock)) + builder.setInsertionPointAfter(scope); +} + void CIRGenFunction::FullExprCleanupScope::exit( ArrayRef<mlir::Value *> valuesToReload) { assert(!exited && "FullExprCleanupScope::exit called twice"); exited = true; + cgf.currentFullExprCleanupScope = oldFullExprCleanupScope; + size_t oldSize = deferredCleanupStackSize; - bool hasDeferredCleanups = - cgf.deferredConditionalCleanupStack.size() > oldSize; if (!scope) { + assert(cgf.conditionalCleanupScopes.size() == conditionalScopeDepth && + "conditional cleanup scope opened without one to close it"); cgf.deferredConditionalCleanupStack.truncate(oldSize); cleanups.forceCleanup(valuesToReload); return; @@ -216,6 +303,13 @@ void CIRGenFunction::FullExprCleanupScope::exit( cgf.builder.createStore(val.getLoc(), val, temp); } + // Close the cleanup scopes opened by conditionals in this full expression, + // innermost first. The EH cleanups popped below own the scopes enclosing + // these, so they are closed after. + while (cgf.conditionalCleanupScopes.size() > conditionalScopeDepth) + closeConditionalCleanupScope(cgf, + cgf.conditionalCleanupScopes.pop_back_val()); + // Pop any EH cleanups that were pushed during the expression but leave // any lifetime-extended cleanups so that they can be promoted to the EH // stack after we've finished emitting any deferred cleanups. @@ -231,57 +325,18 @@ void CIRGenFunction::FullExprCleanupScope::exit( cgf.builder.createYield(scope.getLoc()); } - // Each deferred conditional cleanup will reference its addr from the - // sibling cleanup region we are about to fill. If the alloca that backs - // that addr was created inside this scope's body region, hoist it out so it - // dominates the cleanup region. - if (hasDeferredCleanups) { - for (const PendingCleanupEntry &entry : - llvm::make_range(cgf.deferredConditionalCleanupStack.begin() + oldSize, - cgf.deferredConditionalCleanupStack.end())) { - hoistAllocaOutOfCleanupScope(cgf, entry.addr, scope); - } - } - - // Emit any deferred cleanups. + // Deferred cleanups are emitted into the conditional scopes closed above, + // so this scope's cleanup region is empty and canonicalization will inline + // the scope away. { mlir::OpBuilder::InsertionGuard guard(cgf.builder); mlir::Block &cleanupBlock = scope.getCleanupRegion().front(); cgf.builder.setInsertionPointToEnd(&cleanupBlock); - - if (hasDeferredCleanups) { - for (const PendingCleanupEntry &entry : llvm::reverse(llvm::make_range( - cgf.deferredConditionalCleanupStack.begin() + oldSize, - cgf.deferredConditionalCleanupStack.end()))) { - if (entry.activeFlag.isValid()) { - // We may have hoisted this alloca out of the cleanup scope. If so, - // we will have also hoisted any casts between it and the address that - // we stored in the deferredConditionalCleanupStack. While I can't - // find a case where this actually happens, there is a theoretical - // possibility that we could have a second address that uses an - // alloca that has already been hoisted but a different cast chain. - // This assert guards against that possibility. - assert(entry.addr.getUnderlyingAllocaOp() && - (entry.addr.getUnderlyingAllocaOp()->getBlock() == - entry.addr.getPointer().getDefiningOp()->getBlock()) && - "alloca and cast are in different blocks"); - mlir::Value flag = - cgf.builder.createLoad(scope.getLoc(), entry.activeFlag); - cir::IfOp::create( - cgf.builder, scope.getLoc(), flag, /*withElseRegion=*/false, - [&](mlir::OpBuilder &b, mlir::Location loc) { - cgf.emitDestroy(entry.addr, entry.type, entry.destroyer); - cgf.builder.createYield(loc); - }); - } else { - cgf.emitDestroy(entry.addr, entry.type, entry.destroyer); - } - } - } cgf.builder.createYield(scope.getLoc()); } - cgf.deferredConditionalCleanupStack.truncate(oldSize); + assert(cgf.deferredConditionalCleanupStack.size() == oldSize && + "deferred cleanups were not consumed by a conditional scope"); cgf.builder.setInsertionPointAfter(scope); // Promote any lifetime-extended cleanups onto the EH scope stack. The new diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index e12899cfd35fd7..a895275afc9c30 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -2623,6 +2623,14 @@ cir::IfOp CIRGenFunction::emitIfOnBoolExpr( // Emit the code with the fully general case. mlir::Value condV = emitOpOnBoolExpr(loc, cond); + return emitIfOnBoolValue(condV, loc, thenBuilder, thenLoc, elseBuilder, + elseLoc); +} + +cir::IfOp CIRGenFunction::emitIfOnBoolValue( + mlir::Value condV, mlir::Location loc, BuilderCallbackRef thenBuilder, + mlir::Location thenLoc, BuilderCallbackRef elseBuilder, + std::optional<mlir::Location> elseLoc) { cir::IfOp ifOp = cir::IfOp::create(builder, loc, condV, elseLoc.has_value(), /*thenBuilder=*/thenBuilder, /*elseBuilder=*/elseBuilder); diff --git a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp index f9a4eb2b2033a2..c4c5ee9abd8142 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp @@ -462,6 +462,11 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> { mlir::Location loc = cgf.getLoc(e->getSourceRange()); CIRGenFunction::OpaqueValueMapping binding(cgf, e); + + // Emit the condition before opening the conditional evaluation, so that + // the cleanup scope of any temporary the condition creates encloses the + // one the evaluation opens. + mlir::Value condV = cgf.emitOpOnBoolExpr(loc, e->getCond()); CIRGenFunction::ConditionalEvaluation eval(cgf); // Save whether the destination's lifetime is externally managed. @@ -471,10 +476,10 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> { e->getType().isDestructedType() == QualType::DK_nontrivial_c_struct; isExternallyDestructed |= destructNonTrivialCStruct; - // emitIfOnBoolExpr terminates each region; an unconditional yield here + // emitIfOnBoolValue terminates each region; an unconditional yield here // would keep alive the dead block a noreturn arm leaves behind. - cgf.emitIfOnBoolExpr( - e->getCond(), + cgf.emitIfOnBoolValue( + condV, loc, /*thenBuilder=*/ [&](mlir::OpBuilder &b, mlir::Location loc) { eval.beginEvaluation(); diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 6fb9fd43aef6e6..63a94a52203c70 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -127,10 +127,15 @@ class CIRGenFunction : public CIRGenTypeCache { typedef void Destroyer(CIRGenFunction &cgf, Address addr, QualType ty); - /// A cleanup entry that will be promoted onto the EH scope stack at a later - /// point. Used by both the lifetime-extended cleanup stack (promoted when - /// the enclosing scope exits) and the deferred conditional cleanup stack - /// (promoted at the enclosing full-expression level). + /// 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 + /// conditional cleanup stack, whose entries are emitted directly into a + /// conditional scope's cleanup region. + /// + /// A valid \c activeFlag means the cleanup is conditional. The flag tracks + /// at run time whether the object has been constructed, and guards the + /// destructor call. /// /// Currently only DestroyObject cleanups use this. When other cleanup types /// are needed (e.g., CallLifetimeEnd), this struct can be extended with a @@ -145,8 +150,54 @@ class CIRGenFunction : public CIRGenTypeCache { llvm::SmallVector<PendingCleanupEntry> lifetimeExtendedCleanupStack; + /// Cleanups for temporaries constructed inside a conditional. + /// + /// Temporaries are destroyed in reverse order of construction at the end of + /// the full expression. A cleanup lives in the cleanup region of a + /// cir.cleanup.scope, and those scopes nest, so nesting is what encodes the + /// order. + /// + /// For an unconditionally constructed temporary, EHScopeStack::pushCleanup + /// opens a scope where the temporary is constructed and leaves the builder in + /// its body, so each later temporary nests one level deeper and is destroyed + /// first. + /// + /// For a temporary constructed inside a conditional, pushFullExprCleanup + /// records the cleanup on this stack, paired with an active flag that is + /// cleared ahead of the conditional and set once the object is constructed. + /// The destructor call is emitted guarded by that flag, so it runs only on + /// the paths that constructed the object. + /// + /// ConditionalEvaluation opens the region holding those calls where the + /// conditional begins, nesting it inside the scopes of earlier temporaries + /// and outside later ones. Its body stays open for the rest of the full + /// expression, so its cleanup region runs at the end of it. + /// FullExprCleanupScope::exit closes these innermost first, then pops the + /// enclosing EH-stack cleanups. llvm::SmallVector<PendingCleanupEntry> deferredConditionalCleanupStack; + /// One record per cir.cleanup.scope opened by a ConditionalEvaluation, as + /// described above. These are bookkeeping owned by CIRGenFunction, not RAII + /// objects. The scope outlives the ConditionalEvaluation that opened it, + /// and the enclosing FullExprCleanupScope is what closes it. + struct ConditionalCleanupScope { + cir::CleanupScopeOp scope; + + /// The size of deferredConditionalCleanupStack when this scope was + /// opened. Every open conditional scope shares that one stack, so the + /// entries from this index onward are the ones deferred from inside this + /// conditional. They are emitted into this scope's cleanup region, in + /// reverse, when it is closed. + size_t deferredCleanupStackSize; + }; + llvm::SmallVector<ConditionalCleanupScope> conditionalCleanupScopes; + + /// The cir.cleanup.scope of the innermost FullExprCleanupScope that + /// materialized one, or null. ConditionalEvaluation opens a conditional + /// cleanup scope only while this is set, since a FullExprCleanupScope is + /// what closes those scopes. + cir::CleanupScopeOp currentFullExprCleanupScope = 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 @@ -1210,12 +1261,13 @@ class CIRGenFunction : public CIRGenTypeCache { /// conditionally-evaluated expression. template <class T, class... As> void pushFullExprCleanup(CleanupKind kind, As... a) { + // Outside a conditional the EH stack opens a scope here. if (!isInConditionalBranch()) return ehStack.pushCleanup<T>(kind, a...); - // Defer the cleanup until the FullExprCleanupScope exits. We can't push - // to the EH stack now because the ternary's inner LexicalScope would pop - // it prematurely. + // Inside a conditional, record the cleanup for the scope that + // ConditionalEvaluation opened where the conditional began, guarded by a + // flag set only where the object is constructed. Address activeFlag = createCleanupActiveFlag(); deferredConditionalCleanupStack.push_back( PendingCleanupEntry{kind, a..., activeFlag}); @@ -1389,7 +1441,9 @@ class CIRGenFunction : public CIRGenTypeCache { CIRGenFunction &cgf; RunCleanupsScope cleanups; cir::CleanupScopeOp scope; + cir::CleanupScopeOp oldFullExprCleanupScope; size_t deferredCleanupStackSize; + size_t conditionalScopeDepth; bool exited = false; public: @@ -2223,6 +2277,13 @@ class CIRGenFunction : public CIRGenTypeCache { BuilderCallbackRef elseBuilder, std::optional<mlir::Location> elseLoc = {}); + /// Build the cir.if for an already-emitted condition value. + cir::IfOp emitIfOnBoolValue(mlir::Value condV, mlir::Location loc, + BuilderCallbackRef thenBuilder, + mlir::Location thenLoc, + BuilderCallbackRef elseBuilder, + std::optional<mlir::Location> elseLoc = {}); + mlir::Value emitOpOnBoolExpr(mlir::Location loc, const clang::Expr *cond); LValue emitPointerToDataMemberBinaryExpr(const BinaryOperator *e); @@ -2466,8 +2527,27 @@ class CIRGenFunction : public CIRGenTypeCache { mlir::Operation *anchorAfter = nullptr; public: - ConditionalEvaluation(CIRGenFunction &cgf) - : cgf(cgf), anchorBlock(cgf.builder.getInsertionBlock()) { + ConditionalEvaluation(CIRGenFunction &cgf) : cgf(cgf) { + // Open the cleanup scope that hosts cleanups deferred from inside this + // conditional (see deferredConditionalCleanupStack). Only the outermost + // conditional opens one, and only while a FullExprCleanupScope is + // active to close it. When nothing is deferred into it the cleanup + // region stays trivial and canonicalization inlines the scope away. + if (cgf.currentFullExprCleanupScope && !cgf.isInConditionalBranch()) { + mlir::Location loc = cgf.builder.getUnknownLoc(); + cir::CleanupKind cleanupKind = cgf.getLangOpts().Exceptions + ? cir::CleanupKind::All + : cir::CleanupKind::Normal; + cir::CleanupScopeOp scope = cir::CleanupScopeOp::create( + cgf.builder, loc, cleanupKind, + /*bodyBuilder=*/[](mlir::OpBuilder &, mlir::Location) {}, + /*cleanupBuilder=*/[](mlir::OpBuilder &, mlir::Location) {}); + cgf.conditionalCleanupScopes.push_back( + {scope, cgf.deferredConditionalCleanupStack.size()}); + cgf.builder.setInsertionPointToEnd(&scope.getBodyRegion().front()); + } + + anchorBlock = cgf.builder.getInsertionBlock(); assert(anchorBlock && "conditional evaluation needs an insertion point"); mlir::Block::iterator ip = cgf.builder.getInsertionPoint(); if (ip != anchorBlock->begin()) diff --git a/clang/test/CIR/CodeGen/cleanup-conditional-with-wrapper.cpp b/clang/test/CIR/CodeGen/cleanup-conditional-with-wrapper.cpp index d408091a73e48a..0d7c64165ae53b 100644 --- a/clang/test/CIR/CodeGen/cleanup-conditional-with-wrapper.cpp +++ b/clang/test/CIR/CodeGen/cleanup-conditional-with-wrapper.cpp @@ -42,10 +42,10 @@ Wrapper makeWrapper() { // CIR: cir.func {{.*}} @_Z11makeWrapperv(%[[RETVAL:.*]]: !cir.ptr<!rec_Wrapper> {llvm.align = 1 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_Wrapper, llvm.writable}{{.*}}) // CIR: %[[CLEANUP_COND:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> // CIR: %[[AGG_TMP0:.*]] = cir.alloca "agg.tmp0" {{.*}} : !cir.ptr<!rec_std3A3Aunique_ptr3CBase3E> +// CIR: %[[FLAG:.*]] = cir.load{{.*}} %{{.*}} // CIR: cir.cleanup.scope { // CIR: %[[FALSE:.*]] = cir.const #false // CIR: cir.store %[[FALSE]], %[[CLEANUP_COND]] -// CIR: %[[FLAG:.*]] = cir.load{{.*}} %{{.*}} // CIR: cir.if %[[FLAG]] { // CIR: %[[SOURCE:.*]] = cir.call @_Z9getSourcev() // CIR: cir.call @_ZNSt10unique_ptrI4BaseEC1EPS0_(%[[AGG_TMP0]], %[[SOURCE]]) @@ -146,13 +146,13 @@ void APFixedPoint::add(int x) const { // CIR: %[[X_BOOL:.*]] = cir.cast int_to_bool %[[X]] // CIR: cir.if %[[X_BOOL]] { // CIR: %[[AGG_TMP:.*]] = cir.alloca "agg.tmp.ensured" {{.*}} : !cir.ptr<!rec_APInt> +// CIR: %[[X2:.*]] = cir.load{{.*}} %[[X_ADDR]] +// CIR: %[[X2_BOOL:.*]] = cir.cast int_to_bool %[[X2]] // CIR: cir.cleanup.scope { // CIR: %[[FALSE:.*]] = cir.const #false // CIR: cir.store{{.*}} %[[FALSE]], %[[CLEANUP_COND_TRUE]] // CIR: %[[FALSE:.*]] = cir.const #false // CIR: cir.store{{.*}} %[[FALSE]], %[[CLEANUP_COND_FALSE]] -// CIR: %[[X2:.*]] = cir.load{{.*}} %[[X_ADDR]] -// CIR: %[[X2_BOOL:.*]] = cir.cast int_to_bool %[[X2]] // CIR: cir.if %[[X2_BOOL]] { // CIR: %[[TRUE:.*]] = cir.const #true // CIR: cir.store %[[TRUE]], %[[CLEANUP_COND_TRUE]] @@ -256,22 +256,9 @@ struct Entry { }; // A conditional expression whose condition itself produces a temporary that -// needs cleanup (here, the Iter() temporary destroyed by ~Iter) nests the -// deferred-conditional cleanup of a temporary in one of the conditional's -// arms inside that condition's cleanup scope. The alloca for the -// conditionally-destroyed Path temporary must be hoisted out of the outer -// (full-expr) cleanup scope, even though in the freshly emitted IR its -// direct parent cleanup scope is the inner one created for the Iter -// temporary. -// -// FIXME: The destruction order below is wrong. Iter() is constructed first and -// the Path temporary second, so reverse-of-construction order requires ~Path -// to run before ~Iter, as the OGCG checks show. CIR emits them the other way -// around because an unconditional cleanup goes on the EH stack and gets its -// own nested cir.cleanup.scope, whose cleanup region fires when the inner body -// ends, while a conditional cleanup is deferred to the enclosing -// full-expression scope and fires later. Mixing the two therefore yields push -// order instead of reverse-push order. +// needs cleanup, here the Iter() temporary destroyed by ~Iter. Iter() is +// constructed before the conditional, so its cleanup scope is the outer one +// and the conditionally-destroyed Path temporary is destroyed first. void makeEntry() { Iter() ? Entry() : g_path; } @@ -279,12 +266,12 @@ void makeEntry() { // CIR: cir.func {{.*}} @_Z9makeEntryv() // CIR: %[[REF_TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_Iter> // CIR: %[[CLEANUP_COND:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> -// CIR: %[[AGG_TMP0:.*]] = cir.alloca "agg.tmp0" {{.*}} : !cir.ptr<!rec_Path> // CIR: cir.cleanup.scope { -// CIR: %[[FALSE:.*]] = cir.const #false -// CIR: cir.store %[[FALSE]], %[[CLEANUP_COND]] +// CIR: %[[AGG_TMP0:.*]] = cir.alloca "agg.tmp0" {{.*}} : !cir.ptr<!rec_Path> +// CIR: %[[CALL:.*]] = cir.call @_ZN4ItercvbEv(%[[REF_TMP]]) // CIR: cir.cleanup.scope { -// CIR: %[[CALL:.*]] = cir.call @_ZN4ItercvbEv(%[[REF_TMP]]) +// CIR: %[[FALSE:.*]] = cir.const #false +// CIR: cir.store %[[FALSE]], %[[CLEANUP_COND]] // CIR: cir.if %[[CALL]] { // CIR: %[[ENSURED_T:.*]] = cir.alloca "agg.tmp.ensured" {{.*}} : !cir.ptr<!rec_Entry> // CIR: cir.call @_ZN5EntryC1Ev(%[[ENSURED_T]]) @@ -296,32 +283,28 @@ void makeEntry() { // CIR: cir.call @_ZN5EntryC1E4Path(%[[ENSURED_F]], %[[AGG_TMP0]]) : ({{.*}}, !cir.ptr<!rec_Path> {llvm.align = 1 : i64, llvm.dereferenceable = 1 : i64, llvm.nofreeobj, llvm.noundef}) -> () // CIR: } // CIR: cir.yield -// FIXME: ~Iter runs here, when the inner scope's body ends, but it should run -// after ~Path below. // CIR: } cleanup normal { -// CIR: cir.call @_ZN4IterD1Ev(%[[REF_TMP]]) +// CIR: %[[FLAG:.*]] = cir.load{{.*}} %[[CLEANUP_COND]] +// CIR: cir.if %[[FLAG]] { +// CIR: cir.call @_ZN4PathD1Ev(%[[AGG_TMP0]]) +// CIR: } // CIR: cir.yield // CIR: } // CIR: cir.yield // CIR: } cleanup normal { -// CIR: %[[FLAG:.*]] = cir.load{{.*}} %[[CLEANUP_COND]] -// CIR: cir.if %[[FLAG]] { -// CIR: cir.call @_ZN4PathD1Ev(%[[AGG_TMP0]]) -// CIR: } +// CIR: cir.call @_ZN4IterD1Ev(%[[REF_TMP]]) // CIR: cir.yield // CIR: } // CIR: cir.return // LLVM: define {{.*}} void @_Z9makeEntryv() +// LLVM: %[[AGG_TMP0:.*]] = alloca %struct.Path // LLVM: %[[ENSURED_T:.*]] = alloca %struct.Entry // LLVM: %[[ENSURED_F:.*]] = alloca %struct.Entry // LLVM: %[[REF_TMP:.*]] = alloca %struct.Iter // LLVM: %[[CLEANUP_COND:.*]] = alloca i8 -// LLVM: %[[AGG_TMP0:.*]] = alloca %struct.Path -// LLVM: br label %[[INIT:.*]] -// LLVM: [[INIT]]: -// LLVM: store i8 0, ptr %[[CLEANUP_COND]] // LLVM: %[[CALL:.*]] = call {{.*}} i1 @_ZN4ItercvbEv(ptr {{.*}} %[[REF_TMP]]) +// LLVM: store i8 0, ptr %[[CLEANUP_COND]] // LLVM: br i1 %[[CALL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] // LLVM: [[TRUE_BB]]: // LLVM: call void @_ZN5EntryC1Ev(ptr {{.*}} %[[ENSURED_T]]) @@ -331,13 +314,6 @@ void makeEntry() { // LLVM: call void @_ZN5EntryC1E4Path(ptr {{.*}} %[[ENSURED_F]], ptr nofreeobj noundef align 1 dereferenceable(1) %[[AGG_TMP0]]) // LLVM: br label %[[COND_END]] // LLVM: [[COND_END]]: -// LLVM: br label %[[AFTER_INNER:.*]] -// FIXME: ~Iter before ~Path; compare the OGCG sequence below, which destroys -// them in the correct reverse-of-construction order. -// LLVM: [[AFTER_INNER]]: -// LLVM: call void @_ZN4IterD1Ev(ptr {{.*}} %[[REF_TMP]]) -// LLVM: br label %[[CHECK_FLAG:.*]] -// LLVM: [[CHECK_FLAG]]: // LLVM: %[[FLAG_BYTE:.*]] = load i8, ptr %[[CLEANUP_COND]] // LLVM: %[[FLAG:.*]] = trunc i8 %[[FLAG_BYTE]] to i1 // LLVM: br i1 %[[FLAG]], label %[[DO_PATH_DTOR:.*]], label %[[DONE:.*]] @@ -345,6 +321,7 @@ void makeEntry() { // LLVM: call void @_ZN4PathD1Ev(ptr {{.*}} %[[AGG_TMP0]]) // LLVM: br label %[[DONE]] // LLVM: [[DONE]]: +// LLVM: call void @_ZN4IterD1Ev(ptr {{.*}} %[[REF_TMP]]) // LLVM: ret void // OGCG: define {{.*}} void @_Z9makeEntryv() diff --git a/clang/test/CIR/CodeGen/dtors.cpp b/clang/test/CIR/CodeGen/dtors.cpp index a03926271925ee..1ae95a7c9bb0e7 100644 --- a/clang/test/CIR/CodeGen/dtors.cpp +++ b/clang/test/CIR/CodeGen/dtors.cpp @@ -35,27 +35,19 @@ bool make_temp(const B &) { return false; } bool test_temp_or() { return make_temp(1) || make_temp(2); } // The B(2) temporary lives in the right-hand side of the ||, which is only -// evaluated when the left-hand side is false. Its destructor is therefore a -// conditional cleanup guarded by an active flag, deferred to the enclosing -// full-expression scope so that the temporary lives to the end of the full -// expression rather than to the end of the right-hand operand. -// -// FIXME: The destruction order is wrong. B(1) is constructed first and B(2) -// second, so ~B(2) must run before ~B(1), as the OGCG checks below show. CIR -// runs them the other way around because the unconditional cleanup for B(1) -// gets its own nested cir.cleanup.scope that fires when the inner body ends, -// while the conditional cleanup for B(2) is deferred to the outer scope and -// fires later. +// evaluated when the left-hand side is false, so its destructor is guarded by +// an active flag. B(1) is constructed first, so its cleanup scope is the outer +// one and B(2) is destroyed first. // CIR: cir.func{{.*}} @_Z12test_temp_orv() // CIR: %[[RET_ADDR:.*]] = cir.alloca "__retval" {{.*}} : !cir.ptr<!cir.bool> // CIR: %[[REF_TMP0:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_B> // CIR: %[[REF_TMP1:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_B> // CIR: %[[CLEANUP_COND:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[ONE:.*]] = cir.const #cir.int<1> +// CIR: cir.call @_ZN1BC2Ei(%[[REF_TMP0]], %[[ONE]]) // CIR: cir.cleanup.scope { -// CIR: %[[ONE:.*]] = cir.const #cir.int<1> -// CIR: cir.call @_ZN1BC2Ei(%[[REF_TMP0]], %[[ONE]]) +// CIR: %[[MAKE_TEMP0:.*]] = cir.call @_Z9make_tempRK1B(%[[REF_TMP0]]) // CIR: cir.cleanup.scope { -// CIR: %[[MAKE_TEMP0:.*]] = cir.call @_Z9make_tempRK1B(%[[REF_TMP0]]) // CIR: %[[FALSE:.*]] = cir.const #false // CIR: cir.store %[[FALSE]], %[[CLEANUP_COND]] // CIR: %[[TERNARY:.*]] = cir.ternary(%[[MAKE_TEMP0]], true { @@ -71,17 +63,16 @@ bool test_temp_or() { return make_temp(1) || make_temp(2); } // CIR: }) // CIR: cir.store{{.*}} %[[TERNARY]], %[[RET_ADDR]] // CIR: cir.yield -// FIXME: ~B(1) should run after the guarded ~B(2) below, not before it. // CIR: } cleanup normal { -// CIR: cir.call @_ZN1BD2Ev(%[[REF_TMP0]]) +// CIR: %[[IS_ACTIVE:.*]] = cir.load{{.*}} %[[CLEANUP_COND]] +// CIR: cir.if %[[IS_ACTIVE]] { +// CIR: cir.call @_ZN1BD2Ev(%[[REF_TMP1]]) +// CIR: } // CIR: cir.yield // CIR: } // CIR: cir.yield // CIR: } cleanup normal { -// CIR: %[[IS_ACTIVE:.*]] = cir.load{{.*}} %[[CLEANUP_COND]] -// CIR: cir.if %[[IS_ACTIVE]] { -// CIR: cir.call @_ZN1BD2Ev(%[[REF_TMP1]]) -// CIR: } +// CIR: cir.call @_ZN1BD2Ev(%[[REF_TMP0]]) // CIR: cir.yield // CIR: } // CIR: %[[RETVAL:.*]] = cir.load{{.*}} %[[RET_ADDR]] @@ -105,15 +96,14 @@ bool test_temp_or() { return make_temp(1) || make_temp(2); } // LLVM: br label %[[RESULT_BLOCK]] // LLVM: [[RESULT_BLOCK]]: // LLVM: %[[RESULT:.*]] = phi i1 [ %[[MAKE_TEMP1]], %[[TERN_FALSE]] ], [ true, %[[TERN_TRUE]] ] -// FIXME: ~B(1) should run after the guarded ~B(2) below. Compare the OGCG -// sequence, which destroys them in reverse order of construction. -// LLVM: call void @_ZN1BD2Ev(ptr {{.*}} %[[REF_TMP0]]) // LLVM: %[[FLAG_BYTE:.*]] = load i8, ptr %[[CLEANUP_COND]] // LLVM: %[[FLAG:.*]] = trunc i8 %[[FLAG_BYTE]] to i1 // LLVM: br i1 %[[FLAG]], label %[[DTOR1:.*]], label %[[DONE:.*]] // LLVM: [[DTOR1]]: // LLVM: call void @_ZN1BD2Ev(ptr {{.*}} %[[REF_TMP1]]) // LLVM: br label %[[DONE]] +// LLVM: [[DONE]]: +// LLVM: call void @_ZN1BD2Ev(ptr {{.*}} %[[REF_TMP0]]) // OGCG: define {{.*}} i1 @_Z12test_temp_orv() // OGCG: [[ENTRY:.*]]: @@ -145,19 +135,17 @@ bool test_temp_and() { return make_temp(1) && make_temp(2); } // As with test_temp_or, the B(2) temporary is created in the conditionally // evaluated right-hand side, so its destructor is guarded by an active flag -// and deferred to the enclosing full-expression scope. -// -// FIXME: ~B(2) should run before ~B(1); see the OGCG checks below. +// and lives in the inner scope, which runs first. // CIR: cir.func{{.*}} @_Z13test_temp_andv() // CIR: %[[RET_ADDR:.*]] = cir.alloca "__retval" {{.*}} : !cir.ptr<!cir.bool> // CIR: %[[REF_TMP0:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_B> // CIR: %[[REF_TMP1:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_B> // CIR: %[[CLEANUP_COND:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[ONE:.*]] = cir.const #cir.int<1> +// CIR: cir.call @_ZN1BC2Ei(%[[REF_TMP0]], %[[ONE]]) // CIR: cir.cleanup.scope { -// CIR: %[[ONE:.*]] = cir.const #cir.int<1> -// CIR: cir.call @_ZN1BC2Ei(%[[REF_TMP0]], %[[ONE]]) +// CIR: %[[MAKE_TEMP0:.*]] = cir.call @_Z9make_tempRK1B(%[[REF_TMP0]]) // CIR: cir.cleanup.scope { -// CIR: %[[MAKE_TEMP0:.*]] = cir.call @_Z9make_tempRK1B(%[[REF_TMP0]]) // CIR: %[[FALSE:.*]] = cir.const #false // CIR: cir.store %[[FALSE]], %[[CLEANUP_COND]] // CIR: %[[TERNARY:.*]] = cir.ternary(%[[MAKE_TEMP0]], true { @@ -174,15 +162,15 @@ bool test_temp_and() { return make_temp(1) && make_temp(2); } // CIR: cir.store{{.*}} %[[TERNARY]], %[[RET_ADDR]] // CIR: cir.yield // CIR: } cleanup normal { -// CIR: cir.call @_ZN1BD2Ev(%[[REF_TMP0]]) +// CIR: %[[IS_ACTIVE:.*]] = cir.load{{.*}} %[[CLEANUP_COND]] +// CIR: cir.if %[[IS_ACTIVE]] { +// CIR: cir.call @_ZN1BD2Ev(%[[REF_TMP1]]) +// CIR: } // CIR: cir.yield // CIR: } // CIR: cir.yield // CIR: } cleanup normal { -// CIR: %[[IS_ACTIVE:.*]] = cir.load{{.*}} %[[CLEANUP_COND]] -// CIR: cir.if %[[IS_ACTIVE]] { -// CIR: cir.call @_ZN1BD2Ev(%[[REF_TMP1]]) -// CIR: } +// CIR: cir.call @_ZN1BD2Ev(%[[REF_TMP0]]) // CIR: cir.yield // CIR: } // CIR: %[[RETVAL:.*]] = cir.load{{.*}} %[[RET_ADDR]] @@ -206,8 +194,6 @@ bool test_temp_and() { return make_temp(1) && make_temp(2); } // LLVM: br label %[[RESULT_BLOCK]] // LLVM: [[RESULT_BLOCK]]: // LLVM: %[[RESULT:.*]] = phi i1 [ false, %[[TERN_FALSE]] ], [ %[[MAKE_TEMP1]], %[[TERN_TRUE]] ] -// FIXME: ~B(1) should run after the guarded ~B(2) below; see OGCG. -// LLVM: call void @_ZN1BD2Ev(ptr {{.*}} %[[REF_TMP0]]) // LLVM: %[[FLAG_BYTE:.*]] = load i8, ptr %[[CLEANUP_COND]] // LLVM: %[[FLAG:.*]] = trunc i8 %[[FLAG_BYTE]] to i1 // LLVM: br i1 %[[FLAG]], label %[[DTOR1:.*]], label %[[DONE:.*]] @@ -215,6 +201,7 @@ bool test_temp_and() { return make_temp(1) && make_temp(2); } // LLVM: call void @_ZN1BD2Ev(ptr {{.*}} %[[REF_TMP1]]) // LLVM: br label %[[DONE]] // LLVM: [[DONE]]: +// LLVM: call void @_ZN1BD2Ev(ptr {{.*}} %[[REF_TMP0]]) // LLVM: %[[RET_LOAD:.*]] = load i8, ptr %[[RETVAL]], align 1 // LLVM: %[[RET_TRUNC:.*]] = trunc i8 %[[RET_LOAD]] to i1 // LLVM: ret i1 %[[RET_TRUNC]] diff --git a/clang/test/CIR/CodeGen/new-null.cpp b/clang/test/CIR/CodeGen/new-null.cpp index 42c6a41122e8dc..3e2c4f105535dd 100644 --- a/clang/test/CIR/CodeGen/new-null.cpp +++ b/clang/test/CIR/CodeGen/new-null.cpp @@ -139,10 +139,10 @@ U *test_nothrow_new_temp() { // CHECK: cir.func {{.*}} @_Z21test_nothrow_new_tempv() // CHECK: %[[TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_T> // CHECK: %[[TMP_ACTIVE:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CHECK: %[[ALLOC:.*]] = cir.call @_ZnwmRKSt9nothrow_t({{.*}}) nothrow +// CHECK: %[[NULL:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void> +// CHECK: %[[IS_NOT_NULL:.*]] = cir.cmp ne %[[ALLOC]], %[[NULL]] : !cir.ptr<!void> // CHECK: cir.cleanup.scope { -// CHECK: %[[ALLOC:.*]] = cir.call @_ZnwmRKSt9nothrow_t({{.*}}) nothrow -// CHECK: %[[NULL:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void> -// CHECK: %[[IS_NOT_NULL:.*]] = cir.cmp ne %[[ALLOC]], %[[NULL]] : !cir.ptr<!void> // CHECK: %[[FALSE:.*]] = cir.const #false // CHECK: cir.store %[[FALSE]], %[[TMP_ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> // CHECK: cir.if %[[IS_NOT_NULL]] { @@ -249,10 +249,10 @@ U *test_nothrow_new_nested_temps() { // CHECK: %[[INNER_TMP:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_InnerT> // CHECK: %[[INNER_ACTIVE:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> // CHECK: %[[OUTER_ACTIVE:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CHECK: %[[ALLOC:.*]] = cir.call @_ZnwmRKSt9nothrow_t({{.*}}) nothrow +// CHECK: %[[NULL:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void> +// CHECK: %[[IS_NOT_NULL:.*]] = cir.cmp ne %[[ALLOC]], %[[NULL]] : !cir.ptr<!void> // CHECK: cir.cleanup.scope { -// CHECK: %[[ALLOC:.*]] = cir.call @_ZnwmRKSt9nothrow_t({{.*}}) nothrow -// CHECK: %[[NULL:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void> -// CHECK: %[[IS_NOT_NULL:.*]] = cir.cmp ne %[[ALLOC]], %[[NULL]] : !cir.ptr<!void> // CHECK: %[[FALSE:.*]] = cir.const #false // CHECK: cir.store %[[FALSE]], %[[INNER_ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> // CHECK: %[[FALSE2:.*]] = cir.const #false >From 69fc8bb77d0674d541995bf9526b75515d462b32 Mon Sep 17 00:00:00 2001 From: Andy Kaylor <[email protected]> Date: Wed, 23 Sep 2026 10:25:49 -0700 Subject: [PATCH 2/4] Update cleanup-conditional tests --- .../CIR/CodeGen/cleanup-conditional-eh.cpp | 50 +++++----- .../cleanup-conditional-with-wrapper-eh.cpp | 93 +++++++----------- .../test/CIR/CodeGen/cleanup-conditional.cpp | 94 ++++++++++--------- 3 files changed, 113 insertions(+), 124 deletions(-) diff --git a/clang/test/CIR/CodeGen/cleanup-conditional-eh.cpp b/clang/test/CIR/CodeGen/cleanup-conditional-eh.cpp index ca109028ab2f18..ae6ca57d4ce0e8 100644 --- a/clang/test/CIR/CodeGen/cleanup-conditional-eh.cpp +++ b/clang/test/CIR/CodeGen/cleanup-conditional-eh.cpp @@ -34,8 +34,8 @@ void test_ternary_temporary(bool c, int x) { // CIR-LABEL: @_Z22test_ternary_temporarybi // CIR: %[[TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_S> // CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[COND:.*]] = cir.load {{.*}} : !cir.ptr<!cir.bool>, !cir.bool // CIR: cir.cleanup.scope { -// CIR: %[[COND:.*]] = cir.load {{.*}} : !cir.ptr<!cir.bool>, !cir.bool // CIR: %[[FALSE:.*]] = cir.const #false // CIR: cir.store %[[FALSE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> // CIR: %{{.*}} = cir.ternary(%[[COND]], true { @@ -139,8 +139,8 @@ void test_ternary_both_branches(bool c) { // CIR: %[[ACTA:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> // CIR: %[[TMPB:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_B> // CIR: %[[ACTB:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[COND:.*]] = cir.load {{.*}} : !cir.ptr<!cir.bool>, !cir.bool // CIR: cir.cleanup.scope { -// CIR: %[[COND:.*]] = cir.load {{.*}} : !cir.ptr<!cir.bool>, !cir.bool // CIR: %[[FALSE_A:.*]] = cir.const #false // CIR: cir.store %[[FALSE_A]], %[[ACTA]] : !cir.bool, !cir.ptr<!cir.bool> // CIR: %[[FALSE_B:.*]] = cir.const #false @@ -281,8 +281,8 @@ int test_return_ternary(bool c) { // CIR: %[[ACTA:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> // CIR: %[[TMPB:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_B> // CIR: %[[ACTB:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[COND:.*]] = cir.load {{.*}} : !cir.ptr<!cir.bool>, !cir.bool // CIR: cir.cleanup.scope { -// CIR: %[[COND:.*]] = cir.load {{.*}} : !cir.ptr<!cir.bool>, !cir.bool // CIR: %[[FALSE_A:.*]] = cir.const #false // CIR: cir.store %[[FALSE_A]], %[[ACTA]] : !cir.bool, !cir.ptr<!cir.bool> // CIR: %[[FALSE_B:.*]] = cir.const #false @@ -685,56 +685,60 @@ void test_flag_cleared_before_cond_cleanup() { // CIR-LABEL: @_Z37test_flag_cleared_before_cond_cleanupv // CIR: %[[REF_TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_Guard> // CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> -// CIR: %[[AGG_TMP:.*]] = cir.alloca "agg.tmp0" {{.*}} : !cir.ptr<!rec_Payload> +// Guard is constructed before the conditional, so its cleanup scope encloses +// the conditional's and the Payload temporary is destroyed first. +// CIR: cir.call @_ZN5GuardC1Ev(%[[REF_TMP]]) // CIR: cir.cleanup.scope { -// The clear precedes both the Guard constructor and the nested cleanup scope. -// CIR: %[[FALSE:.*]] = cir.const #false -// CIR: cir.store %[[FALSE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> -// CIR: cir.call @_ZN5GuardC1Ev(%[[REF_TMP]]) +// CIR: %[[AGG_TMP:.*]] = cir.alloca "agg.tmp0" {{.*}} : !cir.ptr<!rec_Payload> +// CIR: %[[COND:.*]] = cir.call @_ZN5GuardcvbEv(%[[REF_TMP]]) // CIR: cir.cleanup.scope { -// CIR: %[[COND:.*]] = cir.call @_ZN5GuardcvbEv(%[[REF_TMP]]) +// The clear precedes the conditional, so it runs whichever arm is taken and +// also dominates the unwind paths out of the arms. +// CIR: %[[FALSE:.*]] = cir.const #false +// CIR: cir.store %[[FALSE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> // CIR: cir.if %[[COND]] { // CIR: } else { // CIR: %[[TRUE:.*]] = cir.const #true // CIR: cir.store %[[TRUE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> // CIR: } // CIR: } cleanup all { -// CIR: cir.call @_ZN5GuardD1Ev(%[[REF_TMP]]) +// CIR: %[[IS_ACTIVE:.*]] = cir.load{{.*}} %[[ACTIVE]] +// CIR: cir.if %[[IS_ACTIVE]] { +// CIR: cir.call @_ZN7PayloadD1Ev(%[[AGG_TMP]]) +// CIR: } // CIR: } // CIR: } cleanup all { -// CIR: %[[IS_ACTIVE:.*]] = cir.load{{.*}} %[[ACTIVE]] -// CIR: cir.if %[[IS_ACTIVE]] { -// CIR: cir.call @_ZN7PayloadD1Ev(%[[AGG_TMP]]) -// CIR: } +// CIR: cir.call @_ZN5GuardD1Ev(%[[REF_TMP]]) // CIR: } // LLVM-LABEL: define dso_local void @_Z37test_flag_cleared_before_cond_cleanupv( +// LLVM: %[[AGG_TMP:.*]] = alloca %struct.Payload // LLVM: %[[REF_TMP:.*]] = alloca %struct.Guard // LLVM: %[[ACTIVE:.*]] = alloca i8 -// LLVM: %[[AGG_TMP:.*]] = alloca %struct.Payload -// The clear dominates the invokes, so both landingpads see an initialized flag. -// LLVM: store i8 0, ptr %[[ACTIVE]] -// LLVM: invoke void @_ZN5GuardC1Ev(ptr {{.*}} %[[REF_TMP]]) +// LLVM: call void @_ZN5GuardC1Ev(ptr {{.*}} %[[REF_TMP]]) // LLVM: %[[COND:.*]] = invoke {{.*}} i1 @_ZN5GuardcvbEv(ptr {{.*}} %[[REF_TMP]]) +// The clear dominates the arms, so both landingpads see an initialized flag. +// LLVM: store i8 0, ptr %[[ACTIVE]] // LLVM: br i1 %[[COND]], label %[[TRUE_BR:.*]], label %[[FALSE_BR:.*]] // LLVM: [[FALSE_BR]]: // LLVM: store i8 1, ptr %[[ACTIVE]] -// Normal path. -// LLVM: call void @_ZN5GuardD1Ev(ptr {{.*}} %[[REF_TMP]]) -// LLVM: landingpad { ptr, i32 } -// LLVM: call void @_ZN5GuardD1Ev(ptr {{.*}} %[[REF_TMP]]) +// Normal path destroys the conditional Payload first. // LLVM: %[[BYTE:.*]] = load i8, ptr %[[ACTIVE]] // LLVM: %[[BOOL:.*]] = trunc i8 %[[BYTE]] to i1 // LLVM: br i1 %[[BOOL]], label %[[DTOR:.*]], label %{{.*}} // LLVM: [[DTOR]]: // LLVM: call void @_ZN7PayloadD1Ev(ptr {{.*}} %[[AGG_TMP]]) -// Unwind path reads the same flag. +// Unwinding out of an arm reads the same flag. // LLVM: landingpad { ptr, i32 } // LLVM: %[[EH_BYTE:.*]] = load i8, ptr %[[ACTIVE]] // LLVM: %[[EH_BOOL:.*]] = trunc i8 %[[EH_BYTE]] to i1 // LLVM: br i1 %[[EH_BOOL]], label %[[EH_DTOR:.*]], label %{{.*}} // LLVM: [[EH_DTOR]]: // LLVM: call void @_ZN7PayloadD1Ev(ptr {{.*}} %[[AGG_TMP]]) +// Then Guard, on the normal path and again from its own landingpad. +// LLVM: call void @_ZN5GuardD1Ev(ptr {{.*}} %[[REF_TMP]]) +// LLVM: landingpad { ptr, i32 } +// LLVM: call void @_ZN5GuardD1Ev(ptr {{.*}} %[[REF_TMP]]) // LLVM: resume { ptr, i32 } // OGCG-LABEL: define dso_local void @_Z37test_flag_cleared_before_cond_cleanupv( diff --git a/clang/test/CIR/CodeGen/cleanup-conditional-with-wrapper-eh.cpp b/clang/test/CIR/CodeGen/cleanup-conditional-with-wrapper-eh.cpp index c98e3acc415222..9ae31396ca63f1 100644 --- a/clang/test/CIR/CodeGen/cleanup-conditional-with-wrapper-eh.cpp +++ b/clang/test/CIR/CodeGen/cleanup-conditional-with-wrapper-eh.cpp @@ -42,10 +42,10 @@ Wrapper makeWrapper() { // CIR: cir.func {{.*}} @_Z11makeWrapperv(%[[RETVAL:.*]]: !cir.ptr<!rec_Wrapper> {llvm.align = 1 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_Wrapper, llvm.writable}{{.*}}) // CIR: %[[CLEANUP_COND:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> // CIR: %[[AGG_TMP0:.*]] = cir.alloca "agg.tmp0" {{.*}} : !cir.ptr<!rec_std3A3Aunique_ptr3CBase3E> +// CIR: %[[FLAG:.*]] = cir.load{{.*}} %{{.*}} // CIR: cir.cleanup.scope { // CIR: %[[FALSE:.*]] = cir.const #false // CIR: cir.store %[[FALSE]], %[[CLEANUP_COND]] -// CIR: %[[FLAG:.*]] = cir.load{{.*}} %{{.*}} // CIR: cir.if %[[FLAG]] { // CIR: %[[SOURCE:.*]] = cir.call @_Z9getSourcev() // CIR: cir.call @_ZNSt10unique_ptrI4BaseEC1EPS0_(%[[AGG_TMP0]], %[[SOURCE]]) @@ -193,13 +193,13 @@ void APFixedPoint::add(int x) const { // CIR: %[[X_BOOL:.*]] = cir.cast int_to_bool %[[X]] // CIR: cir.if %[[X_BOOL]] { // CIR: %[[AGG_TMP:.*]] = cir.alloca "agg.tmp.ensured" {{.*}} : !cir.ptr<!rec_APInt> +// CIR: %[[X2:.*]] = cir.load{{.*}} %[[X_ADDR]] +// CIR: %[[X2_BOOL:.*]] = cir.cast int_to_bool %[[X2]] // CIR: cir.cleanup.scope { // CIR: %[[FALSE:.*]] = cir.const #false // CIR: cir.store{{.*}} %[[FALSE]], %[[CLEANUP_COND_TRUE]] // CIR: %[[FALSE:.*]] = cir.const #false // CIR: cir.store{{.*}} %[[FALSE]], %[[CLEANUP_COND_FALSE]] -// CIR: %[[X2:.*]] = cir.load{{.*}} %[[X_ADDR]] -// CIR: %[[X2_BOOL:.*]] = cir.cast int_to_bool %[[X2]] // CIR: cir.if %[[X2_BOOL]] { // CIR: %[[TRUE:.*]] = cir.const #true // CIR: cir.store %[[TRUE]], %[[CLEANUP_COND_TRUE]] @@ -335,23 +335,10 @@ struct Entry { }; // A conditional expression whose condition itself produces a temporary that -// needs cleanup (here, the Iter() temporary destroyed by ~Iter) nests the -// deferred-conditional cleanup of a temporary in one of the conditional's -// arms inside that condition's cleanup scope. The alloca for the -// conditionally-destroyed Path temporary must be hoisted out of the outer -// (full-expr) cleanup scope, even though in the freshly emitted IR its -// direct parent cleanup scope is the inner one created for the Iter -// temporary. -// -// FIXME: The destruction order below is wrong, on both the normal and the -// unwind path. Iter() is constructed first and the Path temporary second, so -// reverse-of-construction order requires ~Path to run before ~Iter, as the -// OGCG checks show. CIR emits them the other way around because an -// unconditional cleanup goes on the EH stack and gets its own nested -// cir.cleanup.scope, whose cleanup region fires when the inner body ends, -// while a conditional cleanup is deferred to the enclosing full-expression -// scope and fires later. Mixing the two therefore yields push order instead -// of reverse-push order. +// needs cleanup, here the Iter() temporary destroyed by ~Iter. Iter() is +// constructed before the conditional, so its cleanup scope is the outer one +// and the conditionally-destroyed Path temporary is destroyed first, on both +// the normal and the unwind path. void makeEntry() { Iter() ? Entry() : g_path; } @@ -359,10 +346,12 @@ void makeEntry() { // CIR: cir.func {{.*}} @_Z9makeEntryv() // CIR: %[[REF_TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_Iter> // CIR: %[[CLEANUP_COND:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> -// CIR: %[[AGG_TMP0:.*]] = cir.alloca "agg.tmp0" {{.*}} : !cir.ptr<!rec_Path> // CIR: cir.cleanup.scope { +// CIR: %[[AGG_TMP0:.*]] = cir.alloca "agg.tmp0" {{.*}} : !cir.ptr<!rec_Path> +// CIR: %[[CALL:.*]] = cir.call @_ZN4ItercvbEv(%[[REF_TMP]]) // CIR: cir.cleanup.scope { -// CIR: %[[CALL:.*]] = cir.call @_ZN4ItercvbEv(%[[REF_TMP]]) +// CIR: %[[FALSE:.*]] = cir.const #false +// CIR: cir.store %[[FALSE]], %[[CLEANUP_COND]] // CIR: cir.if %[[CALL]] { // CIR: %[[ENSURED_T:.*]] = cir.alloca "agg.tmp.ensured" {{.*}} : !cir.ptr<!rec_Entry> // CIR: cir.call @_ZN5EntryC1Ev(%[[ENSURED_T]]) @@ -374,79 +363,69 @@ void makeEntry() { // CIR: cir.call @_ZN5EntryC1E4Path(%[[ENSURED_F]], %[[AGG_TMP0]]) : ({{.*}}, !cir.ptr<!rec_Path> {llvm.align = 1 : i64, llvm.dereferenceable = 1 : i64, llvm.nofreeobj, llvm.noundef}) -> () // CIR: } // CIR: cir.yield -// FIXME: ~Iter runs here, when the inner scope's body ends, but it should run -// after ~Path below. // CIR: } cleanup all { -// CIR: cir.call @_ZN4IterD1Ev(%[[REF_TMP]]) +// CIR: %[[FLAG:.*]] = cir.load{{.*}} %[[CLEANUP_COND]] +// CIR: cir.if %[[FLAG]] { +// CIR: cir.call @_ZN4PathD1Ev(%[[AGG_TMP0]]) +// CIR: } // CIR: cir.yield // CIR: } // CIR: cir.yield // CIR: } cleanup all { -// CIR: %[[FLAG:.*]] = cir.load{{.*}} %[[CLEANUP_COND]] -// CIR: cir.if %[[FLAG]] { -// CIR: cir.call @_ZN4PathD1Ev(%[[AGG_TMP0]]) -// CIR: } +// CIR: cir.call @_ZN4IterD1Ev(%[[REF_TMP]]) // CIR: cir.yield // CIR: } // CIR: cir.return // LLVM: define {{.*}} void @_Z9makeEntryv(){{.*}} personality ptr @__gxx_personality_v0 +// LLVM: %[[AGG_TMP0:.*]] = alloca %struct.Path // LLVM: %[[ENSURED_T:.*]] = alloca %struct.Entry // LLVM: %[[ENSURED_F:.*]] = alloca %struct.Entry // LLVM: %[[REF_TMP:.*]] = alloca %struct.Iter // LLVM: %[[CLEANUP_COND:.*]] = alloca i8 -// LLVM: %[[AGG_TMP0:.*]] = alloca %struct.Path -// LLVM: store i8 0, ptr %[[CLEANUP_COND]] // LLVM: %[[CALL:.*]] = invoke {{.*}} i1 @_ZN4ItercvbEv(ptr {{.*}} %[[REF_TMP]]) -// LLVM: to label %[[CALL_CONT:.*]] unwind label %[[LPAD:.*]] +// LLVM: to label %[[CALL_CONT:.*]] unwind label %[[LPAD_ITER:.*]] // LLVM: [[CALL_CONT]]: +// The flag is cleared after Iter is constructed but before the conditional, +// so it is initialized on the arms' unwind paths too. +// LLVM: store i8 0, ptr %[[CLEANUP_COND]] // LLVM: br i1 %[[CALL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] // LLVM: [[TRUE_BB]]: // LLVM: invoke void @_ZN5EntryC1Ev(ptr {{.*}} %[[ENSURED_T]]) -// LLVM: to label %[[TRUE_CONT:.*]] unwind label %[[LPAD]] +// LLVM: to label %[[TRUE_CONT:.*]] unwind label %[[LPAD_PATH:.*]] // LLVM: [[TRUE_CONT]]: // LLVM: br label %[[COND_END:.*]] // LLVM: [[FALSE_BB]]: // LLVM: store i8 1, ptr %[[CLEANUP_COND]] // LLVM: invoke void @_ZN5EntryC1E4Path(ptr {{.*}} %[[ENSURED_F]], ptr nofreeobj noundef align 1 dereferenceable(1) %[[AGG_TMP0]]) -// LLVM: to label %[[FALSE_CONT:.*]] unwind label %[[LPAD]] +// LLVM: to label %[[FALSE_CONT:.*]] unwind label %[[LPAD_PATH]] // LLVM: [[FALSE_CONT]]: // LLVM: br label %[[COND_END]] +// Normal path destroys the conditional Path first. // LLVM: [[COND_END]]: -// LLVM: br label %[[AFTER_INNER:.*]] -// LLVM: [[AFTER_INNER]]: -// LLVM: call void @_ZN4IterD1Ev(ptr {{.*}} %[[REF_TMP]]) -// LLVM: br label %[[INNER_DONE:.*]] -// LLVM: [[INNER_DONE]]: -// LLVM: br label %[[NORMAL_OUTER:.*]] -// LLVM: [[LPAD]]: -// LLVM: landingpad { ptr, i32 } -// LLVM: cleanup -// LLVM: call void @_ZN4IterD1Ev(ptr {{.*}} %[[REF_TMP]]) -// LLVM: br label %[[EH_OUTER:.*]] -// LLVM: [[NORMAL_OUTER]]: -// LLVM: br label %[[CHECK_FLAG:.*]] -// LLVM: [[CHECK_FLAG]]: // LLVM: %[[FLAG_BYTE:.*]] = load i8, ptr %[[CLEANUP_COND]] // LLVM: %[[FLAG:.*]] = trunc i8 %[[FLAG_BYTE]] to i1 // LLVM: br i1 %[[FLAG]], label %[[DO_PATH_DTOR:.*]], label %[[DONE_PATH:.*]] // LLVM: [[DO_PATH_DTOR]]: // LLVM: call void @_ZN4PathD1Ev(ptr {{.*}} %[[AGG_TMP0]]) // LLVM: br label %[[DONE_PATH]] -// LLVM: [[DONE_PATH]]: -// LLVM: br label %[[BEFORE_RET:.*]] -// LLVM: [[BEFORE_RET]]: -// LLVM: br label %[[DONE:.*]] -// LLVM: [[EH_OUTER]]: +// Unwinding out of an arm destroys Path the same way. +// LLVM: [[LPAD_PATH]]: +// LLVM: landingpad { ptr, i32 } +// LLVM: cleanup // LLVM: %[[EH_FLAG_BYTE:.*]] = load i8, ptr %[[CLEANUP_COND]] // LLVM: %[[EH_FLAG:.*]] = trunc i8 %[[EH_FLAG_BYTE]] to i1 -// LLVM: br i1 %[[EH_FLAG]], label %[[EH_PATH_DTOR:.*]], label %[[EH_RESUME:.*]] +// LLVM: br i1 %[[EH_FLAG]], label %[[EH_PATH_DTOR:.*]], label %{{.*}} // LLVM: [[EH_PATH_DTOR]]: // LLVM: call void @_ZN4PathD1Ev(ptr {{.*}} %[[AGG_TMP0]]) -// LLVM: br label %[[EH_RESUME]] -// LLVM: [[EH_RESUME]]: +// Iter is destroyed after Path, on the normal path and from its own +// landingpad. +// LLVM: call void @_ZN4IterD1Ev(ptr {{.*}} %[[REF_TMP]]) +// LLVM: [[LPAD_ITER]]: +// LLVM: landingpad { ptr, i32 } +// LLVM: cleanup +// LLVM: call void @_ZN4IterD1Ev(ptr {{.*}} %[[REF_TMP]]) // LLVM: resume -// LLVM: [[DONE]]: // LLVM: ret void // OGCG: define {{.*}} void @_Z9makeEntryv(){{.*}} personality ptr @__gxx_personality_v0 diff --git a/clang/test/CIR/CodeGen/cleanup-conditional.cpp b/clang/test/CIR/CodeGen/cleanup-conditional.cpp index 71254afb7863f4..4dea02e4b4a245 100644 --- a/clang/test/CIR/CodeGen/cleanup-conditional.cpp +++ b/clang/test/CIR/CodeGen/cleanup-conditional.cpp @@ -17,10 +17,11 @@ void test_ternary_temporary(bool c, int x) { // CIR-LABEL: @_Z22test_ternary_temporarybi // CIR: %[[TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_S> // CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> -// The cleanup scope wraps the full expression so cleanups run on all exits. +// CIR: %[[COND:.*]] = cir.load {{.*}} : !cir.ptr<!cir.bool>, !cir.bool +// The cleanup scope opens at the conditional and stays open to the end of the +// full expression, so cleanups run on all exits. // CIR: cir.cleanup.scope { -// Load condition, then active flag false before the ternary (destructor guard). -// CIR: %[[COND:.*]] = cir.load {{.*}} : !cir.ptr<!cir.bool>, !cir.bool +// Active flag false before the ternary (destructor guard). // CIR: %[[FALSE:.*]] = cir.const #false // CIR: cir.store %[[FALSE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> // CIR: %{{.*}} = cir.ternary(%[[COND]], true { @@ -45,10 +46,10 @@ void test_ternary_temporary(bool c, int x) { // LLVMCIR: %[[TMP:.*]] = alloca %struct.S // LLVMCIR: %[[ACTIVE:.*]] = alloca i8 // LLVMCIR: %[[RESULT_TMP:.*]] = alloca i32 -// LLVMCIR: br label %[[INIT:.*]] -// LLVMCIR: [[INIT]]: // LLVMCIR: %[[COND_BYTE:.*]] = load i8, ptr %{{.*}} // LLVMCIR: %[[COND_BOOL:.*]] = trunc i8 %[[COND_BYTE]] to i1 +// LLVMCIR: br label %[[INIT:.*]] +// LLVMCIR: [[INIT]]: // LLVMCIR: store i8 0, ptr %[[ACTIVE]] // LLVMCIR: br i1 %[[COND_BOOL]], label %[[TRUE_BR:.*]], label %[[FALSE_BR:.*]] // LLVMCIR: [[TRUE_BR]]: @@ -121,9 +122,9 @@ void test_ternary_both_branches(bool c) { // CIR: %[[ACTA:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> // CIR: %[[TMPB:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_B> // CIR: %[[ACTB:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[COND:.*]] = cir.load {{.*}} : !cir.ptr<!cir.bool>, !cir.bool // CIR: cir.cleanup.scope { // Both active flags start false; each branch sets its own to true when it runs. -// CIR: %[[COND:.*]] = cir.load {{.*}} : !cir.ptr<!cir.bool>, !cir.bool // CIR: %[[FALSE_A:.*]] = cir.const #false // CIR: cir.store %[[FALSE_A]], %[[ACTA]] : !cir.bool, !cir.ptr<!cir.bool> // CIR: %[[FALSE_B:.*]] = cir.const #false @@ -161,10 +162,10 @@ void test_ternary_both_branches(bool c) { // LLVMCIR: %[[TMPB:.*]] = alloca %struct.B // LLVMCIR: %[[ACTB:.*]] = alloca i8 // LLVMCIR: %[[RESULT_TMP:.*]] = alloca i32 -// LLVMCIR: br label %[[INIT:.*]] -// LLVMCIR: [[INIT]]: // LLVMCIR: %[[COND_BYTE:.*]] = load i8, ptr %{{.*}} // LLVMCIR: %[[COND_BOOL:.*]] = trunc i8 %[[COND_BYTE]] to i1 +// LLVMCIR: br label %[[INIT:.*]] +// LLVMCIR: [[INIT]]: // LLVMCIR: store i8 0, ptr %[[ACTA]] // LLVMCIR: store i8 0, ptr %[[ACTB]] // LLVMCIR: br i1 %[[COND_BOOL]], label %[[CONSTRUCT_A:.*]], label %[[CONSTRUCT_B:.*]] @@ -238,8 +239,8 @@ int test_return_ternary(bool c) { // CIR: %[[ACTA:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> // CIR: %[[TMPB:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_B> // CIR: %[[ACTB:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[COND:.*]] = cir.load {{.*}} : !cir.ptr<!cir.bool>, !cir.bool // CIR: cir.cleanup.scope { -// CIR: %[[COND:.*]] = cir.load {{.*}} : !cir.ptr<!cir.bool>, !cir.bool // CIR: %[[FALSE_A:.*]] = cir.const #false // CIR: cir.store %[[FALSE_A]], %[[ACTA]] : !cir.bool, !cir.ptr<!cir.bool> // CIR: %[[FALSE_B:.*]] = cir.const #false @@ -282,10 +283,10 @@ int test_return_ternary(bool c) { // LLVMCIR: %[[ACTA:.*]] = alloca i8 // LLVMCIR: %[[TMPB:.*]] = alloca %struct.B // LLVMCIR: %[[ACTB:.*]] = alloca i8 -// LLVMCIR: br label %[[INIT:.*]] -// LLVMCIR: [[INIT]]: // LLVMCIR: %[[COND_BYTE:.*]] = load i8, ptr %{{.*}} // LLVMCIR: %[[COND_BOOL:.*]] = trunc i8 %[[COND_BYTE]] to i1 +// LLVMCIR: br label %[[INIT:.*]] +// LLVMCIR: [[INIT]]: // LLVMCIR: store i8 0, ptr %[[ACTA]] // LLVMCIR: store i8 0, ptr %[[ACTB]] // LLVMCIR: br i1 %[[COND_BOOL]], label %[[CONSTRUCT_A:.*]], label %[[CONSTRUCT_B:.*]] @@ -775,9 +776,10 @@ _Complex float test_complex_cond_cleanup(bool b, _Complex float x) { // CIR-LABEL: @_Z25test_complex_cond_cleanupbCf // CIR: %[[TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_CplxD> // CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> -// The full expression is wrapped in a single cleanup scope. +// CIR: %[[COND:.*]] = cir.load {{.*}} : !cir.ptr<!cir.bool>, !cir.bool +// The conditional's cleanup scope stays open to the end of the full +// expression. // CIR: cir.cleanup.scope { -// CIR: %[[COND:.*]] = cir.load {{.*}} : !cir.ptr<!cir.bool>, !cir.bool // Active flag is initialized to false before the ternary so the dtor only runs // when the true branch was actually taken. // CIR: %[[FALSE:.*]] = cir.const #false @@ -911,10 +913,10 @@ void test_combined_cleanups(bool c) { // CIR: %[[TMP_B:.*]] = cir.alloca "ref.tmp2" {{.*}} : !cir.ptr<!rec_B> // CIR: %[[ACT_B:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> // CIR: %[[SPILL:.*]] = cir.alloca "tmp.exprcleanup" {{.*}} : !cir.ptr<!cir.ptr<!rec_LE>> +// CIR: cir.call @_ZN1SC1Ev(%[[TMP_S]]) // CIR: cir.cleanup.scope { -// CIR: cir.call @_ZN1SC1Ev(%[[TMP_S]]) +// CIR: cir.call @_ZN1S3getEv(%[[TMP_S]]) // CIR: cir.cleanup.scope { -// CIR: cir.call @_ZN1S3getEv(%[[TMP_S]]) // CIR: cir.store {{.*}}, %[[ACT_B]] // CIR: %{{.*}} = cir.ternary({{.*}}, true { // CIR: cir.call @_ZN1BC1Ev(%[[TMP_B]]) @@ -925,16 +927,17 @@ void test_combined_cleanups(bool c) { // CIR: cir.call @_ZN2LEC1Ei(%[[TMP_LE]], %{{.*}}) // CIR: cir.store {{.*}} %[[TMP_LE]], %[[SPILL]] // CIR: cir.yield +// B is constructed after S, so the conditional ~B runs before ~S. // CIR: } cleanup normal { -// CIR: cir.call @_ZN1SD1Ev(%[[TMP_S]]) nothrow +// CIR: %[[FLAG:.*]] = cir.load {{.*}} %[[ACT_B]] +// CIR: cir.if %[[FLAG]] { +// CIR: cir.call @_ZN1BD1Ev(%[[TMP_B]]) nothrow +// CIR: } // CIR: cir.yield // CIR: } // CIR: cir.yield // CIR: } cleanup normal { -// CIR: %[[FLAG:.*]] = cir.load {{.*}} %[[ACT_B]] -// CIR: cir.if %[[FLAG]] { -// CIR: cir.call @_ZN1BD1Ev(%[[TMP_B]]) nothrow -// CIR: } +// CIR: cir.call @_ZN1SD1Ev(%[[TMP_S]]) nothrow // CIR: cir.yield // CIR: } // CIR: cir.cleanup.scope { @@ -966,13 +969,14 @@ void test_combined_cleanups(bool c) { // LLVMCIR: phi i32 [ 0, %[[F]] ], [ %{{.*}}, %[[T]] ] // LLVMCIR: call void @_ZN2LEC1Ei(ptr {{.*}} %[[TMP_LE]], i32 {{.*}}) // LLVMCIR: store ptr %[[TMP_LE]], ptr %[[SPILL]] -// LLVMCIR: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP_S]]) +// B is constructed after S, so the conditional ~B runs before ~S. // LLVMCIR: %[[FLAG_BYTE:.*]] = load i8, ptr %[[ACT_B]] // LLVMCIR: %[[FLAG:.*]] = trunc i8 %[[FLAG_BYTE]] to i1 // LLVMCIR: br i1 %[[FLAG]], label %[[B_DTOR:.*]], label %[[B_DONE:.*]] // LLVMCIR: [[B_DTOR]]: // LLVMCIR: call void @_ZN1BD1Ev(ptr {{.*}} %[[TMP_B]]) // LLVMCIR: [[B_DONE]]: +// LLVMCIR: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP_S]]) // LLVMCIR: %[[RELOAD:.*]] = load ptr, ptr %[[SPILL]] // LLVMCIR: store ptr %[[RELOAD]], ptr %[[R]] // LLVMCIR: call void @_ZN2LED1Ev(ptr {{.*}} %[[TMP_LE]]) @@ -1025,14 +1029,16 @@ void test_flag_cleared_before_cond_cleanup() { // CIR-LABEL: @_Z37test_flag_cleared_before_cond_cleanupv // CIR: %[[REF_TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_Guard> // CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> -// CIR: %[[AGG_TMP:.*]] = cir.alloca "agg.tmp0" {{.*}} : !cir.ptr<!rec_Payload> +// Guard is constructed before the conditional, so its cleanup scope encloses +// the conditional's and the Payload temporary is destroyed first. +// CIR: cir.call @_ZN5GuardC1Ev(%[[REF_TMP]]) // CIR: cir.cleanup.scope { -// The clear precedes both the Guard constructor and the nested cleanup scope. -// CIR: %[[FALSE:.*]] = cir.const #false -// CIR: cir.store %[[FALSE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> -// CIR: cir.call @_ZN5GuardC1Ev(%[[REF_TMP]]) +// CIR: %[[AGG_TMP:.*]] = cir.alloca "agg.tmp0" {{.*}} : !cir.ptr<!rec_Payload> +// CIR: %[[COND:.*]] = cir.call @_ZN5GuardcvbEv(%[[REF_TMP]]) // CIR: cir.cleanup.scope { -// CIR: %[[COND:.*]] = cir.call @_ZN5GuardcvbEv(%[[REF_TMP]]) +// The clear precedes the conditional, so it runs whichever arm is taken. +// CIR: %[[FALSE:.*]] = cir.const #false +// CIR: cir.store %[[FALSE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> // CIR: cir.if %[[COND]] { // CIR: cir.call @_ZN6HolderC1Ev(%{{.*}}) // CIR: } else { @@ -1041,41 +1047,41 @@ void test_flag_cleared_before_cond_cleanup() { // CIR: cir.call @_ZN6HolderC1E7Payload(%{{.*}}, %[[AGG_TMP]]) // CIR: } // CIR: } cleanup normal { -// CIR: cir.call @_ZN5GuardD1Ev(%[[REF_TMP]]) +// CIR: %[[IS_ACTIVE:.*]] = cir.load{{.*}} %[[ACTIVE]] +// CIR: cir.if %[[IS_ACTIVE]] { +// CIR: cir.call @_ZN7PayloadD1Ev(%[[AGG_TMP]]) +// CIR: } // CIR: } // CIR: } cleanup normal { -// CIR: %[[IS_ACTIVE:.*]] = cir.load{{.*}} %[[ACTIVE]] -// CIR: cir.if %[[IS_ACTIVE]] { -// CIR: cir.call @_ZN7PayloadD1Ev(%[[AGG_TMP]]) -// CIR: } +// CIR: cir.call @_ZN5GuardD1Ev(%[[REF_TMP]]) // CIR: } // LLVM-LABEL: define dso_local void @_Z37test_flag_cleared_before_cond_cleanupv( -// LLVM: %[[REF_TMP:.*]] = alloca %struct.Guard +// LLVMCIR: %[[AGG_TMP:.*]] = alloca %struct.Payload +// LLVMCIR: %[[REF_TMP:.*]] = alloca %struct.Guard // LLVMCIR: %[[ACTIVE:.*]] = alloca i8 -// LLVM: %[[AGG_TMP:.*]] = alloca %struct.Payload +// OGCG: %[[REF_TMP:.*]] = alloca %struct.Guard +// OGCG: %[[AGG_TMP:.*]] = alloca %struct.Payload // OGCG: %[[ACTIVE:.*]] = alloca i1 -// LLVMCIR: store i8 0, ptr %[[ACTIVE]] // LLVM: call void @_ZN5GuardC1Ev(ptr {{.*}} %[[REF_TMP]]) // LLVM: %[[COND:.*]] = call {{.*}} i1 @_ZN5GuardcvbEv(ptr {{.*}} %[[REF_TMP]]) +// The clear dominates the branch, so the flag is initialized on both arms. +// LLVMCIR: store i8 0, ptr %[[ACTIVE]] // OGCG: store i1 false, ptr %[[ACTIVE]] // LLVM: br i1 %[[COND]], label %[[TRUE_BR:.*]], label %[[FALSE_BR:.*]] // LLVM: [[FALSE_BR]]: // LLVMCIR: store i8 1, ptr %[[ACTIVE]] -// FIXME: CIR destroys Guard before Payload; reverse-of-construction order -// requires ~Payload to run first, as OGCG below does. Tracked separately from -// the active-flag placement this test covers. -// LLVMCIR: call void @_ZN5GuardD1Ev(ptr {{.*}} %[[REF_TMP]]) +// OGCG: store i1 true, ptr %[[ACTIVE]] +// Guard is constructed first, so the conditional ~Payload runs before ~Guard. // LLVMCIR: %[[ACTIVE_BYTE:.*]] = load i8, ptr %[[ACTIVE]] // LLVMCIR: %[[ACTIVE_BOOL:.*]] = trunc i8 %[[ACTIVE_BYTE]] to i1 -// LLVMCIR: br i1 %[[ACTIVE_BOOL]], label %[[DTOR:.*]], label %[[SKIP:.*]] -// OGCG: store i1 true, ptr %[[ACTIVE]] +// LLVMCIR: br i1 %[[ACTIVE_BOOL]], label %[[DTOR:.*]], label %[[DONE:.*]] // OGCG: %[[IS_ACTIVE:.*]] = load i1, ptr %[[ACTIVE]] // OGCG: br i1 %[[IS_ACTIVE]], label %[[DTOR:.*]], label %[[DONE:.*]] // LLVM: [[DTOR]]: // LLVM: call void @_ZN7PayloadD1Ev(ptr {{.*}} %[[AGG_TMP]]) -// OGCG: [[DONE]]: -// OGCG: call void @_ZN5GuardD1Ev(ptr {{.*}} %[[REF_TMP]]) +// LLVM: [[DONE]]: +// LLVM: call void @_ZN5GuardD1Ev(ptr {{.*}} %[[REF_TMP]]) struct Q { Q(); ~Q(); int get() const; }; bool pred(int); @@ -1096,8 +1102,8 @@ void test_short_circuit_cond_temp(bool always, bool c, int n) { // CIR: %[[ACTIVE0:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> // CIR: %[[REF_TMP1:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_Q> // CIR: %[[ACTIVE1:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[ALWAYS:.*]] = cir.load{{.*}} %{{.*}} // CIR: cir.cleanup.scope { -// CIR: %[[ALWAYS:.*]] = cir.load{{.*}} %{{.*}} // Both clears are emitted before the || ternary, not inside its false region. // CIR: %[[FALSE0:.*]] = cir.const #false // CIR: cir.store %[[FALSE0]], %[[ACTIVE0]] : !cir.bool, !cir.ptr<!cir.bool> >From b8d3698ad01ff3e4e80e4c6728d24f0ebd8fb63e Mon Sep 17 00:00:00 2001 From: Andy Kaylor <[email protected]> Date: Wed, 23 Sep 2026 10:36:55 -0700 Subject: [PATCH 3/4] Pass location to ConditionalEvaluation --- clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 2 +- clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp | 2 +- clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp | 2 +- clang/lib/CIR/CodeGen/CIRGenExprComplex.cpp | 2 +- clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp | 6 +++--- clang/lib/CIR/CodeGen/CIRGenFunction.h | 5 +++-- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index a895275afc9c30..dcfbe5b320f912 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -3100,7 +3100,7 @@ CIRGenFunction::emitConditionalBlocks(const AbstractConditionalOperator *e, mlir::Value condV = emitOpOnBoolExpr(loc, e->getCond()); - ConditionalEvaluation eval(*this); + ConditionalEvaluation eval(*this, loc); auto emitBranch = [&](mlir::OpBuilder &b, mlir::Location loc, const Expr *expr, std::optional<LValue> &resultLV) { diff --git a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp index c4c5ee9abd8142..8ee72b543c56fe 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp @@ -467,7 +467,7 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> { // the cleanup scope of any temporary the condition creates encloses the // one the evaluation opens. mlir::Value condV = cgf.emitOpOnBoolExpr(loc, e->getCond()); - CIRGenFunction::ConditionalEvaluation eval(cgf); + CIRGenFunction::ConditionalEvaluation eval(cgf, loc); // Save whether the destination's lifetime is externally managed. bool isExternallyDestructed = dest.isExternallyDestructed(); diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp index d0105c96cfd396..090a3d15e2aab9 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp @@ -1778,7 +1778,7 @@ mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) { // provides the cleanup region for the deferred destructors. mlir::Value isNotNull = builder.createPtrIsNotNull(allocation.getPointer()); - ConditionalEvaluation eval(*this); + ConditionalEvaluation eval(*this, getLoc(e->getSourceRange())); nullCheckOp = cir::IfOp::create(builder, getLoc(e->getSourceRange()), isNotNull, /*withElseRegion=*/false, diff --git a/clang/lib/CIR/CodeGen/CIRGenExprComplex.cpp b/clang/lib/CIR/CodeGen/CIRGenExprComplex.cpp index 135178b95a4667..91535c57132bd0 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprComplex.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprComplex.cpp @@ -1006,7 +1006,7 @@ mlir::Value ComplexExprEmitter::VisitAbstractConditionalOperator( Expr *cond = e->getCond()->IgnoreParens(); mlir::Value condValue = cgf.evaluateExprAsBool(cond); - CIRGenFunction::ConditionalEvaluation eval(cgf); + CIRGenFunction::ConditionalEvaluation eval(cgf, loc); return cir::TernaryOp::create( builder, loc, condValue, diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index 1e6e1c3190bb71..ffe24666c0bfe1 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -1419,7 +1419,7 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> { mlir::Value lhsCondV = cgf.evaluateExprAsBool(e->getLHS()); - CIRGenFunction::ConditionalEvaluation eval(cgf); + CIRGenFunction::ConditionalEvaluation eval(cgf, loc); auto resOp = cir::TernaryOp::create( builder, loc, lhsCondV, /*trueBuilder=*/ @@ -1465,7 +1465,7 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> { mlir::Value lhsCondV = cgf.evaluateExprAsBool(e->getLHS()); - CIRGenFunction::ConditionalEvaluation eval(cgf); + CIRGenFunction::ConditionalEvaluation eval(cgf, loc); auto resOp = cir::TernaryOp::create( builder, loc, lhsCondV, /*trueBuilder=*/ @@ -3264,7 +3264,7 @@ mlir::Value ScalarExprEmitter::VisitAbstractConditionalOperator( } mlir::Value condV = cgf.emitOpOnBoolExpr(loc, condExpr); - CIRGenFunction::ConditionalEvaluation eval(cgf); + CIRGenFunction::ConditionalEvaluation eval(cgf, loc); auto emitBranch = [&](mlir::OpBuilder &b, mlir::Location loc, Expr *expr) { CIRGenFunction::LexicalScope lexScope{cgf, loc, b.getInsertionBlock()}; diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 63a94a52203c70..b232abf5d9299d 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -2527,14 +2527,15 @@ class CIRGenFunction : public CIRGenTypeCache { mlir::Operation *anchorAfter = nullptr; public: - ConditionalEvaluation(CIRGenFunction &cgf) : cgf(cgf) { + /// \p loc is the location of the conditional expression. It is used for + /// the cleanup scope this may open. + ConditionalEvaluation(CIRGenFunction &cgf, mlir::Location loc) : cgf(cgf) { // Open the cleanup scope that hosts cleanups deferred from inside this // conditional (see deferredConditionalCleanupStack). Only the outermost // conditional opens one, and only while a FullExprCleanupScope is // active to close it. When nothing is deferred into it the cleanup // region stays trivial and canonicalization inlines the scope away. if (cgf.currentFullExprCleanupScope && !cgf.isInConditionalBranch()) { - mlir::Location loc = cgf.builder.getUnknownLoc(); cir::CleanupKind cleanupKind = cgf.getLangOpts().Exceptions ? cir::CleanupKind::All : cir::CleanupKind::Normal; >From e96628665865a32dd09409fd6d8cfab9c88e8f1d Mon Sep 17 00:00:00 2001 From: Andy Kaylor <[email protected]> Date: Wed, 23 Sep 2026 12:48:50 -0700 Subject: [PATCH 4/4] Add more test cases --- .../cleanup-conditional-with-wrapper-eh.cpp | 474 ++++++++++++++++++ 1 file changed, 474 insertions(+) diff --git a/clang/test/CIR/CodeGen/cleanup-conditional-with-wrapper-eh.cpp b/clang/test/CIR/CodeGen/cleanup-conditional-with-wrapper-eh.cpp index 9ae31396ca63f1..8051ff3b2d5dfd 100644 --- a/clang/test/CIR/CodeGen/cleanup-conditional-with-wrapper-eh.cpp +++ b/clang/test/CIR/CodeGen/cleanup-conditional-with-wrapper-eh.cpp @@ -478,3 +478,477 @@ void makeEntry() { // OGCG: br label %[[EH_RESUME:.*]] // OGCG: [[EH_RESUME]]: // OGCG: resume + +struct AltArg { AltArg(); ~AltArg(); }; +struct Arg { Arg(); Arg(const AltArg &); ~Arg(); }; +struct Extra { Extra(); ~Extra(); }; +void consume(const Arg &, const Extra &); + +// A conditional argument followed by an unconditional one. +// +// Both arms of the conditional produce an Arg, the true arm directly and the +// false arm through the converting constructor from AltArg, so ~Arg runs on +// every path that leaves the conditional and needs no active flag. AltArg is +// built only on the false arm, so it is the one that gets the flag. +// +// The arms are emitted in the body of the conditional's scope, outside the +// nested scope that holds ~Arg. Unwinding out of an arm has entered only the +// conditional's scope, so it runs the guarded ~AltArg and nothing else. +// +// Arg is constructed before Extra, so the destructors run Extra, then Arg, +// then the guarded AltArg. This is the mirror of makeEntry above, where the +// conditionally destroyed temporary is the one constructed last. +void callCondArg(bool c) { + consume(c ? Arg() : AltArg(), Extra()); +} + + +// CIR: cir.func {{.*}} @_Z11callCondArgb +// CIR: %[[ARG:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_Arg> +// CIR: %[[ALT:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_AltArg> +// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[EXTRA:.*]] = cir.alloca "ref.tmp2" {{.*}} : !cir.ptr<!rec_Extra> +// The conditional scope opens at the conditional and is outermost, since the +// AltArg temporary it guards is the first one constructed. +// CIR: cir.cleanup.scope { +// CIR: %[[FALSE:.*]] = cir.const #false +// CIR: cir.store %[[FALSE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR: cir.if %{{.*}} { +// CIR: cir.call @_ZN3ArgC1Ev(%[[ARG]]) +// CIR: } else { +// CIR: cir.call @_ZN6AltArgC1Ev(%[[ALT]]) +// CIR: %[[TRUE:.*]] = cir.const #true +// CIR: cir.store %[[TRUE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR: cir.call @_ZN3ArgC1ERK6AltArg(%[[ARG]], %[[ALT]]) +// CIR: } +// Arg is built on both arms, so its cleanup is unconditional and gets a +// plain scope nested inside the conditional one. +// CIR: cir.cleanup.scope { +// CIR: cir.call @_ZN5ExtraC1Ev(%[[EXTRA]]) +// Extra is constructed last, so its scope is innermost and it dies first. +// CIR: cir.cleanup.scope { +// CIR: cir.call @_Z7consumeRK3ArgRK5Extra(%[[ARG]], %[[EXTRA]]) +// CIR: cir.yield +// CIR: } cleanup all { +// CIR: cir.call @_ZN5ExtraD1Ev(%[[EXTRA]]) +// CIR: cir.yield +// CIR: } +// CIR: cir.yield +// CIR: } cleanup all { +// CIR: cir.call @_ZN3ArgD1Ev(%[[ARG]]) +// CIR: cir.yield +// CIR: } +// CIR: cir.yield +// CIR: } cleanup all { +// CIR: %[[IS_ACTIVE:.*]] = cir.load{{.*}} %[[ACTIVE]] +// CIR: cir.if %[[IS_ACTIVE]] { +// CIR: cir.call @_ZN6AltArgD1Ev(%[[ALT]]) +// CIR: } +// CIR: cir.yield +// CIR: } + +// LLVM: define {{.*}} void @_Z11callCondArgb({{.*}} personality ptr @__gxx_personality_v0 +// LLVM: %[[ARG:.*]] = alloca %struct.Arg +// LLVM: %[[ALT:.*]] = alloca %struct.AltArg +// LLVM: %[[ACTIVE:.*]] = alloca i8 +// LLVM: %[[EXTRA:.*]] = alloca %struct.Extra +// LLVM: store i8 0, ptr %[[ACTIVE]] +// LLVM: br i1 %{{.*}}, label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// Every unwind edge out of the conditional lands on one pad, which runs only +// the guarded ~AltArg. No Arg exists on any of those edges, and the flag is +// set only where AltArg has been built. +// +// The two bare constructors unwind here but are plain calls in OGCG below. +// The conditional's scope is opened before its arms are emitted, so the whole +// conditional unwinds to it, while OGCG starts using invoke only once the +// AltArg cleanup is live. The flag is false on those two edges, so the pad +// reaches the resume without running a destructor. +// LLVM: [[TRUE_BB]]: +// LLVM: invoke void @_ZN3ArgC1Ev(ptr {{.*}} %[[ARG]]) +// LLVM: to label %[[TRUE_CONT:.*]] unwind label %[[LPAD_ARMS:.*]] +// LLVM: [[TRUE_CONT]]: +// LLVM: br label %[[COND_END:.*]] +// LLVM: [[FALSE_BB]]: +// LLVM: invoke void @_ZN6AltArgC1Ev(ptr {{.*}} %[[ALT]]) +// LLVM: to label %[[ALT_CONT:.*]] unwind label %[[LPAD_ARMS]] +// LLVM: [[ALT_CONT]]: +// LLVM: store i8 1, ptr %[[ACTIVE]] +// LLVM: invoke void @_ZN3ArgC1ERK6AltArg(ptr {{.*}} %[[ARG]], ptr {{.*}} %[[ALT]]) +// LLVM: to label %[[CONV_CONT:.*]] unwind label %[[LPAD_ARMS]] +// LLVM: [[CONV_CONT]]: +// LLVM: br label %[[COND_END]] +// LLVM: [[COND_END]]: +// LLVM: invoke void @_ZN5ExtraC1Ev(ptr {{.*}} %[[EXTRA]]) +// LLVM: to label %[[EXTRA_CONT:.*]] unwind label %[[LPAD_EXTRA:.*]] +// LLVM: [[EXTRA_CONT]]: +// LLVM: invoke void @_Z7consumeRK3ArgRK5Extra(ptr {{.*}} %[[ARG]], ptr {{.*}} %[[EXTRA]]) +// LLVM: to label %[[CALL_CONT:.*]] unwind label %[[LPAD_CONSUME:.*]] +// Normal path starts with Extra, the last temporary constructed. +// LLVM: [[CALL_CONT]]: +// LLVM: call void @_ZN5ExtraD1Ev(ptr {{.*}} %[[EXTRA]]) +// Unwinding from consume destroys Extra, then joins Arg's cleanup. +// LLVM: [[LPAD_CONSUME]]: +// LLVM: landingpad { ptr, i32 } +// LLVM: cleanup +// LLVM: call void @_ZN5ExtraD1Ev(ptr {{.*}} %[[EXTRA]]) +// LLVM: br label %[[EH_ARG:.*]] +// Normal path continues with Arg. +// LLVM: call void @_ZN3ArgD1Ev(ptr {{.*}} %[[ARG]]) +// Unwinding from Extra's constructor skips ~Extra and joins the same pad. +// LLVM: [[LPAD_EXTRA]]: +// LLVM: landingpad { ptr, i32 } +// LLVM: cleanup +// LLVM: br label %[[EH_ARG]] +// LLVM: [[EH_ARG]]: +// LLVM: call void @_ZN3ArgD1Ev(ptr {{.*}} %[[ARG]]) +// LLVM: br label %[[EH_ALT:.*]] +// Normal path ends with the guarded AltArg. +// LLVM: %[[BYTE:.*]] = load i8, ptr %[[ACTIVE]] +// LLVM: %[[BOOL:.*]] = trunc i8 %[[BYTE]] to i1 +// LLVM: br i1 %[[BOOL]], label %[[ALT_DTOR:.*]], label %{{.*}} +// LLVM: [[ALT_DTOR]]: +// LLVM: call void @_ZN6AltArgD1Ev(ptr {{.*}} %[[ALT]]) +// The arms' pad skips ~Extra and ~Arg and joins the guarded AltArg directly. +// LLVM: [[LPAD_ARMS]]: +// LLVM: landingpad { ptr, i32 } +// LLVM: cleanup +// LLVM: br label %[[EH_ALT]] +// LLVM: [[EH_ALT]]: +// LLVM: %[[EH_BYTE:.*]] = load i8, ptr %[[ACTIVE]] +// LLVM: %[[EH_BOOL:.*]] = trunc i8 %[[EH_BYTE]] to i1 +// LLVM: br i1 %[[EH_BOOL]], label %[[EH_ALT_DTOR:.*]], label %{{.*}} +// LLVM: [[EH_ALT_DTOR]]: +// LLVM: call void @_ZN6AltArgD1Ev(ptr {{.*}} %[[ALT]]) +// LLVM: resume + +// OGCG: define {{.*}} void @_Z11callCondArgb({{.*}} personality ptr @__gxx_personality_v0 +// OGCG: %[[ARG:.*]] = alloca %struct.Arg +// OGCG: %[[ALT:.*]] = alloca %struct.AltArg +// OGCG: %[[ACTIVE:.*]] = alloca i1 +// OGCG: %[[EXTRA:.*]] = alloca %struct.Extra +// OGCG: store i1 false, ptr %[[ACTIVE]] +// OGCG: br i1 %{{.*}}, label %[[COND_TRUE:.*]], label %[[COND_FALSE:.*]] +// Nothing is live yet when either arm starts, so those constructors need no +// unwind edge. Only the conversion, which runs with AltArg live, invokes. +// The CIR pipeline invokes all three, as noted above. +// OGCG: [[COND_TRUE]]: +// OGCG: call void @_ZN3ArgC1Ev(ptr {{.*}} %[[ARG]]) +// OGCG: br label %[[COND_END:.*]] +// OGCG: [[COND_FALSE]]: +// OGCG: call void @_ZN6AltArgC1Ev(ptr {{.*}} %[[ALT]]) +// OGCG: store i1 true, ptr %[[ACTIVE]] +// OGCG: invoke void @_ZN3ArgC1ERK6AltArg(ptr {{.*}} %[[ARG]], ptr {{.*}} %[[ALT]]) +// OGCG: to label %[[CONV_CONT:.*]] unwind label %[[LPAD_CONV:.*]] +// OGCG: [[CONV_CONT]]: +// OGCG: br label %[[COND_END]] +// OGCG: [[COND_END]]: +// OGCG: invoke void @_ZN5ExtraC1Ev(ptr {{.*}} %[[EXTRA]]) +// OGCG: to label %[[EXTRA_CONT:.*]] unwind label %[[LPAD_EXTRA:.*]] +// OGCG: [[EXTRA_CONT]]: +// OGCG: invoke void @_Z7consumeRK3ArgRK5Extra(ptr {{.*}} %[[ARG]], ptr {{.*}} %[[EXTRA]]) +// OGCG: to label %[[CALL_CONT:.*]] unwind label %[[LPAD_CONSUME:.*]] +// Normal path: Extra, then Arg, then the guarded AltArg. +// OGCG: [[CALL_CONT]]: +// OGCG: call void @_ZN5ExtraD1Ev(ptr {{.*}} %[[EXTRA]]) +// OGCG: call void @_ZN3ArgD1Ev(ptr {{.*}} %[[ARG]]) +// OGCG: %[[IS_ACTIVE:.*]] = load i1, ptr %[[ACTIVE]] +// OGCG: br i1 %[[IS_ACTIVE]], label %[[ALT_DTOR:.*]], label %[[DONE:.*]] +// OGCG: [[ALT_DTOR]]: +// OGCG: call void @_ZN6AltArgD1Ev(ptr {{.*}} %[[ALT]]) +// OGCG: [[DONE]]: +// OGCG: ret void +// The conversion's pad runs only the guarded ~AltArg, since Arg is not +// constructed there. +// OGCG: [[LPAD_CONV]]: +// OGCG: landingpad { ptr, i32 } +// OGCG: cleanup +// OGCG: br label %[[EH_ALT:.*]] +// Unwinding from Extra's constructor skips ~Extra and joins Arg's cleanup. +// OGCG: [[LPAD_EXTRA]]: +// OGCG: landingpad { ptr, i32 } +// OGCG: cleanup +// OGCG: br label %[[EH_ARG:.*]] +// Unwinding from consume destroys Extra first, then joins the same pad. +// OGCG: [[LPAD_CONSUME]]: +// OGCG: landingpad { ptr, i32 } +// OGCG: cleanup +// OGCG: call void @_ZN5ExtraD1Ev(ptr {{.*}} %[[EXTRA]]) +// OGCG: br label %[[EH_ARG]] +// OGCG: [[EH_ARG]]: +// OGCG: call void @_ZN3ArgD1Ev(ptr {{.*}} %[[ARG]]) +// OGCG: br label %[[EH_ALT]] +// OGCG: [[EH_ALT]]: +// OGCG: %[[EH_IS_ACTIVE:.*]] = load i1, ptr %[[ACTIVE]] +// OGCG: br i1 %[[EH_IS_ACTIVE]], label %[[EH_ALT_DTOR:.*]], label %{{.*}} +// OGCG: [[EH_ALT_DTOR]]: +// OGCG: call void @_ZN6AltArgD1Ev(ptr {{.*}} %[[ALT]]) +// OGCG: resume + +struct Mid { Mid(); ~Mid(); }; +void take(const Arg &, const Mid &, const Arg &); + +// Two conditionals in one full expression with an unconditional temporary +// between them. Each conditional opens its own cleanup scope where it begins, +// so the second nests inside the first, and Mid's scope sits between them. +// +// Construction runs AltArg1 (false arm only), Arg1, Mid, AltArg2 (false arm +// only), Arg2, so destruction runs Arg2, AltArg2, Mid, Arg1, AltArg1. That is +// five nested scopes closing innermost first. +void twoConditionals(bool c1, bool c2) { + take(c1 ? Arg() : AltArg(), Mid(), c2 ? Arg() : AltArg()); +} + +// CIR: cir.func {{.*}} @_Z15twoConditionalsbb +// CIR: %[[ARG1:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_Arg> +// CIR: %[[ALT1:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_AltArg> +// CIR: %[[FLAG1:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[MID:.*]] = cir.alloca "ref.tmp2" {{.*}} : !cir.ptr<!rec_Mid> +// CIR: %[[ARG2:.*]] = cir.alloca "ref.tmp3" {{.*}} : !cir.ptr<!rec_Arg> +// CIR: %[[ALT2:.*]] = cir.alloca "ref.tmp4" {{.*}} : !cir.ptr<!rec_AltArg> +// CIR: %[[FLAG2:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// Scope 1: the first conditional, holding the guarded ~AltArg1. +// CIR: cir.cleanup.scope { +// CIR: %[[FALSE1:.*]] = cir.const #false +// CIR: cir.store %[[FALSE1]], %[[FLAG1]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR: cir.if %{{.*}} { +// CIR: cir.call @_ZN3ArgC1Ev(%[[ARG1]]) +// CIR: } else { +// CIR: cir.call @_ZN6AltArgC1Ev(%[[ALT1]]) +// CIR: %[[TRUE1:.*]] = cir.const #true +// CIR: cir.store %[[TRUE1]], %[[FLAG1]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR: cir.call @_ZN3ArgC1ERK6AltArg(%[[ARG1]], %[[ALT1]]) +// CIR: } +// Scope 2: Arg1, built on both arms and so destroyed unconditionally. +// CIR: cir.cleanup.scope { +// CIR: cir.call @_ZN3MidC1Ev(%[[MID]]) +// Scope 3: Mid, constructed between the two conditionals. +// CIR: cir.cleanup.scope { +// Scope 4: the second conditional, holding the guarded ~AltArg2. +// CIR: cir.cleanup.scope { +// CIR: %[[FALSE2:.*]] = cir.const #false +// CIR: cir.store %[[FALSE2]], %[[FLAG2]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR: cir.if %{{.*}} { +// CIR: cir.call @_ZN3ArgC1Ev(%[[ARG2]]) +// CIR: } else { +// CIR: cir.call @_ZN6AltArgC1Ev(%[[ALT2]]) +// CIR: %[[TRUE2:.*]] = cir.const #true +// CIR: cir.store %[[TRUE2]], %[[FLAG2]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR: cir.call @_ZN3ArgC1ERK6AltArg(%[[ARG2]], %[[ALT2]]) +// CIR: } +// Scope 5: Arg2, the last temporary constructed and the first destroyed. +// CIR: cir.cleanup.scope { +// CIR: cir.call @_Z4takeRK3ArgRK3MidS1_(%[[ARG1]], %[[MID]], %[[ARG2]]) +// CIR: cir.yield +// CIR: } cleanup all { +// CIR: cir.call @_ZN3ArgD1Ev(%[[ARG2]]) +// CIR: cir.yield +// CIR: } +// CIR: cir.yield +// CIR: } cleanup all { +// CIR: %[[IS2:.*]] = cir.load{{.*}} %[[FLAG2]] +// CIR: cir.if %[[IS2]] { +// CIR: cir.call @_ZN6AltArgD1Ev(%[[ALT2]]) +// CIR: } +// CIR: cir.yield +// CIR: } +// CIR: cir.yield +// CIR: } cleanup all { +// CIR: cir.call @_ZN3MidD1Ev(%[[MID]]) +// CIR: cir.yield +// CIR: } +// CIR: cir.yield +// CIR: } cleanup all { +// CIR: cir.call @_ZN3ArgD1Ev(%[[ARG1]]) +// CIR: cir.yield +// CIR: } +// CIR: cir.yield +// CIR: } cleanup all { +// CIR: %[[IS1:.*]] = cir.load{{.*}} %[[FLAG1]] +// CIR: cir.if %[[IS1]] { +// CIR: cir.call @_ZN6AltArgD1Ev(%[[ALT1]]) +// CIR: } +// CIR: cir.yield +// CIR: } + +// LLVM: define {{.*}} void @_Z15twoConditionalsbb({{.*}} personality ptr @__gxx_personality_v0 +// LLVM: %[[ARG1:.*]] = alloca %struct.Arg +// LLVM: %[[ALT1:.*]] = alloca %struct.AltArg +// LLVM: %[[FLAG1:.*]] = alloca i8 +// LLVM: %[[MID:.*]] = alloca %struct.Mid +// LLVM: %[[ARG2:.*]] = alloca %struct.Arg +// LLVM: %[[ALT2:.*]] = alloca %struct.AltArg +// LLVM: %[[FLAG2:.*]] = alloca i8 +// LLVM: store i8 0, ptr %[[FLAG1]] +// LLVM: br i1 %{{.*}}, label %[[T1:.*]], label %[[F1:.*]] +// Both arms of the first conditional share one pad. +// LLVM: [[T1]]: +// LLVM: invoke void @_ZN3ArgC1Ev(ptr {{.*}} %[[ARG1]]) +// LLVM: to label %{{.*}} unwind label %[[LPAD_ARMS1:.*]] +// LLVM: [[F1]]: +// LLVM: invoke void @_ZN6AltArgC1Ev(ptr {{.*}} %[[ALT1]]) +// LLVM: to label %{{.*}} unwind label %[[LPAD_ARMS1]] +// LLVM: store i8 1, ptr %[[FLAG1]] +// LLVM: invoke void @_ZN3ArgC1ERK6AltArg(ptr {{.*}} %[[ARG1]], ptr {{.*}} %[[ALT1]]) +// LLVM: to label %{{.*}} unwind label %[[LPAD_ARMS1]] +// LLVM: invoke void @_ZN3MidC1Ev(ptr {{.*}} %[[MID]]) +// LLVM: to label %{{.*}} unwind label %[[LPAD_MID:.*]] +// LLVM: store i8 0, ptr %[[FLAG2]] +// LLVM: br i1 %{{.*}}, label %[[T2:.*]], label %[[F2:.*]] +// Both arms of the second conditional share a different pad. +// LLVM: [[T2]]: +// LLVM: invoke void @_ZN3ArgC1Ev(ptr {{.*}} %[[ARG2]]) +// LLVM: to label %{{.*}} unwind label %[[LPAD_ARMS2:.*]] +// LLVM: [[F2]]: +// LLVM: invoke void @_ZN6AltArgC1Ev(ptr {{.*}} %[[ALT2]]) +// LLVM: to label %{{.*}} unwind label %[[LPAD_ARMS2]] +// LLVM: store i8 1, ptr %[[FLAG2]] +// LLVM: invoke void @_ZN3ArgC1ERK6AltArg(ptr {{.*}} %[[ARG2]], ptr {{.*}} %[[ALT2]]) +// LLVM: to label %{{.*}} unwind label %[[LPAD_ARMS2]] +// LLVM: invoke void @_Z4takeRK3ArgRK3MidS1_(ptr {{.*}} %[[ARG1]], ptr {{.*}} %[[MID]], ptr {{.*}} %[[ARG2]]) +// LLVM: to label %{{.*}} unwind label %[[LPAD_TAKE:.*]] +// Normal path starts with Arg2, the last temporary constructed. +// LLVM: call void @_ZN3ArgD1Ev(ptr {{.*}} %[[ARG2]]) +// Unwinding from take destroys Arg2 and joins the second conditional's pad. +// LLVM: [[LPAD_TAKE]]: +// LLVM: landingpad { ptr, i32 } +// LLVM: cleanup +// LLVM: call void @_ZN3ArgD1Ev(ptr {{.*}} %[[ARG2]]) +// LLVM: br label %[[EH_ALT2:.*]] +// Normal path continues with the guarded AltArg2. +// LLVM: %[[B2:.*]] = load i8, ptr %[[FLAG2]] +// LLVM: %[[C2:.*]] = trunc i8 %[[B2]] to i1 +// LLVM: br i1 %[[C2]], label %[[ALT2_DTOR:.*]], label %{{.*}} +// LLVM: [[ALT2_DTOR]]: +// LLVM: call void @_ZN6AltArgD1Ev(ptr {{.*}} %[[ALT2]]) +// The second conditional's arms skip ~Arg2 and enter at the same level. +// LLVM: [[LPAD_ARMS2]]: +// LLVM: landingpad { ptr, i32 } +// LLVM: cleanup +// LLVM: br label %[[EH_ALT2]] +// LLVM: [[EH_ALT2]]: +// LLVM: %[[EB2:.*]] = load i8, ptr %[[FLAG2]] +// LLVM: %[[EC2:.*]] = trunc i8 %[[EB2]] to i1 +// LLVM: br i1 %[[EC2]], label %[[EH_ALT2_DTOR:.*]], label %{{.*}} +// LLVM: [[EH_ALT2_DTOR]]: +// LLVM: call void @_ZN6AltArgD1Ev(ptr {{.*}} %[[ALT2]]) +// Normal path then Mid, and the unwind chain reaches Mid next as well. +// LLVM: call void @_ZN3MidD1Ev(ptr {{.*}} %[[MID]]) +// LLVM: call void @_ZN3MidD1Ev(ptr {{.*}} %[[MID]]) +// LLVM: br label %[[EH_ARG1:.*]] +// Unwinding from Mid's constructor skips ~Mid and joins Arg1's cleanup. +// LLVM: [[LPAD_MID]]: +// LLVM: landingpad { ptr, i32 } +// LLVM: cleanup +// LLVM: br label %[[EH_ARG1]] +// LLVM: [[EH_ARG1]]: +// LLVM: call void @_ZN3ArgD1Ev(ptr {{.*}} %[[ARG1]]) +// LLVM: br label %[[EH_ALT1:.*]] +// Normal path ends with the guarded AltArg1. +// LLVM: %[[B1:.*]] = load i8, ptr %[[FLAG1]] +// LLVM: %[[C1:.*]] = trunc i8 %[[B1]] to i1 +// LLVM: br i1 %[[C1]], label %[[ALT1_DTOR:.*]], label %{{.*}} +// LLVM: [[ALT1_DTOR]]: +// LLVM: call void @_ZN6AltArgD1Ev(ptr {{.*}} %[[ALT1]]) +// The first conditional's arms enter the outermost level directly. +// LLVM: [[LPAD_ARMS1]]: +// LLVM: landingpad { ptr, i32 } +// LLVM: cleanup +// LLVM: br label %[[EH_ALT1]] +// LLVM: [[EH_ALT1]]: +// LLVM: %[[EB1:.*]] = load i8, ptr %[[FLAG1]] +// LLVM: %[[EC1:.*]] = trunc i8 %[[EB1]] to i1 +// LLVM: br i1 %[[EC1]], label %[[EH_ALT1_DTOR:.*]], label %{{.*}} +// LLVM: [[EH_ALT1_DTOR]]: +// LLVM: call void @_ZN6AltArgD1Ev(ptr {{.*}} %[[ALT1]]) +// LLVM: resume + +// OGCG: define {{.*}} void @_Z15twoConditionalsbb({{.*}} personality ptr @__gxx_personality_v0 +// OGCG: %[[ARG1:.*]] = alloca %struct.Arg +// OGCG: %[[ALT1:.*]] = alloca %struct.AltArg +// OGCG: %[[FLAG1:.*]] = alloca i1 +// OGCG: %[[MID:.*]] = alloca %struct.Mid +// OGCG: %[[ARG2:.*]] = alloca %struct.Arg +// OGCG: %[[ALT2:.*]] = alloca %struct.AltArg +// OGCG: %[[FLAG2:.*]] = alloca i1 +// OGCG: store i1 false, ptr %[[FLAG1]] +// OGCG: br i1 %{{.*}}, label %[[T1:.*]], label %[[F1:.*]] +// Nothing is live yet in the first conditional, so its arms use plain calls. +// The second conditional's arms invoke, because Arg1 and Mid are live by +// then. The CIR pipeline invokes both, since each conditional's scope is +// opened before its arms are emitted. +// OGCG: [[T1]]: +// OGCG: call void @_ZN3ArgC1Ev(ptr {{.*}} %[[ARG1]]) +// OGCG: [[F1]]: +// OGCG: call void @_ZN6AltArgC1Ev(ptr {{.*}} %[[ALT1]]) +// OGCG: store i1 true, ptr %[[FLAG1]] +// OGCG: invoke void @_ZN3ArgC1ERK6AltArg(ptr {{.*}} %[[ARG1]], ptr {{.*}} %[[ALT1]]) +// OGCG: to label %{{.*}} unwind label %[[LPAD_CONV1:.*]] +// OGCG: invoke void @_ZN3MidC1Ev(ptr {{.*}} %[[MID]]) +// OGCG: to label %{{.*}} unwind label %[[LPAD_MID:.*]] +// OGCG: store i1 false, ptr %[[FLAG2]] +// OGCG: br i1 %{{.*}}, label %[[T2:.*]], label %[[F2:.*]] +// OGCG: [[T2]]: +// OGCG: invoke void @_ZN3ArgC1Ev(ptr {{.*}} %[[ARG2]]) +// OGCG: to label %{{.*}} unwind label %[[LPAD_ARMS2:.*]] +// OGCG: [[F2]]: +// OGCG: invoke void @_ZN6AltArgC1Ev(ptr {{.*}} %[[ALT2]]) +// OGCG: to label %{{.*}} unwind label %[[LPAD_ARMS2]] +// OGCG: store i1 true, ptr %[[FLAG2]] +// OGCG: invoke void @_ZN3ArgC1ERK6AltArg(ptr {{.*}} %[[ARG2]], ptr {{.*}} %[[ALT2]]) +// OGCG: to label %{{.*}} unwind label %[[LPAD_CONV2:.*]] +// OGCG: invoke void @_Z4takeRK3ArgRK3MidS1_(ptr {{.*}} %[[ARG1]], ptr {{.*}} %[[MID]], ptr {{.*}} %[[ARG2]]) +// OGCG: to label %{{.*}} unwind label %[[LPAD_TAKE:.*]] +// Normal path: Arg2, AltArg2, Mid, Arg1, AltArg1. +// OGCG: call void @_ZN3ArgD1Ev(ptr {{.*}} %[[ARG2]]) +// OGCG: %[[IS2:.*]] = load i1, ptr %[[FLAG2]] +// OGCG: br i1 %[[IS2]], label %[[ALT2_DTOR:.*]], label %[[DONE2:.*]] +// OGCG: [[ALT2_DTOR]]: +// OGCG: call void @_ZN6AltArgD1Ev(ptr {{.*}} %[[ALT2]]) +// OGCG: [[DONE2]]: +// OGCG: call void @_ZN3MidD1Ev(ptr {{.*}} %[[MID]]) +// OGCG: call void @_ZN3ArgD1Ev(ptr {{.*}} %[[ARG1]]) +// OGCG: %[[IS1:.*]] = load i1, ptr %[[FLAG1]] +// OGCG: br i1 %[[IS1]], label %[[ALT1_DTOR:.*]], label %[[DONE1:.*]] +// OGCG: [[ALT1_DTOR]]: +// OGCG: call void @_ZN6AltArgD1Ev(ptr {{.*}} %[[ALT1]]) +// OGCG: [[DONE1]]: +// OGCG: ret void +// The unwind chain enters at the level matching what is already built. +// OGCG: [[LPAD_CONV1]]: +// OGCG: landingpad { ptr, i32 } +// OGCG: cleanup +// OGCG: br label %[[EH_ALT1:.*]] +// OGCG: [[LPAD_MID]]: +// OGCG: landingpad { ptr, i32 } +// OGCG: cleanup +// OGCG: br label %[[EH_ARG1:.*]] +// OGCG: [[LPAD_ARMS2]]: +// OGCG: landingpad { ptr, i32 } +// OGCG: cleanup +// OGCG: br label %[[EH_MID:.*]] +// OGCG: [[LPAD_CONV2]]: +// OGCG: landingpad { ptr, i32 } +// OGCG: cleanup +// OGCG: br label %[[EH_ALT2:.*]] +// OGCG: [[LPAD_TAKE]]: +// OGCG: landingpad { ptr, i32 } +// OGCG: cleanup +// OGCG: call void @_ZN3ArgD1Ev(ptr {{.*}} %[[ARG2]]) +// OGCG: br label %[[EH_ALT2]] +// OGCG: [[EH_ALT2]]: +// OGCG: %[[EIS2:.*]] = load i1, ptr %[[FLAG2]] +// OGCG: br i1 %[[EIS2]], label %[[EH_ALT2_DTOR:.*]], label %{{.*}} +// OGCG: [[EH_ALT2_DTOR]]: +// OGCG: call void @_ZN6AltArgD1Ev(ptr {{.*}} %[[ALT2]]) +// OGCG: [[EH_MID]]: +// OGCG: call void @_ZN3MidD1Ev(ptr {{.*}} %[[MID]]) +// OGCG: br label %[[EH_ARG1]] +// OGCG: [[EH_ARG1]]: +// OGCG: call void @_ZN3ArgD1Ev(ptr {{.*}} %[[ARG1]]) +// OGCG: br label %[[EH_ALT1]] +// OGCG: [[EH_ALT1]]: +// OGCG: %[[EIS1:.*]] = load i1, ptr %[[FLAG1]] +// OGCG: br i1 %[[EIS1]], label %[[EH_ALT1_DTOR:.*]], label %{{.*}} +// OGCG: [[EH_ALT1_DTOR]]: +// OGCG: call void @_ZN6AltArgD1Ev(ptr {{.*}} %[[ALT1]]) +// OGCG: resume _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
