diff --git a/include/swift/SIL/BasicBlockDatastructures.h b/include/swift/SIL/BasicBlockDatastructures.h index de399f03446a2..45ca56c3a9e5d 100644 --- a/include/swift/SIL/BasicBlockDatastructures.h +++ b/include/swift/SIL/BasicBlockDatastructures.h @@ -83,6 +83,9 @@ class BasicBlockWorklist { push(initialBlock); } + /// Whether there are any remaining blocks to process. + bool empty() { return worklist.empty(); } + /// Pops the last added element from the worklist or returns null, if the /// worklist is empty. SILBasicBlock *pop() { diff --git a/include/swift/SIL/SILFunction.h b/include/swift/SIL/SILFunction.h index 47a6c28f7db33..0fa722760a1ac 100644 --- a/include/swift/SIL/SILFunction.h +++ b/include/swift/SIL/SILFunction.h @@ -1565,6 +1565,17 @@ class SILFunction } } + /// Populate \p output with every block terminated by an unreachable + /// instruction. + void visitUnreachableTerminatedBlocks( + llvm::function_ref visitor) const { + for (auto &block : const_cast(*this)) { + if (isa(block.getTerminator())) { + visitor(block); + } + } + } + //===--------------------------------------------------------------------===// // Argument Helper Methods //===--------------------------------------------------------------------===// diff --git a/include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h b/include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h index abdd9bad72f79..d7402cc809e05 100644 --- a/include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h +++ b/include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h @@ -38,10 +38,32 @@ class SILLoopInfo; /// Compute the set of reachable blocks. class ReachableBlocks { BasicBlockSet visited; + bool isComputed; public: - ReachableBlocks(SILFunction *function) : visited(function) {} + ReachableBlocks(SILFunction *function) + : visited(function), isComputed(false) {} + /// Populate `visited` with the blocks reachable in the function. + void compute(); + + /// Whether `block` is reachable from the entry block. + bool isReachable(SILBasicBlock *block) const { + assert(isComputed); + return visited.contains(block); + } + + bool hasUnreachableBlocks() const { + assert(isComputed); + for (auto &block : *visited.getFunction()) { + if (!isReachable(&block)) { + return true; + } + } + return false; + } + +private: /// Invoke \p visitor for each reachable block in \p f in worklist order (at /// least one predecessor has been visited--defs are always visited before /// uses except for phi-type block args). The \p visitor takes a block @@ -50,9 +72,6 @@ class ReachableBlocks { /// /// Returns true if all reachable blocks were visited. bool visit(function_ref visitor); - - /// Return true if \p bb has been visited. - bool isVisited(SILBasicBlock *bb) const { return visited.contains(bb); } }; /// Computes the set of blocks from which a path to the return-block exists. diff --git a/lib/SILOptimizer/Mandatory/ClosureLifetimeFixup.cpp b/lib/SILOptimizer/Mandatory/ClosureLifetimeFixup.cpp index dacb5479a59b6..3fce080a9a451 100644 --- a/lib/SILOptimizer/Mandatory/ClosureLifetimeFixup.cpp +++ b/lib/SILOptimizer/Mandatory/ClosureLifetimeFixup.cpp @@ -587,8 +587,7 @@ static SILValue tryRewriteToPartialApplyStack( ConvertEscapeToNoEscapeInst *cvt, SILInstruction *closureUser, DominanceAnalysis *dominanceAnalysis, InstructionDeleter &deleter, llvm::DenseMap &memoized, - llvm::DenseSet &unreachableBlocks, - const bool &modifiedCFG) { + ReachableBlocks const &reachableBlocks, const bool &modifiedCFG) { auto *origPA = dyn_cast(skipConvert(cvt->getOperand())); if (!origPA) @@ -972,8 +971,8 @@ static SILValue tryRewriteToPartialApplyStack( // Don't run insertDeallocOfCapturedArguments if newPA is in an unreachable // block insertDeallocOfCapturedArguments will run code that computes the DF - // for newPA that will loop infinetly. - if (unreachableBlocks.count(newPA->getParent())) + // for newPA that will loop infinitely. + if (!reachableBlocks.isReachable(newPA->getParent())) return closureOp; auto getAddressToDealloc = [&](SILValue argAddress) -> SILValue { @@ -995,8 +994,8 @@ static bool tryExtendLifetimeToLastUse( ConvertEscapeToNoEscapeInst *cvt, DominanceAnalysis *dominanceAnalysis, DeadEndBlocksAnalysis *deadEndBlocksAnalysis, llvm::DenseMap &memoized, - llvm::DenseSet &unreachableBlocks, - InstructionDeleter &deleter, const bool &modifiedCFG) { + ReachableBlocks const &reachableBlocks, InstructionDeleter &deleter, + const bool &modifiedCFG) { // If there is a single user, this is simple: extend the // lifetime of the operand until the use ends. auto *singleUser = lookThroughRebastractionUsers(cvt, memoized); @@ -1019,7 +1018,7 @@ static bool tryExtendLifetimeToLastUse( if (SILValue closureOp = tryRewriteToPartialApplyStack( cvt, singleUser, dominanceAnalysis, deleter, memoized, - unreachableBlocks, /*const*/ modifiedCFG)) { + reachableBlocks, /*const*/ modifiedCFG)) { if (endAsyncLet) { // Add the closure as a second operand to the endAsyncLet builtin. // This ensures that the closure arguments are kept alive until the @@ -1428,22 +1427,6 @@ static bool fixupCopyBlockWithoutEscaping(CopyBlockWithoutEscapingInst *cb, return true; } -static void computeUnreachableBlocks( - llvm::DenseSet &unreachableBlocks, - SILFunction &fn) { - - ReachableBlocks isReachable(&fn); - llvm::DenseSet reachable; - isReachable.visit([&] (SILBasicBlock *block) -> bool { - reachable.insert(block); - return true; - }); - for (auto &block : fn) { - if (!reachable.count(&block)) - unreachableBlocks.insert(&block); - } -} - static bool fixupClosureLifetimes(SILFunction &fn, DominanceAnalysis *dominanceAnalysis, DeadEndBlocksAnalysis *deadEndBlocksAnalysis, @@ -1454,8 +1437,8 @@ static bool fixupClosureLifetimes(SILFunction &fn, // queries. llvm::DenseMap memoizedQueries; - llvm::DenseSet unreachableBlocks; - computeUnreachableBlocks(unreachableBlocks, fn); + ReachableBlocks reachableBlocks(&fn); + reachableBlocks.compute(); for (auto &block : fn) { SILSSAUpdater updater; @@ -1485,7 +1468,7 @@ static bool fixupClosureLifetimes(SILFunction &fn, if (tryExtendLifetimeToLastUse(cvt, dominanceAnalysis, deadEndBlocksAnalysis, memoizedQueries, - unreachableBlocks, updater.getDeleter(), + reachableBlocks, updater.getDeleter(), /*const*/ modifiedCFG)) { changed = true; checkStackNesting = true; diff --git a/lib/SILOptimizer/Mandatory/SILGenCleanup.cpp b/lib/SILOptimizer/Mandatory/SILGenCleanup.cpp index 36358374636c7..4cc9a2d291b96 100644 --- a/lib/SILOptimizer/Mandatory/SILGenCleanup.cpp +++ b/lib/SILOptimizer/Mandatory/SILGenCleanup.cpp @@ -18,6 +18,8 @@ #include "swift/Basic/Assertions.h" #include "swift/Basic/Defer.h" +#include "swift/SIL/BasicBlockBits.h" +#include "swift/SIL/BasicBlockDatastructures.h" #include "swift/SIL/BasicBlockUtils.h" #include "swift/SIL/OSSALifetimeCompletion.h" #include "swift/SIL/PrettyStackTrace.h" @@ -26,8 +28,10 @@ #include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h" #include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h" #include "swift/SILOptimizer/PassManager/Transforms.h" +#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h" #include "swift/SILOptimizer/Utils/CanonicalizeInstruction.h" #include "swift/SILOptimizer/Utils/InstOptUtils.h" +#include "llvm/ADT/PostOrderIterator.h" using namespace swift; @@ -104,39 +108,222 @@ struct SILGenCleanup : SILModuleTransform { void run() override; bool completeOSSALifetimes(SILFunction *function); + template + bool completeLifetimesInRange(Range const &range, + OSSALifetimeCompletion &completion, + BasicBlockSet &completed); }; +// Iterate over `iterator` until finding a block in `include` and not in +// `exclude`. +SILBasicBlock * +findFirstBlock(SILFunction *function, SILFunction::iterator &iterator, + llvm::function_ref include, + llvm::function_ref exclude) { + while (iterator != function->end()) { + auto *block = &*iterator; + iterator = std::next(iterator); + if (!include(block)) + continue; + if (exclude(block)) + continue; + return block; + } + return nullptr; +} + +// Walk backward from `from` following first predecessors until finding the +// first already-reached block. +SILBasicBlock *findFirstLoop(SILFunction *function, SILBasicBlock *from) { + BasicBlockSet path(function); + auto *current = from; + while (auto *block = current) { + current = nullptr; + if (!path.insert(block)) { + return block; + } + if (block->pred_empty()) { + return nullptr; + } + current = *block->getPredecessorBlocks().begin(); + } + llvm_unreachable("finished function-exiting loop!?"); +} + +/// Populate `roots` with the last blocks that are discovered via backwards +/// walks along any non-repeating paths starting at the ends in `backward`. +void collectReachableRoots(SILFunction *function, BasicBlockWorklist &backward, + StackList &roots) { + assert(!backward.empty()); + assert(roots.empty()); + + // Always include the entry block as a root. Currently SILGen will emit + // consumes in unreachable blocks of values defined in reachable blocks (e.g. + // test/SILGen/unreachable_code.swift:testUnreachableCatchClause). + // TODO: [fix_silgen_destroy_unreachable] Fix SILGen not to emit such + // destroys and don't add the entry + // block to roots here. + roots.push_back(function->getEntryBlock()); + + // First, find all blocks backwards-reachable from dead-end blocks. + while (auto *block = backward.pop()) { + for (auto *predecessor : block->getPredecessorBlocks()) { + backward.pushIfNotVisited(predecessor); + } + } + + // Simple case: unpredecessored blocks. + // + // Every unpredecessored block reachable from some dead-end block is a root. + for (auto &block : *function) { + if (&block == function->getEntryBlock()) { + // TODO: [fix_silgen_destroy_unreachable] Remove this condition. + continue; + } + if (!block.pred_empty()) + continue; + if (!backward.isVisited(&block)) + continue; + roots.push_back(&block); + } + + // Complex case: unreachable loops. + // + // Iteratively (the first time, these are the roots discovered in "Simple + // case" above), determine which blocks are forward-reachable from roots. + // Then, look for a block that is backwards-reachable from dead-end blocks + // but not forwards-reachable from roots so far discovered. If one is found, + // it is forwards-reachable from an unreachable loop. Walk backwards from + // that block to find a representative block in the loop. Add that + // representative block to roots and iterate. If no such block is found, all + // roots have been found. + BasicBlockWorklist forward(function); + for (auto *root : roots) { + forward.push(root); + } + bool changed = false; + auto iterator = function->begin(); + do { + changed = false; + + // Propagate forward-reachability from roots discovered so far. + while (auto *block = forward.pop()) { + for (auto *successor : block->getSuccessorBlocks()) { + forward.pushIfNotVisited(successor); + } + } + // Any block in `backward` but not in `forward` is forward-reachable from an + // unreachable loop. + if (auto *target = findFirstBlock( + function, iterator, /*include=*/ + [&backward](auto *block) { return backward.isVisited(block); }, + /*exclude=*/ + [&forward](auto *block) { return forward.isVisited(block); })) { + // Find the first unreachable loop it's forwards-reachable from. + auto *loop = findFirstLoop(function, target); + ASSERT(loop); + forward.push(loop); + roots.push_back(loop); + changed = true; + } + } while (changed); +} + bool SILGenCleanup::completeOSSALifetimes(SILFunction *function) { if (!getModule()->getOptions().OSSACompleteLifetimes) return false; + LLVM_DEBUG(llvm::dbgs() << "Completing lifetimes in " << function->getName() + << "\n"); + + BasicBlockWorklist deadends(function); + DeadEndBlocks *deba = getAnalysis()->get(function); + for (auto &block : *function) { + if (deba->isDeadEnd(&block)) + deadends.push(&block); + } + + if (deadends.empty()) { + // There are no dead-end blocks, so there are no lifetimes to complete. + // (SILGen may emit incomplete lifetimes, but not underconsumed lifetimes.) + return false; + } + + // Lifetimes must be completed in unreachable blocks that are reachable via + // backwards walk from dead-end blocks. First, check whether there are any + // unreachable blocks. + ReachableBlocks reachableBlocks(function); + reachableBlocks.compute(); + StackList roots(function); + if (!reachableBlocks.hasUnreachableBlocks()) { + // There are no blocks that are unreachable from the entry block. Thus + // every block will be completed when completing the post-order of the + // entry block. + roots.push_back(function->getEntryBlock()); + } else { + // There are unreachable blocks. Determine the roots that can be reached + // when walking from the unreachable blocks. + collectReachableRoots(function, deadends, roots); + } + bool changed = false; + OSSALifetimeCompletion completion(function, /*DomInfo*/ nullptr, *deba); + BasicBlockSet completed(function); + for (auto *root : roots) { + if (root == function->getEntryBlock()) { + assert(!completed.contains(root)); + // When completing from the entry block, prefer the PostOrderAnalysis so + // the result is cached. + PostOrderFunctionInfo *postOrder = + getAnalysis()->get(function); + changed |= completeLifetimesInRange(postOrder->getPostOrder(), completion, + completed); + } + if (completed.contains(root)) { + // This block has already been completed in some other post-order + // traversal. Thus the entire post-order rooted at it has already been + // completed. + continue; + } + changed |= completeLifetimesInRange( + make_range(po_begin(root), po_end(root)), completion, completed); + } + function->verifyOwnership(/*deadEndBlocks=*/nullptr); + return changed; +} - // Lifetimes must be completed inside out (bottom-up in the CFG). - PostOrderFunctionInfo *postOrder = - getAnalysis()->get(function); - DeadEndBlocks *deb = getAnalysis()->get(function); - OSSALifetimeCompletion completion(function, /*DomInfo*/ nullptr, *deb); - for (auto *block : postOrder->getPostOrder()) { +template +bool SILGenCleanup::completeLifetimesInRange(Range const &range, + OSSALifetimeCompletion &completion, + BasicBlockSet &completed) { + bool changed = false; + for (auto *block : range) { + if (!completed.insert(block)) + continue; + LLVM_DEBUG(llvm::dbgs() + << "Completing lifetimes in bb" << block->getDebugID() << "\n"); for (SILInstruction &inst : reverse(*block)) { for (auto result : inst.getResults()) { + LLVM_DEBUG(llvm::dbgs() << "completing " << result << "\n"); if (completion.completeOSSALifetime( result, OSSALifetimeCompletion::Boundary::Availability) == LifetimeCompletion::WasCompleted) { + LLVM_DEBUG(llvm::dbgs() << "\tcompleted!\n"); changed = true; } } } for (SILArgument *arg : block->getArguments()) { + LLVM_DEBUG(llvm::dbgs() << "completing " << *arg << "\n"); assert(!arg->isReborrow() && "reborrows not legal at this SIL stage"); if (completion.completeOSSALifetime( arg, OSSALifetimeCompletion::Boundary::Availability) == LifetimeCompletion::WasCompleted) { + LLVM_DEBUG(llvm::dbgs() << "\tcompleted!\n"); changed = true; } } } - function->verifyOwnership(/*deadEndBlocks=*/nullptr); return changed; } diff --git a/lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp b/lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp index 58328c1015d9d..32bf17da930df 100644 --- a/lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp +++ b/lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp @@ -10,13 +10,14 @@ // //===----------------------------------------------------------------------===// -#include "swift/Basic/Assertions.h" #include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h" +#include "swift/Basic/Assertions.h" +#include "swift/SIL/LoopInfo.h" +#include "swift/SIL/StackList.h" #include "swift/SILOptimizer/Utils/CFGOptUtils.h" #include "swift/SILOptimizer/Utils/InstOptUtils.h" #include "swift/SILOptimizer/Utils/OwnershipOptUtils.h" #include "swift/SILOptimizer/Utils/SILSSAUpdater.h" -#include "swift/SIL/LoopInfo.h" using namespace swift; @@ -25,9 +26,11 @@ using namespace swift; bool ReachableBlocks::visit(function_ref visitor) { // Walk over the CFG, starting at the entry block, until all reachable blocks // are visited. - SILBasicBlock *entryBB = visited.getFunction()->getEntryBlock(); - SmallVector worklist = {entryBB}; - visited.insert(entryBB); + auto *function = visited.getFunction(); + auto *entry = function->getEntryBlock(); + StackList worklist(function); + worklist.push_back(entry); + visited.insert(entry); while (!worklist.empty()) { SILBasicBlock *bb = worklist.pop_back_val(); if (!visitor(bb)) @@ -41,6 +44,12 @@ bool ReachableBlocks::visit(function_ref visitor) { return true; } +void ReachableBlocks::compute() { + // Visit all the blocks without doing any extra work. + visit([](SILBasicBlock *) { return true; }); + isComputed = true; +} + ReachingReturnBlocks::ReachingReturnBlocks(SILFunction *function) : worklist(function) { for (SILBasicBlock &block : *function) { @@ -57,15 +66,14 @@ ReachingReturnBlocks::ReachingReturnBlocks(SILFunction *function) bool swift::removeUnreachableBlocks(SILFunction &f) { ReachableBlocks reachable(&f); - // Visit all the blocks without doing any extra work. - reachable.visit([](SILBasicBlock *) { return true; }); + reachable.compute(); // Remove the blocks we never reached. Assume the entry block is visited. // Reachable's visited set contains dangling pointers during this loop. bool changed = false; for (auto ii = std::next(f.begin()), end = f.end(); ii != end;) { auto *bb = &*ii++; - if (!reachable.isVisited(bb)) { + if (!reachable.isReachable(bb)) { bb->removeDeadBlock(); changed = true; } diff --git a/test/SILOptimizer/silgen_cleanup_complete_ossa.sil b/test/SILOptimizer/silgen_cleanup_complete_ossa.sil index 479c176385eaa..fe6bfe9346034 100644 --- a/test/SILOptimizer/silgen_cleanup_complete_ossa.sil +++ b/test/SILOptimizer/silgen_cleanup_complete_ossa.sil @@ -141,3 +141,229 @@ exit: die: unreachable } + +// CHECK-LABEL: sil [ossa] @unreachable_def : {{.*}} { +// CHECK: bb2: +// CHECK-NEXT: [[DEF:%[^,]+]] = apply +// CHECK-NEXT: br bb3 +// CHECK: bb3: +// CHECK-NEXT: destroy_value [dead_end] [[DEF]] +// CHECK: unreachable +// CHECK-LABEL: } // end sil function 'unreachable_def' +sil [ossa] @unreachable_def : $@convention(thin) () -> () { +entry: + br exit + +exit: + %retval = tuple () + return %retval : $() + +nowhere: + %def = apply undef() : $@convention(thin) () -> (@owned C) + br die + +die: + unreachable +} + +// CHECK-LABEL: sil [ossa] @unreachable_def_2 : {{.*}} { +// CHECK: {{bb[0-9]+}}([[C:%[^,]+]] : +// CHECK: cond_br undef, [[LEFT:bb[0-9]+]], [[RIGHT:bb[0-9]+]] +// CHECK: [[LEFT]]: +// CHECK-NEXT: destroy_value [dead_end] [[C]] +// CHECK-NEXT: unreachable +// CHECK: [[RIGHT]]: +// CHECK-NEXT: destroy_value [dead_end] [[C]] +// CHECK: unreachable +// CHECK-LABEL: } // end sil function 'unreachable_def_2' +sil [ossa] @unreachable_def_2 : $@convention(thin) () -> () { +entry: + %t = tuple () + return %t : $() + +not(%c : @owned $C): + cond_br undef, left, right + +left: + unreachable + +right: + unreachable +} + +// CHECK-LABEL: sil [ossa] @unreachable_def_3 : {{.*}} { +// CHECK: {{bb[0-9]+}}([[C:%[^,]+]] : +// CHECK: cond_br +// CHECK: cond_br undef, [[LEFT:bb[0-9]+]], [[RIGHT:bb[0-9]+]] +// CHECK: [[LEFT]]: +// CHECK-NEXT: destroy_value [dead_end] [[C]] +// CHECK-NEXT: unreachable +// CHECK: [[RIGHT]]: +// CHECK-NEXT: destroy_value [dead_end] [[C]] +// CHECK: unreachable +// CHECK-LABEL: } // end sil function 'unreachable_def_3' +sil [ossa] @unreachable_def_3 : $@convention(thin) () -> () { +entry: + %t = tuple () + return %t : $() + +header(%c : @owned $C): + br body + +body: + cond_br undef, die, backedge + +backedge: + br header(%c) + +die: + cond_br undef, left, right + +left: + unreachable + +right: + unreachable +} + +// Ensure that `header` is completed before `postloop` (postloop would +// erroneously be added as a root if a search for loops wasn't done). +// CHECK-LABEL: sil [ossa] @unreachable_def_4 : {{.*}} { +// CHECK: [[C2:%[^,]+]] = apply +// CHECK: {{bb[0-9]+}}([[C:%[^,]+]] : +// CHECK: cond_br +// CHECK: cond_br undef, [[LEFT:bb[0-9]+]], [[RIGHT:bb[0-9]+]] +// CHECK: [[LEFT]]: +// CHECK-NEXT: destroy_value [dead_end] [[C2]] +// CHECK-NEXT: destroy_value [dead_end] [[C]] +// CHECK-NEXT: unreachable +// CHECK: [[RIGHT]]: +// CHECK-NEXT: destroy_value [dead_end] [[C2]] +// CHECK-NEXT: destroy_value [dead_end] [[C]] +// CHECK: unreachable +// CHECK-LABEL: } // end sil function 'unreachable_def_4' +sil [ossa] @unreachable_def_4 : $@convention(thin) () -> () { +entry: + %t = tuple () + return %t : $() + +// Put this here so it appears before the loop when iterating over the +// function's blocks. +postloop: + %c2 = apply undef() : $@convention(thin) () -> (@owned C) + br die + +header(%c : @owned $C): + br body + +body: + cond_br undef, postloop, backedge + +backedge: + br header(%c) + +die: + cond_br undef, left, right + +left: + unreachable + +right: + unreachable +} + +// Check that values backwards reachable from dead-end loops are completed. +// CHECK-LABEL: sil [ossa] @unreachable_def_5 : {{.*}} { +// CHECK: apply undef([[C:%[^)]+]]) +// CHECK-NEXT: extend_lifetime [[C]] +// CHECK-LABEL: } // end sil function 'unreachable_def_5' +sil [ossa] @unreachable_def_5 : $@convention(thin) () -> () { +entry: + %t = tuple () + return %t : $() + +header: + %c = apply undef() : $@convention(thin) () -> (@owned C) + br body + +body: + cond_br undef, die, backedge + +backedge: + destroy_value %c + br header + +die: + br deadend_header + +deadend_header: + apply undef(%c) : $@convention(thin) (@guaranteed C) -> () + br deadend_backedge + +deadend_backedge: + br deadend_header +} + +// CHECK-LABEL: sil [ossa] @unreachable_def_6 : {{.*}} { +// CHECK: [[C2:%[^,]+]] = apply +// CHECK: {{bb[0-9]+}}([[C:%[^,]+]] : @owned +// CHECK: {{bb[0-9]+}}([[C3:%[^,]+]] : @owned +// CHECK: cond_br +// CHECK: cond_br undef, [[LEFT:bb[0-9]+]], [[RIGHT:bb[0-9]+]] +// CHECK: [[LEFT]]: +// CHECK-NEXT: destroy_value [dead_end] [[C2]] +// CHECK-NEXT: destroy_value [dead_end] [[C3]] +// CHECK-NEXT: destroy_value [dead_end] [[C]] +// CHECK-NEXT: unreachable +// CHECK: [[RIGHT]]: +// CHECK-NEXT: destroy_value [dead_end] [[C2]] +// CHECK-NEXT: destroy_value [dead_end] [[C3]] +// CHECK-NEXT: destroy_value [dead_end] [[C]] +// CHECK: unreachable +// CHECK-LABEL: } // end sil function 'unreachable_def_6' +sil [ossa] @unreachable_def_6 : $@convention(thin) () -> () { +entry: + %t = tuple () + return %t : $() + +// Put this here so it appears before the loop when iterating over the +// function's blocks. +postloop: + %c2 = apply undef() : $@convention(thin) () -> (@owned C) + br die + +header_1(%c : @owned $C): + br body_1 + +body_1: + cond_br undef, postloop_1, backedge_1 + +backedge_1: + br header_1(%c) + +postloop_1: + br postloop + +header_2(%c3 : @owned $C): + br body_2 + +body_2: + cond_br undef, postloop_2, backedge_2 + +backedge_2: + br header_2(%c3) + +postloop_2: + br postloop + +die: + apply undef(%c) : $@convention(thin) (@guaranteed C) -> () + apply undef(%c3) : $@convention(thin) (@guaranteed C) -> () + cond_br undef, left, right + +left: + unreachable + +right: + unreachable +}