Using LLVM's New Pass Manager to Write a Simple Function Pass
Learn how to write and register a simple function pass using LLVM's new Pass Manager, with a complete C++ example, usage notes, and common pitfalls.
29 Jul 2026, 03:39 UTC

Useful answer
The new Pass Manager (introduced in LLVM 13) separates pass registration from execution. This lets you add a custom optimization pass either through a plugin or directly in an LLVM tool, improving modularity and compile‑time performance.
How the new PM works – a worked example
First, define a function pass that inherits from PassInfoMixin. The mixin supplies the boilerplate needed for the new PM to discover and invoke your pass.
#include "llvm/IR/PassManager.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"
struct HelloPass : public llvm::PassInfoMixin<HelloPass> {
// The run method is called for each function.
llvm::PreservedAnalyses run(llvm::Function &F,
llvm::FunctionAnalysisManager &) {
llvm::errs() << "Hello: " << F.getName() << '\n';
// This pass does not modify the IR, so all analyses are preserved.
return llvm::PreservedAnalyses::all();
}
};
Next, register the pass so that the opt tool can find it by name. This is done with a pipeline parsing callback passed to PassBuilder.
#include "llvm/IR/PassBuilder.h"
#include "llvm/IR/Registry.h"
static llvm::PassBuilder::PipelineParsingCallback
registerHelloPass(const llvm::StringRef Name,
llvm::FunctionPassManager &FPM,
llvm::ArrayRef) {
if (Name == "hello") {
FPM.addPass(HelloPass());
return true; // callback handled the name
}
return false; // let other callbacks try
}
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo() {
return {LLVM_PLUGIN_API_VERSION, "HelloPass", "v0.1",
[](llvm::PassBuilder &PB) {
PB.registerPipelineParsingCallback(registerHelloPass);
}};
}
Compile the above as a shared library (e.g., libHelloPass.so) linking against LLVM’s Core, IRReader, and Support libraries.
Using the pass with opt
Once the plugin is built, load it and request the pass by the name you registered:
opt -load-pass-plugin=./libHelloPass.so -passes=hello -S < input.ll > output.ll
The -S flag emits LLVM IR in textual form; you should see lines like Hello: @my_function printed to stderr for each function in the module.
Limits of the new Pass Manager
- The new PM cannot directly use legacy passes; mixing requires an explicit adapter (e.g.,
LegacyPassManagerWrapper). - Some analyses (e.g., certain loop information) are only available in the new PM, so legacy passes may need rewriting to obtain them.
- If a pass modifies the IR but returns
PreservedAnalyses::none()when it should preserve some, downstream optimizations may be incorrectly skipped or applied.
Common mistakes
- Incorrect analysis preservation: Returning
PreservedAnalyses::none()when the pass does not change the IR wastes analysis results and can slow compilation. - Missing registration: Forgetting the
llvmGetPassPluginInfosymbol or the pipeline callback makes the pass invisible tooptandclang. - Wrong return type: The
runmethod must returnllvm::PreservedAnalyses; returningboolorvoidwill not compile with the new PM.
Verification steps (do not claim they were tested)
- Clone LLVM source, build with
-DLLVM_ENABLE_PROJECTS='clang' -DLLVM_TARGETS_TO_BUILD='host'. - Compile the pass as a shared library, linking
-lLLVMCore -lLLVMSupport -lLLVMIRReader. - Run
opt -load-pass-plugin=libHelloPass.so -passes=hello -S < input.lland observeHello:messages on stderr. - Optionally, run the resulting IR through
clang -O2and compare program behavior to ensure the pass did not alter semantics (except for the prints).
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.