[llvm] b432afc - [CycleAnalysis] Methods to verify cycles and their nesting. (#102300)

via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 20 02:53:51 PDT 2024


Author: Sameer Sahasrabuddhe
Date: 2024-08-20T15:23:48+05:30
New Revision: b432afc28406b670a58933c2fe56c73e6f85911e

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

LOG: [CycleAnalysis] Methods to verify cycles and their nesting. (#102300)

The original implementation provided a simple method to check whether
the forest of nested cycles is well-formed. This is now augmented with
other methods to check well-formedness of all cycles, either
invdividually, or as the entire forest. These will be used by future
transforms that modify CycleInfo.

Added: 
    

Modified: 
    llvm/include/llvm/ADT/GenericCycleImpl.h
    llvm/include/llvm/ADT/GenericCycleInfo.h
    llvm/include/llvm/ADT/GenericSSAContext.h
    llvm/include/llvm/Analysis/CycleAnalysis.h
    llvm/include/llvm/CodeGen/MachineSSAContext.h
    llvm/include/llvm/IR/SSAContext.h
    llvm/lib/Analysis/CycleAnalysis.cpp
    llvm/lib/CodeGen/MachineCycleAnalysis.cpp
    llvm/lib/IR/CycleInfo.cpp
    llvm/lib/Passes/PassRegistry.def
    llvm/test/Analysis/CycleInfo/basic.ll
    llvm/test/Analysis/CycleInfo/unreachable-predecessor.ll

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/ADT/GenericCycleImpl.h b/llvm/include/llvm/ADT/GenericCycleImpl.h
index 56d5ba6e060772..dc3d3d3f3fe328 100644
--- a/llvm/include/llvm/ADT/GenericCycleImpl.h
+++ b/llvm/include/llvm/ADT/GenericCycleImpl.h
@@ -26,6 +26,7 @@
 #include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/DepthFirstIterator.h"
 #include "llvm/ADT/GenericCycleInfo.h"
+#include "llvm/ADT/StringExtras.h"
 
 #define DEBUG_TYPE "generic-cycle-impl"
 
@@ -119,6 +120,104 @@ auto GenericCycle<ContextT>::getCyclePredecessor() const -> BlockT * {
   return Out;
 }
 
+/// \brief Verify that this is actually a well-formed cycle in the CFG.
+template <typename ContextT> void GenericCycle<ContextT>::verifyCycle() const {
+#ifndef NDEBUG
+  assert(!Blocks.empty() && "Cycle cannot be empty.");
+  DenseSet<BlockT *> Blocks;
+  for (BlockT *BB : blocks()) {
+    assert(Blocks.insert(BB).second); // duplicates in block list?
+  }
+  assert(!Entries.empty() && "Cycle must have one or more entries.");
+
+  DenseSet<BlockT *> Entries;
+  for (BlockT *Entry : entries()) {
+    assert(Entries.insert(Entry).second); // duplicate entry?
+    assert(contains(Entry));
+  }
+
+  // Setup for using a depth-first iterator to visit every block in the cycle.
+  SmallVector<BlockT *, 8> ExitBBs;
+  getExitBlocks(ExitBBs);
+  df_iterator_default_set<BlockT *> VisitSet;
+  VisitSet.insert(ExitBBs.begin(), ExitBBs.end());
+
+  // Keep track of the BBs visited.
+  SmallPtrSet<BlockT *, 8> VisitedBBs;
+
+  // Check the individual blocks.
+  for (BlockT *BB : depth_first_ext(getHeader(), VisitSet)) {
+    assert(llvm::any_of(llvm::children<BlockT *>(BB),
+                        [&](BlockT *B) { return contains(B); }) &&
+           "Cycle block has no in-cycle successors!");
+
+    assert(llvm::any_of(llvm::inverse_children<BlockT *>(BB),
+                        [&](BlockT *B) { return contains(B); }) &&
+           "Cycle block has no in-cycle predecessors!");
+
+    DenseSet<BlockT *> OutsideCyclePreds;
+    for (BlockT *B : llvm::inverse_children<BlockT *>(BB))
+      if (!contains(B))
+        OutsideCyclePreds.insert(B);
+
+    if (Entries.contains(BB)) {
+      assert(!OutsideCyclePreds.empty() && "Entry is unreachable!");
+    } else if (!OutsideCyclePreds.empty()) {
+      // A non-entry block shouldn't be reachable from outside the cycle,
+      // though it is permitted if the predecessor is not itself actually
+      // reachable.
+      BlockT *EntryBB = &BB->getParent()->front();
+      for (BlockT *CB : depth_first(EntryBB))
+        assert(!OutsideCyclePreds.contains(CB) &&
+               "Non-entry block reachable from outside!");
+    }
+    assert(BB != &getHeader()->getParent()->front() &&
+           "Cycle contains function entry block!");
+
+    VisitedBBs.insert(BB);
+  }
+
+  if (VisitedBBs.size() != getNumBlocks()) {
+    dbgs() << "The following blocks are unreachable in the cycle:\n  ";
+    ListSeparator LS;
+    for (auto *BB : Blocks) {
+      if (!VisitedBBs.count(BB)) {
+        dbgs() << LS;
+        BB->printAsOperand(dbgs());
+      }
+    }
+    dbgs() << "\n";
+    llvm_unreachable("Unreachable block in cycle");
+  }
+
+  verifyCycleNest();
+#endif
+}
+
+/// \brief Verify the parent-child relations of this cycle.
+///
+/// Note that this does \em not check that cycle is really a cycle in the CFG.
+template <typename ContextT>
+void GenericCycle<ContextT>::verifyCycleNest() const {
+#ifndef NDEBUG
+  // Check the subcycles.
+  for (GenericCycle *Child : children()) {
+    // Each block in each subcycle should be contained within this cycle.
+    for (BlockT *BB : Child->blocks()) {
+      assert(contains(BB) &&
+             "Cycle does not contain all the blocks of a subcycle!");
+    }
+    assert(Child->Depth == Depth + 1);
+  }
+
+  // Check the parent cycle pointer.
+  if (ParentCycle) {
+    assert(is_contained(ParentCycle->children(), this) &&
+           "Cycle is not a subcycle of its parent!");
+  }
+#endif
+}
+
 /// \brief Helper class for computing cycle information.
 template <typename ContextT> class GenericCycleInfoCompute {
   using BlockT = typename ContextT::BlockT;
@@ -400,8 +499,6 @@ void GenericCycleInfo<ContextT>::compute(FunctionT &F) {
   LLVM_DEBUG(errs() << "Computing cycles for function: " << F.getName()
                     << "\n");
   Compute.run(&F.front());
-
-  assert(validateTree());
 }
 
 template <typename ContextT>
@@ -414,7 +511,7 @@ void GenericCycleInfo<ContextT>::splitCriticalEdge(BlockT *Pred, BlockT *Succ,
     return;
 
   addBlockToCycle(NewBlock, Cycle);
-  assert(validateTree());
+  verifyCycleNest();
 }
 
 /// \brief Find the innermost cycle containing a given block.
@@ -468,73 +565,63 @@ unsigned GenericCycleInfo<ContextT>::getCycleDepth(const BlockT *Block) const {
   return Cycle->getDepth();
 }
 
-#ifndef NDEBUG
-/// \brief Validate the internal consistency of the cycle tree.
+/// \brief Verify the internal consistency of the cycle tree.
 ///
 /// Note that this does \em not check that cycles are really cycles in the CFG,
 /// or that the right set of cycles in the CFG were found.
+///
+/// Every natural loop has a corresponding cycle (possibly irreducible) with the
+/// same header, and every reducible cycle is a natural loop with the same
+/// header. We check this by comparing headers encountered in the two forests.
 template <typename ContextT>
-bool GenericCycleInfo<ContextT>::validateTree() const {
-  DenseSet<BlockT *> Blocks;
-  DenseSet<BlockT *> Entries;
-
-  auto reportError = [](const char *File, int Line, const char *Cond) {
-    errs() << File << ':' << Line
-           << ": GenericCycleInfo::validateTree: " << Cond << '\n';
-  };
-#define check(cond)                                                            \
-  do {                                                                         \
-    if (!(cond)) {                                                             \
-      reportError(__FILE__, __LINE__, #cond);                                  \
-      return false;                                                            \
-    }                                                                          \
-  } while (false)
+void GenericCycleInfo<ContextT>::verifyCycleNest(bool VerifyFull,
+                                                 LoopInfoT *LI) const {
+#ifndef NDEBUG
+  DenseSet<BlockT *> LoopHeaders;
+  DenseSet<BlockT *> CycleHeaders;
 
-  for (const auto *TLC : toplevel_cycles()) {
-    for (const CycleT *Cycle : depth_first(TLC)) {
-      if (Cycle->ParentCycle)
-        check(is_contained(Cycle->ParentCycle->children(), Cycle));
-
-      for (BlockT *Block : Cycle->Blocks) {
-        auto MapIt = BlockMap.find(Block);
-        check(MapIt != BlockMap.end());
-        check(Cycle->contains(MapIt->second));
-        check(Blocks.insert(Block).second); // duplicates in block list?
+  if (LI) {
+    for (LoopT *TopLoop : *LI) {
+      for (LoopT *Loop : depth_first(TopLoop)) {
+        LoopHeaders.insert(Loop->getHeader());
       }
-      Blocks.clear();
+    }
+  }
 
-      check(!Cycle->Entries.empty());
-      for (BlockT *Entry : Cycle->Entries) {
-        check(Entries.insert(Entry).second); // duplicate entry?
-        check(is_contained(Cycle->Blocks, Entry));
+  for (CycleT *TopCycle : toplevel_cycles()) {
+    for (CycleT *Cycle : depth_first(TopCycle)) {
+      if (VerifyFull)
+        Cycle->verifyCycle();
+      else
+        Cycle->verifyCycleNest();
+      // Check the block map entries for blocks contained in this cycle.
+      for (BlockT *BB : Cycle->blocks()) {
+        auto MapIt = BlockMap.find(BB);
+        assert(MapIt != BlockMap.end());
+        assert(Cycle->contains(MapIt->second));
       }
-      Entries.clear();
-
-      unsigned ChildDepth = 0;
-      for (const CycleT *Child : Cycle->children()) {
-        check(Child->Depth > Cycle->Depth);
-        if (!ChildDepth) {
-          ChildDepth = Child->Depth;
-        } else {
-          check(ChildDepth == Child->Depth);
-        }
+      if (LI) {
+        BlockT *Header = Cycle->getHeader();
+        assert(CycleHeaders.insert(Header).second);
+        if (Cycle->isReducible())
+          assert(LoopHeaders.contains(Header));
       }
     }
   }
 
-  for (const auto &Entry : BlockMap) {
-    BlockT *Block = Entry.first;
-    for (const CycleT *Cycle = Entry.second; Cycle;
-         Cycle = Cycle->ParentCycle) {
-      check(is_contained(Cycle->Blocks, Block));
+  if (LI) {
+    for (BlockT *Header : LoopHeaders) {
+      assert(CycleHeaders.contains(Header));
     }
   }
+#endif
+}
 
-#undef check
-
-  return true;
+/// \brief Verify that the entire cycle tree well-formed.
+template <typename ContextT>
+void GenericCycleInfo<ContextT>::verify(LoopInfoT *LI) const {
+  verifyCycleNest(/*VerifyFull=*/true, LI);
 }
-#endif
 
 /// \brief Print the cycle info.
 template <typename ContextT>

diff  --git a/llvm/include/llvm/ADT/GenericCycleInfo.h b/llvm/include/llvm/ADT/GenericCycleInfo.h
index b601fc9bae38af..b5d719c6313c43 100644
--- a/llvm/include/llvm/ADT/GenericCycleInfo.h
+++ b/llvm/include/llvm/ADT/GenericCycleInfo.h
@@ -140,6 +140,9 @@ template <typename ContextT> class GenericCycle {
   /// it, otherwise return nullptr.
   BlockT *getCyclePredecessor() const;
 
+  void verifyCycle() const;
+  void verifyCycleNest() const;
+
   /// Iteration over child cycles.
   //@{
   using const_child_iterator_base =
@@ -228,6 +231,8 @@ template <typename ContextT> class GenericCycleInfo {
   using BlockT = typename ContextT::BlockT;
   using CycleT = GenericCycle<ContextT>;
   using FunctionT = typename ContextT::FunctionT;
+  using LoopT = typename ContextT::LoopT;
+  using LoopInfoT = typename ContextT::LoopInfoT;
   template <typename> friend class GenericCycle;
   template <typename> friend class GenericCycleInfoCompute;
 
@@ -277,9 +282,8 @@ template <typename ContextT> class GenericCycleInfo {
 
   /// Methods for debug and self-test.
   //@{
-#ifndef NDEBUG
-  bool validateTree() const;
-#endif
+  void verifyCycleNest(bool VerifyFull = false, LoopInfoT *LI = nullptr) const;
+  void verify(LoopInfoT *LI = nullptr) const;
   void print(raw_ostream &Out) const;
   void dump() const { print(dbgs()); }
   Printable print(const CycleT *Cycle) { return Cycle->print(Context); }

diff  --git a/llvm/include/llvm/ADT/GenericSSAContext.h b/llvm/include/llvm/ADT/GenericSSAContext.h
index 6aa3a8b9b6e0b6..316014cd3c4fee 100644
--- a/llvm/include/llvm/ADT/GenericSSAContext.h
+++ b/llvm/include/llvm/ADT/GenericSSAContext.h
@@ -77,6 +77,12 @@ template <typename _FunctionT> class GenericSSAContext {
   // a given funciton.
   using DominatorTreeT = DominatorTreeBase<BlockT, false>;
 
+  // A LoopT is a natural loop in the CFG.
+  using LoopT = typename SSATraits::LoopT;
+
+  // A LoopInfoT contains a forest of natural loops in the CFG.
+  using LoopInfoT = typename SSATraits::LoopInfoT;
+
   GenericSSAContext() = default;
   GenericSSAContext(const FunctionT *F) : F(F) {}
 

diff  --git a/llvm/include/llvm/Analysis/CycleAnalysis.h b/llvm/include/llvm/Analysis/CycleAnalysis.h
index 6b45cece199913..40c42d5495f7c7 100644
--- a/llvm/include/llvm/Analysis/CycleAnalysis.h
+++ b/llvm/include/llvm/Analysis/CycleAnalysis.h
@@ -59,15 +59,17 @@ class CycleAnalysis : public AnalysisInfoMixin<CycleAnalysis> {
   // TODO: verify analysis?
 };
 
-/// Printer pass for the \c DominatorTree.
 class CycleInfoPrinterPass : public PassInfoMixin<CycleInfoPrinterPass> {
   raw_ostream &OS;
 
 public:
   explicit CycleInfoPrinterPass(raw_ostream &OS);
-
   PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
+  static bool isRequired() { return true; }
+};
 
+struct CycleInfoVerifierPass : public PassInfoMixin<CycleInfoVerifierPass> {
+  PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
   static bool isRequired() { return true; }
 };
 

diff  --git a/llvm/include/llvm/CodeGen/MachineSSAContext.h b/llvm/include/llvm/CodeGen/MachineSSAContext.h
index b70450c19f283a..a5d07d41fce864 100644
--- a/llvm/include/llvm/CodeGen/MachineSSAContext.h
+++ b/llvm/include/llvm/CodeGen/MachineSSAContext.h
@@ -22,6 +22,8 @@
 namespace llvm {
 class MachineInstr;
 class MachineFunction;
+class MachineLoop;
+class MachineLoopInfo;
 class Register;
 
 inline unsigned succ_size(const MachineBasicBlock *BB) {
@@ -39,6 +41,8 @@ template <> struct GenericSSATraits<MachineFunction> {
   using ValueRefT = Register;
   using ConstValueRefT = Register;
   using UseT = MachineOperand;
+  using LoopT = MachineLoop;
+  using LoopInfoT = MachineLoopInfo;
 };
 
 using MachineSSAContext = GenericSSAContext<MachineFunction>;

diff  --git a/llvm/include/llvm/IR/SSAContext.h b/llvm/include/llvm/IR/SSAContext.h
index d0da5e222a8e63..c665672cdc8dca 100644
--- a/llvm/include/llvm/IR/SSAContext.h
+++ b/llvm/include/llvm/IR/SSAContext.h
@@ -21,6 +21,8 @@
 namespace llvm {
 class BasicBlock;
 class Function;
+class Loop;
+class LoopInfo;
 class Instruction;
 class Value;
 
@@ -35,6 +37,8 @@ template <> struct GenericSSATraits<Function> {
   using ValueRefT = Value *;
   using ConstValueRefT = const Value *;
   using UseT = Use;
+  using LoopT = Loop;
+  using LoopInfoT = LoopInfo;
 };
 
 using SSAContext = GenericSSAContext<Function>;

diff  --git a/llvm/lib/Analysis/CycleAnalysis.cpp b/llvm/lib/Analysis/CycleAnalysis.cpp
index 4d7980af2beae9..85c30da02b7d47 100644
--- a/llvm/lib/Analysis/CycleAnalysis.cpp
+++ b/llvm/lib/Analysis/CycleAnalysis.cpp
@@ -8,6 +8,7 @@
 
 #include "llvm/Analysis/CycleAnalysis.h"
 #include "llvm/ADT/GenericCycleImpl.h"
+#include "llvm/Analysis/LoopInfo.h"
 #include "llvm/IR/CFG.h" // for successors found by ADL in GenericCycleImpl.h
 #include "llvm/InitializePasses.h"
 
@@ -35,6 +36,14 @@ PreservedAnalyses CycleInfoPrinterPass::run(Function &F,
   return PreservedAnalyses::all();
 }
 
+PreservedAnalyses CycleInfoVerifierPass::run(Function &F,
+                                             FunctionAnalysisManager &AM) {
+  CycleInfo &CI = AM.getResult<CycleAnalysis>(F);
+  LoopInfo &LI = AM.getResult<LoopAnalysis>(F);
+  CI.verify(&LI);
+  return PreservedAnalyses::all();
+}
+
 //===----------------------------------------------------------------------===//
 //  CycleInfoWrapperPass Implementation
 //===----------------------------------------------------------------------===//

diff  --git a/llvm/lib/CodeGen/MachineCycleAnalysis.cpp b/llvm/lib/CodeGen/MachineCycleAnalysis.cpp
index 57f7a098ac1757..5e1e56d59d5e0d 100644
--- a/llvm/lib/CodeGen/MachineCycleAnalysis.cpp
+++ b/llvm/lib/CodeGen/MachineCycleAnalysis.cpp
@@ -8,6 +8,7 @@
 
 #include "llvm/CodeGen/MachineCycleAnalysis.h"
 #include "llvm/ADT/GenericCycleImpl.h"
+#include "llvm/CodeGen/MachineLoopInfo.h"
 #include "llvm/CodeGen/MachineRegisterInfo.h"
 #include "llvm/CodeGen/MachineSSAContext.h"
 #include "llvm/CodeGen/TargetInstrInfo.h"

diff  --git a/llvm/lib/IR/CycleInfo.cpp b/llvm/lib/IR/CycleInfo.cpp
index a9b9129f24f098..4a87380dac8595 100644
--- a/llvm/lib/IR/CycleInfo.cpp
+++ b/llvm/lib/IR/CycleInfo.cpp
@@ -8,6 +8,7 @@
 
 #include "llvm/IR/CycleInfo.h"
 #include "llvm/ADT/GenericCycleImpl.h"
+#include "llvm/Analysis/LoopInfo.h"
 #include "llvm/IR/CFG.h"
 
 using namespace llvm;

diff  --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index 6b5e1cf83c4698..d6067089c6b5c1 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -475,6 +475,7 @@ FUNCTION_PASS("typepromotion", TypePromotionPass(TM))
 FUNCTION_PASS("unify-loop-exits", UnifyLoopExitsPass())
 FUNCTION_PASS("vector-combine", VectorCombinePass())
 FUNCTION_PASS("verify", VerifierPass())
+FUNCTION_PASS("verify<cycles>", CycleInfoVerifierPass())
 FUNCTION_PASS("verify<domtree>", DominatorTreeVerifierPass())
 FUNCTION_PASS("verify<loops>", LoopVerifierPass())
 FUNCTION_PASS("verify<memoryssa>", MemorySSAVerifierPass())

diff  --git a/llvm/test/Analysis/CycleInfo/basic.ll b/llvm/test/Analysis/CycleInfo/basic.ll
index f8326f13589146..e459b0c317f948 100644
--- a/llvm/test/Analysis/CycleInfo/basic.ll
+++ b/llvm/test/Analysis/CycleInfo/basic.ll
@@ -1,4 +1,4 @@
-; RUN: opt < %s -disable-output -passes='print<cycles>' 2>&1 | FileCheck %s -check-prefix=CHECK
+; RUN: opt < %s -disable-output -passes='verify<cycles>,print<cycles>' 2>&1 | FileCheck %s -check-prefix=CHECK
 
 define void @empty() {
 ; CHECK-LABEL: CycleInfo for function: empty

diff  --git a/llvm/test/Analysis/CycleInfo/unreachable-predecessor.ll b/llvm/test/Analysis/CycleInfo/unreachable-predecessor.ll
index 36a2115bd7a943..3655095ff6d827 100644
--- a/llvm/test/Analysis/CycleInfo/unreachable-predecessor.ll
+++ b/llvm/test/Analysis/CycleInfo/unreachable-predecessor.ll
@@ -1,4 +1,4 @@
-; RUN: opt < %s -disable-output -passes='print<cycles>' 2>&1 | FileCheck %s
+; RUN: opt < %s -disable-output -passes='verify<cycles>,print<cycles>' 2>&1 | FileCheck %s
 ; CHECK-LABEL: CycleInfo for function: unreachable
 ; CHECK:    depth=1: entries(loop.body) loop.latch inner.block
 define void @unreachable(i32 %n) {


        


More information about the llvm-commits mailing list