From aa75d000d76514c92f673f92f0f3cd22d6a4a9af Mon Sep 17 00:00:00 2001 From: Nate Chandler Date: Wed, 4 Oct 2023 16:03:34 -0700 Subject: [PATCH 1/3] [SIL] NFC: Improved block id printing. --- include/swift/SIL/SILBasicBlock.h | 6 +++--- lib/SIL/IR/SILPrinter.cpp | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/include/swift/SIL/SILBasicBlock.h b/include/swift/SIL/SILBasicBlock.h index 8fb8f083ab42a..b16309ea484cb 100644 --- a/include/swift/SIL/SILBasicBlock.h +++ b/include/swift/SIL/SILBasicBlock.h @@ -518,13 +518,13 @@ public SwiftObjectHeader { #ifndef NDEBUG /// Print the ID of the block, bbN. - void dumpID() const; + void dumpID(bool newline = true) const; /// Print the ID of the block with \p OS, bbN. - void printID(llvm::raw_ostream &OS) const; + void printID(llvm::raw_ostream &OS, bool newline = true) const; /// Print the ID of the block with \p Ctx, bbN. - void printID(SILPrintContext &Ctx) const; + void printID(SILPrintContext &Ctx, bool newline = true) const; #endif /// getSublistAccess() - returns pointer to member of instruction list diff --git a/lib/SIL/IR/SILPrinter.cpp b/lib/SIL/IR/SILPrinter.cpp index 578b6bfb85f75..6636478e42673 100644 --- a/lib/SIL/IR/SILPrinter.cpp +++ b/lib/SIL/IR/SILPrinter.cpp @@ -853,8 +853,11 @@ class SILPrinter : public SILInstructionVisitor { } #ifndef NDEBUG - void printID(const SILBasicBlock *BB) { - *this << Ctx.getID(BB) << "\n"; + void printID(const SILBasicBlock *BB, bool newline) { + *this << Ctx.getID(BB); + if (newline) { + *this << "\n"; + } } #endif @@ -3108,17 +3111,17 @@ void SILBasicBlock::print(SILPrintContext &Ctx) const { } #ifndef NDEBUG -void SILBasicBlock::dumpID() const { - printID(llvm::errs()); +void SILBasicBlock::dumpID(bool newline) const { + printID(llvm::errs(), newline); } -void SILBasicBlock::printID(llvm::raw_ostream &OS) const { +void SILBasicBlock::printID(llvm::raw_ostream &OS, bool newline) const { SILPrintContext Ctx(OS); - printID(Ctx); + printID(Ctx, newline); } -void SILBasicBlock::printID(SILPrintContext &Ctx) const { - SILPrinter(Ctx).printID(this); +void SILBasicBlock::printID(SILPrintContext &Ctx, bool newline) const { + SILPrinter(Ctx).printID(this, newline); } #endif From 75c36d8ed1b8b6a84867e2bb223f12f3abbb8f04 Mon Sep 17 00:00:00 2001 From: Nate Chandler Date: Wed, 4 Oct 2023 16:22:22 -0700 Subject: [PATCH 2/3] [CanOSSALifetime] Record "unreachable" insts. OSSALifetimeCompletion needs to insert not at unreachable instructions that appear after the non-lifetime-ending boundary of a value but rather at the terminators of the availability boundary of the value within that region. Once it does so, it will no longer be sufficient to check whether the insertion point is an unreachable because such terminators may be another terminator that appears on the availability boundary. Prepare for that by recording the instructions that were found and checking whether the destroy insertion point is such an instruction before bailing rather than specifically checking for `unreachable`. --- .../Utils/CanonicalizeOSSALifetime.h | 16 ++++++++++++++++ .../Utils/CanonicalizeOSSALifetime.cpp | 9 ++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/include/swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h b/include/swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h index 2563e4829354f..cc45ed55e4594 100644 --- a/include/swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h +++ b/include/swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h @@ -136,6 +136,10 @@ class CanonicalOSSAConsumeInfo final { /// Map blocks on the lifetime boundary to the last consuming instruction. llvm::SmallDenseMap finalBlockConsumes; + /// The instructions on the availability boundary of the dead-end region where + /// this value is not consumed. + SmallPtrSet unreachableLifetimeEnds; + public: void clear() { finalBlockConsumes.clear(); } @@ -161,6 +165,14 @@ class CanonicalOSSAConsumeInfo final { return false; } + void recordUnreachableLifetimeEnd(SILInstruction *inst) { + unreachableLifetimeEnds.insert(inst); + } + + bool isUnreachableLifetimeEnd(SILInstruction *inst) { + return unreachableLifetimeEnds.contains(inst); + } + CanonicalOSSAConsumeInfo() {} CanonicalOSSAConsumeInfo(CanonicalOSSAConsumeInfo const &) = delete; CanonicalOSSAConsumeInfo & @@ -409,6 +421,10 @@ class CanonicalizeOSSALifetime final { void recordConsumingUser(SILInstruction *user) { consumingBlocks.insert(user->getParent()); } + void recordUnreachableLifetimeEnd(SILInstruction *user) { + recordConsumingUser(user); + consumes.recordUnreachableLifetimeEnd(user); + } bool computeCanonicalLiveness(); bool endsAccessOverlappingPrunedBoundary(SILInstruction *inst); diff --git a/lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp b/lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp index 2f6f5f31fdb47..531f8dd0d9c2c 100644 --- a/lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp +++ b/lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp @@ -261,7 +261,7 @@ void CanonicalizeOSSALifetime::extendLivenessToDeinitBarriers() { OSSALifetimeCompletion::visitUnreachableLifetimeEnds( getCurrentDef(), completeLiveness, [&](auto *unreachable) { - recordConsumingUser(unreachable); + recordUnreachableLifetimeEnd(unreachable); if (auto *previous = unreachable->getPreviousInstruction()) { if (liveness->isInterestingUser(previous) == PrunedLiveness::IsInterestingUser::NonUser) { @@ -923,8 +923,11 @@ static void insertDestroyBeforeInstruction(SILInstruction *nextInstruction, InstModCallbacks &callbacks) { // OSSALifetimeCompletion: This conditional clause can be deleted with // complete lifetimes. - if (isa(nextInstruction)) { - // Don't create a destroy_value if the next instruction is an unreachable. + if (consumes.isUnreachableLifetimeEnd(nextInstruction)) { + // Don't create a destroy_value if the next instruction is an unreachable + // (or a terminator on the availability boundary of the dead-end region + // starting from the non-lifetime-ending boundary of `currentDef`). + // // If there was a destroy here already, it would be reused. Avoids // creating an explicit destroy of a value which might have an unclosed // borrow scope. Doing so would result in From 983eaba6da67b3a335c710e1e1bbc82a56194c14 Mon Sep 17 00:00:00 2001 From: Nate Chandler Date: Wed, 4 Oct 2023 16:04:23 -0700 Subject: [PATCH 3/3] [OSSALifetimeCompletion] Handle available boundary Not every block in a region which begins with the non-lifetime-ending boundary of a value and ending with unreachable-terminated blocks has the value available. If the unreachable-terminated blocks in this boundary are not available, it is incorrect to insert destroys of the value in them: it is an overconsume on some paths. Previously, however, destroys were simply being inserted at the unreachable. Here, this is fixed by finding the boundary of availability within that region and inserting destroys before the terminators of the blocks on that boundary. rdar://116255254 --- include/swift/SIL/OSSALifetimeCompletion.h | 2 +- .../Utils/CanonicalizeOSSALifetime.h | 14 +- lib/SIL/Utils/OSSALifetimeCompletion.cpp | 206 ++++++++++++++++-- test/SILOptimizer/mem2reg_lifetime.sil | 3 +- .../SILOptimizer/ossa_lifetime_completion.sil | 186 ++++++++++++++++ 5 files changed, 391 insertions(+), 20 deletions(-) diff --git a/include/swift/SIL/OSSALifetimeCompletion.h b/include/swift/SIL/OSSALifetimeCompletion.h index 9098fdd34b29b..cc33d41be545c 100644 --- a/include/swift/SIL/OSSALifetimeCompletion.h +++ b/include/swift/SIL/OSSALifetimeCompletion.h @@ -87,7 +87,7 @@ class OSSALifetimeCompletion { static void visitUnreachableLifetimeEnds( SILValue value, const SSAPrunedLiveness &liveness, - llvm::function_ref visit); + llvm::function_ref visit); protected: bool analyzeAndUpdateLifetime(SILValue value, bool forceBoundaryCompletion); diff --git a/include/swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h b/include/swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h index cc45ed55e4594..be89565244781 100644 --- a/include/swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h +++ b/include/swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h @@ -247,9 +247,12 @@ class CanonicalizeOSSALifetime final { SILValue currentDef; /// Original points in the CFG where the current value's lifetime is consumed - /// or destroyed. For guaranteed values it remains empty. A backward walk from - /// these blocks must discover all uses on paths that lead to a return or - /// throw. + /// or destroyed. Each block either contains a consuming instruction (e.g. + /// `destroy_value`) or is on the availability boundary of the value in a + /// dead-end region (e.g. `unreachable`). + /// + /// For guaranteed values it remains empty. A backward walk from these blocks + /// must discover all uses on paths that lead to a return or throw. /// /// These blocks are not necessarily in the pruned live blocks since /// pruned liveness does not consider destroy_values. @@ -418,6 +421,11 @@ class CanonicalizeOSSALifetime final { void recordDebugValue(DebugValueInst *dvi) { debugValues.insert(dvi); } void recordConsumingUse(Operand *use) { recordConsumingUser(use->getUser()); } + /// Record that the value is consumed at `user`. + /// + /// Either `user` is a consuming use (e.g. `destroy_value`) or it is the + /// terminator of a block on the availability boundary of the value in a + /// dead-end region (e.g. `unreachable`). void recordConsumingUser(SILInstruction *user) { consumingBlocks.insert(user->getParent()); } diff --git a/lib/SIL/Utils/OSSALifetimeCompletion.cpp b/lib/SIL/Utils/OSSALifetimeCompletion.cpp index 94df9612284ab..1eafcca87caa2 100644 --- a/lib/SIL/Utils/OSSALifetimeCompletion.cpp +++ b/lib/SIL/Utils/OSSALifetimeCompletion.cpp @@ -51,8 +51,10 @@ #include "swift/SIL/OSSALifetimeCompletion.h" #include "swift/SIL/SILBuilder.h" +#include "swift/SIL/SILFunction.h" #include "swift/SIL/SILInstruction.h" #include "swift/SIL/Test.h" +#include "llvm/ADT/STLExtras.h" using namespace swift; @@ -99,40 +101,217 @@ static bool endLifetimeAtBoundary(SILValue value, return changed; } -void OSSALifetimeCompletion::visitUnreachableLifetimeEnds( - SILValue value, const SSAPrunedLiveness &liveness, - llvm::function_ref visit) { +namespace { +/// Implements OSSALifetimeCompletion::visitUnreachableLifetimeEnds. Finds +/// positions as near as possible to unreachables at which `value`'s lifetime +/// is available. +/// +/// Finding these positions is a three step process: +/// 1) computeRegion: Forward CFG walk from non-lifetime-ending boundary to find +/// the dead-end region in which the value might be available. +/// 2) propagateAvailability: Forward iterative dataflow within the region to +/// determine which blocks the value is available in. +/// 3) visitAvailabilityBoundary: Visits the final blocks in the region where +/// the value is available--these are the blocks +/// without successors or with at least one +/// unavailable successor. +class VisitUnreachableLifetimeEnds { + /// The value whose dead-end block lifetime ends are to be visited. + SILValue value; + + /// The non-lifetime-ending boundary of `value`. + BasicBlockSet starts; + /// The region between (inclusive) the `starts` and the unreachable blocks. + BasicBlockSetVector region; + +public: + VisitUnreachableLifetimeEnds(SILValue value) + : value(value), starts(value->getFunction()), + region(value->getFunction()) {} + + /// Region discovery. + /// + /// Forward CFG walk from non-lifetime-ending boundary to unreachable + /// instructions. + void computeRegion(const SSAPrunedLiveness &liveness); + + struct Result; + + /// Iterative dataflow to determine availability for each block in `region`. + void propagateAvailablity(Result &result); + + /// Visit the terminators of blocks on the boundary of availability. + void + visitAvailabilityBoundary(Result const &result, + llvm::function_ref visit); + + struct State { + enum class Value : uint8_t { + Unavailable = 0, + Available, + Unknown, + }; + Value value; + + State(Value value) : value(value){}; + operator Value() const { return value; } + State meet(State const other) const { + return *this < other ? *this : other; + } + + static State Unavailable() { return {Value::Unavailable}; } + static State Available() { return {Value::Available}; } + static State Unknown() { return {Value::Unknown}; } + }; + + struct Result { + BasicBlockBitfield states; + + Result(SILFunction *function) : states(function, 2) {} + + State getState(SILBasicBlock *block) const { + return {(State::Value)states.get(block)}; + } + + void setState(SILBasicBlock *block, State newState) { + states.set(block, (unsigned)newState.value); + } + + /// Propagate predecessors' state into `block`. + /// + /// states[block] ∧= state[predecessor_1] ∧ ... ∧ state[predecessor_n] + bool updateState(SILBasicBlock *block) { + auto oldState = getState(block); + auto state = oldState; + for (auto *predecessor : block->getPredecessorBlocks()) { + state = state.meet(getState(predecessor)); + } + setState(block, state); + return state != oldState; + } + }; +}; + +void VisitUnreachableLifetimeEnds::computeRegion( + const SSAPrunedLiveness &liveness) { + // Find the non-lifetime-ending boundary of `value`. PrunedLivenessBoundary boundary; liveness.computeBoundary(boundary); - BasicBlockWorklist deadEndBlocks(value->getFunction()); for (SILInstruction *lastUser : boundary.lastUsers) { if (liveness.isInterestingUser(lastUser) != PrunedLiveness::LifetimeEndingUse) { - deadEndBlocks.push(lastUser->getParent()); + region.insert(lastUser->getParent()); + starts.insert(lastUser->getParent()); } } for (SILBasicBlock *edge : boundary.boundaryEdges) { - deadEndBlocks.push(edge); + region.insert(edge); + starts.insert(edge); } for (SILNode *deadDef : boundary.deadDefs) { - deadEndBlocks.push(deadDef->getParentBlock()); + region.insert(deadDef->getParentBlock()); + starts.insert(deadDef->getParentBlock()); + } + + // Forward walk to find the region in which `value` might be available. + BasicBlockWorklist regionWorklist(value->getFunction()); + // Start the forward walk from the non-lifetime-ending boundary. + for (auto *start : region) { + regionWorklist.push(start); } - // Forward CFG walk from the non-lifetime-ending boundary to the unreachable - // instructions. - while (auto *block = deadEndBlocks.pop()) { + while (auto *block = regionWorklist.pop()) { if (block->succ_empty()) { // This assert will fail unless there are already lifetime-ending // instruction on all paths to normal function exits. - auto *unreachable = cast(block->getTerminator()); - visit(unreachable); + assert(isa(block->getTerminator())); } for (auto *successor : block->getSuccessorBlocks()) { - deadEndBlocks.pushIfNotVisited(successor); + regionWorklist.pushIfNotVisited(successor); + region.insert(successor); } } } +void VisitUnreachableLifetimeEnds::propagateAvailablity(Result &result) { + // Initialize per-block state. + // - all blocks outside of the region are ::Unavailable (automatically + // initialized) + // - non-initial in-region blocks are Unknown + // - start blocks are ::Available + for (auto *block : region) { + if (starts.contains(block)) + result.setState(block, State::Available()); + else + result.setState(block, State::Unknown()); + } + + BasicBlockWorklist worklist(value->getFunction()); + + // Initialize worklist with all participating blocks. + // + // Only perform dataflow in the non-initial region. Every initial block is + // by definition ::Available. + for (auto *block : region) { + if (starts.contains(block)) + continue; + worklist.push(block); + } + + // Iterate over blocks which are successors of blocks whose state changed. + while (auto *block = worklist.popAndForget()) { + // Only propagate availability in non-initial, in-region blocks. + if (!region.contains(block) || starts.contains(block)) + continue; + auto changed = result.updateState(block); + if (!changed) { + continue; + } + // The state has changed. Propagate the new state into successors. + for (auto *successor : block->getSuccessorBlocks()) { + worklist.pushIfNotVisited(successor); + } + } +} + +void VisitUnreachableLifetimeEnds::visitAvailabilityBoundary( + Result const &result, llvm::function_ref visit) { + for (auto *block : region) { + auto available = result.getState(block) == State::Available(); + if (!available) { + continue; + } + auto hasUnreachableSuccessor = [&]() { + // Use a lambda to avoid checking if possible. + return llvm::any_of(block->getSuccessorBlocks(), [&result](auto *block) { + return result.getState(block) == State::Unavailable(); + }); + }; + if (!block->succ_empty() && !hasUnreachableSuccessor()) { + continue; + } + assert(hasUnreachableSuccessor() || + isa(block->getTerminator())); + visit(block->getTerminator()); + } +} +} // end anonymous namespace + +void OSSALifetimeCompletion::visitUnreachableLifetimeEnds( + SILValue value, const SSAPrunedLiveness &liveness, + llvm::function_ref visit) { + + VisitUnreachableLifetimeEnds visitor(value); + + visitor.computeRegion(liveness); + + VisitUnreachableLifetimeEnds::Result result(value->getFunction()); + + visitor.propagateAvailablity(result); + + visitor.visitAvailabilityBoundary(result, visit); +} + static bool endLifetimeAtUnreachableBlocks(SILValue value, const SSAPrunedLiveness &liveness) { bool changed = false; @@ -274,4 +453,3 @@ bool UnreachableLifetimeCompletion::completeLifetimes() { } return changed; } - diff --git a/test/SILOptimizer/mem2reg_lifetime.sil b/test/SILOptimizer/mem2reg_lifetime.sil index de47163b0f08e..5a905824290db 100644 --- a/test/SILOptimizer/mem2reg_lifetime.sil +++ b/test/SILOptimizer/mem2reg_lifetime.sil @@ -1858,8 +1858,7 @@ right: // CHECK: {{bb[^,]+}}([[INSTANCE:%[^,]+]] : @owned $C): // CHECK: cond_br undef, [[BB1:bb[0-9]+]], [[BB4:bb[0-9]+]] // CHECK: [[BB1]]: -// CHECK: [[COPY:%[^,]+]] = copy_value [[INSTANCE]] -// CHECK: [[LIFETIME_OWNED:%[^,]+]] = move_value [lexical] [[COPY]] +// CHECK: [[LIFETIME_OWNED:%[^,]+]] = move_value [lexical] [[INSTANCE]] // CHECK: cond_br undef, [[BB2:bb[0-9]+]], [[BB3:bb[0-9]+]] // CHECK: [[BB2]]: // CHECK: destroy_value [[LIFETIME_OWNED]] diff --git a/test/SILOptimizer/ossa_lifetime_completion.sil b/test/SILOptimizer/ossa_lifetime_completion.sil index 744965f1d923f..4a16670859391 100644 --- a/test/SILOptimizer/ossa_lifetime_completion.sil +++ b/test/SILOptimizer/ossa_lifetime_completion.sil @@ -212,3 +212,189 @@ bb0(%0 : @guaranteed $C): return %12 : $() } +// Insert destroy on availability boundary of `value` within the region after +// the non-lifetime-ending boundary of `value`. Namely, in to_die_11. +// CHECK-LABEL: sil [ossa] @availability_boundary_1 : {{.*}} { +// CHECK: [[VALUE:%[^,]+]] = move_value [lexical] +// CHECK: br [[CONDITION_1:bb[0-9]+]] +// CHECK: [[CONDITION_1]]: +// CHECK: cond_br undef, [[CONDITION_2:bb[0-9]+]], [[TO_DIE_1:bb[0-9]+]] +// CHECK: [[CONDITION_2]]: +// CHECK: destroy_value [[VALUE]] +// CHECK: cond_br undef, [[EXIT:bb[0-9]+]], [[TO_DIE_2:bb[0-9]+]] +// CHECK: [[TO_DIE_1]]: +// CHECK: br [[TO_DIE_11:bb[0-9]+]] +// CHECK: [[TO_DIE_11]]: +// CHECK: destroy_value [[VALUE]] +// CHECK: br [[DIE:bb[0-9]+]] +// CHECK: [[TO_DIE_2]]: +// CHECK: br [[DIE]] +// CHECK: [[DIE]]: +// CHECK: unreachable +// CHECK: [[EXIT]]: +// CHECK-LABEL: } // end sil function 'availability_boundary_1' +sil [ossa] @availability_boundary_1 : $@convention(thin) () -> () { +entry: + %value = apply undef() : $@convention(thin) () -> @owned C + %lexical = move_value [lexical] %value : $C // required (for lexicality) + debug_value [trace] %lexical : $C + test_specification "ossa-lifetime-completion @trace[0]" + br condition_1 + +condition_1: + cond_br undef, condition_2, to_die_1 + +condition_2: + destroy_value %lexical : $C + cond_br undef, exit, to_die_2 + +to_die_1: + br to_die_11 + +to_die_11: + // End lifetime here. + br die + +to_die_2: + br die + +die: + unreachable + +exit: + %retval = tuple () + return %retval : $() +} + +// CHECK-LABEL: sil [ossa] @availability_boundary_2_after_loop : {{.*}} { +// CHECK: [[REGISTER_1:%[^,]+]] = move_value [lexical] +// CHECK: br [[CONDITION_1:bb[0-9]+]] +// CHECK: [[CONDITION_1]]: +// CHECK: cond_br undef, [[CONDITION_2:bb[0-9]+]], [[PREHEADER:bb[0-9]+]] +// CHECK: [[CONDITION_2]]: +// CHECK: destroy_value [[REGISTER_1]] +// CHECK: cond_br undef, [[EXIT:bb[0-9]+]], [[TO_DIE_2:bb[0-9]+]] +// CHECK: [[PREHEADER]]: +// CHECK: br [[HEADER:bb[0-9]+]] +// CHECK: [[HEADER]]: +// CHECK: br [[LATCH:bb[0-9]+]] +// CHECK: [[LATCH]]: +// CHECK: cond_br undef, [[BACKEDGE:bb[0-9]+]], [[TO_DIE_1:bb[0-9]+]] +// CHECK: [[BACKEDGE]]: +// CHECK: br [[HEADER]] +// CHECK: [[TO_DIE_1]]: +// CHECK: destroy_value [[REGISTER_1]] +// CHECK: br [[DIE:bb[0-9]+]] +// CHECK: [[TO_DIE_2]]: +// CHECK: br [[DIE]] +// CHECK: [[DIE]]: +// CHECK: unreachable +// CHECK: [[EXIT]]: +// CHECK-LABEL: } // end sil function 'availability_boundary_2_after_loop' +sil [ossa] @availability_boundary_2_after_loop : $@convention(thin) () -> () { +entry: + %value = apply undef() : $@convention(thin) () -> @owned C + %lexical = move_value [lexical] %value : $C // required (for lexicality) + debug_value [trace] %lexical : $C + test_specification "ossa-lifetime-completion @trace[0]" + br condition_1 + +condition_1: + cond_br undef, condition_2, preheader + +condition_2: + destroy_value %lexical : $C + cond_br undef, exit, to_die_2 + +preheader: + br header + +header: + br latch + +latch: + cond_br undef, backedge, to_die_1 + +backedge: + br header + +to_die_1: + br die + +to_die_2: + br die + +die: + unreachable + +exit: + %retval = tuple () + return %retval : $() +} + +// CHECK-LABEL: sil [ossa] @availability_boundary_3_after_loop : {{.*}} { +// CHECK: [[VALUE:%[^,]+]] = move_value [lexical] +// CHECK: br [[CONDITION_1:bb[0-9]+]] +// CHECK: [[CONDITION_1]]: +// CHECK: cond_br undef, [[CONDITION_2:bb[0-9]+]], [[PREHEADER:bb[0-9]+]] +// CHECK: [[CONDITION_2]]: +// CHECK: destroy_value [[VALUE]] +// CHECK: cond_br undef, [[EXIT:bb[0-9]+]], [[TO_BODY:bb[0-9]+]] +// CHECK: [[TO_BODY]]: +// CHECK: br [[BODY:bb[0-9]+]] +// CHECK: [[PREHEADER]]: +// CHECK: destroy_value [[VALUE]] +// CHECK: br [[HEADER:bb[0-9]+]] +// CHECK: [[HEADER]]: +// CHECK: br [[BODY]] +// CHECK: [[BODY]]: +// CHECK: br [[LATCH:bb[0-9]+]] +// CHECK: [[LATCH]]: +// CHECK: cond_br undef, [[BACKEDGE:bb[0-9]+]], [[DIE:bb[0-9]+]] +// CHECK: [[BACKEDGE]]: +// CHECK: br [[HEADER]] +// CHECK: [[DIE]]: +// CHECK: unreachable +// CHECK: [[EXIT]]: +// CHECK-LABEL: } // end sil function 'availability_boundary_3_after_loop' +sil [ossa] @availability_boundary_3_after_loop : $@convention(thin) () -> () { +entry: + %value = apply undef() : $@convention(thin) () -> @owned C + %lexical = move_value [lexical] %value : $C // required (for lexicality) + debug_value [trace] %lexical : $C + test_specification "ossa-lifetime-completion @trace[0]" + br condition_1 + +condition_1: + cond_br undef, condition_2, preheader + +condition_2: + destroy_value %lexical : $C + cond_br undef, exit, to_body2 + +to_body2: + br body + +preheader: + // End lifetime here. + br header + +header: + br body + +body: + br latch + +latch: + cond_br undef, backedge, die + +backedge: + br header + +die: + unreachable + +exit: + %retval = tuple () + return %retval : $() +}