[llvm] [CodeGen] Support --print-changed for legacy codegen IR passes (PR #202252)
Fangrui Song via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 9 22:18:10 PDT 2026
https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/202252
>From 039b1a328694d7842076e271224ad76ee6fb9bf4 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sun, 7 Jun 2026 19:33:51 -0700
Subject: [PATCH 1/2] [CodeGen] Support --print-changed for legacy codegen IR
passes
--print-changed is only wired into MachineFunctionPass (
https://reviews.llvm.org/D133055), so the IR-level passes in the codegen
pipeline (atomic-expand, codegenprepare, etc.) are not reported.
Report them from FPPassManager/MPPassManager instead, via a new
Pass::printIRUnit hook that MachineFunctionPass overrides to print MIR.
Analyses are skipped, matching the new pass manager.
Aided by Claude Opus 4.8
---
.../llvm/CodeGen/MachineFunctionPass.h | 2 +
llvm/include/llvm/IR/PrintPasses.h | 7 ++
llvm/include/llvm/Pass.h | 5 ++
llvm/lib/CodeGen/MachineFunctionPass.cpp | 72 +++----------------
llvm/lib/IR/LegacyPassManager.cpp | 67 ++++++++++++++++-
llvm/lib/IR/Pass.cpp | 5 ++
llvm/lib/IR/PrintPasses.cpp | 50 +++++++++++++
llvm/test/Other/print-changed-machine.ll | 34 ++++++++-
8 files changed, 177 insertions(+), 65 deletions(-)
diff --git a/llvm/include/llvm/CodeGen/MachineFunctionPass.h b/llvm/include/llvm/CodeGen/MachineFunctionPass.h
index 9d7afef5b79af..55124b717c69e 100644
--- a/llvm/include/llvm/CodeGen/MachineFunctionPass.h
+++ b/llvm/include/llvm/CodeGen/MachineFunctionPass.h
@@ -74,6 +74,8 @@ class LLVM_ABI MachineFunctionPass : public FunctionPass {
const std::string &Banner) const override;
bool runOnFunction(Function &F) override;
+
+ bool printIRUnit(raw_ostream &OS, Function &F) override;
};
} // End llvm namespace
diff --git a/llvm/include/llvm/IR/PrintPasses.h b/llvm/include/llvm/IR/PrintPasses.h
index 0aa1b379c35cf..6c724bf76fe6c 100644
--- a/llvm/include/llvm/IR/PrintPasses.h
+++ b/llvm/include/llvm/IR/PrintPasses.h
@@ -81,6 +81,13 @@ std::string doSystemDiff(StringRef Before, StringRef After,
StringRef OldLineFormat, StringRef NewLineFormat,
StringRef UnchangedLineFormat);
+// Report a -print-changed diff for one pass over one IR unit (function or
+// module). IsInteresting is isPassInPrintList(PassID); ShouldReport is whether
+// the unit passed all filters (Before/After are only set then).
+void reportChangedIR(StringRef Before, StringRef After, StringRef PassName,
+ StringRef PassID, StringRef IRName, bool IsInteresting,
+ bool ShouldReport);
+
} // namespace llvm
#endif // LLVM_IR_PRINTPASSES_H
diff --git a/llvm/include/llvm/Pass.h b/llvm/include/llvm/Pass.h
index d4991b6a2212c..b97139b39d882 100644
--- a/llvm/include/llvm/Pass.h
+++ b/llvm/include/llvm/Pass.h
@@ -323,6 +323,11 @@ class LLVM_ABI FunctionPass : public Pass {
/// per-function processing of the pass.
virtual bool runOnFunction(Function &F) = 0;
+ /// For --print-changed, serialize the IR unit this pass operates on. The
+ /// default prints \p F; MachineFunctionPass prints its MachineFunction.
+ /// Returns false if there is nothing to report.
+ virtual bool printIRUnit(raw_ostream &OS, Function &F);
+
void assignPassManager(PMStack &PMS, PassManagerType T) override;
/// Return what kind of Pass Manager can manage this pass.
diff --git a/llvm/lib/CodeGen/MachineFunctionPass.cpp b/llvm/lib/CodeGen/MachineFunctionPass.cpp
index 16dd00455a3d4..d32d8d2a5f28d 100644
--- a/llvm/lib/CodeGen/MachineFunctionPass.cpp
+++ b/llvm/lib/CodeGen/MachineFunctionPass.cpp
@@ -32,7 +32,6 @@
#include "llvm/IR/Dominators.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Module.h"
-#include "llvm/IR/PrintPasses.h"
using namespace llvm;
using namespace ore;
@@ -82,23 +81,6 @@ bool MachineFunctionPass::runOnFunction(Function &F) {
if (ShouldEmitSizeRemarks)
CountBefore = MF.getInstructionCount();
- // For --print-changed, if the function name is a candidate, save the
- // serialized MF to be compared later.
- SmallString<0> BeforeStr, AfterStr;
- StringRef PassID;
- if (PrintChanged != ChangePrinter::None) {
- if (const PassInfo *PI = Pass::lookupPassInfo(getPassID()))
- PassID = PI->getPassArgument();
- }
- const bool IsInterestingPass = isPassInPrintList(PassID);
- const bool ShouldPrintChanged = PrintChanged != ChangePrinter::None &&
- IsInterestingPass &&
- isFunctionInPrintList(MF.getName());
- if (ShouldPrintChanged) {
- raw_svector_ostream OS(BeforeStr);
- MF.print(OS);
- }
-
MFProps.reset(ClearedProperties);
bool RV;
@@ -137,54 +119,18 @@ bool MachineFunctionPass::runOnFunction(Function &F) {
MFProps.set(SetProperties);
- // For --print-changed, print if the serialized MF has changed. Modes other
- // than quiet/verbose are unimplemented and treated the same as 'quiet'.
- if (ShouldPrintChanged || !IsInterestingPass) {
- if (ShouldPrintChanged) {
- raw_svector_ostream OS(AfterStr);
- MF.print(OS);
- }
- if (IsInterestingPass && BeforeStr != AfterStr) {
- errs() << ("*** IR Dump After " + getPassName() + " (" + PassID +
- ") on " + MF.getName() + " ***\n");
- switch (PrintChanged) {
- case ChangePrinter::None:
- llvm_unreachable("");
- case ChangePrinter::Quiet:
- case ChangePrinter::Verbose:
- case ChangePrinter::DotCfgQuiet: // unimplemented
- case ChangePrinter::DotCfgVerbose: // unimplemented
- errs() << AfterStr;
- break;
- case ChangePrinter::DiffQuiet:
- case ChangePrinter::DiffVerbose:
- case ChangePrinter::ColourDiffQuiet:
- case ChangePrinter::ColourDiffVerbose: {
- bool Color = llvm::is_contained(
- {ChangePrinter::ColourDiffQuiet, ChangePrinter::ColourDiffVerbose},
- PrintChanged.getValue());
- StringRef Removed = Color ? "\033[31m-%l\033[0m\n" : "-%l\n";
- StringRef Added = Color ? "\033[32m+%l\033[0m\n" : "+%l\n";
- StringRef NoChange = " %l\n";
- errs() << doSystemDiff(BeforeStr, AfterStr, Removed, Added, NoChange);
- break;
- }
- }
- } else if (llvm::is_contained({ChangePrinter::Verbose,
- ChangePrinter::DiffVerbose,
- ChangePrinter::ColourDiffVerbose},
- PrintChanged.getValue())) {
- const char *Reason =
- IsInterestingPass ? " omitted because no change" : " filtered out";
- errs() << "*** IR Dump After " << getPassName();
- if (!PassID.empty())
- errs() << " (" << PassID << ")";
- errs() << " on " << MF.getName() + Reason + " ***\n";
- }
- }
return RV;
}
+bool MachineFunctionPass::printIRUnit(raw_ostream &OS, Function &F) {
+ // available_externally functions are not codegen'd (see runOnFunction).
+ if (F.hasAvailableExternallyLinkage())
+ return false;
+ MachineModuleInfo &MMI = getAnalysis<MachineModuleInfoWrapperPass>().getMMI();
+ MMI.getOrCreateMachineFunction(F).print(OS);
+ return true;
+}
+
void MachineFunctionPass::getAnalysisUsage(AnalysisUsage &AU) const {
AU.addRequired<MachineModuleInfoWrapperPass>();
AU.addPreserved<MachineModuleInfoWrapperPass>();
diff --git a/llvm/lib/IR/LegacyPassManager.cpp b/llvm/lib/IR/LegacyPassManager.cpp
index 641c49a213b80..bbe75ab275092 100644
--- a/llvm/lib/IR/LegacyPassManager.cpp
+++ b/llvm/lib/IR/LegacyPassManager.cpp
@@ -12,6 +12,7 @@
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/ADT/MapVector.h"
+#include "llvm/ADT/SmallString.h"
#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/IRPrintingPasses.h"
#include "llvm/IR/LLVMContext.h"
@@ -19,6 +20,7 @@
#include "llvm/IR/Module.h"
#include "llvm/IR/PassTimingInfo.h"
#include "llvm/IR/PrintPasses.h"
+#include "llvm/PassInfo.h"
#include "llvm/Support/Chrono.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Compiler.h"
@@ -1366,7 +1368,7 @@ bool FPPassManager::runOnFunction(Function &F) {
// Store name outside of loop to avoid redundant calls.
const StringRef Name = F.getName();
llvm::TimeTraceScope FunctionScope("OptFunction", Name);
-
+ SmallString<0> BeforeStr, AfterStr;
for (unsigned Index = 0; Index < getNumContainedPasses(); ++Index) {
FunctionPass *FP = getContainedPass(Index);
bool LocalChanged = false;
@@ -1381,6 +1383,30 @@ bool FPPassManager::runOnFunction(Function &F) {
initializeAnalysisImpl(FP);
+ // For --print-changed, capture the IR before the pass. Skip analyses (like
+ // the new pass manager) and passes with no registry info (pass managers and
+ // unregistered infrastructure passes).
+ StringRef PassID;
+ bool ReportChanged = PrintChanged != ChangePrinter::None;
+ bool IsInteresting = false, ShouldPrintChanged = false;
+ if (ReportChanged) {
+ const PassInfo *PI = Pass::lookupPassInfo(FP->getPassID());
+ if (PI && !PI->isAnalysis()) {
+ PassID = PI->getPassArgument();
+ IsInteresting = isPassInPrintList(PassID);
+ ShouldPrintChanged = IsInteresting && isFunctionInPrintList(Name);
+ } else {
+ ReportChanged = false;
+ }
+ }
+ if (ShouldPrintChanged) {
+ BeforeStr.clear();
+ AfterStr.clear();
+ raw_svector_ostream OS(BeforeStr);
+ // printIRUnit returns false if there is nothing to report.
+ ShouldPrintChanged = FP->printIRUnit(OS, F);
+ }
+
{
PassManagerPrettyStackEntry X(FP, F);
TimeRegion PassTimer(getPassTimer(FP));
@@ -1413,6 +1439,14 @@ bool FPPassManager::runOnFunction(Function &F) {
}
}
+ if (ShouldPrintChanged) {
+ raw_svector_ostream OS(AfterStr);
+ FP->printIRUnit(OS, F);
+ }
+ if (ReportChanged)
+ reportChangedIR(BeforeStr, AfterStr, FP->getPassName(), PassID, Name,
+ IsInteresting, ShouldPrintChanged);
+
Changed |= LocalChanged;
if (LocalChanged)
dumpPassInfo(FP, MODIFICATION_MSG, ON_FUNCTION_MSG, Name);
@@ -1485,6 +1519,7 @@ MPPassManager::runOnModule(Module &M) {
if (EmitICRemark)
InstrCount = initSizeRemarkInfo(M, FunctionToInstrCount);
+ SmallString<0> BeforeStr, AfterStr;
for (unsigned Index = 0; Index < getNumContainedPasses(); ++Index) {
ModulePass *MP = getContainedPass(Index);
bool LocalChanged = false;
@@ -1494,6 +1529,27 @@ MPPassManager::runOnModule(Module &M) {
initializeAnalysisImpl(MP);
+ // As in FPPassManager, but for module passes. Not subject to
+ // -filter-print-funcs.
+ StringRef PassID;
+ bool ReportChanged = PrintChanged != ChangePrinter::None;
+ bool IsInteresting = false, ShouldPrintChanged = false;
+ if (ReportChanged) {
+ const PassInfo *PI = Pass::lookupPassInfo(MP->getPassID());
+ if (PI && !PI->isAnalysis()) {
+ PassID = PI->getPassArgument();
+ IsInteresting = ShouldPrintChanged = isPassInPrintList(PassID);
+ } else {
+ ReportChanged = false;
+ }
+ }
+ if (ShouldPrintChanged) {
+ BeforeStr.clear();
+ AfterStr.clear();
+ raw_svector_ostream OS(BeforeStr);
+ M.print(OS, /*AAW=*/nullptr);
+ }
+
{
PassManagerPrettyStackEntry X(MP, M);
TimeRegion PassTimer(getPassTimer(MP));
@@ -1522,6 +1578,15 @@ MPPassManager::runOnModule(Module &M) {
}
}
+ if (ShouldPrintChanged) {
+ raw_svector_ostream OS(AfterStr);
+ M.print(OS, /*AAW=*/nullptr);
+ }
+ if (ReportChanged)
+ reportChangedIR(BeforeStr, AfterStr, MP->getPassName(), PassID,
+ M.getModuleIdentifier(), IsInteresting,
+ ShouldPrintChanged);
+
Changed |= LocalChanged;
if (LocalChanged)
dumpPassInfo(MP, MODIFICATION_MSG, ON_MODULE_MSG,
diff --git a/llvm/lib/IR/Pass.cpp b/llvm/lib/IR/Pass.cpp
index dec7c9a9ab18c..08d58379b7e69 100644
--- a/llvm/lib/IR/Pass.cpp
+++ b/llvm/lib/IR/Pass.cpp
@@ -177,6 +177,11 @@ Pass *FunctionPass::createPrinterPass(raw_ostream &OS,
return createPrintFunctionPass(OS, Banner);
}
+bool FunctionPass::printIRUnit(raw_ostream &OS, Function &F) {
+ F.print(OS);
+ return true;
+}
+
PassManagerType FunctionPass::getPotentialPassManagerType() const {
return PMT_FunctionPassManager;
}
diff --git a/llvm/lib/IR/PrintPasses.cpp b/llvm/lib/IR/PrintPasses.cpp
index 74d97d1fe5835..5c14e3bc2a1e6 100644
--- a/llvm/lib/IR/PrintPasses.cpp
+++ b/llvm/lib/IR/PrintPasses.cpp
@@ -7,12 +7,16 @@
//===----------------------------------------------------------------------===//
#include "llvm/IR/PrintPasses.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/Twine.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Errc.h"
+#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/IOSandbox.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Program.h"
+#include "llvm/Support/raw_ostream.h"
#include <unordered_set>
using namespace llvm;
@@ -254,3 +258,49 @@ std::string llvm::doSystemDiff(StringRef Before, StringRef After,
return Diff;
}
+
+void llvm::reportChangedIR(StringRef Before, StringRef After,
+ StringRef PassName, StringRef PassID,
+ StringRef IRName, bool IsInteresting,
+ bool ShouldReport) {
+ if (!ShouldReport && IsInteresting)
+ return;
+
+ if (IsInteresting && Before != After) {
+ errs() << ("*** IR Dump After " + PassName + " (" + PassID + ") on " +
+ IRName + " ***\n");
+ switch (PrintChanged) {
+ case ChangePrinter::None:
+ llvm_unreachable("");
+ case ChangePrinter::Quiet:
+ case ChangePrinter::Verbose:
+ case ChangePrinter::DotCfgQuiet: // unimplemented
+ case ChangePrinter::DotCfgVerbose: // unimplemented
+ errs() << After;
+ break;
+ case ChangePrinter::DiffQuiet:
+ case ChangePrinter::DiffVerbose:
+ case ChangePrinter::ColourDiffQuiet:
+ case ChangePrinter::ColourDiffVerbose: {
+ bool Color = llvm::is_contained(
+ {ChangePrinter::ColourDiffQuiet, ChangePrinter::ColourDiffVerbose},
+ PrintChanged.getValue());
+ StringRef Removed = Color ? "\033[31m-%l\033[0m\n" : "-%l\n";
+ StringRef Added = Color ? "\033[32m+%l\033[0m\n" : "+%l\n";
+ StringRef NoChange = " %l\n";
+ errs() << doSystemDiff(Before, After, Removed, Added, NoChange);
+ break;
+ }
+ }
+ } else if (llvm::is_contained({ChangePrinter::Verbose,
+ ChangePrinter::DiffVerbose,
+ ChangePrinter::ColourDiffVerbose},
+ PrintChanged.getValue())) {
+ const char *Reason =
+ IsInteresting ? " omitted because no change" : " filtered out";
+ errs() << "*** IR Dump After " << PassName;
+ if (!PassID.empty())
+ errs() << " (" << PassID << ")";
+ errs() << " on " << IRName + Reason + " ***\n";
+ }
+}
diff --git a/llvm/test/Other/print-changed-machine.ll b/llvm/test/Other/print-changed-machine.ll
index 3dea1d7436bc0..494fafaee94e8 100644
--- a/llvm/test/Other/print-changed-machine.ll
+++ b/llvm/test/Other/print-changed-machine.ll
@@ -1,5 +1,8 @@
; REQUIRES: aarch64-registered-target
-; RUN: llc -filetype=null -mtriple=aarch64 -O0 -print-changed %s 2>&1 | FileCheck %s --check-prefixes=VERBOSE,VERBOSE-BAR
+;; --implicit-check-not verifies that analyses passes are not reported.
+; RUN: llc -filetype=null -mtriple=aarch64 -O0 -print-changed %s 2>&1 | \
+; RUN: FileCheck %s --check-prefixes=VERBOSE,VERBOSE-BAR \
+; RUN: --implicit-check-not='(cseinfo)' --implicit-check-not='Free MachineFunction'
; RUN: llc -filetype=null -mtriple=aarch64 -O0 -print-changed -filter-print-funcs=foo %s 2>&1 | FileCheck %s --check-prefixes=VERBOSE,NO-BAR
; VERBOSE: *** IR Dump After IRTranslator (irtranslator) on foo ***
@@ -35,10 +38,29 @@
; QUIET-FILTER: *** IR Dump After IRTranslator (irtranslator) on foo ***
; QUIET-FILTER: *** IR Dump After IRTranslator (irtranslator) on bar ***
+; QUIET-FILTER: *** IR Dump After IRTranslator (irtranslator) on atomic_load ***
+; QUIET-FILTER: *** IR Dump After IRTranslator (irtranslator) on lr ***
;; dot-cfg/dot-cfg-quiet are unimplemented. Currently they behave like 'quiet'.
; RUN: llc -filetype=null -mtriple=aarch64 -O0 -print-changed=dot-cfg %s 2>&1 | FileCheck %s --check-prefix=QUIET
+;; Covers the IR-level FunctionPasses run by the legacy codegen pass manager.
+;; atomic-expand lowers the wide atomic load below before instruction selection.
+; RUN: llc -filetype=null -mtriple=aarch64 -O0 -print-changed=diff-quiet \
+; RUN: -filter-passes=atomic-expand -filter-print-funcs=atomic_load %s 2>&1 | \
+; RUN: FileCheck %s --check-prefix=IR-PASS
+; IR-PASS: *** IR Dump After Expand Atomic instructions (atomic-expand) on atomic_load ***
+; IR-PASS-NEXT: define i128 @atomic_load
+; IR-PASS: - %v = load atomic i128, ptr %p seq_cst, align 16
+
+;; Covers the module passes run by MPPassManager; pre-isel-intrinsic-lowering
+;; lowers the llvm.load.relative call below.
+; RUN: llc -filetype=null -mtriple=aarch64 -O0 -print-changed=diff-quiet \
+; RUN: -filter-passes=pre-isel-intrinsic-lowering %s 2>&1 | \
+; RUN: FileCheck %s --check-prefix=MODULE
+; MODULE: *** IR Dump After Pre-ISel Intrinsic Lowering (pre-isel-intrinsic-lowering) on {{.*}} ***
+; MODULE: - %v = call ptr @llvm.load.relative.i32(ptr %p, i32 %n)
+
@var = global i32 0
define void @foo(i32 %a) {
@@ -54,3 +76,13 @@ entry:
store i32 %b, ptr @var
ret void
}
+
+define i128 @atomic_load(ptr %p) {
+ %v = load atomic i128, ptr %p seq_cst, align 16
+ ret i128 %v
+}
+
+define ptr @lr(ptr %p, i32 %n) {
+ %v = call ptr @llvm.load.relative.i32(ptr %p, i32 %n)
+ ret ptr %v
+}
>From 5238620ca4f1c5056046e82eae3572d0f4b1e04e Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Tue, 9 Jun 2026 22:17:59 -0700
Subject: [PATCH 2/2] avoid diff to support windows
---
llvm/test/Other/print-changed-machine.ll | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/llvm/test/Other/print-changed-machine.ll b/llvm/test/Other/print-changed-machine.ll
index 494fafaee94e8..b30e1f4a8771f 100644
--- a/llvm/test/Other/print-changed-machine.ll
+++ b/llvm/test/Other/print-changed-machine.ll
@@ -46,20 +46,21 @@
;; Covers the IR-level FunctionPasses run by the legacy codegen pass manager.
;; atomic-expand lowers the wide atomic load below before instruction selection.
-; RUN: llc -filetype=null -mtriple=aarch64 -O0 -print-changed=diff-quiet \
+; RUN: llc -filetype=null -mtriple=aarch64 -O0 -print-changed=quiet \
; RUN: -filter-passes=atomic-expand -filter-print-funcs=atomic_load %s 2>&1 | \
; RUN: FileCheck %s --check-prefix=IR-PASS
; IR-PASS: *** IR Dump After Expand Atomic instructions (atomic-expand) on atomic_load ***
-; IR-PASS-NEXT: define i128 @atomic_load
-; IR-PASS: - %v = load atomic i128, ptr %p seq_cst, align 16
+; IR-PASS-NEXT: define i128 @atomic_load(ptr %p) {
+; IR-PASS-NEXT: %1 = cmpxchg ptr %p, i128 0, i128 0 seq_cst seq_cst, align 16
;; Covers the module passes run by MPPassManager; pre-isel-intrinsic-lowering
;; lowers the llvm.load.relative call below.
-; RUN: llc -filetype=null -mtriple=aarch64 -O0 -print-changed=diff-quiet \
+; RUN: llc -filetype=null -mtriple=aarch64 -O0 -print-changed=quiet \
; RUN: -filter-passes=pre-isel-intrinsic-lowering %s 2>&1 | \
; RUN: FileCheck %s --check-prefix=MODULE
; MODULE: *** IR Dump After Pre-ISel Intrinsic Lowering (pre-isel-intrinsic-lowering) on {{.*}} ***
-; MODULE: - %v = call ptr @llvm.load.relative.i32(ptr %p, i32 %n)
+; MODULE: define ptr @lr(ptr %p, i32 %n) {
+; MODULE-NEXT: %1 = getelementptr i8, ptr %p, i32 %n
@var = global i32 0
More information about the llvm-commits
mailing list