[llvm] [GenericDomTreeConstruction] Speed up Semi-NCA construction (PR #207474)
Fangrui Song via llvm-commits
llvm-commits at lists.llvm.org
Sat Jul 4 08:27:11 PDT 2026
https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/207474
>From 48b0a2a9551bf183dd5c9db9ed329897063d334c Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Wed, 1 Jul 2026 10:45:07 -0700
Subject: [PATCH 1/2] [GenericDomTreeConstruction] Speed up Semi-NCA
construction
Run the immediate-dominator step in DFS-number space instead of chasing
NodePtr IDom through getNodeInfo()/getNumber(), and drop the redundant
NumToInfo[Semi]->DFSNum lookup. Use an inline capacity 32 (sqlite3's p90
is ~29 blocks), with no measurable gain from a larger inline buffer.
Make getChildren return a lazy filtered range and iterate it directly in
runDFS's common case, instead of materializing a SmallVector per node just to
push its successors onto the worklist.
clang::CFGBlock::AdjacentBlock still needs special handling: it is a proxy
convertible to CFGBlock* that can be null for unreachable edges (also see
commit 7ea0ee30588e), and it is not easy to migrate off.
---
.../llvm/Support/GenericDomTreeConstruction.h | 63 +++++++++++--------
1 file changed, 37 insertions(+), 26 deletions(-)
diff --git a/llvm/include/llvm/Support/GenericDomTreeConstruction.h b/llvm/include/llvm/Support/GenericDomTreeConstruction.h
index 511c28400e347..1c196f04acb7b 100644
--- a/llvm/include/llvm/Support/GenericDomTreeConstruction.h
+++ b/llvm/include/llvm/Support/GenericDomTreeConstruction.h
@@ -111,19 +111,25 @@ template <typename DomTreeT> struct SemiNCAInfo {
static SmallVector<NodePtr, 8> getChildren(NodePtr N, BatchUpdatePtr BUI) {
if (BUI)
return BUI->PreViewCFG.template getChildren<Inversed>(N);
- return getChildren<Inversed>(N);
+ // Force the element type to NodePtr. some graphs (clang's
+ // CFGBlock::AdjacentBlock) yield a proxy convertible to NodePtr rather than
+ // NodePtr itself.
+ auto Children = getChildren<Inversed>(N);
+ return SmallVector<NodePtr, 8>(Children.begin(), Children.end());
}
- template <bool Inversed>
- static SmallVector<NodePtr, 8> getChildren(NodePtr N) {
+ // Returns a lazy range over N's children, reversed for non-inverted graphs so
+ // a LIFO worklist visits them in their natural order. Graphs whose children
+ // aren't NodePtr (clang's CFGBlock::AdjacentBlock) may include nullptr, which
+ // are filtered out.
+ template <bool Inversed> static auto getChildren(NodePtr N) {
using DirectedNodeT =
std::conditional_t<Inversed, Inverse<NodePtr>, NodePtr>;
- auto R = children<DirectedNodeT>(N);
- SmallVector<NodePtr, 8> Res(detail::reverse_if<!Inversed>(R));
-
- // Remove nullptr children for clang.
- llvm::erase(Res, nullptr);
- return Res;
+ auto R = detail::reverse_if<!Inversed>(children<DirectedNodeT>(N));
+ if constexpr (std::is_same_v<std::decay_t<decltype(*R.begin())>, NodePtr>)
+ return R;
+ else
+ return llvm::make_filter_range(R, [](NodePtr C) { return C != nullptr; });
}
InfoRec &getNodeInfo(NodePtr BB) {
@@ -212,6 +218,16 @@ template <typename DomTreeT> struct SemiNCAInfo {
NumToNode.push_back(BB);
constexpr bool Direction = IsReverse != IsPostDom; // XOR.
+ // Common case: iterate the lazy successor range directly. Materializing
+ // is only needed to reorder by SuccOrder or to consult a batch update
+ // view.
+ if (!SuccOrder && !BatchUpdates) {
+ for (const NodePtr Succ : getChildren<Direction>(BB))
+ if (Condition(BB, Succ))
+ WorkList.push_back({Succ, LastNum});
+ continue;
+ }
+
auto Successors = getChildren<Direction>(BB, BatchUpdates);
if (SuccOrder && Successors.size() > 1)
llvm::sort(
@@ -277,13 +293,14 @@ template <typename DomTreeT> struct SemiNCAInfo {
// This function requires DFS to be run before calling it.
void runSemiNCA() {
const unsigned NextDFSNum(NumToNode.size());
- SmallVector<InfoRec *, 8> NumToInfo = {nullptr};
+ SmallVector<InfoRec *, 32> NumToInfo = {nullptr};
NumToInfo.reserve(NextDFSNum);
- // Initialize IDoms to spanning tree parents.
+ // Immediate dominators in DFS-number space, initialized to spanning tree
+ // parents.
+ SmallVector<unsigned, 32> IDoms(NextDFSNum);
for (unsigned i = 1; i < NextDFSNum; ++i) {
- const NodePtr V = NumToNode[i];
- auto &VInfo = getNodeInfo(V);
- VInfo.IDom = NumToNode[VInfo.Parent];
+ auto &VInfo = getNodeInfo(NumToNode[i]);
+ IDoms[i] = VInfo.Parent;
NumToInfo.push_back(&VInfo);
}
@@ -303,21 +320,15 @@ template <typename DomTreeT> struct SemiNCAInfo {
// Step #2: Explicitly define the immediate dominator of each vertex.
// IDom[i] = NCA(SDom[i], SpanningTreeParent(i)).
- // Note that the parents were stored in IDoms and later got invalidated
- // during path compression in Eval.
+ // SDom[i]'s DFS number is just Semi.
for (unsigned i = 2; i < NextDFSNum; ++i) {
auto &WInfo = *NumToInfo[i];
assert(WInfo.Semi != 0);
- const unsigned SDomNum = NumToInfo[WInfo.Semi]->DFSNum;
- NodePtr WIDomCandidate = WInfo.IDom;
- while (true) {
- auto &WIDomCandidateInfo = getNodeInfo(WIDomCandidate);
- if (WIDomCandidateInfo.DFSNum <= SDomNum)
- break;
- WIDomCandidate = WIDomCandidateInfo.IDom;
- }
-
- WInfo.IDom = WIDomCandidate;
+ unsigned WIDom = IDoms[i];
+ while (WIDom > WInfo.Semi)
+ WIDom = IDoms[WIDom];
+ IDoms[i] = WIDom;
+ WInfo.IDom = NumToNode[WIDom];
}
}
>From 25747c97e0728a8993f400683c43b2b21e016072 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sat, 4 Jul 2026 08:27:00 -0700
Subject: [PATCH 2/2] use resize_for_overwrite; improve clang CFGBlock comments
---
.../llvm/Support/GenericDomTreeConstruction.h | 21 +++++++++++--------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/llvm/include/llvm/Support/GenericDomTreeConstruction.h b/llvm/include/llvm/Support/GenericDomTreeConstruction.h
index 1c196f04acb7b..869087cda219a 100644
--- a/llvm/include/llvm/Support/GenericDomTreeConstruction.h
+++ b/llvm/include/llvm/Support/GenericDomTreeConstruction.h
@@ -119,13 +119,14 @@ template <typename DomTreeT> struct SemiNCAInfo {
}
// Returns a lazy range over N's children, reversed for non-inverted graphs so
- // a LIFO worklist visits them in their natural order. Graphs whose children
- // aren't NodePtr (clang's CFGBlock::AdjacentBlock) may include nullptr, which
- // are filtered out.
+ // a LIFO worklist visits them in their natural order.
template <bool Inversed> static auto getChildren(NodePtr N) {
using DirectedNodeT =
std::conditional_t<Inversed, Inverse<NodePtr>, NodePtr>;
auto R = detail::reverse_if<!Inversed>(children<DirectedNodeT>(N));
+ // Most graphs' iterators yield NodePtr directly; return the range as is.
+ // clang's CFGBlock instead yields a CFGBlock::AdjacentBlock proxy that is
+ // convertible to NodePtr but can be null for AB_Unreachable.
if constexpr (std::is_same_v<std::decay_t<decltype(*R.begin())>, NodePtr>)
return R;
else
@@ -293,15 +294,17 @@ template <typename DomTreeT> struct SemiNCAInfo {
// This function requires DFS to be run before calling it.
void runSemiNCA() {
const unsigned NextDFSNum(NumToNode.size());
- SmallVector<InfoRec *, 32> NumToInfo = {nullptr};
- NumToInfo.reserve(NextDFSNum);
- // Immediate dominators in DFS-number space, initialized to spanning tree
- // parents.
- SmallVector<unsigned, 32> IDoms(NextDFSNum);
+ // NumToInfo and IDoms are indexed by DFS number; index 0 is an unused
+ // sentinel. IDoms holds immediate dominators in DFS-number space,
+ // initialized below to spanning tree parents.
+ SmallVector<InfoRec *, 32> NumToInfo;
+ NumToInfo.resize_for_overwrite(NextDFSNum);
+ SmallVector<unsigned, 32> IDoms;
+ IDoms.resize_for_overwrite(NextDFSNum);
for (unsigned i = 1; i < NextDFSNum; ++i) {
auto &VInfo = getNodeInfo(NumToNode[i]);
IDoms[i] = VInfo.Parent;
- NumToInfo.push_back(&VInfo);
+ NumToInfo[i] = &VInfo;
}
// Step #1: Calculate the semidominators of all vertices.
More information about the llvm-commits
mailing list