[llvm] [Analysis] Visit orphan Functions in GCGPassManager catch-up loop (PR #194219)
via llvm-commits
llvm-commits at lists.llvm.org
Sat May 2 14:50:02 PDT 2026
https://github.com/SjoerdNijboer updated https://github.com/llvm/llvm-project/pull/194219
>From 291192304f619b29d9702e00a190312010089ff3 Mon Sep 17 00:00:00 2001
From: Sjoerd Nijboer <github at sjoerdnijboer.com>
Date: Sun, 26 Apr 2026 08:44:31 +0200
Subject: [PATCH 1/4] [CodeGen] Add IPRA regression tests for empty/orphan
internal functions
Unit tests covering the empty-MachineFunction crash and the orphan
cyclic-SCC silent skip with -enable-ipra across AArch64, ARM, RISC-V,
X86, and AMDGPU. These tests should succeed once
https://github.com/llvm/llvm-project/issues/119556 is fixed.
---
llvm/test/CodeGen/AArch64/ipra-empty-mf.ll | 19 +++++++++++++
.../test/CodeGen/AArch64/ipra-orphan-cycle.ll | 24 +++++++++++++++++
.../AMDGPU/codegen-internal-only-func.ll | 9 ++++++-
llvm/test/CodeGen/ARM/ipra-empty-mf.ll | 16 +++++++++++
llvm/test/CodeGen/ARM/ipra-orphan-cycle.ll | 24 +++++++++++++++++
llvm/test/CodeGen/RISCV/ipra-empty-mf.ll | 18 +++++++++++++
llvm/test/CodeGen/RISCV/ipra-orphan-cycle.ll | 24 +++++++++++++++++
llvm/test/CodeGen/X86/ipra-empty-mf.ll | 21 +++++++++++++++
llvm/test/CodeGen/X86/ipra-orphan-cycle.ll | 27 +++++++++++++++++++
9 files changed, 181 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/CodeGen/AArch64/ipra-empty-mf.ll
create mode 100644 llvm/test/CodeGen/AArch64/ipra-orphan-cycle.ll
create mode 100644 llvm/test/CodeGen/ARM/ipra-empty-mf.ll
create mode 100644 llvm/test/CodeGen/ARM/ipra-orphan-cycle.ll
create mode 100644 llvm/test/CodeGen/RISCV/ipra-empty-mf.ll
create mode 100644 llvm/test/CodeGen/RISCV/ipra-orphan-cycle.ll
create mode 100644 llvm/test/CodeGen/X86/ipra-empty-mf.ll
create mode 100644 llvm/test/CodeGen/X86/ipra-orphan-cycle.ll
diff --git a/llvm/test/CodeGen/AArch64/ipra-empty-mf.ll b/llvm/test/CodeGen/AArch64/ipra-empty-mf.ll
new file mode 100644
index 0000000000000..9f11885c67aa2
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/ipra-empty-mf.ll
@@ -0,0 +1,19 @@
+; Regression test for a crash when compiling a trivial unused internal
+; function with -enable-ipra on AArch64. Enabling IPRA causes
+; `-enable-ipra` (via setRequiresCodeGenSCCOrder) to skip codegen for
+; `@empty_internal_func` inside the CallGraphSCCPassManager. The
+; MachineOutliner ModulePass (enabled by default on AArch64) then closed
+; that inner FunctionPassManager and the post-outliner FPM visited
+; `@empty_internal_func` anyway, created a fresh empty MachineFunction for
+; it, and crashed in `Branch relaxation pass`. See
+; https://github.com/llvm/llvm-project/issues/119556.
+
+; RUN: llc -mtriple=aarch64 -enable-ipra < %s | FileCheck %s
+; RUN: llc -mtriple=aarch64_be -enable-ipra < %s | FileCheck %s
+
+; CHECK-LABEL: empty_internal_func:
+; CHECK: ret
+
+define internal void @empty_internal_func() {
+ ret void
+}
diff --git a/llvm/test/CodeGen/AArch64/ipra-orphan-cycle.ll b/llvm/test/CodeGen/AArch64/ipra-orphan-cycle.ll
new file mode 100644
index 0000000000000..1639788fa2f33
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/ipra-orphan-cycle.ll
@@ -0,0 +1,24 @@
+; Regression test for the cyclic-orphan case of the IPRA empty-MachineFunction
+; bug (see https://github.com/llvm/llvm-project/issues/119556). Two mutually
+; recursive `internal` functions with no external entry point: neither is
+; use_empty, neither has external linkage, neither is address-taken, but the
+; 2-node SCC {foo, bar} has no incoming edge from
+; ExternalCallingNode and is therefore not visited by scc_iterator.
+
+; RUN: llc -mtriple=aarch64 -enable-ipra < %s | FileCheck %s
+; RUN: llc -mtriple=aarch64_be -enable-ipra < %s | FileCheck %s
+
+; CHECK-LABEL: foo:
+; CHECK: ret
+; CHECK-LABEL: bar:
+; CHECK: ret
+
+define internal void @foo() {
+ call void @bar()
+ ret void
+}
+
+define internal void @bar() {
+ call void @foo()
+ ret void
+}
diff --git a/llvm/test/CodeGen/AMDGPU/codegen-internal-only-func.ll b/llvm/test/CodeGen/AMDGPU/codegen-internal-only-func.ll
index f198833059572..b0669382d588c 100644
--- a/llvm/test/CodeGen/AMDGPU/codegen-internal-only-func.ll
+++ b/llvm/test/CodeGen/AMDGPU/codegen-internal-only-func.ll
@@ -2,7 +2,14 @@
; RUN: llc -mtriple=amdgcn-amd-amdhsa -mcpu=gfx900 < %s | FileCheck %s
; RUN: llc -O0 -mtriple=amdgcn-amd-amdhsa -mcpu=gfx900 < %s | FileCheck %s
-; CHECK-NOT: func
+; Don't longer silently skips internal Functions that are unreachable from
+; ExternalCallingNode. This aligns with non-SCC-ordered codegen and with
+; NPM-codegen behaviour: an internal function with no IR-level uses is still
+; emitted, and the linker drops it at final-link time (--gc-sections) if
+; truly unused. That also correctly preserves the symbol for
+; inline-asm/linker-script references that IR-level use-tracking doesn't see.
+
+; CHECK-LABEL: func:
define internal i32 @func() {
ret i32 0
diff --git a/llvm/test/CodeGen/ARM/ipra-empty-mf.ll b/llvm/test/CodeGen/ARM/ipra-empty-mf.ll
new file mode 100644
index 0000000000000..97dc21c017b4e
--- /dev/null
+++ b/llvm/test/CodeGen/ARM/ipra-empty-mf.ll
@@ -0,0 +1,16 @@
+; Regression test for a crash when compiling a trivial unused internal
+; function with -enable-ipra on ARM. ARM enables the MachineOutliner in its
+; TargetMachine, which under IPRA triggers the same empty-MF crash as on
+; RISC-V and AArch64. See https://github.com/llvm/llvm-project/issues/119556.
+
+; RUN: llc -mtriple=armv7-unknown-linux -enable-ipra < %s | FileCheck %s
+; RUN: llc -mtriple=armv7eb-unknown-linux -enable-ipra < %s | FileCheck %s
+; RUN: llc -mtriple=thumbv7-unknown-linux -enable-ipra < %s | FileCheck %s
+; RUN: llc -mtriple=thumbv8-unknown-linux -enable-ipra < %s | FileCheck %s
+
+; CHECK-LABEL: empty_internal_func:
+; CHECK: bx lr
+
+define internal void @empty_internal_func() {
+ ret void
+}
diff --git a/llvm/test/CodeGen/ARM/ipra-orphan-cycle.ll b/llvm/test/CodeGen/ARM/ipra-orphan-cycle.ll
new file mode 100644
index 0000000000000..7ca1ba358d900
--- /dev/null
+++ b/llvm/test/CodeGen/ARM/ipra-orphan-cycle.ll
@@ -0,0 +1,24 @@
+; Regression test for the cyclic-orphan case of the IPRA empty-MachineFunction
+; bug (see https://github.com/llvm/llvm-project/issues/119556). Two mutually
+; recursive `internal` functions form an SCC unreachable from
+; ExternalCallingNode. The CGPassManager catch-up loop must visit it.
+
+; RUN: llc -mtriple=armv7-unknown-linux -enable-ipra < %s | FileCheck %s
+; RUN: llc -mtriple=armv7eb-unknown-linux -enable-ipra < %s | FileCheck %s
+; RUN: llc -mtriple=thumbv7-unknown-linux -enable-ipra < %s | FileCheck %s
+; RUN: llc -mtriple=thumbv8-unknown-linux -enable-ipra < %s | FileCheck %s
+
+; CHECK-LABEL: foo:
+; CHECK: pop
+; CHECK-LABEL: bar:
+; CHECK: pop
+
+define internal void @foo() {
+ call void @bar()
+ ret void
+}
+
+define internal void @bar() {
+ call void @foo()
+ ret void
+}
diff --git a/llvm/test/CodeGen/RISCV/ipra-empty-mf.ll b/llvm/test/CodeGen/RISCV/ipra-empty-mf.ll
new file mode 100644
index 0000000000000..9e4ce28276408
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/ipra-empty-mf.ll
@@ -0,0 +1,18 @@
+; Regression test for a crash when compiling a trivial unused internal
+; function with -enable-ipra on RISC-V. Enabling IPRA causes
+; `-enable-ipra` (via setRequiresCodeGenSCCOrder) to skip codegen for
+; `@empty_internal_func` inside the CallGraphSCCPassManager. The
+; MachineOutliner ModulePass then closed that inner FunctionPassManager and
+; the post-outliner FPM visited `@empty_internal_func` anyway, created a
+; fresh empty MachineFunction for it, and crashed the RISC-V AsmPrinter at
+; `MF->front()`.
+;
+; RUN: llc -mtriple=riscv64 -enable-ipra < %s | FileCheck %s
+; RUN: llc -mtriple=riscv32 -enable-ipra < %s | FileCheck %s
+
+; CHECK-LABEL: empty_internal_func:
+; CHECK: ret
+
+define internal void @empty_internal_func() {
+ ret void
+}
diff --git a/llvm/test/CodeGen/RISCV/ipra-orphan-cycle.ll b/llvm/test/CodeGen/RISCV/ipra-orphan-cycle.ll
new file mode 100644
index 0000000000000..054e3c85dd892
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/ipra-orphan-cycle.ll
@@ -0,0 +1,24 @@
+; Regression test for the cyclic-orphan case of the IPRA empty-MachineFunction
+; bug (see https://github.com/llvm/llvm-project/issues/119556). Two mutually
+; recursive `internal` functions with no external entry point: neither is
+; use_empty (they use each other), neither has external linkage, neither is
+; address-taken, but the 2-node SCC {foo, bar} has no incoming
+; edge from ExternalCallingNode and is therefore not visited by scc_iterator.
+
+; RUN: llc -mtriple=riscv64 -enable-ipra < %s | FileCheck %s
+; RUN: llc -mtriple=riscv32 -enable-ipra < %s | FileCheck %s
+
+; CHECK-LABEL: foo:
+; CHECK: ret
+; CHECK-LABEL: bar:
+; CHECK: ret
+
+define internal void @foo() {
+ call void @bar()
+ ret void
+}
+
+define internal void @bar() {
+ call void @foo()
+ ret void
+}
diff --git a/llvm/test/CodeGen/X86/ipra-empty-mf.ll b/llvm/test/CodeGen/X86/ipra-empty-mf.ll
new file mode 100644
index 0000000000000..83bb5ba21ed17
--- /dev/null
+++ b/llvm/test/CodeGen/X86/ipra-empty-mf.ll
@@ -0,0 +1,21 @@
+; Companion regression test for the IPRA empty-MachineFunction crash (see
+; https://github.com/llvm/llvm-project/issues/119556). X86 targets do not
+; enable the Machine Outliner by default (SupportsDefaultOutlining is false),
+; so `-enable-ipra` alone does not trigger the crash — it silently drops the
+; unreferenced function. Forcing the outliner reproduces the same pipeline
+; split as on RISC-V/AArch64/Arm, so these RUN lines pin the fix on x86_64
+; and i386.
+
+; RUN: llc -mtriple=x86_64 -enable-ipra -enable-machine-outliner=always < %s \
+; RUN: | FileCheck %s
+; RUN: llc -mtriple=x86_64 -enable-ipra < %s | FileCheck %s
+; RUN: llc -mtriple=i386 -enable-ipra -enable-machine-outliner=always < %s \
+; RUN: | FileCheck %s
+; RUN: llc -mtriple=i386 -enable-ipra < %s | FileCheck %s
+
+; CHECK-LABEL: empty_internal_func:
+; CHECK: ret
+
+define internal void @empty_internal_func() {
+ ret void
+}
diff --git a/llvm/test/CodeGen/X86/ipra-orphan-cycle.ll b/llvm/test/CodeGen/X86/ipra-orphan-cycle.ll
new file mode 100644
index 0000000000000..0942dee6c9e98
--- /dev/null
+++ b/llvm/test/CodeGen/X86/ipra-orphan-cycle.ll
@@ -0,0 +1,27 @@
+; Companion regression test for the cyclic-orphan case of the IPRA empty-MF
+; bug (see https://github.com/llvm/llvm-project/issues/119556). X86 does not
+; schedule a mid-codegen ModulePass by default under IPRA, so -enable-ipra
+; alone does not trigger the empty-MF crash; forcing the MachineOutliner
+; reproduces the same pipeline split as on RISC-V/AArch64/Arm and exercises
+; the cyclic-orphan path.
+
+; RUN: llc -mtriple=x86_64 -enable-ipra -enable-machine-outliner=always < %s \
+; RUN: | FileCheck %s
+; RUN: llc -mtriple=x86_64 -enable-ipra < %s | FileCheck %s
+; RUN: llc -mtriple=i386 -enable-ipra -enable-machine-outliner=always < %s \
+; RUN: | FileCheck %s
+; RUN: llc -mtriple=i386 -enable-ipra < %s | FileCheck %s
+
+; CHECK-DAG: foo:
+; CHECK-DAG: bar:
+; CHECK: ret
+
+define internal void @foo() {
+ call void @bar()
+ ret void
+}
+
+define internal void @bar() {
+ call void @foo()
+ ret void
+}
>From 555534d6cca693fdf67473d7dc80287e225c487b Mon Sep 17 00:00:00 2001
From: Sjoerd Nijboer <github at sjoerdnijboer.com>
Date: Wed, 22 Apr 2026 18:30:15 +0200
Subject: [PATCH 2/4] [Analysis] Visit orphan Functions in CGPassManager
catch-up loop
Fixes https://github.com/llvm/llvm-project/issues/119556
When codegen is requested to run in CallGraph-SCC order via
setRequiresCodeGenSCCOrder(), CGPassManager::runOnModule walks
SCCs via scc_iterator<CallGraph*>, which starts from ExternalCallingNode
and only visits SCCs reachable from that root. Functions whose
CallGraphNode has no incoming edge from ExternalCallingNode therefore
never have any pass run on them. Two subclasses occur in practice:
* a private Function with no uses and no address taken (no edge from
ExternalCallingNode per CallGraph::addToCallGraph);
* a group of mutually-recursive private Functions with no external
entry point, forming an SCC unreachable from ExternalCallingNode.
That silent skip has two observable consequences:
1. On targets whose codegen pipeline stays entirely inside the CGSCC
For x86-64 and AMDGPU with IPRA enabled, the orphan Function is
silently not generated. The assumption is that no one really missed
this, but It is inconsistent behaviour with non-IPRA compilations.
This patch makes x86-64 and AMDGPU still emit the function when IPRA
is enabled.
2. On targets where a ModulePass (MachineOutliner on RISC-V/AArch64/Arm)
is scheduled in the codegen pipeline after the CGSCC section, the
legacy PassManager closes the inner FunctionPassManager and reopens a
plain outer FPM. That outer FPM iterates via Module::iterator and does
find the skipped Function. MachineFunctionPass::runOnFunction then
materialises a fresh empty MachineFunction via
MMI::getOrCreateMachineFunction, and subsequent late passes crash by
dereferencing MF->front() in the AsmPrinter or anywhere else in a
target-specific pass.
This is fixed by tracking the visited CallGraphNodes scc_iterator visits
Then after the main run of the scc_iterator the CGSCC-scoped passes on every
non-declaration Function whose node is not in that set in
RunAllPassesOnOrphanNodes() This aligns legacy-PM behaviour with NPM-codegen
which already emits orphans correctly on X86 and fixes both the crash and the
silent-elision miscompile.
---
llvm/lib/Analysis/CallGraphSCCPass.cpp | 55 ++++++++++++++++++++++++++
1 file changed, 55 insertions(+)
diff --git a/llvm/lib/Analysis/CallGraphSCCPass.cpp b/llvm/lib/Analysis/CallGraphSCCPass.cpp
index 1228d5b4b78be..164bbe2f41929 100644
--- a/llvm/lib/Analysis/CallGraphSCCPass.cpp
+++ b/llvm/lib/Analysis/CallGraphSCCPass.cpp
@@ -17,6 +17,8 @@
#include "llvm/Analysis/CallGraphSCCPass.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SCCIterator.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Analysis/CallGraph.h"
@@ -113,6 +115,8 @@ class CGPassManager : public ModulePass, public PMDataManager {
bool &DevirtualizedCall);
bool RefreshCallGraph(const CallGraphSCC &CurSCC, CallGraph &CG,
bool IsCheckingMode);
+ bool RunAllPassesOnOrphanNodes(CallGraph &CG, CallGraphSCC &CurSCC,
+ std::vector<const CallGraphNode *> &VisitedNodes);
};
} // end anonymous namespace.
@@ -505,11 +509,21 @@ bool CGPassManager::runOnModule(Module &M) {
scc_iterator<CallGraph*> CGI = scc_begin(&CG);
CallGraphSCC CurSCC(CG, &CGI);
+
+ // Track which CallGraphNodes the main scc_iterator visits so the orphan
+ // catch-up below can run the same passes on any non-declaration Function
+ // whose node was not reached from ExternalCallingNode. Such orphan nodes
+ // would otherwise be silently skipped by the CGSCC pipeline; on targets
+ // that schedule a ModulePass mid-codegen they later trigger an empty
+ // MachineFunction crash in downstream passes (issue #119556).
+ std::vector<const CallGraphNode *> VisitedNodes;
+
while (!CGI.isAtEnd()) {
// Copy the current SCC and increment past it so that the pass can hack
// on the SCC if it wants to without invalidating our iterator.
const std::vector<CallGraphNode *> &NodeVec = *CGI;
CurSCC.initialize(NodeVec);
+ VisitedNodes.insert(VisitedNodes.end(), NodeVec.begin(), NodeVec.end());
++CGI;
// At the top level, we run all the passes in this pass manager on the
@@ -541,10 +555,51 @@ bool CGPassManager::runOnModule(Module &M) {
MaxSCCIterations.updateMax(Iteration);
}
+
+ Changed |= RunAllPassesOnOrphanNodes(CG, CurSCC, VisitedNodes);
Changed |= doFinalization(CG);
return Changed;
}
+bool CGPassManager::RunAllPassesOnOrphanNodes(
+ CallGraph &CG, CallGraphSCC &CurSCC,
+ std::vector<const CallGraphNode *> &VisitedNodes) {
+ bool Changed = false;
+
+ // Collect orphan roots up front so Module iteration cannot be disturbed
+ // by IR-level mutation a pass may perform on an orphan.
+ std::vector<CallGraphNode *> OrphanRoots;
+ for (Function &F : CG.getModule()) {
+ if (!F.isDeclaration()) {
+ CallGraphNode *Node = CG[&F];
+ if (!is_contained(VisitedNodes, Node))
+ OrphanRoots.push_back(Node);
+ }
+ }
+
+ for (CallGraphNode *Root : OrphanRoots) {
+ if (!is_contained(VisitedNodes, Root)) {
+ for (auto SCCI = scc_begin(Root); !SCCI.isAtEnd(); ++SCCI) {
+ const std::vector<CallGraphNode *> &Members = *SCCI;
+ if (!any_of(Members, [&](CallGraphNode *M) {
+ return is_contained(VisitedNodes, M);
+ })) {
+ // Skip any orphaned node that is not in another root and hasn't been
+ // called by another orphaned node yet.
+ // Create a CallGraphSCC for them.
+ // Ignore devirtualization since it really doesn't matter here.
+ CurSCC.initialize(Members);
+ bool UnusedDevirtualizedCall = false;
+ Changed |= RunAllPassesOnSCC(CurSCC, CG, UnusedDevirtualizedCall);
+ VisitedNodes.insert(VisitedNodes.end(), Members.begin(),
+ Members.end());
+ }
+ }
+ }
+ }
+ return Changed;
+}
+
/// Initialize CG
bool CGPassManager::doInitialization(CallGraph &CG) {
bool Changed = false;
>From e2f7a51007d81535da7d1e8bf345efc62f3a043f Mon Sep 17 00:00:00 2001
From: Sjoerd Nijboer <github at sjoerdnijboer.com>
Date: Sat, 2 May 2026 15:28:39 +0200
Subject: [PATCH 3/4] [ADT][Analysis] Add getUnvisitedNodes() to scc_iterator
and use in CGPassManager
Add a getUnvisitedNodes() method to scc_iterator that returns a filter
iterator over nodes from a given collection that were not visited during
SCC traversal. This leverages the existing nodeVisitNumbers DenseMap that
scc_iterator already maintains internally.
Update CGPassManager::RunAllPassesOnOrphanNodes() to use this new API
instead of manually tracking visited nodes in a std::vector with
is_contained() checks. This simplifies the orphan detection logic
introduced in previous commits.
---
llvm/include/llvm/ADT/SCCIterator.h | 42 +++--
llvm/lib/Analysis/CallGraphSCCPass.cpp | 208 ++++++++++++-------------
2 files changed, 122 insertions(+), 128 deletions(-)
diff --git a/llvm/include/llvm/ADT/SCCIterator.h b/llvm/include/llvm/ADT/SCCIterator.h
index 205fa669a12de..2d6bddd743d49 100644
--- a/llvm/include/llvm/ADT/SCCIterator.h
+++ b/llvm/include/llvm/ADT/SCCIterator.h
@@ -25,6 +25,7 @@
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/GraphTraits.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/iterator.h"
#include <cassert>
#include <cstddef>
@@ -54,16 +55,15 @@ class scc_iterator : public iterator_facade_base<
/// Element of VisitStack during DFS.
struct StackElement {
- NodeRef Node; ///< The current node pointer.
- ChildItTy NextChild; ///< The next child, modified inplace during DFS.
- unsigned MinVisited; ///< Minimum uplink value of all children of Node.
+ NodeRef Node; ///< The current node pointer.
+ ChildItTy NextChild; ///< The next child, modified inplace during DFS.
+ unsigned MinVisited; ///< Minimum uplink value of all children of Node.
StackElement(NodeRef Node, const ChildItTy &Child, unsigned Min)
: Node(Node), NextChild(Child), MinVisited(Min) {}
bool operator==(const StackElement &Other) const {
- return Node == Other.Node &&
- NextChild == Other.NextChild &&
+ return Node == Other.Node && NextChild == Other.NextChild &&
MinVisited == Other.MinVisited;
}
};
@@ -145,6 +145,17 @@ class scc_iterator : public iterator_facade_base<
nodeVisitNumbers[New] = tempVal;
nodeVisitNumbers.erase(Old);
}
+
+ /// Returns a filter iterator range containing only nodes from the input
+ /// that haven't been visited (yet) by this scc_iterator.
+ /// This function is only meant to be used after the full iterator has been
+ /// run from beginning to end.
+ // Otherwise the set will contain intermediate results.
+ template <typename RangeT> auto getUnvisitedNodes(RangeT &&Nodes) const {
+ return make_filter_range(std::forward<RangeT>(Nodes), [this](NodeRef N) {
+ return !nodeVisitNumbers.contains(N);
+ });
+ }
};
template <class GraphT, class GT>
@@ -218,16 +229,15 @@ template <class GraphT, class GT> void scc_iterator<GraphT, GT>::GetNextSCC() {
template <class GraphT, class GT>
bool scc_iterator<GraphT, GT>::hasCycle() const {
- assert(!CurrentSCC.empty() && "Dereferencing END SCC iterator!");
- if (CurrentSCC.size() > 1)
+ assert(!CurrentSCC.empty() && "Dereferencing END SCC iterator!");
+ if (CurrentSCC.size() > 1)
+ return true;
+ NodeRef N = CurrentSCC.front();
+ for (ChildItTy CI = GT::child_begin(N), CE = GT::child_end(N); CI != CE; ++CI)
+ if (*CI == N)
return true;
- NodeRef N = CurrentSCC.front();
- for (ChildItTy CI = GT::child_begin(N), CE = GT::child_end(N); CI != CE;
- ++CI)
- if (*CI == N)
- return true;
- return false;
- }
+ return false;
+}
/// Construct the begin iterator for a deduced graph type T.
template <class T> scc_iterator<T> scc_begin(const T &G) {
@@ -351,8 +361,8 @@ scc_member_iterator<GraphT, GT>::scc_member_iterator(
for (const auto *Edge : MSTEdges)
NodeInfoMap[Edge->Target].IncomingMSTEdges.insert(Edge);
- // Walk through SortedEdges to initialize the queue, instead of using NodeInfoMap
- // to ensure an ordered deterministic push.
+ // Walk through SortedEdges to initialize the queue, instead of using
+ // NodeInfoMap to ensure an ordered deterministic push.
for (auto *Edge : SortedEdges) {
auto &Info = NodeInfoMap[Edge->Source];
if (!Info.Visited && Info.IncomingMSTEdges.empty()) {
diff --git a/llvm/lib/Analysis/CallGraphSCCPass.cpp b/llvm/lib/Analysis/CallGraphSCCPass.cpp
index 164bbe2f41929..2e83abef4bf04 100644
--- a/llvm/lib/Analysis/CallGraphSCCPass.cpp
+++ b/llvm/lib/Analysis/CallGraphSCCPass.cpp
@@ -69,8 +69,8 @@ class CGPassManager : public ModulePass, public PMDataManager {
/// whether any of the passes modifies the module, and if so, return true.
bool runOnModule(Module &M) override;
- using ModulePass::doInitialization;
using ModulePass::doFinalization;
+ using ModulePass::doInitialization;
bool doInitialization(CallGraph &CG);
bool doFinalization(CallGraph &CG);
@@ -89,11 +89,11 @@ class CGPassManager : public ModulePass, public PMDataManager {
// Print passes managed by this manager
void dumpPassStructure(unsigned Offset) override {
- errs().indent(Offset*2) << "Call Graph SCC Pass Manager\n";
+ errs().indent(Offset * 2) << "Call Graph SCC Pass Manager\n";
for (unsigned Index = 0; Index < getNumContainedPasses(); ++Index) {
Pass *P = getContainedPass(Index);
P->dumpPassStructure(Offset + 1);
- dumpLastUses(P, Offset+1);
+ dumpLastUses(P, Offset + 1);
}
}
@@ -110,21 +110,20 @@ class CGPassManager : public ModulePass, public PMDataManager {
bool RunAllPassesOnSCC(CallGraphSCC &CurSCC, CallGraph &CG,
bool &DevirtualizedCall);
- bool RunPassOnSCC(Pass *P, CallGraphSCC &CurSCC,
- CallGraph &CG, bool &CallGraphUpToDate,
- bool &DevirtualizedCall);
+ bool RunPassOnSCC(Pass *P, CallGraphSCC &CurSCC, CallGraph &CG,
+ bool &CallGraphUpToDate, bool &DevirtualizedCall);
bool RefreshCallGraph(const CallGraphSCC &CurSCC, CallGraph &CG,
bool IsCheckingMode);
bool RunAllPassesOnOrphanNodes(CallGraph &CG, CallGraphSCC &CurSCC,
- std::vector<const CallGraphNode *> &VisitedNodes);
+ const scc_iterator<CallGraph *> &VisitedCGI);
};
} // end anonymous namespace.
char CGPassManager::ID = 0;
-bool CGPassManager::RunPassOnSCC(Pass *P, CallGraphSCC &CurSCC,
- CallGraph &CG, bool &CallGraphUpToDate,
+bool CGPassManager::RunPassOnSCC(Pass *P, CallGraphSCC &CurSCC, CallGraph &CG,
+ bool &CallGraphUpToDate,
bool &DevirtualizedCall) {
bool Changed = false;
PMDataManager *PM = P->getAsPMDataManager();
@@ -173,7 +172,7 @@ bool CGPassManager::RunPassOnSCC(Pass *P, CallGraphSCC &CurSCC,
assert(PM->getPassManagerType() == PMT_FunctionPassManager &&
"Invalid CGPassManager member");
- FPPassManager *FPP = (FPPassManager*)P;
+ FPPassManager *FPP = (FPPassManager *)P;
// Run pass P on all functions in the current SCC.
for (CallGraphNode *CGN : CurSCC) {
@@ -212,8 +211,7 @@ bool CGPassManager::RefreshCallGraph(const CallGraphSCC &CurSCC, CallGraph &CG,
LLVM_DEBUG(dbgs() << "CGSCCPASSMGR: Refreshing SCC with " << CurSCC.size()
<< " nodes:\n";
- for (CallGraphNode *CGN
- : CurSCC) CGN->dump(););
+ for (CallGraphNode *CGN : CurSCC) CGN->dump(););
bool MadeChange = false;
bool DevirtualizedCall = false;
@@ -224,7 +222,8 @@ bool CGPassManager::RefreshCallGraph(const CallGraphSCC &CurSCC, CallGraph &CG,
SCCIdx != E; ++SCCIdx, ++FunctionNo) {
CallGraphNode *CGN = *SCCIdx;
Function *F = CGN->getFunction();
- if (!F || F->isDeclaration()) continue;
+ if (!F || F->isDeclaration())
+ continue;
// Walk the function body looking for call sites. Sync up the call sites in
// CGN with those actually in the function.
@@ -411,15 +410,16 @@ bool CGPassManager::RefreshCallGraph(const CallGraphSCC &CurSCC, CallGraph &CG,
Calls.clear();
}
- LLVM_DEBUG(if (MadeChange) {
- dbgs() << "CGSCCPASSMGR: Refreshed SCC is now:\n";
- for (CallGraphNode *CGN : CurSCC)
- CGN->dump();
- if (DevirtualizedCall)
- dbgs() << "CGSCCPASSMGR: Refresh devirtualized a call!\n";
- } else {
- dbgs() << "CGSCCPASSMGR: SCC Refresh didn't change call graph.\n";
- });
+ LLVM_DEBUG(
+ if (MadeChange) {
+ dbgs() << "CGSCCPASSMGR: Refreshed SCC is now:\n";
+ for (CallGraphNode *CGN : CurSCC)
+ CGN->dump();
+ if (DevirtualizedCall)
+ dbgs() << "CGSCCPASSMGR: Refresh devirtualized a call!\n";
+ } else {
+ dbgs() << "CGSCCPASSMGR: SCC Refresh didn't change call graph.\n";
+ });
(void)MadeChange;
return DevirtualizedCall;
@@ -442,22 +442,22 @@ bool CGPassManager::RunAllPassesOnSCC(CallGraphSCC &CurSCC, CallGraph &CG,
bool CallGraphUpToDate = true;
// Run all passes on current SCC.
- for (unsigned PassNo = 0, e = getNumContainedPasses();
- PassNo != e; ++PassNo) {
+ for (unsigned PassNo = 0, e = getNumContainedPasses(); PassNo != e;
+ ++PassNo) {
Pass *P = getContainedPass(PassNo);
// If we're in -debug-pass=Executions mode, construct the SCC node list,
// otherwise avoid constructing this string as it is expensive.
if (isPassDebuggingExecutionsOrMore()) {
std::string Functions;
- #ifndef NDEBUG
+#ifndef NDEBUG
raw_string_ostream OS(Functions);
ListSeparator LS;
for (const CallGraphNode *CGN : CurSCC) {
OS << LS;
CGN->print(OS);
}
- #endif
+#endif
dumpPassInfo(P, EXECUTION_MSG, ON_CG_MSG, Functions);
}
dumpRequiredSet(P);
@@ -506,24 +506,15 @@ bool CGPassManager::runOnModule(Module &M) {
bool Changed = doInitialization(CG);
// Walk the callgraph in bottom-up SCC order.
- scc_iterator<CallGraph*> CGI = scc_begin(&CG);
+ scc_iterator<CallGraph *> CGI = scc_begin(&CG);
CallGraphSCC CurSCC(CG, &CGI);
- // Track which CallGraphNodes the main scc_iterator visits so the orphan
- // catch-up below can run the same passes on any non-declaration Function
- // whose node was not reached from ExternalCallingNode. Such orphan nodes
- // would otherwise be silently skipped by the CGSCC pipeline; on targets
- // that schedule a ModulePass mid-codegen they later trigger an empty
- // MachineFunction crash in downstream passes (issue #119556).
- std::vector<const CallGraphNode *> VisitedNodes;
-
while (!CGI.isAtEnd()) {
// Copy the current SCC and increment past it so that the pass can hack
// on the SCC if it wants to without invalidating our iterator.
const std::vector<CallGraphNode *> &NodeVec = *CGI;
CurSCC.initialize(NodeVec);
- VisitedNodes.insert(VisitedNodes.end(), NodeVec.begin(), NodeVec.end());
++CGI;
// At the top level, we run all the passes in this pass manager on the
@@ -556,43 +547,34 @@ bool CGPassManager::runOnModule(Module &M) {
MaxSCCIterations.updateMax(Iteration);
}
- Changed |= RunAllPassesOnOrphanNodes(CG, CurSCC, VisitedNodes);
+ Changed |= RunAllPassesOnOrphanNodes(CG, CurSCC, CGI);
Changed |= doFinalization(CG);
return Changed;
}
bool CGPassManager::RunAllPassesOnOrphanNodes(
CallGraph &CG, CallGraphSCC &CurSCC,
- std::vector<const CallGraphNode *> &VisitedNodes) {
+ const scc_iterator<CallGraph *> &VisitedCGI) {
bool Changed = false;
+ SmallPtrSet<CallGraphNode *, 16> ProcessedOrphans;
- // Collect orphan roots up front so Module iteration cannot be disturbed
- // by IR-level mutation a pass may perform on an orphan.
- std::vector<CallGraphNode *> OrphanRoots;
- for (Function &F : CG.getModule()) {
- if (!F.isDeclaration()) {
- CallGraphNode *Node = CG[&F];
- if (!is_contained(VisitedNodes, Node))
- OrphanRoots.push_back(Node);
- }
- }
+ auto AllFunctionNodes = map_range(
+ make_filter_range(CG.getModule(),
+ [](Function &F) { return !F.isDeclaration(); }),
+ [&](Function &F) { return CG[&F]; });
- for (CallGraphNode *Root : OrphanRoots) {
- if (!is_contained(VisitedNodes, Root)) {
- for (auto SCCI = scc_begin(Root); !SCCI.isAtEnd(); ++SCCI) {
+ for (CallGraphNode *const OrphanRoot :
+ VisitedCGI.getUnvisitedNodes(AllFunctionNodes)) {
+ if (!ProcessedOrphans.contains(OrphanRoot)) {
+ for (auto SCCI = scc_begin(OrphanRoot); !SCCI.isAtEnd(); ++SCCI) {
const std::vector<CallGraphNode *> &Members = *SCCI;
if (!any_of(Members, [&](CallGraphNode *M) {
- return is_contained(VisitedNodes, M);
+ return ProcessedOrphans.contains(M);
})) {
- // Skip any orphaned node that is not in another root and hasn't been
- // called by another orphaned node yet.
- // Create a CallGraphSCC for them.
- // Ignore devirtualization since it really doesn't matter here.
CurSCC.initialize(Members);
bool UnusedDevirtualizedCall = false;
Changed |= RunAllPassesOnSCC(CurSCC, CG, UnusedDevirtualizedCall);
- VisitedNodes.insert(VisitedNodes.end(), Members.begin(),
- Members.end());
+ ProcessedOrphans.insert(Members.begin(), Members.end());
}
}
}
@@ -607,9 +589,10 @@ bool CGPassManager::doInitialization(CallGraph &CG) {
if (PMDataManager *PM = getContainedPass(i)->getAsPMDataManager()) {
assert(PM->getPassManagerType() == PMT_FunctionPassManager &&
"Invalid CGPassManager member");
- Changed |= ((FPPassManager*)PM)->doInitialization(CG.getModule());
+ Changed |= ((FPPassManager *)PM)->doInitialization(CG.getModule());
} else {
- Changed |= ((CallGraphSCCPass*)getContainedPass(i))->doInitialization(CG);
+ Changed |=
+ ((CallGraphSCCPass *)getContainedPass(i))->doInitialization(CG);
}
}
return Changed;
@@ -622,9 +605,9 @@ bool CGPassManager::doFinalization(CallGraph &CG) {
if (PMDataManager *PM = getContainedPass(i)->getAsPMDataManager()) {
assert(PM->getPassManagerType() == PMT_FunctionPassManager &&
"Invalid CGPassManager member");
- Changed |= ((FPPassManager*)PM)->doFinalization(CG.getModule());
+ Changed |= ((FPPassManager *)PM)->doFinalization(CG.getModule());
} else {
- Changed |= ((CallGraphSCCPass*)getContainedPass(i))->doFinalization(CG);
+ Changed |= ((CallGraphSCCPass *)getContainedPass(i))->doFinalization(CG);
}
}
return Changed;
@@ -638,9 +621,10 @@ bool CGPassManager::doFinalization(CallGraph &CG) {
/// Old node has been deleted, and New is to be used in its place.
void CallGraphSCC::ReplaceNode(CallGraphNode *Old, CallGraphNode *New) {
assert(Old != New && "Should not replace node with self");
- for (unsigned i = 0; ; ++i) {
+ for (unsigned i = 0;; ++i) {
assert(i != Nodes.size() && "Node not in SCC");
- if (Nodes[i] != Old) continue;
+ if (Nodes[i] != Old)
+ continue;
if (New)
Nodes[i] = New;
else
@@ -650,7 +634,7 @@ void CallGraphSCC::ReplaceNode(CallGraphNode *Old, CallGraphNode *New) {
// Update the active scc_iterator so that it doesn't contain dangling
// pointers to the old CallGraphNode.
- scc_iterator<CallGraph*> *CGI = (scc_iterator<CallGraph*>*)Context;
+ scc_iterator<CallGraph *> *CGI = (scc_iterator<CallGraph *> *)Context;
CGI->ReplaceNode(Old, New);
}
@@ -674,7 +658,7 @@ void CallGraphSCCPass::assignPassManager(PMStack &PMS,
CGPassManager *CGP;
if (PMS.top()->getPassManagerType() == PMT_CallGraphPassManager)
- CGP = (CGPassManager*)PMS.top();
+ CGP = (CGPassManager *)PMS.top();
else {
// Create new Call Graph SCC Pass Manager if it does not exist.
assert(!PMS.empty() && "Unable to create Call Graph Pass Manager");
@@ -713,63 +697,63 @@ void CallGraphSCCPass::getAnalysisUsage(AnalysisUsage &AU) const {
namespace {
- /// PrintCallGraphPass - Print a Module corresponding to a call graph.
- ///
- class PrintCallGraphPass : public CallGraphSCCPass {
- std::string Banner;
- raw_ostream &OS; // raw_ostream to print on.
+/// PrintCallGraphPass - Print a Module corresponding to a call graph.
+///
+class PrintCallGraphPass : public CallGraphSCCPass {
+ std::string Banner;
+ raw_ostream &OS; // raw_ostream to print on.
- public:
- static char ID;
+public:
+ static char ID;
- PrintCallGraphPass(const std::string &B, raw_ostream &OS)
+ PrintCallGraphPass(const std::string &B, raw_ostream &OS)
: CallGraphSCCPass(ID), Banner(B), OS(OS) {}
- void getAnalysisUsage(AnalysisUsage &AU) const override {
- AU.setPreservesAll();
- }
+ void getAnalysisUsage(AnalysisUsage &AU) const override {
+ AU.setPreservesAll();
+ }
- bool runOnSCC(CallGraphSCC &SCC) override {
- bool BannerPrinted = false;
- auto PrintBannerOnce = [&]() {
- if (BannerPrinted)
- return;
- OS << Banner;
- BannerPrinted = true;
- };
-
- bool NeedModule = llvm::forcePrintModuleIR();
- if (isFunctionInPrintList("*") && NeedModule) {
- PrintBannerOnce();
- OS << "\n";
- SCC.getCallGraph().getModule().print(OS, nullptr);
- return false;
- }
- bool FoundFunction = false;
- for (CallGraphNode *CGN : SCC) {
- if (Function *F = CGN->getFunction()) {
- if (!F->isDeclaration() && isFunctionInPrintList(F->getName())) {
- FoundFunction = true;
- if (!NeedModule) {
- PrintBannerOnce();
- F->print(OS);
- }
+ bool runOnSCC(CallGraphSCC &SCC) override {
+ bool BannerPrinted = false;
+ auto PrintBannerOnce = [&]() {
+ if (BannerPrinted)
+ return;
+ OS << Banner;
+ BannerPrinted = true;
+ };
+
+ bool NeedModule = llvm::forcePrintModuleIR();
+ if (isFunctionInPrintList("*") && NeedModule) {
+ PrintBannerOnce();
+ OS << "\n";
+ SCC.getCallGraph().getModule().print(OS, nullptr);
+ return false;
+ }
+ bool FoundFunction = false;
+ for (CallGraphNode *CGN : SCC) {
+ if (Function *F = CGN->getFunction()) {
+ if (!F->isDeclaration() && isFunctionInPrintList(F->getName())) {
+ FoundFunction = true;
+ if (!NeedModule) {
+ PrintBannerOnce();
+ F->print(OS);
}
- } else if (isFunctionInPrintList("*")) {
- PrintBannerOnce();
- OS << "\nPrinting <null> Function\n";
}
- }
- if (NeedModule && FoundFunction) {
+ } else if (isFunctionInPrintList("*")) {
PrintBannerOnce();
- OS << "\n";
- SCC.getCallGraph().getModule().print(OS, nullptr);
+ OS << "\nPrinting <null> Function\n";
}
- return false;
}
+ if (NeedModule && FoundFunction) {
+ PrintBannerOnce();
+ OS << "\n";
+ SCC.getCallGraph().getModule().print(OS, nullptr);
+ }
+ return false;
+ }
- StringRef getPassName() const override { return "Print CallGraph IR"; }
- };
+ StringRef getPassName() const override { return "Print CallGraph IR"; }
+};
} // end anonymous namespace.
>From 5a88427ca2aa51c9040ac4db86de55f75b172449 Mon Sep 17 00:00:00 2001
From: Sjoerd Nijboer <github at sjoerdnijboer.com>
Date: Sat, 2 May 2026 23:47:21 +0200
Subject: [PATCH 4/4] [Analysis] Unify SCC iteration in CGPassManager with
AllSCCIterator
Refactor CGPassManager::runOnModule to use a single AllSCCIterator that
lazily chains SCCs reachable from external callers with orphan SCCs
(internal functions not reachable from ExternalCallingNode).
This replaces the previous two-loop approach where orphan functions were
handled separately in RunAllPassesOnOrphanNodes. The new design:
- Introduces AllSCCIterator, a local helper that first yields SCCs from
the main scc_iterator<CallGraph*>, then scans module functions to find
orphan roots and yields their SCCs via scc_iterator<CallGraphNode*>
- Applies the devirtualization retry loop uniformly to both reachable
and orphan SCCs
- Uses scc_iterator::getUnvisitedNodes() to identify orphan roots
NFC for modules without orphan internal functions. For modules with
orphan functions, this ensures they receive the same iterative
devirtualization treatment as reachable functions.
---
llvm/lib/Analysis/CallGraphSCCPass.cpp | 142 ++++++++++++++++---------
1 file changed, 89 insertions(+), 53 deletions(-)
diff --git a/llvm/lib/Analysis/CallGraphSCCPass.cpp b/llvm/lib/Analysis/CallGraphSCCPass.cpp
index 2e83abef4bf04..74b7b193b452e 100644
--- a/llvm/lib/Analysis/CallGraphSCCPass.cpp
+++ b/llvm/lib/Analysis/CallGraphSCCPass.cpp
@@ -37,6 +37,7 @@
#include "llvm/Support/Timer.h"
#include "llvm/Support/raw_ostream.h"
#include <cassert>
+#include <optional>
#include <string>
#include <utility>
#include <vector>
@@ -114,8 +115,6 @@ class CGPassManager : public ModulePass, public PMDataManager {
bool &CallGraphUpToDate, bool &DevirtualizedCall);
bool RefreshCallGraph(const CallGraphSCC &CurSCC, CallGraph &CG,
bool IsCheckingMode);
- bool RunAllPassesOnOrphanNodes(CallGraph &CG, CallGraphSCC &CurSCC,
- const scc_iterator<CallGraph *> &VisitedCGI);
};
} // end anonymous namespace.
@@ -499,6 +498,71 @@ bool CGPassManager::RunAllPassesOnSCC(CallGraphSCC &CurSCC, CallGraph &CG,
return Changed;
}
+namespace {
+
+/// Iterator that lazily chains main SCCs with orphan SCCs.
+class AllSCCIterator {
+ CallGraph &CG;
+ scc_iterator<CallGraph *> CGSCCs;
+ Module::iterator OrphanScanPos;
+ std::optional<scc_iterator<CallGraphNode *>> OrphanSCCs;
+ SmallPtrSet<CallGraphNode *, 16> VisitedOrphans;
+
+ bool isUnvisited(CallGraphNode *N) const {
+ return !VisitedOrphans.contains(N) &&
+ !CGSCCs.getUnvisitedNodes(ArrayRef(N)).empty();
+ }
+
+ void findNextOrphanSCC() {
+ while (OrphanScanPos != CG.getModule().end()) {
+ Function &F = *OrphanScanPos++;
+ if (F.isDeclaration())
+ continue;
+ CallGraphNode *Node = CG[&F];
+ if (!isUnvisited(Node))
+ continue;
+ OrphanSCCs.emplace(scc_begin(Node));
+ return;
+ }
+ OrphanSCCs.reset();
+ }
+
+public:
+ AllSCCIterator(CallGraph &CG)
+ : CG(CG), CGSCCs(scc_begin(&CG)), OrphanScanPos(CG.getModule().begin()) {}
+
+ bool isAtEnd() const {
+ if (!CGSCCs.isAtEnd())
+ return false;
+ return !OrphanSCCs || OrphanSCCs->isAtEnd();
+ }
+
+ const std::vector<CallGraphNode *> &operator*() const {
+ if (!CGSCCs.isAtEnd())
+ return *CGSCCs;
+ return **OrphanSCCs;
+ }
+
+ AllSCCIterator &operator++() {
+ if (!CGSCCs.isAtEnd()) {
+ ++CGSCCs;
+ if (CGSCCs.isAtEnd())
+ findNextOrphanSCC();
+ } else {
+ for (CallGraphNode *N : **OrphanSCCs)
+ VisitedOrphans.insert(N);
+ ++(*OrphanSCCs);
+ if (OrphanSCCs->isAtEnd())
+ findNextOrphanSCC();
+ }
+ return *this;
+ }
+
+ scc_iterator<CallGraph *> &getCGSCCIterator() { return CGSCCs; }
+};
+
+} // anonymous namespace
+
/// Execute all of the passes scheduled for execution. Keep track of
/// whether any of the passes modifies the module, and if so, return true.
bool CGPassManager::runOnModule(Module &M) {
@@ -506,29 +570,32 @@ bool CGPassManager::runOnModule(Module &M) {
bool Changed = doInitialization(CG);
// Walk the callgraph in bottom-up SCC order.
- scc_iterator<CallGraph *> CGI = scc_begin(&CG);
-
- CallGraphSCC CurSCC(CG, &CGI);
-
- while (!CGI.isAtEnd()) {
+ AllSCCIterator SCCI(CG);
+ CallGraphSCC CurSCC(CG, &SCCI.getCGSCCIterator());
+
+ // At the top level, we run all the passes in this pass manager on the
+ // functions in this SCC. However, we support iterative compilation in the
+ // case where a function pass devirtualizes a call to a function. For
+ // example, it is very common for a function pass (often GVN or instcombine)
+ // to eliminate the addressing that feeds into a call. With that improved
+ // information, we would like the call to be an inline candidate, infer
+ // mod-ref information etc.
+ //
+ // we walk the callgraph with a custom iterator that first retruns all
+ // non-orphaned nodes and later finds the orphaned nodes and returns those. We
+ // do this because If those orphaned nodes do not get a function generated for
+ // them, we get a miscompilation and a crash later on.
+ //
+ // Because of this, we allow iteration up to a specified iteration count.
+ // This only happens in the case of a devirtualized call, so we only burn
+ // compile time in the case that we're making progress. We also have a hard
+ // iteration count limit in case there is crazy code.
+ while (!SCCI.isAtEnd()) {
// Copy the current SCC and increment past it so that the pass can hack
// on the SCC if it wants to without invalidating our iterator.
- const std::vector<CallGraphNode *> &NodeVec = *CGI;
+ const std::vector<CallGraphNode *> &NodeVec = *SCCI;
CurSCC.initialize(NodeVec);
- ++CGI;
-
- // At the top level, we run all the passes in this pass manager on the
- // functions in this SCC. However, we support iterative compilation in the
- // case where a function pass devirtualizes a call to a function. For
- // example, it is very common for a function pass (often GVN or instcombine)
- // to eliminate the addressing that feeds into a call. With that improved
- // information, we would like the call to be an inline candidate, infer
- // mod-ref information etc.
- //
- // Because of this, we allow iteration up to a specified iteration count.
- // This only happens in the case of a devirtualized call, so we only burn
- // compile time in the case that we're making progress. We also have a hard
- // iteration count limit in case there is crazy code.
+ ++SCCI;
unsigned Iteration = 0;
bool DevirtualizedCall = false;
do {
@@ -547,41 +614,10 @@ bool CGPassManager::runOnModule(Module &M) {
MaxSCCIterations.updateMax(Iteration);
}
- Changed |= RunAllPassesOnOrphanNodes(CG, CurSCC, CGI);
Changed |= doFinalization(CG);
return Changed;
}
-bool CGPassManager::RunAllPassesOnOrphanNodes(
- CallGraph &CG, CallGraphSCC &CurSCC,
- const scc_iterator<CallGraph *> &VisitedCGI) {
- bool Changed = false;
- SmallPtrSet<CallGraphNode *, 16> ProcessedOrphans;
-
- auto AllFunctionNodes = map_range(
- make_filter_range(CG.getModule(),
- [](Function &F) { return !F.isDeclaration(); }),
- [&](Function &F) { return CG[&F]; });
-
- for (CallGraphNode *const OrphanRoot :
- VisitedCGI.getUnvisitedNodes(AllFunctionNodes)) {
- if (!ProcessedOrphans.contains(OrphanRoot)) {
- for (auto SCCI = scc_begin(OrphanRoot); !SCCI.isAtEnd(); ++SCCI) {
- const std::vector<CallGraphNode *> &Members = *SCCI;
- if (!any_of(Members, [&](CallGraphNode *M) {
- return ProcessedOrphans.contains(M);
- })) {
- CurSCC.initialize(Members);
- bool UnusedDevirtualizedCall = false;
- Changed |= RunAllPassesOnSCC(CurSCC, CG, UnusedDevirtualizedCall);
- ProcessedOrphans.insert(Members.begin(), Members.end());
- }
- }
- }
- }
- return Changed;
-}
-
/// Initialize CG
bool CGPassManager::doInitialization(CallGraph &CG) {
bool Changed = false;
More information about the llvm-commits
mailing list