Skip to content

Commit e6da78a

Browse files
committed
Reapply "[CycleAnalysis] Methods to verify cycles and their nesting. (#102300)"
This reverts commit 4aacc60. 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 every cycle, either individually, or as the entire forest. These will be used by future transforms that modify CycleInfo.
1 parent ee572ed commit e6da78a

File tree

7 files changed

+142
-68
lines changed

7 files changed

+142
-68
lines changed

llvm/include/llvm/ADT/GenericCycleImpl.h

Lines changed: 123 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "llvm/ADT/DenseSet.h"
2727
#include "llvm/ADT/DepthFirstIterator.h"
2828
#include "llvm/ADT/GenericCycleInfo.h"
29+
#include "llvm/ADT/StringExtras.h"
2930

3031
#define DEBUG_TYPE "generic-cycle-impl"
3132

@@ -119,6 +120,104 @@ auto GenericCycle<ContextT>::getCyclePredecessor() const -> BlockT * {
119120
return Out;
120121
}
121122

123+
/// \brief Verify that this is actually a well-formed cycle in the CFG.
124+
template <typename ContextT> void GenericCycle<ContextT>::verifyCycle() const {
125+
#ifndef NDEBUG
126+
assert(!Blocks.empty() && "Cycle cannot be empty.");
127+
DenseSet<BlockT *> Blocks;
128+
for (BlockT *BB : blocks()) {
129+
assert(Blocks.insert(BB).second); // duplicates in block list?
130+
}
131+
assert(!Entries.empty() && "Cycle must have one or more entries.");
132+
133+
DenseSet<BlockT *> Entries;
134+
for (BlockT *Entry : entries()) {
135+
assert(Entries.insert(Entry).second); // duplicate entry?
136+
assert(contains(Entry));
137+
}
138+
139+
// Setup for using a depth-first iterator to visit every block in the cycle.
140+
SmallVector<BlockT *, 8> ExitBBs;
141+
getExitBlocks(ExitBBs);
142+
df_iterator_default_set<BlockT *> VisitSet;
143+
VisitSet.insert(ExitBBs.begin(), ExitBBs.end());
144+
145+
// Keep track of the BBs visited.
146+
SmallPtrSet<BlockT *, 8> VisitedBBs;
147+
148+
// Check the individual blocks.
149+
for (BlockT *BB : depth_first_ext(getHeader(), VisitSet)) {
150+
assert(llvm::any_of(llvm::children<BlockT *>(BB),
151+
[&](BlockT *B) { return contains(B); }) &&
152+
"Cycle block has no in-cycle successors!");
153+
154+
assert(llvm::any_of(llvm::inverse_children<BlockT *>(BB),
155+
[&](BlockT *B) { return contains(B); }) &&
156+
"Cycle block has no in-cycle predecessors!");
157+
158+
DenseSet<BlockT *> OutsideCyclePreds;
159+
for (BlockT *B : llvm::inverse_children<BlockT *>(BB))
160+
if (!contains(B))
161+
OutsideCyclePreds.insert(B);
162+
163+
if (Entries.contains(BB)) {
164+
assert(!OutsideCyclePreds.empty() && "Entry is unreachable!");
165+
} else if (!OutsideCyclePreds.empty()) {
166+
// A non-entry block shouldn't be reachable from outside the cycle,
167+
// though it is permitted if the predecessor is not itself actually
168+
// reachable.
169+
BlockT *EntryBB = &BB->getParent()->front();
170+
for (BlockT *CB : depth_first(EntryBB))
171+
assert(!OutsideCyclePreds.contains(CB) &&
172+
"Non-entry block reachable from outside!");
173+
}
174+
assert(BB != &getHeader()->getParent()->front() &&
175+
"Cycle contains function entry block!");
176+
177+
VisitedBBs.insert(BB);
178+
}
179+
180+
if (VisitedBBs.size() != getNumBlocks()) {
181+
dbgs() << "The following blocks are unreachable in the cycle:\n ";
182+
ListSeparator LS;
183+
for (auto *BB : Blocks) {
184+
if (!VisitedBBs.count(BB)) {
185+
dbgs() << LS;
186+
BB->printAsOperand(dbgs());
187+
}
188+
}
189+
dbgs() << "\n";
190+
llvm_unreachable("Unreachable block in cycle");
191+
}
192+
193+
verifyCycleNest();
194+
#endif
195+
}
196+
197+
/// \brief Verify the parent-child relations of this cycle.
198+
///
199+
/// Note that this does \em not check that cycle is really a cycle in the CFG.
200+
template <typename ContextT>
201+
void GenericCycle<ContextT>::verifyCycleNest() const {
202+
#ifndef NDEBUG
203+
// Check the subcycles.
204+
for (GenericCycle *Child : children()) {
205+
// Each block in each subcycle should be contained within this cycle.
206+
for (BlockT *BB : Child->blocks()) {
207+
assert(contains(BB) &&
208+
"Cycle does not contain all the blocks of a subcycle!");
209+
}
210+
assert(Child->Depth == Depth + 1);
211+
}
212+
213+
// Check the parent cycle pointer.
214+
if (ParentCycle) {
215+
assert(is_contained(ParentCycle->children(), this) &&
216+
"Cycle is not a subcycle of its parent!");
217+
}
218+
#endif
219+
}
220+
122221
/// \brief Helper class for computing cycle information.
123222
template <typename ContextT> class GenericCycleInfoCompute {
124223
using BlockT = typename ContextT::BlockT;
@@ -400,8 +499,6 @@ void GenericCycleInfo<ContextT>::compute(FunctionT &F) {
400499
LLVM_DEBUG(errs() << "Computing cycles for function: " << F.getName()
401500
<< "\n");
402501
Compute.run(&F.front());
403-
404-
assert(validateTree());
405502
}
406503

407504
template <typename ContextT>
@@ -414,7 +511,7 @@ void GenericCycleInfo<ContextT>::splitCriticalEdge(BlockT *Pred, BlockT *Succ,
414511
return;
415512

416513
addBlockToCycle(NewBlock, Cycle);
417-
assert(validateTree());
514+
verifyCycleNest();
418515
}
419516

420517
/// \brief Find the innermost cycle containing a given block.
@@ -468,73 +565,38 @@ unsigned GenericCycleInfo<ContextT>::getCycleDepth(const BlockT *Block) const {
468565
return Cycle->getDepth();
469566
}
470567

471-
#ifndef NDEBUG
472-
/// \brief Validate the internal consistency of the cycle tree.
568+
/// \brief Verify the internal consistency of the cycle tree.
473569
///
474570
/// Note that this does \em not check that cycles are really cycles in the CFG,
475571
/// or that the right set of cycles in the CFG were found.
476572
template <typename ContextT>
477-
bool GenericCycleInfo<ContextT>::validateTree() const {
478-
DenseSet<BlockT *> Blocks;
479-
DenseSet<BlockT *> Entries;
480-
481-
auto reportError = [](const char *File, int Line, const char *Cond) {
482-
errs() << File << ':' << Line
483-
<< ": GenericCycleInfo::validateTree: " << Cond << '\n';
484-
};
485-
#define check(cond) \
486-
do { \
487-
if (!(cond)) { \
488-
reportError(__FILE__, __LINE__, #cond); \
489-
return false; \
490-
} \
491-
} while (false)
492-
493-
for (const auto *TLC : toplevel_cycles()) {
494-
for (const CycleT *Cycle : depth_first(TLC)) {
495-
if (Cycle->ParentCycle)
496-
check(is_contained(Cycle->ParentCycle->children(), Cycle));
497-
498-
for (BlockT *Block : Cycle->Blocks) {
499-
auto MapIt = BlockMap.find(Block);
500-
check(MapIt != BlockMap.end());
501-
check(Cycle->contains(MapIt->second));
502-
check(Blocks.insert(Block).second); // duplicates in block list?
503-
}
504-
Blocks.clear();
505-
506-
check(!Cycle->Entries.empty());
507-
for (BlockT *Entry : Cycle->Entries) {
508-
check(Entries.insert(Entry).second); // duplicate entry?
509-
check(is_contained(Cycle->Blocks, Entry));
510-
}
511-
Entries.clear();
512-
513-
unsigned ChildDepth = 0;
514-
for (const CycleT *Child : Cycle->children()) {
515-
check(Child->Depth > Cycle->Depth);
516-
if (!ChildDepth) {
517-
ChildDepth = Child->Depth;
518-
} else {
519-
check(ChildDepth == Child->Depth);
520-
}
573+
void GenericCycleInfo<ContextT>::verifyCycleNest(bool VerifyFull) const {
574+
#ifndef NDEBUG
575+
DenseSet<BlockT *> CycleHeaders;
576+
577+
for (CycleT *TopCycle : toplevel_cycles()) {
578+
for (CycleT *Cycle : depth_first(TopCycle)) {
579+
BlockT *Header = Cycle->getHeader();
580+
assert(CycleHeaders.insert(Header).second);
581+
if (VerifyFull)
582+
Cycle->verifyCycle();
583+
else
584+
Cycle->verifyCycleNest();
585+
// Check the block map entries for blocks contained in this cycle.
586+
for (BlockT *BB : Cycle->blocks()) {
587+
auto MapIt = BlockMap.find(BB);
588+
assert(MapIt != BlockMap.end());
589+
assert(Cycle->contains(MapIt->second));
521590
}
522591
}
523592
}
593+
#endif
594+
}
524595

525-
for (const auto &Entry : BlockMap) {
526-
BlockT *Block = Entry.first;
527-
for (const CycleT *Cycle = Entry.second; Cycle;
528-
Cycle = Cycle->ParentCycle) {
529-
check(is_contained(Cycle->Blocks, Block));
530-
}
531-
}
532-
533-
#undef check
534-
535-
return true;
596+
/// \brief Verify that the entire cycle tree well-formed.
597+
template <typename ContextT> void GenericCycleInfo<ContextT>::verify() const {
598+
verifyCycleNest(/*VerifyFull=*/true);
536599
}
537-
#endif
538600

539601
/// \brief Print the cycle info.
540602
template <typename ContextT>

llvm/include/llvm/ADT/GenericCycleInfo.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ template <typename ContextT> class GenericCycle {
140140
/// it, otherwise return nullptr.
141141
BlockT *getCyclePredecessor() const;
142142

143+
void verifyCycle() const;
144+
void verifyCycleNest() const;
145+
143146
/// Iteration over child cycles.
144147
//@{
145148
using const_child_iterator_base =
@@ -277,9 +280,8 @@ template <typename ContextT> class GenericCycleInfo {
277280

278281
/// Methods for debug and self-test.
279282
//@{
280-
#ifndef NDEBUG
281-
bool validateTree() const;
282-
#endif
283+
void verifyCycleNest(bool VerifyFull = false) const;
284+
void verify() const;
283285
void print(raw_ostream &Out) const;
284286
void dump() const { print(dbgs()); }
285287
Printable print(const CycleT *Cycle) { return Cycle->print(Context); }

llvm/include/llvm/Analysis/CycleAnalysis.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,17 @@ class CycleAnalysis : public AnalysisInfoMixin<CycleAnalysis> {
5959
// TODO: verify analysis?
6060
};
6161

62-
/// Printer pass for the \c DominatorTree.
6362
class CycleInfoPrinterPass : public PassInfoMixin<CycleInfoPrinterPass> {
6463
raw_ostream &OS;
6564

6665
public:
6766
explicit CycleInfoPrinterPass(raw_ostream &OS);
68-
6967
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
68+
static bool isRequired() { return true; }
69+
};
7070

71+
struct CycleInfoVerifierPass : public PassInfoMixin<CycleInfoVerifierPass> {
72+
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
7173
static bool isRequired() { return true; }
7274
};
7375

llvm/lib/Analysis/CycleAnalysis.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ PreservedAnalyses CycleInfoPrinterPass::run(Function &F,
3535
return PreservedAnalyses::all();
3636
}
3737

38+
PreservedAnalyses CycleInfoVerifierPass::run(Function &F,
39+
FunctionAnalysisManager &AM) {
40+
CycleInfo &CI = AM.getResult<CycleAnalysis>(F);
41+
CI.verify();
42+
return PreservedAnalyses::all();
43+
}
44+
3845
//===----------------------------------------------------------------------===//
3946
// CycleInfoWrapperPass Implementation
4047
//===----------------------------------------------------------------------===//

llvm/lib/Passes/PassRegistry.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ FUNCTION_PASS("typepromotion", TypePromotionPass(TM))
475475
FUNCTION_PASS("unify-loop-exits", UnifyLoopExitsPass())
476476
FUNCTION_PASS("vector-combine", VectorCombinePass())
477477
FUNCTION_PASS("verify", VerifierPass())
478+
FUNCTION_PASS("verify<cycles>", CycleInfoVerifierPass())
478479
FUNCTION_PASS("verify<domtree>", DominatorTreeVerifierPass())
479480
FUNCTION_PASS("verify<loops>", LoopVerifierPass())
480481
FUNCTION_PASS("verify<memoryssa>", MemorySSAVerifierPass())

llvm/test/Analysis/CycleInfo/basic.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
; RUN: opt < %s -disable-output -passes='print<cycles>' 2>&1 | FileCheck %s -check-prefix=CHECK
1+
; RUN: opt < %s -disable-output -passes='verify<cycles>,print<cycles>' 2>&1 | FileCheck %s -check-prefix=CHECK
22

33
define void @empty() {
44
; CHECK-LABEL: CycleInfo for function: empty

llvm/test/Analysis/CycleInfo/unreachable-predecessor.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
; RUN: opt < %s -disable-output -passes='print<cycles>' 2>&1 | FileCheck %s
1+
; RUN: opt < %s -disable-output -passes='verify<cycles>,print<cycles>' 2>&1 | FileCheck %s
22
; CHECK-LABEL: CycleInfo for function: unreachable
33
; CHECK: depth=1: entries(loop.body) loop.latch inner.block
44
define void @unreachable(i32 %n) {

0 commit comments

Comments
 (0)