LLVM 22.x tile for building compilers, language runtimes, and out-of-tree tooling
88
83%
Does it follow best practices?
Impact
96%
1.23xAverage score across 5 eval scenarios
Passed
No known issues
Reference: WritingAnLLVMNewPMPass | NewPassManager
In LLVM 22, the legacy
PassManagertypes still exist for compatibility (e.g.TargetMachine::addPassesToEmitFile), but all new passes must use NPM. Never build new work onFunctionPass/ModulePass/getAnalysis<>().
PassManager<Unit> — ordered list of passes over IR unit
AnalysisManager<Unit> — lazy cache of analysis results for IR unit
PassInstrumentation — hooks for debugging / profiling passes
PassBuilder — constructs standard pipelines from stringsFour IR units, four manager pairs:
| IR Unit | PassManager | AnalysisManager | Alias |
|---|---|---|---|
Module | ModulePassManager | ModuleAnalysisManager | MAM |
Function | FunctionPassManager | FunctionAnalysisManager | FAM |
Loop | LoopPassManager | LoopAnalysisManager | LAM |
LazyCallGraph::SCC | CGSCCPassManager | CGSCCAnalysisManager | CGAM |
Passes at a finer granularity nest inside coarser ones via adaptors:
createModuleToFunctionPassAdaptor(FPM) — runs FPM over every Function in a ModulecreateFunctionToLoopPassAdaptor(LPM) — runs LPM over every Loop in a FunctioncreateModuleToPostOrderCGSCCPassAdaptor(CGPM) — runs CGPM over call graph SCCsHeader:
#include "llvm/IR/PassManager.h"
namespace llvm {
class MyTransformPass : public PassInfoMixin<MyTransformPass> {
public:
PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM);
static bool isRequired() { return false; } // skip on optnone
};
} // namespace llvmImplementation:
PreservedAnalyses MyTransformPass::run(Function &F,
FunctionAnalysisManager &FAM) {
bool Changed = false;
// ... modify F ...
if (!Changed) return PreservedAnalyses::all();
PreservedAnalyses PA;
PA.preserveSet<CFGAnalyses>(); // CFG unchanged
return PA;
}// Result struct — what the analysis computes
struct MyAnalysisResult {
unsigned InstructionCount;
};
class MyAnalysis : public AnalysisInfoMixin<MyAnalysis> {
friend AnalysisInfoMixin<MyAnalysis>;
static AnalysisKey Key; // defined in .cpp: AnalysisKey MyAnalysis::Key;
public:
using Result = MyAnalysisResult;
Result run(Function &F, FunctionAnalysisManager &);
};// .cpp
AnalysisKey MyAnalysis::Key;
MyAnalysis::Result MyAnalysis::run(Function &F, FunctionAnalysisManager &) {
unsigned Count = 0;
for (auto &BB : F)
Count += BB.size();
return {Count};
}Consuming an analysis in a transform pass:
auto &Result = FAM.getResult<MyAnalysis>(F);
// Result.InstructionCount is now available// Nothing modified
return PreservedAnalyses::all();
// Everything may have changed
return PreservedAnalyses::none();
// Only CFG structure preserved (dominators, loop info still valid)
PreservedAnalyses PA;
PA.preserveSet<CFGAnalyses>();
return PA;
// Specific analyses preserved
PA.preserve<DominatorTreeAnalysis>();
PA.preserve<LoopAnalysis>();
return PA;Rule: Return the most conservative (most preserving) accurate answer. Over-preserving causes stale analysis results; under-preserving causes unnecessary recomputation.
// Get a result — compute if not cached
auto &DT = FAM.getResult<DominatorTreeAnalysis>(F);
// Get a result only if already cached (returns nullptr if not)
auto *DTPtr = FAM.getCachedResult<DominatorTreeAnalysis>(F);
// Invalidate a function's analyses after modification
FAM.invalidate(F, PA);
// Register a custom analysis (outside of standard registration)
FAM.registerPass([&] { return MyAnalysis(); });#include "llvm/Passes/PassBuilder.h"
PassBuilder PB;
// Create and cross-register all four analysis managers
LoopAnalysisManager LAM;
FunctionAnalysisManager FAM;
CGSCCAnalysisManager CGAM;
ModuleAnalysisManager MAM;
PB.registerModuleAnalyses(MAM);
PB.registerCGSCCAnalyses(CGAM);
PB.registerFunctionAnalyses(FAM);
PB.registerLoopAnalyses(LAM);
PB.crossRegisterProxies(LAM, FAM, CGAM, MAM);
// Pre-built pipelines
ModulePassManager MPM;
MPM = PB.buildPerModuleDefaultPipeline(OptimizationLevel::O0); // -O0
MPM = PB.buildPerModuleDefaultPipeline(OptimizationLevel::O2); // -O2
MPM = PB.buildPerModuleDefaultPipeline(OptimizationLevel::O3); // -O3
MPM = PB.buildPerModuleDefaultPipeline(OptimizationLevel::Os); // -Os
MPM = PB.buildPerModuleDefaultPipeline(OptimizationLevel::Oz); // -Oz
// Parse a pipeline string (same as opt -passes=...)
auto MaybeErr = PB.parsePassPipeline(MPM, "function(instcombine,simplifycfg)");
if (MaybeErr) { /* handle llvm::Error */ }
// Run the pipeline
MPM.run(*M, MAM);module(pass1,pass2) — module-level passes
function(pass1,pass2) — run over all functions
cgscc(pass1) — run over call graph SCCs
loop(pass1) — run over loops
instcombine — instruction combining
simplifycfg — CFG simplification
mem2reg — promote allocas to SSA
gvn — global value numbering
sroa — scalar replacement of aggregates
inline — function inlining
loop-unroll<> — loop unrolling
loop-vectorize — auto-vectorization
early-cse — early CSE
dce — dead code eliminationVerify available pass names:
opt --print-passes 2>&1 | less// Register callback — called when PassBuilder builds O2+ pipelines
PB.registerPipelineEarlySimplificationEPCallback(
[](ModulePassManager &MPM, OptimizationLevel Level) {
if (Level != OptimizationLevel::O0)
MPM.addPass(MyModulePass());
});
// Other extension points:
// registerScalarOptimizerLateEPCallback — after scalar opts
// registerVectorizerStartEPCallback — before vectorizer
// registerOptimizerLastEPCallback — very end of pipeline
// registerPipelineParsingCallback — for custom pipeline string names// Function-level pipeline
FunctionPassManager FPM;
FPM.addPass(InstCombinePass());
FPM.addPass(SimplifyCFGPass());
FPM.addPass(MyTransformPass());
// Wrap in module pipeline
ModulePassManager MPM;
MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));
MPM.run(*M, MAM);Loop passes receive additional context vs function passes:
class MyLoopPass : public PassInfoMixin<MyLoopPass> {
public:
PreservedAnalyses run(Loop &L, LoopAnalysisManager &LAM,
LoopStandardAnalysisResults &AR, LPMUpdater &U);
};LoopStandardAnalysisResults provides cheap access to:
AR.DT // DominatorTree
AR.LI // LoopInfo
AR.SE // ScalarEvolution
AR.AC // AssumptionCache
AR.TLI // TargetLibraryInfoSignal loop deletion to the updater:
U.markLoopAsDeleted(L, L.getName());
return PreservedAnalyses::all(); // or appropriate PAclass MyCGSCCPass : public PassInfoMixin<MyCGSCCPass> {
public:
PreservedAnalyses run(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM,
LazyCallGraph &CG, CGSCCUpdateResult &UR);
};Useful for interprocedural transforms like inlining, argument promotion.
opt (in-tree)In llvm/lib/Passes/PassRegistry.def:
FUNCTION_PASS("my-pass", MyTransformPass())
MODULE_PASS("my-module-pass", MyModulePass())
LOOP_PASS("my-loop-pass", MyLoopPass())
CGSCC_PASS("my-cgscc-pass", MyCGSCCPass())
FUNCTION_ANALYSIS("my-analysis", MyAnalysis())Add the header include to llvm/lib/Passes/PassBuilder.cpp.
For out-of-tree plugins, see the add-npm-pass skill.
// Print IR before/after each pass (like -print-after-all)
PB.registerAfterPassCallback([](StringRef PassName, Any IR,
const PreservedAnalyses &PA) {
errs() << "After " << PassName << ":\n";
});
// Verify IR after every pass (expensive — debug builds only)
// Add VerifierPass to the pipeline:
FPM.addPass(VerifierPass());PM.add(createLegacyPass()) — legacy PM is gone.FAM.getResult<>().crossRegisterProxies — without it, analyses can't cross IR-unit boundaries (e.g., a function pass can't query module analyses).MPM.run() before crossRegisterProxies().PreservedAnalyses::all() for no-op passes — returning none() forces expensive re-computation.docs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
skills
add-alias-analysis
add-attributes-metadata
add-calling-convention
add-debug-info
add-exception-handling
add-gc-statepoints
add-intrinsic
add-lto
add-sanitizer
add-vectorization-hint
frontend-to-ir
jit-setup
lit-filecheck
lower-struct-types
new-target
out-of-tree-setup
tessl-llvm
version-sync