[llvm] [LoopInfo] Add recompute() and use it in SimpleLoopUnswitch (PR #213514)
Fangrui Song via llvm-commits
llvm-commits at lists.llvm.org
Sun Aug 2 00:55:12 PDT 2026
https://github.com/MaskRay created https://github.com/llvm/llvm-project/pull/213514
recomputeLoopBlockSet and rebuildLoopAfterUnswitch reconstruct by hand
with significant complexity, but analyze() cannot replace them: it
allocates a fresh Loop for every header, while ScalarEvolution stores
the loop pointer in every AddRec, the loop analysis manager keys its
cache on it, and the pass manager hands the running pass a `Loop &`.
Add LoopInfoBase::recompute, which reruns the analysis over the current
CFG but refills the loop each header already had.
Block lists now come out in the analysis's canonical order rather than
the original order filtered, renumbering cloned blocks in eight
SimpleLoopUnswitch tests.
Aided by Claude Opus 5
>From 5fc067c0ad19d15d277e3749d59322ebfd2feb2a Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sat, 1 Aug 2026 21:30:12 -0700
Subject: [PATCH] [LoopInfo] Add recompute() and use it in SimpleLoopUnswitch
recomputeLoopBlockSet and rebuildLoopAfterUnswitch reconstruct by hand
with significant complexity, but analyze() cannot replace them: it
allocates a fresh Loop for every header, while ScalarEvolution stores
the loop pointer in every AddRec, the loop analysis manager keys its
cache on it, and the pass manager hands the running pass a `Loop &`.
Add LoopInfoBase::recompute, which reruns the analysis over the current
CFG but refills the loop each header already had.
Block lists now come out in the analysis's canonical order rather than
the original order filtered, renumbering cloned blocks in eight
SimpleLoopUnswitch tests.
Aided by Claude Opus 5
---
llvm/include/llvm/Support/GenericLoopInfo.h | 40 ++-
.../llvm/Support/GenericLoopInfoImpl.h | 49 ++-
.../Transforms/Scalar/SimpleLoopUnswitch.cpp | 326 ++----------------
.../PGO-nontrivial-unswitch2.ll | 12 +-
.../PGO-nontrivial-unswitch3.ll | 12 +-
.../SimpleLoopUnswitch/endless-unswitch.ll | 36 +-
.../Transforms/SimpleLoopUnswitch/guards.ll | 22 +-
.../nontrivial-unswitch-select.ll | 8 +-
.../SimpleLoopUnswitch/nontrivial-unswitch.ll | 38 +-
...al-unswitch-loop-and-block-dispositions.ll | 78 ++---
.../SimpleLoopUnswitch/update-scev-3.ll | 48 +--
llvm/unittests/Analysis/LoopInfoTest.cpp | 116 +++++++
12 files changed, 360 insertions(+), 425 deletions(-)
diff --git a/llvm/include/llvm/Support/GenericLoopInfo.h b/llvm/include/llvm/Support/GenericLoopInfo.h
index 87c140b297b4e..b7c06eaf9234b 100644
--- a/llvm/include/llvm/Support/GenericLoopInfo.h
+++ b/llvm/include/llvm/Support/GenericLoopInfo.h
@@ -495,8 +495,13 @@ template <class BlockT, class LoopT> class LoopBase {
#if LLVM_ENABLE_ABI_BREAKING_CHECKS
IsInvalid = true;
#endif
+ clear();
+ }
+
+ /// Reset to the state of a freshly constructed one. The block storage is
+ /// reclaimed by the owning LoopInfo.
+ void clear() {
SubLoops.clear();
- // The block storage is reclaimed by the owning LoopInfo.
BlockData = nullptr;
BlockLen = 0;
BlockCapacity = 0;
@@ -649,15 +654,28 @@ template <class BlockT, class LoopT> class LoopInfoBase {
return Number < BBMap.size() ? BBMap[Number] : nullptr;
}
+ /// Maps a header to the loop recompute() refills for it, if any.
+ using ReuseLoopT = function_ref<LoopT *(BlockT *)>;
+
/// AllocateLoop for analyze(): stash \p Header (see pendingHeader).
/// getHeader() only works once the layout carve has replaced the stash with
- /// the loop's block list.
- LoopT *allocateLoop(BlockT *Header) {
- LoopT *L = AllocateLoop();
+ /// the loop's block list. \p ReuseLoop, if specified, returns an existing
+ /// loop rather than a fresh one for LoopInfoBase::recompute.
+ LoopT *allocateLoop(BlockT *Header, ReuseLoopT ReuseLoop) {
+ LoopT *L = ReuseLoop ? ReuseLoop(Header) : nullptr;
+ if (!L)
+ L = AllocateLoop();
L->PendingHeader = Header;
return L;
}
+ void analyzeImpl(
+ ParentT F,
+ function_ref<const DominatorTreeBase<BlockT, false> &()> GetDomTree,
+ ReuseLoopT ReuseLoop);
+ void analyzeImpl(const DominatorTreeBase<BlockT, false> &DomTree,
+ ReuseLoopT ReuseLoop);
+
/// The header of a loop under construction, stashed until the layout carve
/// builds the block list.
static BlockT *pendingHeader(const LoopT *L) { return L->PendingHeader; }
@@ -854,6 +872,20 @@ template <class BlockT, class LoopT> class LoopInfoBase {
void analyze(const DominatorTreeBase<BlockT, false> &DomTree);
///@}
+ /// Rebuild the loop forest from the CFG, reusing the existing loop object for
+ /// every block that still heads a loop. Analyses that key on loop pointers,
+ /// such as ScalarEvolution and the loop analysis manager, stay valid for the
+ /// loops that survive.
+ ///
+ /// Returns, in a deterministic order, the loops whose header no longer heads
+ /// one, each with the header it had. They are left empty and unlinked but not
+ /// destroyed: the caller must run its deletion callbacks on them, then call
+ /// destroy().
+ ///
+ /// Every loop's header must still belong to the function.
+ SmallVector<std::pair<LoopT *, BlockT *>, 4>
+ recompute(const DominatorTreeBase<BlockT, false> &DomTree);
+
// Debugging
void print(raw_ostream &OS) const;
diff --git a/llvm/include/llvm/Support/GenericLoopInfoImpl.h b/llvm/include/llvm/Support/GenericLoopInfoImpl.h
index c294583eb77fd..5af010cae636e 100644
--- a/llvm/include/llvm/Support/GenericLoopInfoImpl.h
+++ b/llvm/include/llvm/Support/GenericLoopInfoImpl.h
@@ -16,6 +16,7 @@
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/DepthFirstIterator.h"
+#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/GenericLoopInfo.h"
@@ -461,8 +462,15 @@ void LoopBase<BlockT, LoopT>::print(raw_ostream &OS, bool Verbose,
/// program order.
template <class BlockT, class LoopT>
void LoopInfoBase<BlockT, LoopT>::analyze(const DomTreeBase<BlockT> &DomTree) {
- analyze(DomTree.getRootNode()->getBlock()->getParent(),
- [&]() -> const DomTreeBase<BlockT> & { return DomTree; });
+ analyzeImpl(DomTree, /*ReuseLoop=*/{});
+}
+
+template <class BlockT, class LoopT>
+void LoopInfoBase<BlockT, LoopT>::analyzeImpl(
+ const DomTreeBase<BlockT> &DomTree, ReuseLoopT ReuseLoop) {
+ analyzeImpl(
+ DomTree.getRootNode()->getBlock()->getParent(),
+ [&]() -> const DomTreeBase<BlockT> & { return DomTree; }, ReuseLoop);
}
template <class BlockT, class LoopT>
@@ -477,6 +485,41 @@ void LoopInfoBase<BlockT, LoopT>::analyze(ParentT F) {
template <class BlockT, class LoopT>
void LoopInfoBase<BlockT, LoopT>::analyze(
ParentT F, function_ref<const DomTreeBase<BlockT> &()> GetDomTree) {
+ analyzeImpl(F, GetDomTree, /*ReuseLoop=*/{});
+}
+
+/// Rebuild the forest in place, keeping the loop object of every surviving
+/// header so that pointer-keyed analyses need not be discarded.
+template <class BlockT, class LoopT>
+SmallVector<std::pair<LoopT *, BlockT *>, 4>
+LoopInfoBase<BlockT, LoopT>::recompute(const DomTreeBase<BlockT> &DomTree) {
+ // Index the loops by header so the analysis can find them again, and empty
+ // them out for it to refill. Their block storage belongs to this LoopInfo
+ // and is reclaimed with it.
+ MapVector<BlockT *, LoopT *> ReuseByHeader;
+ for (LoopT *L : getLoopsInPreorder()) {
+ ReuseByHeader[L->getHeader()] = L;
+ L->clear();
+ }
+ BBMap.clear();
+ TopLevelLoops.clear();
+ BlockLayout.reset();
+
+ analyzeImpl(DomTree,
+ [&](BlockT *Header) { return ReuseByHeader.lookup(Header); });
+
+ // ReuseByHeader is in preorder, so the report is deterministic.
+ SmallVector<std::pair<LoopT *, BlockT *>, 4> Removed;
+ for (auto [Header, L] : ReuseByHeader)
+ if (lookupLoopFor(Header) != L)
+ Removed.emplace_back(L, Header);
+ return Removed;
+}
+
+template <class BlockT, class LoopT>
+void LoopInfoBase<BlockT, LoopT>::analyzeImpl(
+ ParentT F, function_ref<const DomTreeBase<BlockT> &()> GetDomTree,
+ ReuseLoopT ReuseLoop) {
using BlockTraits = GraphTraits<BlockT *>;
auto num = [](const BlockT *BB) {
return GraphTraits<const BlockT *>::getNumber(BB);
@@ -701,7 +744,7 @@ void LoopInfoBase<BlockT, LoopT>::analyze(
LoopT *Enclosing = H == NoBlock ? nullptr : BBMap[H];
LoopT *L = Enclosing;
if (Info[B].Pos == IsHeader) {
- L = allocateLoop(BB);
+ L = allocateLoop(BB, ReuseLoop);
L->setParentLoop(Enclosing);
}
BBMap[B] = L;
diff --git a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
index 9425c8a8ac520..f5f071767c987 100644
--- a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
+++ b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
@@ -1843,10 +1843,9 @@ static void deleteDeadBlocksFromLoop(Loop &L,
LI.removeBlocksFromLoopAndAncestors(
&L, nullptr, [&](BasicBlock *BB) { return DeadBlockSet.count(BB); });
- // Now delete the dead child loops. Run the per-child deletion callbacks
- // first, while the loop forest is still fully consistent (markLoopAsDeleted
- // checks the child's position in it), then detach the dead children and
- // destroy them.
+ // Now delete the dead child loops. The later recompute cannot retire them
+ // for us: it needs every loop's header to still be in the function, and
+ // these are about to be erased.
for (Loop *ChildL : L) {
if (!DeadBlockSet.count(ChildL->getHeader()))
continue;
@@ -1887,301 +1886,46 @@ static void deleteDeadBlocksFromLoop(Loop &L,
BB->eraseFromParent();
}
-/// Recompute the set of blocks in a loop after unswitching.
+/// Rebuild the loop forest after unswitching removes some subset of blocks and
+/// edges.
///
-/// This walks from the original headers predecessors to rebuild the loop. We
-/// take advantage of the fact that new blocks can't have been added, and so we
-/// filter by the original loop's blocks. This also handles potentially
-/// unreachable code that we don't want to explore but might be found examining
-/// the predecessors of the header.
+/// Child loops of \p L that ended up elsewhere in the nest are returned in
+/// \p HoistedLoops; ones that are no longer loops at all are reported to
+/// \p LoopUpdater and destroyed.
///
-/// If the original loop is no longer a loop, this will return an empty set. If
-/// it remains a loop, all the blocks within it will be added to the set
-/// (including those blocks in inner loops).
-static SmallPtrSet<const BasicBlock *, 16> recomputeLoopBlockSet(Loop &L,
- LoopInfo &LI) {
- SmallPtrSet<const BasicBlock *, 16> LoopBlockSet;
-
- auto *PH = L.getLoopPreheader();
- auto *Header = L.getHeader();
-
- // A worklist to use while walking backwards from the header.
- SmallVector<BasicBlock *, 16> Worklist;
-
- // First walk the predecessors of the header to find the backedges. This will
- // form the basis of our walk.
- for (auto *Pred : predecessors(Header)) {
- // Skip the preheader.
- if (Pred == PH)
- continue;
-
- // Because the loop was in simplified form, the only non-loop predecessor
- // is the preheader.
- assert(L.contains(Pred) && "Found a predecessor of the loop header other "
- "than the preheader that is not part of the "
- "loop!");
-
- // Insert this block into the loop set and on the first visit and, if it
- // isn't the header we're currently walking, put it into the worklist to
- // recurse through.
- if (LoopBlockSet.insert(Pred).second && Pred != Header)
- Worklist.push_back(Pred);
- }
-
- // If no backedges were found, we're done.
- if (LoopBlockSet.empty())
- return LoopBlockSet;
-
- // We found backedges, recurse through them to identify the loop blocks.
- while (!Worklist.empty()) {
- BasicBlock *BB = Worklist.pop_back_val();
- assert(LoopBlockSet.count(BB) && "Didn't put block into the loop set!");
-
- // No need to walk past the header.
- if (BB == Header)
- continue;
-
- // Because we know the inner loop structure remains valid we can use the
- // loop structure to jump immediately across the entire nested loop.
- // Further, because it is in loop simplified form, we can directly jump
- // to its preheader afterward.
- if (Loop *InnerL = LI.getLoopFor(BB))
- if (InnerL != &L) {
- assert(L.contains(InnerL) &&
- "Should not reach a loop *outside* this loop!");
- // The preheader is the only possible predecessor of the loop so
- // insert it into the set and check whether it was already handled.
- auto *InnerPH = InnerL->getLoopPreheader();
- assert(L.contains(InnerPH) && "Cannot contain an inner loop block "
- "but not contain the inner loop "
- "preheader!");
- if (!LoopBlockSet.insert(InnerPH).second)
- // The only way to reach the preheader is through the loop body
- // itself so if it has been visited the loop is already handled.
- continue;
-
- // Insert all of the blocks (other than those already present) into
- // the loop set. We expect at least the block that led us to find the
- // inner loop to be in the block set, but we may also have other loop
- // blocks if they were already enqueued as predecessors of some other
- // outer loop block.
- for (auto *InnerBB : InnerL->blocks()) {
- if (InnerBB == BB) {
- assert(LoopBlockSet.count(InnerBB) &&
- "Block should already be in the set!");
- continue;
- }
-
- LoopBlockSet.insert(InnerBB);
- }
-
- // Add the preheader to the worklist so we will continue past the
- // loop body.
- Worklist.push_back(InnerPH);
- continue;
- }
-
- // Insert any predecessors that were in the original loop into the new
- // set, and if the insert is successful, add them to the worklist.
- for (auto *Pred : predecessors(BB))
- if (L.contains(Pred) && LoopBlockSet.insert(Pred).second)
- Worklist.push_back(Pred);
- }
-
- assert(LoopBlockSet.count(Header) && "Cannot fail to add the header!");
-
- // We've found all the blocks participating in the loop, return our completed
- // set.
- return LoopBlockSet;
-}
-
-/// Rebuild a loop after unswitching removes some subset of blocks and edges.
-///
-/// The removal may have removed some child loops entirely but cannot have
-/// disturbed any remaining child loops. However, they may need to be hoisted
-/// to the parent loop (or to be top-level loops). The original loop may be
-/// completely removed.
-///
-/// The sibling loops resulting from this update are returned. If the original
-/// loop remains a valid loop, it will be the first entry in this list with all
-/// of the newly sibling loops following it.
-///
-/// Returns true if the loop remains a loop after unswitching, and false if it
-/// is no longer a loop after unswitching (and should not continue to be
-/// referenced).
-static bool rebuildLoopAfterUnswitch(Loop &L, ArrayRef<BasicBlock *> ExitBlocks,
- LoopInfo &LI,
+/// Returns false if \p L is no longer a loop, in which case it should not
+/// continue to be referenced.
+static bool rebuildLoopAfterUnswitch(Loop &L, DominatorTree &DT, LoopInfo &LI,
SmallVectorImpl<Loop *> &HoistedLoops,
- ScalarEvolution *SE) {
- auto *PH = L.getLoopPreheader();
-
- // Compute the actual parent loop from the exit blocks. Because we may have
- // pruned some exits the loop may be different from the original parent.
- Loop *ParentL = nullptr;
- SmallVector<Loop *, 4> ExitLoops;
- SmallVector<BasicBlock *, 4> ExitsInLoops;
- ExitsInLoops.reserve(ExitBlocks.size());
- for (auto *ExitBB : ExitBlocks)
- if (Loop *ExitL = LI.getLoopFor(ExitBB)) {
- ExitLoops.push_back(ExitL);
- ExitsInLoops.push_back(ExitBB);
- if (!ParentL || (ParentL != ExitL && ParentL->contains(ExitL)))
- ParentL = ExitL;
- }
-
- // Recompute the blocks participating in this loop. This may be empty if it
- // is no longer a loop.
- auto LoopBlockSet = recomputeLoopBlockSet(L, LI);
-
- // If we still have a loop, we need to re-set the loop's parent as the exit
- // block set changing may have moved it within the loop nest. Note that this
- // can only happen when this loop has a parent as it can only hoist the loop
- // *up* the nest.
- if (!LoopBlockSet.empty() && L.getParentLoop() != ParentL) {
- // Remove this loop's (original) blocks from all of the intervening loops.
- LI.removeBlocksFromLoopAndAncestors(
- L.getParentLoop(), ParentL,
- [&](BasicBlock *BB) { return BB == PH || L.contains(BB); });
-
- LI.changeLoopFor(PH, ParentL);
- L.getParentLoop()->removeChildLoop(&L);
- if (ParentL)
- ParentL->addChildLoop(&L);
- else
- LI.addTopLevelLoop(&L);
- }
-
- // Now we update all the blocks which are no longer within the loop, building
- // the set of them as they are removed.
- SmallPtrSet<BasicBlock *, 16> UnloopedBlocks;
- LI.removeBlocksIf(L, [&](BasicBlock *BB) {
- if (LoopBlockSet.count(BB))
- return false;
- UnloopedBlocks.insert(BB);
- return true;
- });
- if (LoopBlockSet.empty())
- UnloopedBlocks.insert(PH);
-
- // Sort the exits in ascending loop depth, we'll work backwards across these
- // to process them inside out.
- llvm::stable_sort(ExitsInLoops, [&](BasicBlock *LHS, BasicBlock *RHS) {
- return LI.getLoopDepth(LHS) < LI.getLoopDepth(RHS);
- });
-
- // We'll build up a set for each exit loop.
- SmallPtrSet<BasicBlock *, 16> NewExitLoopBlocks;
- Loop *PrevExitL = L.getParentLoop(); // The deepest possible exit loop.
-
- auto InUnlooped = [&](BasicBlock *BB) { return UnloopedBlocks.count(BB); };
-
- SmallVector<BasicBlock *, 16> Worklist;
- while (!UnloopedBlocks.empty() && !ExitsInLoops.empty()) {
- assert(Worklist.empty() && "Didn't clear worklist!");
- assert(NewExitLoopBlocks.empty() && "Didn't clear loop set!");
-
- // Grab the next exit block, in decreasing loop depth order.
- BasicBlock *ExitBB = ExitsInLoops.pop_back_val();
- Loop &ExitL = *LI.getLoopFor(ExitBB);
- assert(ExitL.contains(&L) && "Exit loop must contain the inner loop!");
-
- // Erase all of the unlooped blocks from the loops between the previous
- // exit loop and this exit loop. This works because the ExitInLoops list is
- // sorted in increasing order of loop depth and thus we visit loops in
- // decreasing order of loop depth.
- LI.removeBlocksFromLoopAndAncestors(PrevExitL, &ExitL, InUnlooped);
- PrevExitL = &ExitL;
-
- // Walk the CFG back until we hit the cloned PH adding everything reachable
- // and in the unlooped set to this exit block's loop.
- Worklist.push_back(ExitBB);
- do {
- BasicBlock *BB = Worklist.pop_back_val();
- // We can stop recursing at the cloned preheader (if we get there).
- if (BB == PH)
- continue;
-
- for (BasicBlock *PredBB : predecessors(BB)) {
- // If this pred has already been moved to our set or is part of some
- // (inner) loop, no update needed.
- if (!UnloopedBlocks.erase(PredBB)) {
- assert((NewExitLoopBlocks.count(PredBB) ||
- ExitL.contains(LI.getLoopFor(PredBB))) &&
- "Predecessor not in a nested loop (or already visited)!");
- continue;
- }
+ ScalarEvolution *SE,
+ LPMUpdater &LoopUpdater) {
+ SmallVector<Loop *, 4> Children(L.begin(), L.end());
- // We just insert into the loop set here. We'll add these blocks to the
- // exit loop after we build up the set in a deterministic order rather
- // than the predecessor-influenced visit order.
- bool Inserted = NewExitLoopBlocks.insert(PredBB).second;
- (void)Inserted;
- assert(Inserted && "Should only visit an unlooped block once!");
+ // Recompute the forest from the CFG. Every loop that survives keeps its
+ // object, so pointer-keyed analyses stay valid across the rebuild.
+ SmallVector<std::pair<Loop *, BasicBlock *>, 4> Removed = LI.recompute(DT);
+ SmallPtrSet<Loop *, 4> RemovedSet;
+ for (Loop *RemovedL : make_first_range(Removed))
+ RemovedSet.insert(RemovedL);
- // And recurse through to its predecessors.
- Worklist.push_back(PredBB);
- }
- } while (!Worklist.empty());
+ for (Loop *ChildL : Children)
+ if (!RemovedSet.contains(ChildL) && ChildL->getParentLoop() != &L)
+ HoistedLoops.push_back(ChildL);
- // If blocks in this exit loop were directly part of the original loop (as
- // opposed to a child loop) update the map to point to this exit loop. This
- // just updates a map and so the fact that the order is unstable is fine.
- for (auto *BB : NewExitLoopBlocks)
- if (Loop *BBL = LI.getLoopFor(BB))
- if (BBL == &L || !L.contains(BBL))
- LI.changeLoopFor(BB, &ExitL);
-
- // We will remove the remaining unlooped blocks from this loop in the next
- // iteration or below.
- NewExitLoopBlocks.clear();
- }
-
- // Any remaining unlooped blocks are no longer part of any loop unless they
- // are part of some child loop.
- LI.removeBlocksFromLoopAndAncestors(PrevExitL, nullptr, InUnlooped);
- for (auto *BB : UnloopedBlocks)
- if (Loop *BBL = LI.getLoopFor(BB))
- if (BBL == &L || !L.contains(BBL))
- LI.changeLoopFor(BB, nullptr);
-
- // Sink all the child loops whose headers are no longer in the loop set to
- // the parent (or to be top level loops).
- for (Loop *HoistedL : LI.takeChildrenIf(&L, [&](Loop *SubL) {
- return !LoopBlockSet.count(SubL->getHeader());
- })) {
- HoistedLoops.push_back(HoistedL);
-
- // To compute the new parent of this hoisted loop we look at where we
- // placed the preheader above. We can't lookup the header itself because we
- // retained the mapping from the header to the hoisted loop. But the
- // preheader and header should have the exact same new parent computed
- // based on the set of exit blocks from the original loop as the preheader
- // is a predecessor of the header and so reached in the reverse walk. And
- // because the loops were all in simplified form the preheader of the
- // hoisted loop can't be part of some *other* loop.
- if (auto *NewParentL = LI.getLoopFor(HoistedL->getLoopPreheader()))
- NewParentL->addChildLoop(HoistedL);
- else
- LI.addTopLevelLoop(HoistedL);
- }
+ if (SE && !Removed.empty())
+ SE->forgetBlockAndLoopDispositions();
- // Actually delete the loop if nothing remained within it.
- if (L.getBlocks().empty()) {
- assert(L.getSubLoops().empty() &&
- "Failed to remove all subloops from the original loop!");
- if (Loop *ParentL = L.getParentLoop())
- ParentL->removeChildLoop(llvm::find(*ParentL, &L));
- else
- LI.removeLoop(llvm::find(LI, &L));
- // markLoopAsDeleted for L should be triggered by the caller (it is
- // typically done within postUnswitch).
- if (SE)
- SE->forgetBlockAndLoopDispositions();
- LI.destroy(&L);
- return false;
+ for (auto [RemovedL, Header] : Removed) {
+ assert((RemovedL == &L || is_contained(Children, RemovedL)) &&
+ "Unswitching can only remove loops from the current nest!");
+ // markLoopAsDeleted for L is triggered by the caller (postUnswitch); after
+ // this destroy its pointer is only used as a key.
+ if (RemovedL != &L)
+ LoopUpdater.markLoopAsDeleted(*RemovedL, Header->getName());
+ LI.destroy(RemovedL);
}
- return true;
+ return !RemovedSet.contains(&L);
}
/// Helper to visit a dominator subtree, invoking a callable on each node.
@@ -2574,7 +2318,7 @@ static void unswitchNontrivialInvariants(
SmallVector<Loop *, 4> HoistedLoops;
bool IsStillLoop =
- rebuildLoopAfterUnswitch(L, ExitBlocks, LI, HoistedLoops, SE);
+ rebuildLoopAfterUnswitch(L, DT, LI, HoistedLoops, SE, LoopUpdater);
if (MSSAU && VerifyMemorySSA)
MSSAU->getMemorySSA()->verifyMemorySSA();
diff --git a/llvm/test/Transforms/SimpleLoopUnswitch/PGO-nontrivial-unswitch2.ll b/llvm/test/Transforms/SimpleLoopUnswitch/PGO-nontrivial-unswitch2.ll
index 3b4478f2dc900..106184a47f474 100644
--- a/llvm/test/Transforms/SimpleLoopUnswitch/PGO-nontrivial-unswitch2.ll
+++ b/llvm/test/Transforms/SimpleLoopUnswitch/PGO-nontrivial-unswitch2.ll
@@ -31,14 +31,8 @@ define void @_Z11hotFunctionbiiPiS_S_(i1 %cond, i32 %M, i32 %N, ptr %A, ptr %B,
; CHECK: for.cond1.preheader.us:
; CHECK-NEXT: [[J_020_US:%.*]] = phi i32 [ 0, [[FOR_COND1_PREHEADER_LR_PH_SPLIT_SPLIT_US]] ], [ [[INC10_US:%.*]], [[FOR_COND_CLEANUP3_US:%.*]] ]
; CHECK-NEXT: br label [[FOR_BODY4_PREHEADER_US:%.*]]
-; CHECK: for.cond.cleanup3.us:
-; CHECK-NEXT: [[INC10_US]] = add nuw i32 [[J_020_US]], 1
-; CHECK-NEXT: [[EXITCOND22_NOT_US:%.*]] = icmp eq i32 [[INC10_US]], [[M]]
-; CHECK-NEXT: br i1 [[EXITCOND22_NOT_US]], label [[FOR_COND_CLEANUP_LOOPEXIT_SPLIT2_US:%.*]], label [[FOR_COND1_PREHEADER_US]], !prof [[PROF17]]
; CHECK: for.body4.preheader.us:
; CHECK-NEXT: br label [[FOR_BODY4_PREHEADER_SPLIT_US_US:%.*]]
-; CHECK: for.cond.cleanup3.loopexit.us:
-; CHECK-NEXT: br label [[FOR_COND_CLEANUP3_US]]
; CHECK: for.body4.preheader.split.us.us:
; CHECK-NEXT: br label [[FOR_BODY4_US_US:%.*]]
; CHECK: for.body4.us.us:
@@ -61,6 +55,12 @@ define void @_Z11hotFunctionbiiPiS_S_(i1 %cond, i32 %M, i32 %N, ptr %A, ptr %B,
; CHECK-NEXT: br i1 [[EXITCOND_NOT_US_US]], label [[FOR_COND_CLEANUP3_LOOPEXIT_SPLIT_US_US:%.*]], label [[FOR_BODY4_US_US]], !prof [[PROF18]]
; CHECK: for.cond.cleanup3.loopexit.split.us.us:
; CHECK-NEXT: br label [[FOR_COND_CLEANUP3_LOOPEXIT_US:%.*]]
+; CHECK: for.cond.cleanup3.loopexit.us:
+; CHECK-NEXT: br label [[FOR_COND_CLEANUP3_US]]
+; CHECK: for.cond.cleanup3.us:
+; CHECK-NEXT: [[INC10_US]] = add nuw i32 [[J_020_US]], 1
+; CHECK-NEXT: [[EXITCOND22_NOT_US:%.*]] = icmp eq i32 [[INC10_US]], [[M]]
+; CHECK-NEXT: br i1 [[EXITCOND22_NOT_US]], label [[FOR_COND_CLEANUP_LOOPEXIT_SPLIT2_US:%.*]], label [[FOR_COND1_PREHEADER_US]], !prof [[PROF17]]
; CHECK: for.cond.cleanup.loopexit.split2.us:
; CHECK-NEXT: br label [[FOR_COND_CLEANUP_LOOPEXIT:%.*]]
; CHECK: for.cond1.preheader.lr.ph.split.split:
diff --git a/llvm/test/Transforms/SimpleLoopUnswitch/PGO-nontrivial-unswitch3.ll b/llvm/test/Transforms/SimpleLoopUnswitch/PGO-nontrivial-unswitch3.ll
index f07b1e71239cb..2091899952c59 100644
--- a/llvm/test/Transforms/SimpleLoopUnswitch/PGO-nontrivial-unswitch3.ll
+++ b/llvm/test/Transforms/SimpleLoopUnswitch/PGO-nontrivial-unswitch3.ll
@@ -31,14 +31,8 @@ define void @_Z11hotFunctionbiiPiS_S_(i1 %cond, i32 %M, i32 %N, ptr %A, ptr %B,
; CHECK: for.cond1.preheader.us:
; CHECK-NEXT: [[J_020_US:%.*]] = phi i32 [ 0, [[FOR_COND1_PREHEADER_LR_PH_SPLIT_SPLIT_US]] ], [ [[INC10_US:%.*]], [[FOR_COND_CLEANUP3_US:%.*]] ]
; CHECK-NEXT: br label [[FOR_BODY4_PREHEADER_US:%.*]]
-; CHECK: for.cond.cleanup3.us:
-; CHECK-NEXT: [[INC10_US]] = add nuw i32 [[J_020_US]], 1
-; CHECK-NEXT: [[EXITCOND22_NOT_US:%.*]] = icmp eq i32 [[INC10_US]], [[M]]
-; CHECK-NEXT: br i1 [[EXITCOND22_NOT_US]], label [[FOR_COND_CLEANUP_LOOPEXIT_SPLIT2_US:%.*]], label [[FOR_COND1_PREHEADER_US]], !prof [[PROF19]]
; CHECK: for.body4.preheader.us:
; CHECK-NEXT: br label [[FOR_BODY4_PREHEADER_SPLIT_US_US:%.*]]
-; CHECK: for.cond.cleanup3.loopexit.us:
-; CHECK-NEXT: br label [[FOR_COND_CLEANUP3_US]]
; CHECK: for.body4.preheader.split.us.us:
; CHECK-NEXT: br label [[FOR_BODY4_US_US:%.*]]
; CHECK: for.body4.us.us:
@@ -61,6 +55,12 @@ define void @_Z11hotFunctionbiiPiS_S_(i1 %cond, i32 %M, i32 %N, ptr %A, ptr %B,
; CHECK-NEXT: br i1 [[EXITCOND_NOT_US_US]], label [[FOR_COND_CLEANUP3_LOOPEXIT_SPLIT_US_US:%.*]], label [[FOR_BODY4_US_US]], !prof [[PROF20]]
; CHECK: for.cond.cleanup3.loopexit.split.us.us:
; CHECK-NEXT: br label [[FOR_COND_CLEANUP3_LOOPEXIT_US:%.*]]
+; CHECK: for.cond.cleanup3.loopexit.us:
+; CHECK-NEXT: br label [[FOR_COND_CLEANUP3_US]]
+; CHECK: for.cond.cleanup3.us:
+; CHECK-NEXT: [[INC10_US]] = add nuw i32 [[J_020_US]], 1
+; CHECK-NEXT: [[EXITCOND22_NOT_US:%.*]] = icmp eq i32 [[INC10_US]], [[M]]
+; CHECK-NEXT: br i1 [[EXITCOND22_NOT_US]], label [[FOR_COND_CLEANUP_LOOPEXIT_SPLIT2_US:%.*]], label [[FOR_COND1_PREHEADER_US]], !prof [[PROF19]]
; CHECK: for.cond.cleanup.loopexit.split2.us:
; CHECK-NEXT: br label [[FOR_COND_CLEANUP_LOOPEXIT:%.*]]
; CHECK: for.cond1.preheader.lr.ph.split.split:
diff --git a/llvm/test/Transforms/SimpleLoopUnswitch/endless-unswitch.ll b/llvm/test/Transforms/SimpleLoopUnswitch/endless-unswitch.ll
index e70bea2d2f7a3..5699d836d28e9 100644
--- a/llvm/test/Transforms/SimpleLoopUnswitch/endless-unswitch.ll
+++ b/llvm/test/Transforms/SimpleLoopUnswitch/endless-unswitch.ll
@@ -28,23 +28,23 @@ define dso_local void @d() {
; CHECK-NEXT: br label [[G_US:%.*]]
; CHECK: g.us:
; CHECK-NEXT: br label [[G_SPLIT_US6:%.*]]
-; CHECK: for.cond1.us1:
+; CHECK: g.split.us1:
+; CHECK-NEXT: br label [[FOR_COND1_US2:%.*]]
+; CHECK: for.cond1.us2:
; CHECK-NEXT: [[TMP2:%.*]] = load i16, ptr null, align 2
; CHECK-NEXT: [[TOBOOL4_NOT_US:%.*]] = icmp eq i16 [[TMP2]], 0
; CHECK-NEXT: br i1 [[TOBOOL4_NOT_US]], label [[FOR_COND5_PREHEADER_US4:%.*]], label [[G_LOOPEXIT_US:%.*]]
-; CHECK: for.cond5.us2:
+; CHECK: for.cond5.preheader.us3:
+; CHECK-NEXT: br label [[FOR_COND5_US2:%.*]]
+; CHECK: for.cond5.us4:
; CHECK-NEXT: br i1 false, label [[FOR_COND1_LOOPEXIT_US5:%.*]], label [[FOR_INC_US3:%.*]]
-; CHECK: for.inc.us3:
+; CHECK: for.inc.us5:
; CHECK-NEXT: store i8 0, ptr @b, align 1
-; CHECK-NEXT: br label [[FOR_COND5_US2:%.*]]
-; CHECK: for.cond5.preheader.us4:
; CHECK-NEXT: br label [[FOR_COND5_US2]]
-; CHECK: for.cond1.loopexit.us5:
-; CHECK-NEXT: br label [[FOR_COND1_US1:%.*]], !llvm.loop [[LOOP0:![0-9]+]]
+; CHECK: for.cond1.loopexit.us6:
+; CHECK-NEXT: br label [[FOR_COND1_US2]], !llvm.loop [[LOOP0:![0-9]+]]
; CHECK: g.loopexit.us:
; CHECK-NEXT: br label [[G_US]]
-; CHECK: g.split.us6:
-; CHECK-NEXT: br label [[FOR_COND1_US1]]
; CHECK: for.end.split:
; CHECK-NEXT: br label [[G:%.*]]
; CHECK: g.loopexit:
@@ -121,23 +121,23 @@ define void @e(ptr %p) {
; CHECK-NEXT: br label [[G_US:%.*]]
; CHECK: g.us:
; CHECK-NEXT: br label [[G_SPLIT_US6:%.*]]
-; CHECK: for.cond1.us1:
+; CHECK: g.split.us1:
+; CHECK-NEXT: br label [[FOR_COND1_US2:%.*]]
+; CHECK: for.cond1.us2:
; CHECK-NEXT: [[TMP2:%.*]] = load i16, ptr [[P]], align 2
; CHECK-NEXT: [[TOBOOL4_NOT_US:%.*]] = trunc i16 [[TMP2]] to i1
; CHECK-NEXT: br i1 [[TOBOOL4_NOT_US]], label [[FOR_COND5_PREHEADER_US4:%.*]], label [[G_LOOPEXIT_US:%.*]]
-; CHECK: for.cond5.us2:
+; CHECK: for.cond5.preheader.us3:
+; CHECK-NEXT: br label [[FOR_COND5_US2:%.*]]
+; CHECK: for.cond5.us4:
; CHECK-NEXT: br i1 false, label [[FOR_COND1_LOOPEXIT_US5:%.*]], label [[FOR_INC_US3:%.*]]
-; CHECK: for.inc.us3:
+; CHECK: for.inc.us5:
; CHECK-NEXT: store i8 0, ptr @b, align 1
-; CHECK-NEXT: br label [[FOR_COND5_US2:%.*]]
-; CHECK: for.cond5.preheader.us4:
; CHECK-NEXT: br label [[FOR_COND5_US2]]
-; CHECK: for.cond1.loopexit.us5:
-; CHECK-NEXT: br label [[FOR_COND1_US1:%.*]], !llvm.loop [[LOOP3:![0-9]+]]
+; CHECK: for.cond1.loopexit.us6:
+; CHECK-NEXT: br label [[FOR_COND1_US2]], !llvm.loop [[LOOP3:![0-9]+]]
; CHECK: g.loopexit.us:
; CHECK-NEXT: br label [[G_US]]
-; CHECK: g.split.us6:
-; CHECK-NEXT: br label [[FOR_COND1_US1]]
; CHECK: for.end.split:
; CHECK-NEXT: br label [[G:%.*]]
; CHECK: g.loopexit:
diff --git a/llvm/test/Transforms/SimpleLoopUnswitch/guards.ll b/llvm/test/Transforms/SimpleLoopUnswitch/guards.ll
index 42b32e769d8d7..5697ad9de89ca 100644
--- a/llvm/test/Transforms/SimpleLoopUnswitch/guards.ll
+++ b/llvm/test/Transforms/SimpleLoopUnswitch/guards.ll
@@ -175,35 +175,35 @@ define void @test_nested_loop(i1 %cond, i32 %N, i1 %arg) {
; CHECK: [[ENTRY_SPLIT]]:
; CHECK-NEXT: br i1 [[ARG]], label %[[ENTRY_SPLIT_SPLIT_US:.*]], label %[[ENTRY_SPLIT_SPLIT:.*]]
; CHECK: [[ENTRY_SPLIT_SPLIT_US]]:
-; CHECK-NEXT: br label %[[OUTER_LOOP_US:.*]]
-; CHECK: [[OUTER_LOOP_US]]:
; CHECK-NEXT: br label %[[OUTER_LOOP_SPLIT_US_US:.*]]
-; CHECK: [[OUTER_BACKEDGE_US:.*]]:
-; CHECK-NEXT: br label %[[OUTER_LOOP_US]]
; CHECK: [[OUTER_LOOP_SPLIT_US_US]]:
+; CHECK-NEXT: br label %[[OUTER_LOOP_US:.*]]
+; CHECK: [[OUTER_LOOP_US]]:
; CHECK-NEXT: br label %[[LOOP_US_US:.*]]
; CHECK: [[LOOP_US_US]]:
-; CHECK-NEXT: [[IV_US_US:%.*]] = phi i32 [ 0, %[[OUTER_LOOP_SPLIT_US_US]] ], [ [[IV_NEXT_US_US:%.*]], %[[GUARDED_US_US:.*]] ]
+; CHECK-NEXT: [[IV_US_US:%.*]] = phi i32 [ 0, %[[OUTER_LOOP_US]] ], [ [[IV_NEXT_US_US:%.*]], %[[GUARDED_US_US:.*]] ]
; CHECK-NEXT: br label %[[GUARDED_US_US]]
; CHECK: [[GUARDED_US_US]]:
; CHECK-NEXT: [[IV_NEXT_US_US]] = add i32 [[IV_US_US]], 1
; CHECK-NEXT: [[LOOP_COND_US_US:%.*]] = icmp slt i32 [[IV_NEXT_US_US]], [[N]]
; CHECK-NEXT: br i1 [[LOOP_COND_US_US]], label %[[LOOP_US_US]], label %[[OUTER_BACKEDGE_SPLIT_US_US:.*]]
; CHECK: [[OUTER_BACKEDGE_SPLIT_US_US]]:
-; CHECK-NEXT: br label %[[OUTER_BACKEDGE_US]]
+; CHECK-NEXT: br label %[[OUTER_BACKEDGE_US:.*]]
+; CHECK: [[OUTER_BACKEDGE_US]]:
+; CHECK-NEXT: br label %[[OUTER_LOOP_SPLIT_US_US]]
; CHECK: [[ENTRY_SPLIT_SPLIT]]:
-; CHECK-NEXT: br label %[[OUTER_LOOP:.*]]
-; CHECK: [[OUTER_LOOP]]:
; CHECK-NEXT: br label %[[OUTER_LOOP_SPLIT_US:.*]]
; CHECK: [[OUTER_LOOP_SPLIT_US]]:
; CHECK-NEXT: br label %[[LOOP_US:.*]]
; CHECK: [[LOOP_US]]:
-; CHECK-NEXT: [[IV_US:%.*]] = phi i32 [ 0, %[[OUTER_LOOP_SPLIT_US]] ], [ [[IV_NEXT_US:%.*]], %[[GUARDED_US:.*]] ]
-; CHECK-NEXT: br label %[[GUARDED_US]]
+; CHECK-NEXT: br label %[[GUARDED_US:.*]]
; CHECK: [[GUARDED_US]]:
+; CHECK-NEXT: [[IV_US:%.*]] = phi i32 [ 0, %[[LOOP_US]] ], [ [[IV_NEXT_US:%.*]], %[[GUARDED_US1:.*]] ]
+; CHECK-NEXT: br label %[[GUARDED_US1]]
+; CHECK: [[GUARDED_US1]]:
; CHECK-NEXT: [[IV_NEXT_US]] = add i32 [[IV_US]], 1
; CHECK-NEXT: [[LOOP_COND_US:%.*]] = icmp slt i32 [[IV_NEXT_US]], [[N]]
-; CHECK-NEXT: br i1 [[LOOP_COND_US]], label %[[LOOP_US]], label %[[OUTER_BACKEDGE_SPLIT_US:.*]]
+; CHECK-NEXT: br i1 [[LOOP_COND_US]], label %[[GUARDED_US]], label %[[OUTER_BACKEDGE_SPLIT_US:.*]]
; CHECK: [[OUTER_BACKEDGE_SPLIT_US]]:
; CHECK-NEXT: br label %[[OUTER_BACKEDGE:.*]]
; CHECK: [[OUTER_LOOP_SPLIT]]:
diff --git a/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch-select.ll b/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch-select.ll
index c86fa349200c5..47411e37613eb 100644
--- a/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch-select.ll
+++ b/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch-select.ll
@@ -603,10 +603,6 @@ define dso_local void @select_nested_loop(i1 noundef zeroext %cond, i32 noundef
; CHECK: for.cond1.preheader.us.us:
; CHECK-NEXT: [[I_018_US_US:%.*]] = phi i32 [ [[INC7_US_US:%.*]], [[FOR_COND1_FOR_COND_CLEANUP3_CRIT_EDGE_US_US:%.*]] ], [ 0, [[FOR_COND1_PREHEADER_US_PREHEADER_SPLIT_US]] ]
; CHECK-NEXT: br label [[FOR_COND1_PREHEADER_US_SPLIT_US_US:%.*]]
-; CHECK: for.cond1.for.cond.cleanup3_crit_edge.us.us:
-; CHECK-NEXT: [[INC7_US_US]] = add nuw i32 [[I_018_US_US]], 1
-; CHECK-NEXT: [[EXITCOND21_NOT_US:%.*]] = icmp eq i32 [[INC7_US_US]], [[N]]
-; CHECK-NEXT: br i1 [[EXITCOND21_NOT_US]], label [[FOR_COND_CLEANUP_LOOPEXIT_SPLIT_US:%.*]], label [[FOR_COND1_PREHEADER_US_US]]
; CHECK: for.cond1.preheader.us.split.us.us:
; CHECK-NEXT: br label [[FOR_BODY4_US_US_US:%.*]]
; CHECK: for.body4.us.us.us:
@@ -622,6 +618,10 @@ define dso_local void @select_nested_loop(i1 noundef zeroext %cond, i32 noundef
; CHECK-NEXT: br i1 [[EXITCOND_NOT_US_US]], label [[FOR_COND1_FOR_COND_CLEANUP3_CRIT_EDGE_US_SPLIT_US_US:%.*]], label [[FOR_BODY4_US_US_US]]
; CHECK: for.cond1.for.cond.cleanup3_crit_edge.us.split.us.us:
; CHECK-NEXT: br label [[FOR_COND1_FOR_COND_CLEANUP3_CRIT_EDGE_US_US]]
+; CHECK: for.cond1.for.cond.cleanup3_crit_edge.us.us:
+; CHECK-NEXT: [[INC7_US_US]] = add nuw i32 [[I_018_US_US]], 1
+; CHECK-NEXT: [[EXITCOND21_NOT_US:%.*]] = icmp eq i32 [[INC7_US_US]], [[N]]
+; CHECK-NEXT: br i1 [[EXITCOND21_NOT_US]], label [[FOR_COND_CLEANUP_LOOPEXIT_SPLIT_US:%.*]], label [[FOR_COND1_PREHEADER_US_US]]
; CHECK: for.cond.cleanup.loopexit.split.us:
; CHECK-NEXT: br label [[FOR_COND_CLEANUP_LOOPEXIT:%.*]]
; CHECK: for.cond1.preheader.us.preheader.split:
diff --git a/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch.ll b/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch.ll
index 8fddaa1839576..6d7928847cca5 100644
--- a/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch.ll
+++ b/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch.ll
@@ -4082,16 +4082,9 @@ inner.body1:
; CHECK: [[OUTER_HEADER_US_US]]:
; CHECK-NEXT: br label %[[OUTER_HEADER_SPLIT_US_US:.*]]
;
-; CHECK: [[OUTER_LATCH_US_US:.*]]:
-; CHECK-NEXT: %[[OUTER_COND_US_US:.*]] = call i1 @cond()
-; CHECK-NEXT: br i1 %[[OUTER_COND_US_US]], label %[[OUTER_HEADER_US_US]], label %[[EXIT_SPLIT_US_SPLIT_US:.*]]
-;
; CHECK: [[OUTER_HEADER_SPLIT_US_US]]:
; CHECK-NEXT: br label %[[OUTER_HEADER_SPLIT_SPLIT_US_US_US:.*]]
;
-; CHECK: [[INNER_LOOPEXIT2_US_US:.*]]:
-; CHECK-NEXT: br label %[[OUTER_LATCH_US_US]]
-;
; CHECK: [[OUTER_HEADER_SPLIT_SPLIT_US_US_US]]:
; CHECK-NEXT: br label %[[INNER_HEADER_US_US_US:.*]]
;
@@ -4119,7 +4112,14 @@ inner.body1:
; CHECK-NEXT: br i1 %[[INNER_COND_US_US_US]], label %[[INNER_HEADER_US_US_US]], label %[[INNER_LOOPEXIT2_SPLIT_US_US_US:.*]]
;
; CHECK: [[INNER_LOOPEXIT2_SPLIT_US_US_US]]:
-; CHECK-NEXT: br label %[[INNER_LOOPEXIT2_US_US]]
+; CHECK-NEXT: br label %[[INNER_LOOPEXIT2_US_US:.*]]
+;
+; CHECK: [[INNER_LOOPEXIT2_US_US]]:
+; CHECK-NEXT: br label %[[OUTER_LATCH_US_US:.*]]
+;
+; CHECK: [[OUTER_LATCH_US_US]]:
+; CHECK-NEXT: %[[OUTER_COND_US_US:.*]] = call i1 @cond()
+; CHECK-NEXT: br i1 %[[OUTER_COND_US_US]], label %[[OUTER_HEADER_US_US]], label %[[EXIT_SPLIT_US_SPLIT_US:.*]]
;
; CHECK: [[EXIT_SPLIT_US_SPLIT_US]]:
; CHECK-NEXT: br label %[[EXIT_SPLIT_US:.*]]
@@ -4136,7 +4136,13 @@ inner.body2:
; CHECK: [[OUTER_HEADER_US]]:
; CHECK-NEXT: br label %[[OUTER_HEADER_SPLIT_US:.*]]
;
-; CHECK: [[INNER_HEADER_US:.*]]:
+; CHECK: [[OUTER_HEADER_SPLIT_US]]:
+; CHECK-NEXT: br label %[[OUTER_HEADER_SPLIT_SPLIT_US:.*]]
+;
+; CHECK: [[OUTER_HEADER_SPLIT_SPLIT_US]]:
+; CHECK-NEXT: br label %[[INNER_HEADER_US:.*]]
+;
+; CHECK: [[INNER_HEADER_US]]:
; CHECK-NEXT: br label %[[INNER_BODY2_US:.*]]
;
; CHECK: [[INNER_BODY2_US]]:
@@ -4161,19 +4167,13 @@ inner.body2:
; CHECK: [[INNER_LOOPEXIT2_SPLIT_US]]:
; CHECK-NEXT: br label %[[INNER_LOOPEXIT2_US:.*]]
;
-; CHECK: [[OUTER_LATCH_US:.*]]:
+; CHECK: [[INNER_LOOPEXIT2_US]]:
+; CHECK-NEXT: br label %[[OUTER_LATCH_US:.*]]
+;
+; CHECK: [[OUTER_LATCH_US]]:
; CHECK-NEXT: %[[OUTER_COND_US:.*]] = call i1 @cond()
; CHECK-NEXT: br i1 %[[OUTER_COND_US]], label %[[OUTER_HEADER_US]], label %[[EXIT_SPLIT_US_SPLIT:.*]]
;
-; CHECK: [[OUTER_HEADER_SPLIT_US]]:
-; CHECK-NEXT: br label %[[OUTER_HEADER_SPLIT_SPLIT_US:.*]]
-;
-; CHECK: [[OUTER_HEADER_SPLIT_SPLIT_US]]:
-; CHECK-NEXT: br label %[[INNER_HEADER_US]]
-;
-; CHECK: [[INNER_LOOPEXIT2_US]]:
-; CHECK-NEXT: br label %[[OUTER_LATCH_US]]
-;
; CHECK: [[EXIT_SPLIT_US]]:
; CHECK-NEXT: br label %exit
diff --git a/llvm/test/Transforms/SimpleLoopUnswitch/partial-unswitch-loop-and-block-dispositions.ll b/llvm/test/Transforms/SimpleLoopUnswitch/partial-unswitch-loop-and-block-dispositions.ll
index a169aa47ea7d5..30397e792e4b4 100644
--- a/llvm/test/Transforms/SimpleLoopUnswitch/partial-unswitch-loop-and-block-dispositions.ll
+++ b/llvm/test/Transforms/SimpleLoopUnswitch/partial-unswitch-loop-and-block-dispositions.ll
@@ -24,47 +24,47 @@ define void @test_pr58564(i16 %a, i1 %c.1, ptr %dst) {
; CHECK-NEXT: br label [[LOOP_1_HEADER_US:%.*]]
; CHECK: loop.1.header.us:
; CHECK-NEXT: br label [[LOOP_1_HEADER_SPLIT_US_US:%.*]]
-; CHECK: loop.4.header.us5:
+; CHECK: loop.1.header.split.us.us:
+; CHECK-NEXT: br label [[LOOP_1_HEADER_SPLIT_US_SPLIT_US5:%.*]]
+; CHECK: loop.1.header.split.us.split.us5:
+; CHECK-NEXT: br label [[LOOP_2_HEADER_US_US6:%.*]]
+; CHECK: loop.2.header.us.us6:
+; CHECK-NEXT: br label [[LOOP_2_HEADER_SPLIT_US_US_US7:%.*]]
+; CHECK: loop.2.header.split.us.us.us7:
+; CHECK-NEXT: br label [[LOOP_2_HEADER_SPLIT_US_SPLIT_US1_US:%.*]]
+; CHECK: loop.2.header.split.us.split.us1.us:
+; CHECK-NEXT: br label [[LOOP_3_HEADER_US_US2_US:%.*]]
+; CHECK: loop.3.header.us.us2.us:
+; CHECK-NEXT: br label [[LOOP_3_LATCH_US_US3_US:%.*]]
+; CHECK: loop.3.latch.us.us3.us:
+; CHECK-NEXT: br label [[LOOP_2_LATCH_SPLIT_US_US_US:%.*]]
+; CHECK: loop.2.latch.split.us.us.us:
+; CHECK-NEXT: br label [[LOOP_2_LATCH_US_US:%.*]]
+; CHECK: loop.2.latch.us.us:
+; CHECK-NEXT: br i1 false, label [[LOOP_2_HEADER_US_US6]], label [[LOOP_4_HEADER_PREHEADER_SPLIT_US_US:%.*]]
+; CHECK: loop.4.header.preheader.split.us.us:
+; CHECK-NEXT: br label [[LOOP_4_HEADER_PREHEADER_US:%.*]]
+; CHECK: loop.4.header.preheader.us:
+; CHECK-NEXT: br i1 false, label [[LOOP_4_HEADER_PREHEADER_SPLIT4_US_SPLIT_US:%.*]], label [[LOOP_4_HEADER_PREHEADER_SPLIT4_US8:%.*]]
+; CHECK: loop.4.header.preheader.split4.us8:
+; CHECK-NEXT: br label [[LOOP_4_HEADER_US5:%.*]]
+; CHECK: loop.4.header.us9:
; CHECK-NEXT: br label [[LOOP_5_US6:%.*]]
-; CHECK: loop.5.us6:
-; CHECK-NEXT: [[IV_US7:%.*]] = phi i16 [ 0, [[LOOP_4_HEADER_US5:%.*]] ], [ [[IV_NEXT_US9:%.*]], [[LOOP_5_US6]] ]
+; CHECK: loop.5.us10:
+; CHECK-NEXT: [[IV_US7:%.*]] = phi i16 [ 0, [[LOOP_4_HEADER_US5]] ], [ [[IV_NEXT_US9:%.*]], [[LOOP_5_US6]] ]
; CHECK-NEXT: [[GEP_US8:%.*]] = getelementptr inbounds ptr, ptr [[DST:%.*]], i16 [[IV_US7]]
; CHECK-NEXT: store ptr null, ptr [[GEP_US8]], align 8
; CHECK-NEXT: [[IV_NEXT_US9]] = add nuw nsw i16 [[IV_US7]], 1
; CHECK-NEXT: [[EC_US10:%.*]] = icmp ne i16 [[IV_US7]], 10000
; CHECK-NEXT: br i1 [[EC_US10]], label [[LOOP_5_US6]], label [[LOOP_4_LATCH_US11:%.*]]
-; CHECK: loop.4.latch.us11:
+; CHECK: loop.4.latch.us15:
; CHECK-NEXT: br label [[LOOP_1_LATCH_US:%.*]]
; CHECK: loop.1.latch.us:
; CHECK-NEXT: br label [[LOOP_1_HEADER_US]]
-; CHECK: loop.4.header.preheader.us:
-; CHECK-NEXT: br i1 false, label [[LOOP_4_HEADER_PREHEADER_SPLIT4_US_SPLIT_US:%.*]], label [[LOOP_4_HEADER_PREHEADER_SPLIT4_US15:%.*]]
-; CHECK: loop.1.header.split.us.us:
-; CHECK-NEXT: br label [[LOOP_1_HEADER_SPLIT_US_SPLIT_US14:%.*]]
-; CHECK: loop.2.header.us.us12:
-; CHECK-NEXT: br label [[LOOP_2_HEADER_SPLIT_US_US_US13:%.*]]
-; CHECK: loop.2.latch.us.us:
-; CHECK-NEXT: br i1 false, label [[LOOP_2_HEADER_US_US12:%.*]], label [[LOOP_4_HEADER_PREHEADER_SPLIT_US_US:%.*]]
-; CHECK: loop.2.header.split.us.us.us13:
-; CHECK-NEXT: br label [[LOOP_2_HEADER_SPLIT_US_SPLIT_US3_US:%.*]]
-; CHECK: loop.3.header.us.us1.us:
-; CHECK-NEXT: br label [[LOOP_3_LATCH_US_US2_US:%.*]]
-; CHECK: loop.3.latch.us.us2.us:
-; CHECK-NEXT: br label [[LOOP_2_LATCH_SPLIT_US_US_US:%.*]]
-; CHECK: loop.2.latch.split.us.us.us:
-; CHECK-NEXT: br label [[LOOP_2_LATCH_US_US:%.*]]
-; CHECK: loop.2.header.split.us.split.us3.us:
-; CHECK-NEXT: br label [[LOOP_3_HEADER_US_US1_US:%.*]]
-; CHECK: loop.4.header.preheader.split.us.us:
-; CHECK-NEXT: br label [[LOOP_4_HEADER_PREHEADER_US:%.*]]
-; CHECK: loop.1.header.split.us.split.us14:
-; CHECK-NEXT: br label [[LOOP_2_HEADER_US_US12]]
-; CHECK: loop.4.header.preheader.split4.us15:
-; CHECK-NEXT: br label [[LOOP_4_HEADER_US5]]
-; CHECK: loop.4.header.preheader.split4.us.split.us:
-; CHECK-NEXT: br label [[LOOP_4_HEADER_PREHEADER_SPLIT4_US:%.*]]
; CHECK: loop.1.header.split.us.split.us.split.us:
; CHECK-NEXT: br label [[LOOP_1_HEADER_SPLIT_US_SPLIT_US:%.*]]
+; CHECK: loop.4.header.preheader.split4.us.split.us:
+; CHECK-NEXT: br label [[LOOP_4_HEADER_PREHEADER_SPLIT4_US:%.*]]
; CHECK: entry.split:
; CHECK-NEXT: br label [[LOOP_1_HEADER:%.*]]
; CHECK: loop.1.header:
@@ -86,22 +86,22 @@ define void @test_pr58564(i16 %a, i1 %c.1, ptr %dst) {
; CHECK-NEXT: br label [[LOOP_2_HEADER_US:%.*]]
; CHECK: loop.2.header.us:
; CHECK-NEXT: br label [[LOOP_2_HEADER_SPLIT_US_US:%.*]]
-; CHECK: loop.2.latch.us:
-; CHECK-NEXT: br i1 false, label [[LOOP_2_HEADER_US]], label [[LOOP_4_HEADER_PREHEADER_SPLIT_US:%.*]]
; CHECK: loop.2.header.split.us.us:
; CHECK-NEXT: br label [[LOOP_2_HEADER_SPLIT_US_SPLIT_US3:%.*]]
-; CHECK: loop.3.header.us.us1:
+; CHECK: loop.2.header.split.us.split.us1:
; CHECK-NEXT: br label [[LOOP_3_LATCH_US_US2:%.*]]
-; CHECK: loop.3.latch.us.us2:
+; CHECK: loop.3.header.us.us2:
; CHECK-NEXT: br label [[LOOP_2_LATCH_SPLIT_US_US:%.*]]
+; CHECK: loop.3.latch.us.us3:
+; CHECK-NEXT: br label [[LOOP_2_LATCH_SPLIT_US_US1:%.*]]
; CHECK: loop.2.latch.split.us.us:
-; CHECK-NEXT: br label [[LOOP_2_LATCH_US:%.*]]
-; CHECK: loop.2.header.split.us.split.us3:
-; CHECK-NEXT: br label [[LOOP_3_HEADER_US_US1:%.*]]
-; CHECK: loop.4.header.preheader.split.us:
; CHECK-NEXT: br label [[LOOP_4_HEADER_PREHEADER:%.*]]
+; CHECK: loop.2.latch.us:
+; CHECK-NEXT: br i1 false, label [[LOOP_2_HEADER_US]], label [[LOOP_4_HEADER_PREHEADER_SPLIT_US:%.*]]
; CHECK: loop.2.header.split.us.split.us.split.us:
; CHECK-NEXT: br label [[LOOP_2_HEADER_SPLIT_US_SPLIT_US:%.*]]
+; CHECK: loop.4.header.preheader.split.us:
+; CHECK-NEXT: br label [[LOOP_4_HEADER_PREHEADER1:%.*]]
; CHECK: loop.1.header.split:
; CHECK-NEXT: br label [[LOOP_2_HEADER:%.*]]
; CHECK: loop.2.header:
@@ -140,7 +140,7 @@ define void @test_pr58564(i16 %a, i1 %c.1, ptr %dst) {
; CHECK: loop.2.latch:
; CHECK-NEXT: br i1 [[C_1]], label [[LOOP_2_HEADER]], label [[LOOP_4_HEADER_PREHEADER_SPLIT:%.*]], !llvm.loop [[LOOP2:![0-9]+]]
; CHECK: loop.4.header.preheader.split:
-; CHECK-NEXT: br label [[LOOP_4_HEADER_PREHEADER]]
+; CHECK-NEXT: br label [[LOOP_4_HEADER_PREHEADER1]]
; CHECK: loop.4.header.preheader:
; CHECK-NEXT: br i1 [[C_1]], label [[LOOP_4_HEADER_PREHEADER_SPLIT4_US_SPLIT:%.*]], label [[LOOP_4_HEADER_PREHEADER_SPLIT4:%.*]]
; CHECK: loop.4.header.preheader.split4.us.split:
diff --git a/llvm/test/Transforms/SimpleLoopUnswitch/update-scev-3.ll b/llvm/test/Transforms/SimpleLoopUnswitch/update-scev-3.ll
index ef00d7ea8f2bb..9f7a8f6d03093 100644
--- a/llvm/test/Transforms/SimpleLoopUnswitch/update-scev-3.ll
+++ b/llvm/test/Transforms/SimpleLoopUnswitch/update-scev-3.ll
@@ -32,34 +32,34 @@ define i32 @foo(i1 %not) {
; CHECK-NEXT: br label [[FOR_COND_US:%.*]]
; CHECK: for.cond.us:
; CHECK-NEXT: br label [[FOR_COND_SPLIT_US_US:%.*]]
-; CHECK: for.inc11.us:
-; CHECK-NEXT: br label [[FOR_COND_US]]
; CHECK: for.cond.split.us.us:
-; CHECK-NEXT: br label [[FOR_COND_SPLIT_US_SPLIT_US11:%.*]]
-; CHECK: for.cond5.preheader.us.us9:
-; CHECK-NEXT: br label [[FOR_COND5_PREHEADER_SPLIT_US_US_US10:%.*]]
-; CHECK: for.inc8.us.us:
-; CHECK-NEXT: br i1 false, label [[FOR_INC8_FOR_COND5_PREHEADER_CRIT_EDGE_US_US:%.*]], label [[FOR_INC11_SPLIT_US_US:%.*]]
-; CHECK: for.inc8.for.cond5.preheader_crit_edge.us.us:
-; CHECK-NEXT: br label [[FOR_COND5_PREHEADER_US_US9:%.*]]
-; CHECK: for.end.us.us:
-; CHECK-NEXT: br i1 false, label [[FOR_INC8_US_US:%.*]], label [[CLEANUP15_SPLIT_US_SPLIT_US:%.*]]
-; CHECK: for.cond5.preheader.split.us.us.us10:
-; CHECK-NEXT: br label [[FOR_COND5_PREHEADER_SPLIT_US_SPLIT_US7_US:%.*]]
-; CHECK: for.body7.us.us4.us:
-; CHECK-NEXT: br label [[HANDLER_POINTER_OVERFLOW_US_US5_US:%.*]]
-; CHECK: handler.pointer_overflow.us.us5.us:
-; CHECK-NEXT: br label [[CONT_US_US6_US:%.*]]
-; CHECK: cont.us.us6.us:
+; CHECK-NEXT: br label [[FOR_COND_SPLIT_US_SPLIT_US9:%.*]]
+; CHECK: for.cond.split.us.split.us9:
+; CHECK-NEXT: br label [[FOR_COND5_PREHEADER_US_US10:%.*]]
+; CHECK: for.cond5.preheader.us.us10:
+; CHECK-NEXT: br label [[FOR_COND5_PREHEADER_SPLIT_US_US_US11:%.*]]
+; CHECK: for.cond5.preheader.split.us.us.us11:
+; CHECK-NEXT: br label [[FOR_COND5_PREHEADER_SPLIT_US_SPLIT_US4_US:%.*]]
+; CHECK: for.cond5.preheader.split.us.split.us4.us:
+; CHECK-NEXT: br label [[FOR_BODY7_US_US5_US:%.*]]
+; CHECK: for.body7.us.us5.us:
+; CHECK-NEXT: br label [[HANDLER_POINTER_OVERFLOW_US_US6_US:%.*]]
+; CHECK: handler.pointer_overflow.us.us6.us:
+; CHECK-NEXT: br label [[CONT_US_US7_US:%.*]]
+; CHECK: cont.us.us7.us:
; CHECK-NEXT: br label [[FOR_END_SPLIT_US_US_US:%.*]]
; CHECK: for.end.split.us.us.us:
; CHECK-NEXT: br label [[FOR_END_US_US:%.*]]
-; CHECK: for.cond5.preheader.split.us.split.us7.us:
-; CHECK-NEXT: br label [[FOR_BODY7_US_US4_US:%.*]]
+; CHECK: for.end.us.us:
+; CHECK-NEXT: br i1 false, label [[FOR_INC8_US_US:%.*]], label [[CLEANUP15_SPLIT_US_SPLIT_US:%.*]]
+; CHECK: for.inc8.us.us:
+; CHECK-NEXT: br i1 false, label [[FOR_INC8_FOR_COND5_PREHEADER_CRIT_EDGE_US_US:%.*]], label [[FOR_INC11_SPLIT_US_US:%.*]]
+; CHECK: for.inc8.for.cond5.preheader_crit_edge.us.us:
+; CHECK-NEXT: br label [[FOR_COND5_PREHEADER_US_US10]]
; CHECK: for.inc11.split.us.us:
; CHECK-NEXT: br label [[FOR_INC11_US:%.*]]
-; CHECK: for.cond.split.us.split.us11:
-; CHECK-NEXT: br label [[FOR_COND5_PREHEADER_US_US9]]
+; CHECK: for.inc11.us:
+; CHECK-NEXT: br label [[FOR_COND_US]]
; CHECK: for.cond.split.us.split.us.split.us:
; CHECK-NEXT: br label [[FOR_COND_SPLIT_US_SPLIT_US:%.*]]
; CHECK: cleanup15.split.us.split.us:
@@ -86,10 +86,10 @@ define i32 @foo(i1 %not) {
; CHECK-NEXT: br label [[FOR_COND5_PREHEADER_SPLIT_US_SPLIT_US_SPLIT_US_SPLIT_US:%.*]]
; CHECK: for.cond5.preheader.split.us.split.us.split.us.split.us:
; CHECK-NEXT: br label [[FOR_COND5_PREHEADER_SPLIT_US_SPLIT_US_SPLIT_US:%.*]]
-; CHECK: cleanup15.split.us:
-; CHECK-NEXT: br label [[CLEANUP15:%.*]]
; CHECK: for.cond5.preheader.split.us.split.us.split.us:
; CHECK-NEXT: br label [[FOR_COND5_PREHEADER_SPLIT_US_SPLIT_US:%.*]]
+; CHECK: cleanup15.split.us:
+; CHECK-NEXT: br label [[CLEANUP15:%.*]]
; CHECK: for.cond.split:
; CHECK-NEXT: br label [[FOR_COND_SPLIT_SPLIT:%.*]]
; CHECK: for.cond.split.split.us:
diff --git a/llvm/unittests/Analysis/LoopInfoTest.cpp b/llvm/unittests/Analysis/LoopInfoTest.cpp
index 5321f72aa1784..77fa03e11e381 100644
--- a/llvm/unittests/Analysis/LoopInfoTest.cpp
+++ b/llvm/unittests/Analysis/LoopInfoTest.cpp
@@ -53,6 +53,13 @@ static std::unique_ptr<Module> makeLLVMModule(LLVMContext &Context,
return parseAssemblyString(ModuleStr, Err, Context);
}
+static BasicBlock *getBlockByName(Function *F, StringRef Name) {
+ for (auto &BB : *F)
+ if (BB.getName() == Name)
+ return &BB;
+ return nullptr;
+}
+
// This tests that for a loop with a single latch, we get the loop id from
// its only latch, even in case the loop may not be in a simplified form.
TEST(LoopInfoTest, LoopWithSingleLatch) {
@@ -1646,3 +1653,112 @@ TEST(LoopInfoTest, TokenLCSSA) {
InnerLoop->isRecursivelyLCSSAForm(DT, LI, /*IgnoreTokens*/ false));
});
}
+
+TEST(LoopInfoTest, Recompute) {
+ const char *ModuleStr = "define void @test(i1 %c) {\n"
+ "entry:\n"
+ " br label %outer\n"
+ "outer:\n"
+ " br label %inner\n"
+ "inner:\n"
+ " br i1 %c, label %inner, label %outer.latch\n"
+ "outer.latch:\n"
+ " br i1 %c, label %outer, label %exit\n"
+ "exit:\n"
+ " ret void\n"
+ "}\n";
+
+ LLVMContext Context;
+ std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleStr);
+ Function *F = M->getFunction("test");
+ DominatorTree DT(*F);
+ LoopInfo LI(DT);
+
+ BasicBlock *Outer = getBlockByName(F, "outer");
+ BasicBlock *Inner = getBlockByName(F, "inner");
+ Loop *OuterL = LI.getLoopFor(Outer);
+ Loop *InnerL = LI.getLoopFor(Inner);
+ ASSERT_NE(OuterL, nullptr);
+ ASSERT_NE(InnerL, nullptr);
+
+ // An unchanged CFG must reproduce the same forest in the same objects.
+ auto Removed = LI.recompute(DT);
+ EXPECT_TRUE(Removed.empty());
+ EXPECT_EQ(LI.getLoopFor(Outer), OuterL);
+ EXPECT_EQ(LI.getLoopFor(Inner), InnerL);
+ EXPECT_EQ(InnerL->getParentLoop(), OuterL);
+ EXPECT_EQ(OuterL->getSubLoops().size(), 1u);
+ EXPECT_EQ(OuterL->getNumBlocks(), 3u);
+ LI.verify();
+
+ // Drop the inner backedge. The outer loop keeps its identity and absorbs the
+ // block; only the inner loop is reported as removed.
+ Inner->getTerminator()->setSuccessor(0, getBlockByName(F, "outer.latch"));
+ DT.recalculate(*F);
+ Removed = LI.recompute(DT);
+ EXPECT_EQ(LI.getLoopFor(Outer), OuterL);
+ EXPECT_EQ(LI.getLoopFor(Inner), OuterL);
+ EXPECT_TRUE(OuterL->getSubLoops().empty());
+ ASSERT_EQ(Removed.size(), 1u);
+ EXPECT_EQ(Removed[0].first, InnerL);
+ EXPECT_EQ(Removed[0].second, Inner);
+ LI.verify();
+ LI.destroy(InnerL);
+
+ // Dropping the last backedge leaves no loops at all.
+ getBlockByName(F, "outer.latch")
+ ->getTerminator()
+ ->setSuccessor(0, getBlockByName(F, "exit"));
+ DT.recalculate(*F);
+ Removed = LI.recompute(DT);
+ EXPECT_TRUE(LI.empty());
+ ASSERT_EQ(Removed.size(), 1u);
+ EXPECT_EQ(Removed[0].first, OuterL);
+ EXPECT_EQ(Removed[0].second, Outer);
+ LI.verify();
+ LI.destroy(OuterL);
+}
+
+TEST(LoopInfoTest, RecomputeHoist) {
+ const char *ModuleStr = "define void @test(i1 %c) {\n"
+ "entry:\n"
+ " br label %outer\n"
+ "outer:\n"
+ " br label %inner\n"
+ "inner:\n"
+ " br i1 %c, label %inner, label %outer.latch\n"
+ "outer.latch:\n"
+ " br i1 %c, label %outer, label %exit\n"
+ "exit:\n"
+ " ret void\n"
+ "}\n";
+
+ LLVMContext Context;
+ std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleStr);
+ Function *F = M->getFunction("test");
+ DominatorTree DT(*F);
+ LoopInfo LI(DT);
+
+ BasicBlock *Outer = getBlockByName(F, "outer");
+ BasicBlock *Inner = getBlockByName(F, "inner");
+ Loop *OuterL = LI.getLoopFor(Outer);
+ Loop *InnerL = LI.getLoopFor(Inner);
+ ASSERT_NE(OuterL, nullptr);
+ ASSERT_NE(InnerL, nullptr);
+
+ // Drop the outer backedge. The inner loop keeps its identity and becomes
+ // top-level; only the outer loop is reported as removed.
+ getBlockByName(F, "outer.latch")
+ ->getTerminator()
+ ->setSuccessor(0, getBlockByName(F, "exit"));
+ DT.recalculate(*F);
+ auto Removed = LI.recompute(DT);
+ EXPECT_EQ(LI.getLoopFor(Inner), InnerL);
+ EXPECT_EQ(InnerL->getParentLoop(), nullptr);
+ EXPECT_EQ(LI.getLoopFor(Outer), nullptr);
+ ASSERT_EQ(Removed.size(), 1u);
+ EXPECT_EQ(Removed[0].first, OuterL);
+ EXPECT_EQ(Removed[0].second, Outer);
+ LI.verify();
+ LI.destroy(OuterL);
+}
More information about the llvm-commits
mailing list