[llvm] [CycleInfo] Store entries in the block layout. NFC (PR #210866)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 20 20:05:31 PDT 2026


https://github.com/MaskRay created https://github.com/llvm/llvm-project/pull/210866

A reducible loop has a single entry (header), which flatten() places at
BlockLayout[IdxBegin], as its minimum-preorder block. A reducible loop
then needs no storage at all.

For the uncommon irreducible loop case, we append its full entry list
(header first) after the Euler tour, referenced by [EntryBegin,
EntryBegin+EntrySize), making `GenericCycleInfo::Cycle` (named only
because "loop" is occupied by LoopInfo) trivially destructible.


>From 59fb1bbb258ebb1ecb596a8997219fa52098aa88 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sat, 18 Jul 2026 16:57:47 -0700
Subject: [PATCH] [CycleInfo] Store entries in the block layout. NFC

A reducible loop has a single entry (header), which flatten() places at
BlockLayout[IdxBegin], as its minimum-preorder block. A reducible loop
then needs no storage at all.

For the uncommon irreducible loop case, we append its full entry list
(header first) after the Euler tour, referenced by [EntryBegin,
EntryBegin+EntrySize), making `GenericCycleInfo::Cycle` (named only
because "loop" is occupied by LoopInfo) trivially destructible.
---
 llvm/include/llvm/ADT/GenericCycleImpl.h | 21 +++++++---
 llvm/include/llvm/ADT/GenericCycleInfo.h | 50 ++++++++++++++----------
 2 files changed, 46 insertions(+), 25 deletions(-)

diff --git a/llvm/include/llvm/ADT/GenericCycleImpl.h b/llvm/include/llvm/ADT/GenericCycleImpl.h
index bbc6230aefe5f..f0a59c386517c 100644
--- a/llvm/include/llvm/ADT/GenericCycleImpl.h
+++ b/llvm/include/llvm/ADT/GenericCycleImpl.h
@@ -242,7 +242,6 @@ template <typename ContextT> class GenericCycleInfoCompute {
   // Per-cycle scratch built in run() and consumed by flatten(), keyed by
   // header-preorder rank.
   struct CycleBuild {
-    BlockT *Header;
     unsigned ChildHead;
     unsigned NextSibling;
     unsigned OwnCount;
@@ -329,6 +328,9 @@ void GenericCycleInfo<ContextT>::addBlockToCycle(BlockT *Block, CycleRef C) {
       ++X.IdxBegin;
       ++X.IdxEnd;
     }
+    // Insert shifts every appended entry list.
+    if (X.EntrySize)
+      ++X.EntryBegin;
   }
   addToBlockMap(Block, C);
   // Cyc and its ancestors gain the new block: extend each one's slice and
@@ -368,7 +370,6 @@ void GenericCycleInfoCompute<ContextT>::flatten(ArrayRef<CycleBuild> Build,
     CycleT &Flat = Info.Cycles[ID];
     Flat.Parent = Parent;
     Flat.Depth = Parent ? Info.deref(Parent).Depth + 1 : 1;
-    Flat.appendEntry(Build[C].Header);
     Cursor += Build[C].OwnCount;
     Flat.IdxBegin = Cursor;
     Stack.push_back({ID, Build[C].ChildHead});
@@ -428,7 +429,7 @@ void GenericCycleInfoCompute<ContextT>::run(FunctionT *F) {
       unsigned &Head = BI.LoopHeader != NoBlock
                            ? Build[info(BI.LoopHeader).LoopIdx].ChildHead
                            : TopHead;
-      Build.push_back({BI.getBlock(), NoCycle, Head, 1}); // OwnCount 1: header.
+      Build.push_back({NoCycle, Head, 1}); // OwnCount 1: the header.
       Head = I;
       LLVM_DEBUG(dbgs() << "Found cycle for header: "
                         << Info.Context.print(BI.getBlock()) << "\n");
@@ -445,18 +446,28 @@ void GenericCycleInfoCompute<ContextT>::run(FunctionT *F) {
 
   // Add the non-header entries recorded during the DFS. Sorting by (header,
   // block) groups each cycle's entries together and in block preorder; a block
-  // may re-enter a cycle via several edges, so skip duplicates.
+  // may re-enter a cycle via several edges, so skip duplicates. Each group
+  // opens an entry slice seeded with the header.
   SmallVector<unsigned, 8> Rank(BlockInfos.size());
   for (auto [R, N] : enumerate(Preorder))
     Rank[N] = R;
   for (auto &[H, B] : Reentries)
     B = Rank[B];
   llvm::sort(Reentries);
+  unsigned PrevH = NoBlock;
   for (unsigned I = 0, E = Reentries.size(); I != E; ++I) {
     if (I && Reentries[I] == Reentries[I - 1])
       continue;
     auto [H, R] = Reentries[I];
-    Info.deref(Info.BlockMap[H]).appendEntry(info(Preorder[R]).getBlock());
+    CycleT &Cyc = Info.deref(Info.BlockMap[H]);
+    if (H != PrevH) {
+      BlockT *Header = Info.BlockLayout[Cyc.IdxBegin];
+      Cyc.EntryBegin = Info.BlockLayout.size();
+      Info.BlockLayout.push_back(Header);
+      PrevH = H;
+    }
+    Info.BlockLayout.push_back(info(Preorder[R]).getBlock());
+    Cyc.EntrySize = Info.BlockLayout.size() - Cyc.EntryBegin;
   }
 }
 
diff --git a/llvm/include/llvm/ADT/GenericCycleInfo.h b/llvm/include/llvm/ADT/GenericCycleInfo.h
index 1245ef5d49bb1..6d6f4dd3c0d43 100644
--- a/llvm/include/llvm/ADT/GenericCycleInfo.h
+++ b/llvm/include/llvm/ADT/GenericCycleInfo.h
@@ -39,6 +39,7 @@
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/raw_ostream.h"
 #include <memory>
+#include <type_traits>
 
 namespace llvm {
 
@@ -88,10 +89,6 @@ template <typename ContextT> class GenericCycleInfo {
     /// The parent cycle; invalid for a top-level cycle.
     CycleRef Parent;
 
-    /// The entry block(s) of the cycle. The header is the only entry if this
-    /// is a loop.
-    SmallVector<BlockT *, 1> Entries;
-
     /// This cycle's blocks (its own and its nested cycles') occupy the
     /// half-open range [IdxBegin, IdxEnd) of BlockLayout, nested like an Euler
     /// tour of the cycle tree, so containment is an interval test (see
@@ -107,17 +104,15 @@ template <typename ContextT> class GenericCycleInfo {
     /// [this, this + 1 + NumDescendants) of Cycles.
     unsigned NumDescendants = 0;
 
-    void appendEntry(BlockT *Block) { Entries.push_back(Block); }
+    /// A reducible cycle (only entry is its header at IdxBegin) has EntrySize
+    /// 0. Otherwise its full entry list (header first) is appended past the
+    /// Euler tour at [EntryBegin, EntryBegin+EntrySize).
+    unsigned EntryBegin = 0, EntrySize = 0;
 
     /// Whether this cycle has a parent, i.e. is not top-level.
     bool hasParent() const { return Parent.isValid(); }
-
-    Cycle() = default;
-    Cycle(const Cycle &) = delete;
-    Cycle &operator=(const Cycle &) = delete;
-    Cycle(Cycle &&) = delete;
-    Cycle &operator=(Cycle &&) = delete;
   };
+  static_assert(std::is_trivially_destructible_v<Cycle>);
   using CycleT = Cycle;
 
   ContextT Context;
@@ -128,7 +123,8 @@ template <typename ContextT> class GenericCycleInfo {
   SmallVector<CycleRef> BlockMap;
 
   /// Euler tour of the cycle forest: every cycle's blocks form a contiguous
-  /// slice [IdxBegin, IdxEnd) of this array, nested inside its parent's.
+  /// slice [IdxBegin, IdxEnd), nested inside its parent's. Entry lists for
+  /// irreducible cycles are appended past the tour (see EntryBegin).
   SmallVector<BlockT *, 8> BlockLayout;
 
   /// All cycles in forest preorder: every cycle is immediately followed by
@@ -219,8 +215,13 @@ template <typename ContextT> class GenericCycleInfo {
     return BlockMap[Number];
   }
 
-  BlockT *getHeader(CycleRef C) const { return deref(C).Entries[0]; }
-  bool isReducible(CycleRef C) const { return deref(C).Entries.size() == 1; }
+  BlockT *getHeader(CycleRef C) const {
+    // Usually IdxBegin, but setSingleEntry() can install a header elsewhere.
+    const CycleT &Cyc = deref(C);
+    return BlockLayout[Cyc.EntrySize ? Cyc.EntryBegin : Cyc.IdxBegin];
+  }
+  // Whether there is a single entry: header only (0) or setSingleEntry (1).
+  bool isReducible(CycleRef C) const { return deref(C).EntrySize <= 1; }
   CycleRef getParentCycle(CycleRef C) const { return deref(C).Parent; }
   unsigned getDepth(CycleRef C) const { return deref(C).Depth; }
   size_t getNumBlocks(CycleRef C) const {
@@ -228,14 +229,23 @@ template <typename ContextT> class GenericCycleInfo {
     return Cyc.IdxEnd - Cyc.IdxBegin;
   }
 
-  ArrayRef<BlockT *> getEntries(CycleRef C) const { return deref(C).Entries; }
+  ArrayRef<BlockT *> getEntries(CycleRef C) const {
+    const CycleT &Cyc = deref(C);
+    if (Cyc.EntrySize)
+      return ArrayRef(BlockLayout).slice(Cyc.EntryBegin, Cyc.EntrySize);
+    return ArrayRef(BlockLayout).slice(Cyc.IdxBegin, 1);
+  }
   bool isEntry(CycleRef C, const BlockT *Block) const {
-    return is_contained(deref(C).Entries, Block);
+    return is_contained(getEntries(C), Block);
   }
+  // Append Block as a one-element entry list (along with irreducible cycles).
+  // getHeader/getEntries read it there, leaving BlockLayout's block order
+  // untouched.
   void setSingleEntry(CycleRef C, BlockT *Block) {
-    auto &Entries = deref(C).Entries;
-    Entries.clear();
-    Entries.push_back(Block);
+    CycleT &Cyc = deref(C);
+    Cyc.EntryBegin = BlockLayout.size();
+    BlockLayout.push_back(Block);
+    Cyc.EntrySize = 1;
   }
   /// Returns true iff \p Outer contains \p Inner. O(1). Non-strict.
   bool contains(CycleRef Outer, CycleRef Inner) const {
@@ -252,7 +262,7 @@ template <typename ContextT> class GenericCycleInfo {
   Printable printEntries(CycleRef C, const ContextT &Ctx) const {
     return Printable([this, C, &Ctx](raw_ostream &Out) {
       ListSeparator LS(" ");
-      for (auto *Entry : deref(C).Entries)
+      for (auto *Entry : getEntries(C))
         Out << LS << Ctx.print(Entry);
     });
   }



More information about the llvm-commits mailing list