[Mlir-commits] [mlir] 571a1de - [LoopInfo] Store blocks using Euler tour representation (#211485)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Fri Jul 24 18:57:07 PDT 2026


Author: Fangrui Song
Date: 2026-07-25T01:57:01Z
New Revision: 571a1deb31125780ffb86fbae027b03efd0dcdd8

URL: https://github.com/llvm/llvm-project/commit/571a1deb31125780ffb86fbae027b03efd0dcdd8
DIFF: https://github.com/llvm/llvm-project/commit/571a1deb31125780ffb86fbae027b03efd0dcdd8.diff

LOG: [LoopInfo] Store blocks using Euler tour representation (#211485)

Block list construction appends each block to all enclosing loops
(O(depth) per block), and each loop owns a separately allocated vector.

Switch to an Euler tour representation: one BlockLayout array per
LoopInfo holding the in-loop blocks in a loop-contiguous reverse
postorder, each loop's list a [begin, end) slice of it, subloop slices
nested inside their parent's. Headers remain first;
`SubLoops`/`TopLevelLoops` orders are unchanged. A few tests observe the
order and are updated. (Previously a loop's list was the function's
reverse postorder restricted to its members, so a subloop's blocks were
interleaved with the parent's own blocks at their RPO positions.)

Mutations first copy a borrowed slice into private storage from
LoopInfo's allocator. `contains(BlockT *)` remains map-based: a
materialized loop's slice is not a membership test. This copy is rare:
across default<O2> over dagcombiner and sqlite3, only 12--14% of loops
built by analyze() are ever mutated; the rest keep the borrowed slice.

AllocateLoop() no longer forwards constructor arguments; analyze() uses
allocateLoop(Header), removing the header-taking loop constructors.

Aided by Claude Fable 5

Added: 
    

Modified: 
    bolt/include/bolt/Core/BinaryLoop.h
    bolt/test/X86/loop-nest.test
    llvm/include/llvm/Analysis/LoopInfo.h
    llvm/include/llvm/CodeGen/MachineLoopInfo.h
    llvm/include/llvm/Support/GenericLoopInfo.h
    llvm/include/llvm/Support/GenericLoopInfoImpl.h
    llvm/test/Transforms/LoopVectorize/early_exit_with_outer_loop.ll
    llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch-freeze.ll
    llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch.ll
    mlir/include/mlir/Analysis/CFGLoopInfo.h
    mlir/lib/Analysis/CFGLoopInfo.cpp

Removed: 
    


################################################################################
diff  --git a/bolt/include/bolt/Core/BinaryLoop.h b/bolt/include/bolt/Core/BinaryLoop.h
index b425c75715d8b..1c52a051a84cc 100644
--- a/bolt/include/bolt/Core/BinaryLoop.h
+++ b/bolt/include/bolt/Core/BinaryLoop.h
@@ -36,11 +36,6 @@ class BinaryLoop : public LoopBase<BinaryBasicBlock, BinaryLoop> {
   uint64_t ExitCount{0};
 
   // Most of the public interface is provided by LoopBase.
-
-protected:
-  friend class LoopInfoBase<BinaryBasicBlock, BinaryLoop>;
-  explicit BinaryLoop(BinaryBasicBlock *BB)
-      : LoopBase<BinaryBasicBlock, BinaryLoop>(BB) {}
 };
 
 class BinaryLoopInfo : public LoopInfoBase<BinaryBasicBlock, BinaryLoop> {

diff  --git a/bolt/test/X86/loop-nest.test b/bolt/test/X86/loop-nest.test
index 51c8fcdb32eaa..208cc9ebdfaaf 100644
--- a/bolt/test/X86/loop-nest.test
+++ b/bolt/test/X86/loop-nest.test
@@ -18,6 +18,6 @@ CHECK-NEXT: Loop basic blocks: .Ltmp[[#MAIN_OUTER_HDR]], .Ltmp[[#]]
 CHECK:      Binary Function "foo" after building cfg
 CHECK:      Loop Info for Function "foo"
 CHECK:      Outer loop header: .Ltmp[[#FOO_OUTER_HDR:]]
-CHECK-NEXT: Loop basic blocks: .Ltmp[[#FOO_OUTER_HDR]], .Ltmp[[#]], .LFT[[#]], .Ltmp[[#]], .Ltmp[[#FOO_INNER_HDR:]], .Ltmp[[#]], .Ltmp[[#FOO_INNER_BB:]]
+CHECK-NEXT: Loop basic blocks: .Ltmp[[#FOO_OUTER_HDR]], .Ltmp[[#]], .LFT[[#]], .Ltmp[[#]], .Ltmp[[#FOO_INNER_HDR:]], .Ltmp[[#FOO_INNER_BB:]], .Ltmp[[#]]
 CHECK:      Nested loop header: .Ltmp[[#FOO_INNER_HDR]]
 CHECK-NEXT: Loop basic blocks: .Ltmp[[#FOO_INNER_HDR]], .Ltmp[[#FOO_INNER_BB]]

diff  --git a/llvm/include/llvm/Analysis/LoopInfo.h b/llvm/include/llvm/Analysis/LoopInfo.h
index 13b404505b5e2..15c22e43c44bf 100644
--- a/llvm/include/llvm/Analysis/LoopInfo.h
+++ b/llvm/include/llvm/Analysis/LoopInfo.h
@@ -417,7 +417,6 @@ class LLVM_ABI Loop : public LoopBase<BasicBlock, Loop> {
 
   friend class LoopInfoBase<BasicBlock, Loop>;
   friend class LoopBase<BasicBlock, Loop>;
-  explicit Loop(BasicBlock *BB) : LoopBase<BasicBlock, Loop>(BB) {}
   ~Loop() = default;
 };
 

diff  --git a/llvm/include/llvm/CodeGen/MachineLoopInfo.h b/llvm/include/llvm/CodeGen/MachineLoopInfo.h
index 60b3f6c6a3ba5..7893f70cd353c 100644
--- a/llvm/include/llvm/CodeGen/MachineLoopInfo.h
+++ b/llvm/include/llvm/CodeGen/MachineLoopInfo.h
@@ -96,9 +96,6 @@ class MachineLoop : public LoopBase<MachineBasicBlock, MachineLoop> {
   /// Returns true if the given physreg has no defs inside the loop.
   bool isLoopInvariantImplicitPhysReg(Register Reg) const;
 
-  explicit MachineLoop(MachineBasicBlock *MBB)
-    : LoopBase<MachineBasicBlock, MachineLoop>(MBB) {}
-
   MachineLoop() = default;
 };
 

diff  --git a/llvm/include/llvm/Support/GenericLoopInfo.h b/llvm/include/llvm/Support/GenericLoopInfo.h
index 0bdfce7dc889e..e2819a21fe0ce 100644
--- a/llvm/include/llvm/Support/GenericLoopInfo.h
+++ b/llvm/include/llvm/Support/GenericLoopInfo.h
@@ -51,7 +51,6 @@ namespace llvm {
 
 template <class N, class M> class LoopInfoBase;
 template <class N, class M> class LoopBase;
-template <class N, class M> class PopulateLoopsDFS;
 
 //===----------------------------------------------------------------------===//
 /// Instances of this class are used to represent loops that are detected in the
@@ -62,8 +61,21 @@ template <class BlockT, class LoopT> class LoopBase {
   // Loops contained entirely within this one.
   std::vector<LoopT *> SubLoops;
 
-  // The list of blocks in this loop. First entry is the header node.
-  std::vector<BlockT *> Blocks;
+  // The list of blocks in this loop; first entry is the header. Either borrows
+  // a slice of the owning LoopInfo's BlockLayout, marked by the
+  // BorrowedCapacity sentinel, or is a private allocation of BlockCapacity
+  // slots from its allocator.
+  //
+  // Until analyze()'s layout carve runs, PendingHeader stashes the loop header
+  // (see pendingHeader()).
+  union {
+    BlockT *PendingHeader;
+    BlockT **BlockData = nullptr;
+  };
+  unsigned BlockLen = 0;
+  unsigned BlockCapacity = 0;
+
+  static constexpr unsigned BorrowedCapacity = -1u;
 
   // The LoopInfo that owns this loop. Used to answer contains(BlockT *) from
   // the central block-to-loop map.
@@ -186,7 +198,7 @@ template <class BlockT, class LoopT> class LoopBase {
   /// Get a list of the basic blocks which make up this loop.
   ArrayRef<BlockT *> getBlocks() const {
     assert(!isInvalid() && "Loop not in a valid state!");
-    return Blocks;
+    return ArrayRef<BlockT *>(BlockData, BlockLen);
   }
   using block_iterator = typename ArrayRef<BlockT *>::const_iterator;
   block_iterator block_begin() const { return getBlocks().begin(); }
@@ -200,7 +212,7 @@ template <class BlockT, class LoopT> class LoopBase {
   /// Invalidate the loop, indicating that it is no longer a loop.
   unsigned getNumBlocks() const {
     assert(!isInvalid() && "Loop not in a valid state!");
-    return Blocks.size();
+    return BlockLen;
   }
 
   /// Return true if this loop is no longer valid.  The only valid use of this
@@ -400,19 +412,19 @@ template <class BlockT, class LoopT> class LoopBase {
   /// transformations should use addBasicBlockToLoop.
   void addBlockEntry(BlockT *BB) {
     assert(!isInvalid() && "Loop not in a valid state!");
-    Blocks.push_back(BB);
-  }
-
-  /// interface to reverse Blocks[from, end of loop] in this loop
-  void reverseBlock(unsigned from) {
-    assert(!isInvalid() && "Loop not in a valid state!");
-    std::reverse(Blocks.begin() + from, Blocks.end());
+    // A borrowed slice or a full private allocation grows into fresh private
+    // storage before appending.
+    if (BlockCapacity == BorrowedCapacity || BlockLen == BlockCapacity)
+      LI->reallocBlocks(*static_cast<LoopT *>(this),
+                        std::max(2 * BlockLen, 4u));
+    BlockData[BlockLen++] = BB;
   }
 
   /// interface to do reserve() for Blocks
-  void reserveBlocks(unsigned size) {
+  void reserveBlocks(unsigned Size) {
     assert(!isInvalid() && "Loop not in a valid state!");
-    Blocks.reserve(size);
+    if (BlockCapacity < Size)
+      LI->reallocBlocks(*static_cast<LoopT *>(this), Size);
   }
 
   /// interface to do reserve() for SubLoops
@@ -421,21 +433,18 @@ template <class BlockT, class LoopT> class LoopBase {
     SubLoops.reserve(Size);
   }
 
-  /// Capacity of the block list; input to an enclosing loop's reserveBlocks()
-  /// during construction, when this loop's list is not yet fully populated.
-  unsigned getBlocksCapacity() const { return Blocks.capacity(); }
-
   /// This method is used to move BB (which must be part of this loop) to be the
   /// loop header of the loop (the block that dominates all others).
   void moveToHeader(BlockT *BB) {
     assert(!isInvalid() && "Loop not in a valid state!");
-    if (Blocks[0] == BB)
+    if (BlockData[0] == BB)
       return;
+    LI->materializeBlocks(*static_cast<LoopT *>(this));
     for (unsigned i = 0;; ++i) {
-      assert(i != Blocks.size() && "Loop does not contain BB!");
-      if (Blocks[i] == BB) {
-        Blocks[i] = Blocks[0];
-        Blocks[0] = BB;
+      assert(i != BlockLen && "Loop does not contain BB!");
+      if (BlockData[i] == BB) {
+        BlockData[i] = BlockData[0];
+        BlockData[0] = BB;
         return;
       }
     }
@@ -446,9 +455,12 @@ template <class BlockT, class LoopT> class LoopBase {
   /// class.
   void removeBlockFromLoop(BlockT *BB) {
     assert(!isInvalid() && "Loop not in a valid state!");
-    auto I = find(Blocks, BB);
+    LI->materializeBlocks(*static_cast<LoopT *>(this));
+    MutableArrayRef<BlockT *> Blocks(BlockData, BlockLen);
+    auto *I = llvm::find(Blocks, BB);
     assert(I != Blocks.end() && "N is not in this list!");
-    Blocks.erase(I);
+    std::move(I + 1, Blocks.end(), I);
+    --BlockLen;
   }
 
   /// Verify loop structure
@@ -469,15 +481,10 @@ template <class BlockT, class LoopT> class LoopBase {
 
 protected:
   friend class LoopInfoBase<BlockT, LoopT>;
-  friend class PopulateLoopsDFS<BlockT, LoopT>;
 
   /// This creates an empty loop.
   LoopBase() : ParentLoop(nullptr) {}
 
-  explicit LoopBase(BlockT *BB) : ParentLoop(nullptr) {
-    Blocks.push_back(BB);
-  }
-
   // Since loop passes like SCEV are allowed to key analysis results off of
   // `Loop` pointers, we cannot re-use pointers within a loop pass manager.
   // This means loop passes should not be `delete` ing `Loop` objects directly
@@ -495,7 +502,10 @@ template <class BlockT, class LoopT> class LoopBase {
     IsInvalid = true;
 #endif
     SubLoops.clear();
-    Blocks.clear();
+    // The block storage is reclaimed by the owning LoopInfo.
+    BlockData = nullptr;
+    BlockLen = 0;
+    BlockCapacity = 0;
     ParentLoop = nullptr;
   }
 };
@@ -520,11 +530,16 @@ template <class BlockT, class LoopT> class LoopInfoBase {
   // occurs in (or null).
   SmallVector<LoopT *> BBMap;
 
-  using ParentT = decltype(std::declval<const BlockT *>()->getParent());
+  using ParentT = decltype(std::declval<BlockT *>()->getParent());
   ParentT ParentPtr = nullptr;
   unsigned BlockNumberEpoch;
 
   std::vector<LoopT *> TopLevelLoops;
+
+  // Shared reverse postorder layout of the in-loop blocks. Each initial loop is
+  // a slice of this array, subloop slices nested inside their parent's.
+  std::unique_ptr<BlockT *[]> BlockLayout;
+
   BumpPtrAllocator LoopAllocator;
 
   friend class LoopBase<BlockT, LoopT>;
@@ -540,6 +555,7 @@ template <class BlockT, class LoopT> class LoopInfoBase {
   LoopInfoBase(LoopInfoBase &&Arg)
       : BBMap(std::move(Arg.BBMap)),
         TopLevelLoops(std::move(Arg.TopLevelLoops)),
+        BlockLayout(std::move(Arg.BlockLayout)),
         LoopAllocator(std::move(Arg.LoopAllocator)) {
     ParentPtr = Arg.ParentPtr;
     BlockNumberEpoch = Arg.BlockNumberEpoch;
@@ -556,6 +572,7 @@ template <class BlockT, class LoopT> class LoopInfoBase {
       L->~LoopT();
 
     TopLevelLoops = std::move(RHS.TopLevelLoops);
+    BlockLayout = std::move(RHS.BlockLayout);
     LoopAllocator = std::move(RHS.LoopAllocator);
     resetLoopInfoOwners();
     RHS.TopLevelLoops.clear();
@@ -568,12 +585,13 @@ template <class BlockT, class LoopT> class LoopInfoBase {
     for (auto *L : TopLevelLoops)
       L->~LoopT();
     TopLevelLoops.clear();
+    BlockLayout.reset();
     LoopAllocator.Reset();
   }
 
-  template <typename... ArgsTy> LoopT *AllocateLoop(ArgsTy &&...Args) {
+  LoopT *AllocateLoop() {
     LoopT *Storage = LoopAllocator.Allocate<LoopT>();
-    LoopT *L = new (Storage) LoopT(std::forward<ArgsTy>(Args)...);
+    LoopT *L = new (Storage) LoopT();
     L->LI = this;
     return L;
   }
@@ -621,7 +639,8 @@ template <class BlockT, class LoopT> class LoopInfoBase {
   }
 
   /// Verify that used block numbers are still valid.
-  void verifyBlockNumberEpoch(ParentT BBParent) const {
+  void
+  verifyBlockNumberEpoch(const std::remove_pointer_t<ParentT> *BBParent) const {
     assert(ParentPtr == BBParent &&
            "loop info queried with block of other function");
     assert(BlockNumberEpoch ==
@@ -636,6 +655,45 @@ template <class BlockT, class LoopT> class LoopInfoBase {
     return Number < BBMap.size() ? BBMap[Number] : nullptr;
   }
 
+  /// 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();
+    L->PendingHeader = Header;
+    return L;
+  }
+
+  /// 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; }
+
+  void discoverAndMapSubloop(LoopT *L, BlockT *Header,
+                             ArrayRef<BlockT *> Backedges,
+                             const DominatorTreeBase<BlockT, false> &DomTree);
+
+  /// True if \p L borrows its block list from BlockLayout.
+  static bool hasBorrowedBlocks(const LoopT &L) {
+    return L.BlockCapacity == LoopT::BorrowedCapacity;
+  }
+
+  /// Replace \p L's block list with a private allocation of NewCapacity
+  /// slots. The old storage is abandoned in place so slices sharing it stay
+  /// intact; it is reclaimed when this LoopInfo is cleared.
+  void reallocBlocks(LoopT &L, unsigned NewCapacity) {
+    assert(NewCapacity >= L.BlockLen && "capacity below size");
+    BlockT **New = LoopAllocator.Allocate<BlockT *>(NewCapacity);
+    llvm::copy(L.getBlocks(), New);
+    L.BlockData = New;
+    L.BlockCapacity = NewCapacity;
+  }
+
+  /// Copy \p L's borrowed block list into private storage before a mutation.
+  void materializeBlocks(LoopT &L) {
+    if (hasBorrowedBlocks(L))
+      reallocBlocks(L, L.BlockLen);
+  }
+
 public:
   /// Return the inner most loop that BB lives in. If a basic block is in no
   /// loop (for example the entry node), null is returned.
@@ -672,7 +730,10 @@ template <class BlockT, class LoopT> class LoopInfoBase {
   /// ancestors or descendants, and not the block-to-loop mapping.
   template <typename PredicateT>
   void removeBlocksIf(LoopT &L, PredicateT Pred) {
-    llvm::erase_if(L.Blocks, Pred);
+    materializeBlocks(L);
+    L.BlockLen = llvm::remove_if(
+                     MutableArrayRef<BlockT *>(L.BlockData, L.BlockLen), Pred) -
+                 L.BlockData;
   }
 
   /// Remove every block satisfying \p Pred from \p Start and each of its

diff  --git a/llvm/include/llvm/Support/GenericLoopInfoImpl.h b/llvm/include/llvm/Support/GenericLoopInfoImpl.h
index 170f05e9d3a49..5ea9f440dc28b 100644
--- a/llvm/include/llvm/Support/GenericLoopInfoImpl.h
+++ b/llvm/include/llvm/Support/GenericLoopInfoImpl.h
@@ -282,7 +282,7 @@ void LoopBase<BlockT, LoopT>::addBasicBlockToLoop(
     BlockT *NewBB, LoopInfoBase<BlockT, LoopT> &LIB) {
   assert(!isInvalid() && "Loop not in a valid state!");
 #ifndef NDEBUG
-  if (!Blocks.empty()) {
+  if (!getBlocks().empty()) {
     auto SameHeader = LIB[getHeader()];
     assert(contains(SameHeader) && getHeader() == SameHeader->getHeader() &&
            "Incorrect LI specified for this loop!");
@@ -325,7 +325,7 @@ template <class BlockT, class LoopT>
 void LoopBase<BlockT, LoopT>::verifyLoop() const {
   assert(!isInvalid() && "Loop not in a valid state!");
 #ifndef NDEBUG
-  assert(!Blocks.empty() && "Loop header is missing");
+  assert(!getBlocks().empty() && "Loop header is missing");
 
   // Setup for using a depth-first iterator to visit every block in the loop.
   SmallVector<BlockT *, 8> ExitBBs;
@@ -371,7 +371,7 @@ void LoopBase<BlockT, LoopT>::verifyLoop() const {
 
   if (VisitedBBs.size() != getNumBlocks()) {
     dbgs() << "The following blocks are unreachable in the loop: ";
-    for (auto *BB : Blocks) {
+    for (auto *BB : getBlocks()) {
       if (!VisitedBBs.count(BB)) {
         dbgs() << *BB << "\n";
       }
@@ -455,12 +455,11 @@ void LoopBase<BlockT, LoopT>::print(raw_ostream &OS, bool Verbose,
 /// this loop are mapped to this loop or a subloop. And all subloops within this
 /// loop have their parent loop set to this loop or a subloop.
 template <class BlockT, class LoopT>
-static void discoverAndMapSubloop(LoopT *L, ArrayRef<BlockT *> Backedges,
-                                  LoopInfoBase<BlockT, LoopT> *LI,
-                                  const DomTreeBase<BlockT> &DomTree) {
+void LoopInfoBase<BlockT, LoopT>::discoverAndMapSubloop(
+    LoopT *L, BlockT *Header, ArrayRef<BlockT *> Backedges,
+    const DominatorTreeBase<BlockT, false> &DomTree) {
   using InvBlockTraits = GraphTraits<Inverse<BlockT *>>;
 
-  unsigned NumBlocks = 0;
   unsigned NumSubloops = 0;
 
   // Perform a backward CFG traversal using a worklist.
@@ -469,15 +468,14 @@ static void discoverAndMapSubloop(LoopT *L, ArrayRef<BlockT *> Backedges,
     BlockT *PredBB = ReverseCFGWorklist.back();
     ReverseCFGWorklist.pop_back();
 
-    LoopT *Subloop = LI->getLoopFor(PredBB);
+    LoopT *Subloop = getLoopFor(PredBB);
     if (!Subloop) {
       if (!DomTree.isReachableFromEntry(PredBB))
         continue;
 
       // This is an undiscovered block. Map it to the current loop.
-      LI->changeLoopFor(PredBB, L);
-      ++NumBlocks;
-      if (PredBB == L->getHeader())
+      changeLoopFor(PredBB, L);
+      if (PredBB == Header)
         continue;
       // Push all block predecessors on the worklist.
       ReverseCFGWorklist.insert(ReverseCFGWorklist.end(),
@@ -494,84 +492,29 @@ static void discoverAndMapSubloop(LoopT *L, ArrayRef<BlockT *> Backedges,
       // Discover a subloop of this loop.
       Subloop->setParentLoop(L);
       ++NumSubloops;
-      NumBlocks += Subloop->getBlocksCapacity();
-      PredBB = Subloop->getHeader();
+      PredBB = pendingHeader(Subloop);
       // Continue traversal along predecessors that are not loop-back edges from
       // within this subloop tree itself. Note that a predecessor may directly
       // reach another subloop that is not yet discovered to be a subloop of
       // this loop, which we must traverse.
       for (const auto Pred : inverse_children<BlockT *>(PredBB)) {
-        if (LI->getLoopFor(Pred) != Subloop)
+        if (getLoopFor(Pred) != Subloop)
           ReverseCFGWorklist.push_back(Pred);
       }
     }
   }
   L->reserveSubLoops(NumSubloops);
-  L->reserveBlocks(NumBlocks);
-}
-
-/// Populate all loop data in a stable order during a single forward DFS.
-template <class BlockT, class LoopT> class PopulateLoopsDFS {
-  using BlockTraits = GraphTraits<BlockT *>;
-  using SuccIterTy = typename BlockTraits::ChildIteratorType;
-
-  LoopInfoBase<BlockT, LoopT> *LI;
-
-public:
-  PopulateLoopsDFS(LoopInfoBase<BlockT, LoopT> *li) : LI(li) {}
-
-  void traverse(BlockT *EntryBlock);
-
-protected:
-  void insertIntoLoop(BlockT *Block);
-};
-
-/// Top-level driver for the forward DFS within the loop.
-template <class BlockT, class LoopT>
-void PopulateLoopsDFS<BlockT, LoopT>::traverse(BlockT *EntryBlock) {
-  for (BlockT *BB : post_order(EntryBlock))
-    insertIntoLoop(BB);
-}
-
-/// Add a single Block to its ancestor loops in PostOrder. If the block is a
-/// subloop header, add the subloop to its parent in PostOrder, then reverse the
-/// Block and Subloop vectors of the now complete subloop to achieve RPO.
-template <class BlockT, class LoopT>
-void PopulateLoopsDFS<BlockT, LoopT>::insertIntoLoop(BlockT *Block) {
-  LoopT *Subloop = LI->getLoopFor(Block);
-  if (Subloop && Block == Subloop->getHeader()) {
-    // We reach this point once per subloop after processing all the blocks in
-    // the subloop.
-    if (!Subloop->isOutermost())
-      Subloop->getParentLoop()->SubLoops.push_back(Subloop);
-    else
-      LI->addTopLevelLoop(Subloop);
-
-    // For convenience, Blocks and Subloops are inserted in postorder. Reverse
-    // the lists, except for the loop header, which is always at the beginning.
-    Subloop->reverseBlock(1);
-    std::reverse(Subloop->SubLoops.begin(), Subloop->SubLoops.end());
-
-    Subloop = Subloop->getParentLoop();
-  }
-  for (; Subloop; Subloop = Subloop->getParentLoop())
-    Subloop->addBlockEntry(Block);
 }
 
 /// Analyze LoopInfo discovers loops during a reverse preorder DominatorTree
 /// traversal interleaved with backward CFG traversals within each subloop
 /// (discoverAndMapSubloop). The backward traversal skips inner subloops, so
-/// this part of the algorithm is linear in the number of CFG edges. Subloop and
-/// Block vectors are then populated during a single forward CFG traversal
-/// (PopulateLoopDFS).
+/// this part of the algorithm is linear in the number of CFG edges.
 ///
-/// During the two CFG traversals each block is seen three times:
-/// 1) Discovered and mapped by a reverse CFG traversal.
-/// 2) Visited during a forward DFS CFG traversal.
-/// 3) Reverse-inserted in the loop in postorder following forward DFS.
-///
-/// The Block vectors are inclusive, so step 3 requires loop-depth number of
-/// insertions per block.
+/// Then build a loop-contiguous reverse postorder for in-loops blocks. Lists
+/// are header-first with each subloop's blocks contiguous, ordered by first
+/// appearance in RPO; SubLoops keep program order, TopLevelLoops reverse
+/// program order.
 template <class BlockT, class LoopT>
 void LoopInfoBase<BlockT, LoopT>::analyze(const DomTreeBase<BlockT> &DomTree) {
   const DomTreeNodeBase<BlockT> *DomRoot = DomTree.getRootNode();
@@ -587,6 +530,7 @@ void LoopInfoBase<BlockT, LoopT>::analyze(const DomTreeBase<BlockT> &DomTree) {
   for (const DomTreeNodeBase<BlockT> *Node : DomTree.nodes())
     PreorderNodes[Node->getDFSNumIn()] = Node;
 
+  bool HasLoops = false;
   for (const DomTreeNodeBase<BlockT> *DomNode : llvm::reverse(PreorderNodes)) {
     BlockT *Header = DomNode->getBlock();
     SmallVector<BlockT *, 4> Backedges;
@@ -600,14 +544,64 @@ void LoopInfoBase<BlockT, LoopT>::analyze(const DomTreeBase<BlockT> &DomTree) {
     }
     // Perform a backward CFG traversal to discover and map blocks in this loop.
     if (!Backedges.empty()) {
-      LoopT *L = AllocateLoop(Header);
-      discoverAndMapSubloop(L, ArrayRef<BlockT *>(Backedges), this, DomTree);
+      HasLoops = true;
+      LoopT *L = allocateLoop(Header);
+      discoverAndMapSubloop(L, Header, Backedges, DomTree);
     }
   }
-  // Perform a single forward CFG traversal to populate block and subloop
-  // vectors for all loops.
-  PopulateLoopsDFS<BlockT, LoopT> DFS(this);
-  DFS.traverse(DomRoot->getBlock());
+  // Most functions have no loops; skip the layout construction.
+  if (!HasLoops)
+    return;
+
+  // Record each in-loop block with its innermost loop in forward CFG postorder,
+  // and build the loop list in PO.
+  SmallVector<std::pair<BlockT *, LoopT *>, 32> PO;
+  SmallVector<LoopT *, 4> LoopsPO;
+  PO.reserve(BBMap.size());
+  for (BlockT *BB : post_order(ParentPtr)) {
+    LoopT *L = lookupLoopFor(BB);
+    if (!L)
+      continue;
+    PO.emplace_back(BB, L);
+    ++L->BlockLen;
+    if (BB != pendingHeader(L))
+      continue;
+    LoopsPO.push_back(L);
+    if (LoopT *Parent = L->getParentLoop())
+      Parent->BlockLen += L->BlockLen;
+    else
+      TopLevelLoops.push_back(L);
+  }
+  // Headers are dominator-tree nodes, hence reachable and in the postorder.
+  assert(!LoopsPO.empty() && "discovered loops but found no header");
+
+  BlockLayout.reset(new BlockT *[PO.size()]);
+  BlockT **RootCursor = BlockLayout.get();
+  for (auto &[BB, L] : llvm::reverse(PO)) {
+    if (L->BlockCapacity == 0) {
+      // The first block of a L is its the header. Carve its slice from the
+      // parent (already visited)'s cursor.
+      if (LoopT *Parent = L->getParentLoop()) {
+        assert(Parent->BlockCapacity != 0 &&
+               "parent slice not carved before child");
+        L->BlockData = Parent->BlockData + Parent->BlockCapacity;
+        Parent->BlockCapacity += L->BlockLen;
+        Parent->SubLoops.push_back(L);
+      } else {
+        L->BlockData = RootCursor;
+        RootCursor += L->BlockLen;
+      }
+    }
+    // Each block lands once, at its innermost loop's cursor.
+    L->BlockData[L->BlockCapacity++] = BB;
+  }
+
+  // Mark every slice as borrowed from BlockLayout; a later mutation copies it
+  // into private storage (see materializeBlocks).
+  for (LoopT *L : LoopsPO) {
+    assert(L->BlockCapacity == L->BlockLen && "layout slice not fully used");
+    L->BlockCapacity = LoopT::BorrowedCapacity;
+  }
 }
 
 template <class BlockT, class LoopT>

diff  --git a/llvm/test/Transforms/LoopVectorize/early_exit_with_outer_loop.ll b/llvm/test/Transforms/LoopVectorize/early_exit_with_outer_loop.ll
index 69ef9f3e530ad..aec401ece0b3a 100644
--- a/llvm/test/Transforms/LoopVectorize/early_exit_with_outer_loop.ll
+++ b/llvm/test/Transforms/LoopVectorize/early_exit_with_outer_loop.ll
@@ -46,7 +46,7 @@ loop.inner.end:
 ; loops at depths 1 and 2, respectively.
 define void @early_exit_in_outer_loop2() {
 ; CHECK-LABEL: Loop info for function 'early_exit_in_outer_loop2':
-; CHECK: Loop at depth 1 containing: %loop.outer<header>,%loop.middle,%loop.inner.found,%loop.inner.end,%loop.middle.end,%loop.outer.latch<latch>,%vector.ph,%vector.body,%vector.body.interim,%middle.block,%vector.early.exit
+; CHECK: Loop at depth 1 containing: %loop.outer<header>,%loop.middle,%loop.inner.end,%loop.inner.found,%loop.middle.end,%loop.outer.latch<latch>,%vector.ph,%vector.body,%vector.body.interim,%middle.block,%vector.early.exit
 ; CHECK:    Loop at depth 2 containing: %loop.middle<header>,%loop.inner.end<latch><exiting>,%vector.ph,%vector.body<exiting>,%vector.body.interim,%middle.block
 ; CHECK:        Loop at depth 3 containing: %vector.body<header><exiting>,%vector.body.interim<latch><exiting>
 entry:
@@ -268,7 +268,7 @@ early.exit.leave:
 ; (one going to a middle loop, one going to the outer loop).
 define i32 @multi_early_exit_two_
diff erent_loops(i1 %c, ptr dereferenceable(1024) %src) nofree {
 ; CHECK-LABEL: Loop info for function 'multi_early_exit_two_
diff erent_loops':
-; CHECK-NEXT: Loop at depth 1 containing: %outer.header<header>,%middle.header,%early.exit.outer,%early.exit.middle,%middle.latch,%outer.latch<latch>,%middle.latch.loopexit,%outer.latch.loopexit,%vector.ph,%vector.body,%vector.body.interim,%middle.block,%vector.early.exit.check,%vector.early.exit.1,%vector.early.exit.0
+; CHECK-NEXT: Loop at depth 1 containing: %outer.header<header>,%middle.header,%early.exit.middle,%middle.latch,%early.exit.outer,%outer.latch<latch>,%middle.latch.loopexit,%outer.latch.loopexit,%vector.ph,%vector.body,%vector.body.interim,%middle.block,%vector.early.exit.check,%vector.early.exit.1,%vector.early.exit.0
 ; CHECK-NEXT:     Loop at depth 2 containing: %middle.header<header>,%early.exit.middle,%middle.latch<latch><exiting>,%middle.latch.loopexit,%vector.ph,%vector.body,%vector.body.interim,%middle.block,%vector.early.exit.check<exiting>,%vector.early.exit.0
 ; CHECK-NEXT:         Loop at depth 3 containing: %vector.body<header><exiting>,%vector.body.interim<latch><exiting>
 entry:

diff  --git a/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch-freeze.ll b/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch-freeze.ll
index f55ec1bedc0b8..925b919c5a397 100644
--- a/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch-freeze.ll
+++ b/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch-freeze.ll
@@ -1134,10 +1134,10 @@ define i32 @test13a(ptr %ptr, i1 %cond, ptr %a.ptr, ptr %b.ptr) {
 ; CHECK:       loop_b_inner_body.us:
 ; CHECK-NEXT:    [[V4_US:%.*]] = load i1, ptr [[PTR]], align 1
 ; CHECK-NEXT:    br i1 [[V4_US]], label [[LOOP_B_INNER_LATCH_US]], label [[LOOP_B_INNER_EXIT_US:%.*]]
-; CHECK:       loop_b_inner_exit.us:
-; CHECK-NEXT:    br label [[LOOP_LATCH_US:%.*]]
 ; CHECK:       loop_b_inner_latch.us:
 ; CHECK-NEXT:    br label [[LOOP_B_INNER_HEADER_US]]
+; CHECK:       loop_b_inner_exit.us:
+; CHECK-NEXT:    br label [[LOOP_LATCH_US:%.*]]
 ; CHECK:       loop_a.us:
 ; CHECK-NEXT:    [[V2_US:%.*]] = load i1, ptr [[PTR]], align 1
 ; CHECK-NEXT:    br i1 [[V2_US]], label [[LOOP_EXIT_SPLIT_US:%.*]], label [[LOOP_LATCH_US]]
@@ -1426,10 +1426,10 @@ define i32 @test29(i32 %arg) {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[ARG_FR:%.*]] = freeze i32 [[ARG:%.*]]
 ; CHECK-NEXT:    switch i32 [[ARG_FR]], label [[ENTRY_SPLIT:%.*]] [
-; CHECK-NEXT:    i32 0, label [[ENTRY_SPLIT_US:%.*]]
-; CHECK-NEXT:    i32 1, label [[ENTRY_SPLIT_US]]
-; CHECK-NEXT:    i32 2, label [[ENTRY_SPLIT_US1:%.*]]
-; CHECK-NEXT:    i32 3, label [[ENTRY_SPLIT]]
+; CHECK-NEXT:      i32 0, label [[ENTRY_SPLIT_US:%.*]]
+; CHECK-NEXT:      i32 1, label [[ENTRY_SPLIT_US]]
+; CHECK-NEXT:      i32 2, label [[ENTRY_SPLIT_US1:%.*]]
+; CHECK-NEXT:      i32 3, label [[ENTRY_SPLIT]]
 ; CHECK-NEXT:    ]
 ; CHECK:       entry.split.us:
 ; CHECK-NEXT:    br label [[HEADER_US:%.*]]
@@ -1587,10 +1587,10 @@ define i32 @test30(i32 %arg) {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[ARG_FR:%.*]] = freeze i32 [[ARG:%.*]]
 ; CHECK-NEXT:    switch i32 [[ARG_FR]], label [[ENTRY_SPLIT:%.*]] [
-; CHECK-NEXT:    i32 -1, label [[ENTRY_SPLIT]]
-; CHECK-NEXT:    i32 0, label [[ENTRY_SPLIT_US:%.*]]
-; CHECK-NEXT:    i32 1, label [[ENTRY_SPLIT_US1:%.*]]
-; CHECK-NEXT:    i32 2, label [[ENTRY_SPLIT_US1]]
+; CHECK-NEXT:      i32 -1, label [[ENTRY_SPLIT]]
+; CHECK-NEXT:      i32 0, label [[ENTRY_SPLIT_US:%.*]]
+; CHECK-NEXT:      i32 1, label [[ENTRY_SPLIT_US1:%.*]]
+; CHECK-NEXT:      i32 2, label [[ENTRY_SPLIT_US1]]
 ; CHECK-NEXT:    ]
 ; CHECK:       entry.split.us:
 ; CHECK-NEXT:    br label [[HEADER_US:%.*]]
@@ -2259,9 +2259,9 @@ define void @hoist_inner_loop_switch(ptr %ptr) {
 ; CHECK-NEXT:    [[V1:%.*]] = call i32 @cond.i32()
 ; CHECK-NEXT:    [[V1_FR:%.*]] = freeze i32 [[V1]]
 ; CHECK-NEXT:    switch i32 [[V1_FR]], label [[B_HEADER_SPLIT:%.*]] [
-; CHECK-NEXT:    i32 1, label [[B_HEADER_SPLIT_US:%.*]]
-; CHECK-NEXT:    i32 2, label [[B_HEADER_SPLIT_US]]
-; CHECK-NEXT:    i32 3, label [[B_HEADER_SPLIT_US]]
+; CHECK-NEXT:      i32 1, label [[B_HEADER_SPLIT_US:%.*]]
+; CHECK-NEXT:      i32 2, label [[B_HEADER_SPLIT_US]]
+; CHECK-NEXT:      i32 3, label [[B_HEADER_SPLIT_US]]
 ; CHECK-NEXT:    ]
 ; CHECK:       b.header.split.us:
 ; CHECK-NEXT:    br label [[C_HEADER_US:%.*]]

diff  --git a/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch.ll b/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch.ll
index 0cb0e0d1cca2b..8fddaa1839576 100644
--- a/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch.ll
+++ b/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch.ll
@@ -2116,12 +2116,12 @@ loop_latch:
 ; CHECK-NEXT:    %[[V:.*]] = load i1, ptr %ptr
 ; CHECK-NEXT:    br i1 %[[V]], label %loop_b_inner_latch.us, label %loop_b_inner_exit.us
 ;
-; CHECK:       loop_b_inner_exit.us:
-; CHECK-NEXT:    br label %loop_latch.us
-;
 ; CHECK:       loop_b_inner_latch.us:
 ; CHECK-NEXT:    br label %loop_b_inner_header.us
 ;
+; CHECK:       loop_b_inner_exit.us:
+; CHECK-NEXT:    br label %loop_latch.us
+;
 ; CHECK:       loop_a.us:
 ; CHECK-NEXT:    %[[V:.*]] = load i1, ptr %ptr
 ; CHECK-NEXT:    br i1 %[[V]], label %loop_exit.split.us, label %loop_latch.us

diff  --git a/mlir/include/mlir/Analysis/CFGLoopInfo.h b/mlir/include/mlir/Analysis/CFGLoopInfo.h
index 15ac0d71cb94d..7060a9df7de2d 100644
--- a/mlir/include/mlir/Analysis/CFGLoopInfo.h
+++ b/mlir/include/mlir/Analysis/CFGLoopInfo.h
@@ -36,7 +36,7 @@ namespace mlir {
 /// class provides accessors to the loop analysis.
 class CFGLoop : public llvm::LoopBase<mlir::Block, mlir::CFGLoop> {
 private:
-  explicit CFGLoop(mlir::Block *block);
+  CFGLoop() = default;
 
   friend class llvm::LoopBase<mlir::Block, CFGLoop>;
   friend class llvm::LoopInfoBase<mlir::Block, CFGLoop>;

diff  --git a/mlir/lib/Analysis/CFGLoopInfo.cpp b/mlir/lib/Analysis/CFGLoopInfo.cpp
index 9fe220344dcea..e03b664f9c72e 100644
--- a/mlir/lib/Analysis/CFGLoopInfo.cpp
+++ b/mlir/lib/Analysis/CFGLoopInfo.cpp
@@ -16,9 +16,6 @@ template class llvm::LoopInfoBase<mlir::Block, mlir::CFGLoop>;
 
 using namespace mlir;
 
-CFGLoop::CFGLoop(mlir::Block *block)
-    : llvm::LoopBase<mlir::Block, CFGLoop>(block) {}
-
 CFGLoopInfo::CFGLoopInfo(
     const llvm::DominatorTreeBase<mlir::Block, false> &domTree) {
   analyze(domTree);


        


More information about the Mlir-commits mailing list