LegacyPass与NewPass的核心差异在于:前者是全局单例、函数指针注册、runOnFunction接口;后者是按阶段注册的类对象、需继承PassInfoMixin、接口改为返回PreservedAnalyses的run函数,且分析依赖由AnalysisManager自动推导。

旧版 LegacyPassManager 无法直接复用,必须重写注册逻辑和 Pass 类型声明,否则 clang -fpass-plugin 加载时会静默失败或崩溃。
LegacyPass 和 NewPass 的核心差异在哪
LegacyPass 是全局单例、基于 PassManagerBuilder::addExtension 注册的函数指针机制,所有 Pass 共享一个 PassManager 实例;NewPassManager(即 PassBuilder)是按阶段(EP_EarlyAsPossible、EP_CGSCCOptimizerLate 等)注册回调的类对象,每个阶段可绑定多个独立 Pass 实例,且 Pass 必须继承 PassInfoMixin<MyPass> 并实现 run 成员函数。
最直观的破坏性变化是:Legacy 中的 virtual bool runOnFunction(Function &F) 在 NewPass 中已不存在,取而代之的是 PreservedAnalyses run(Function &F, FunctionAnalysisManager &) —— 返回值语义完全不同,分析结果需显式声明保留项。
- LegacyPass 编译后可被
opt -load=lib.so -mypass直接调用;NewPass 只能通过-fpass-plugin=lib.so+-passes=...或插件内注册回调触发 - LegacyPass 的
initializePass()和getAnalysisUsage()在 NewPass 中全部移除,依赖AnalysisManager自动推导 - Legacy 的模块级 Pass(
ModulePass)在 NewPass 中对应ModulePass类型,但签名变为PreservedAnalyses run(Module &M, ModuleAnalysisManager &),且不能混用FunctionPass的分析器
llvmGetPassPluginInfo 回调里怎么注册你的 Pass
必须在 llvmGetPassPluginInfo 返回的回调中,对 PassBuilder &PB 调用 registerPipelineParsingCallback 或 registerVectorizerStartEPCallback 等扩展点(EP)注册函数。不能只写 PB.registerPipelineParsingCallback(...) 就完事——漏掉 EP 类型会导致 Pass 完全不被执行。
常见错误是把注册写成:
[](PassBuilder &PB) {
PB.registerPipelineParsingCallback(
[](StringRef Name, FunctionPassManager &FPM,
ArrayRef<PassBuilder::PipelineElement> PEs) -> bool {
if (Name == "my-pass") {
FPM.addPass(MyFunctionPass());
return true;
}
return false;
});
}
这只能让 opt -passes=my-pass 生效,但 clang -O2 -fpass-plugin=... 不会触发它,因为 clang 默认走的是 EP_OptimizerLast 这类 EP,而非 pipeline parser。
正确做法是双注册:
- 用
registerPipelineParsingCallback支持手动-passes=调试 - 用
registerOptimizerLastEPCallback确保-O2下自动插入(推荐位置) - 若 Pass 需访问模块级分析(如
CallGraphAnalysis),改用registerCGSCCOptimizerLateEPCallback
从 runOnFunction 到 run 的参数和返回值怎么改
旧函数签名:bool MyPass::runOnFunction(Function &F);新函数签名:PreservedAnalyses MyPass::run(Function &F, FunctionAnalysisManager &AM)。关键不是“改个名字”,而是语义重构:
- 返回
PreservedAnalyses::all()表示你没动任何分析结果,但实际很可能改了LoopInfo或DominatorTree,应返回PreservedAnalyses::none()或精准列出保留项(如PA.preserveSet<AllAnalysesOn<function>>()</function>) - 若需要访问
DominatorTree,旧版用getAnalysis<DominatorTreeWrapperPass>().getDomTree();新版改用AM.getResult<DominatorTreeAnalysis>(F) - 旧版中
getAnalysisUsage(AU)声明依赖,新版完全由getResult调用自动触发,无需手动声明 - 若 Pass 内部缓存了
Function指针或LLVMContext*,注意 NewPass 的run是每次调用都新建实例,不能跨调用复用状态
容易被忽略的 ABI 和版本陷阱
LLVM 插件是 C++ ABI 敏感的,llvmGetPassPluginInfo 的 APIVersion 字段必须与构建插件时链接的 LLVM 头文件版本严格一致。比如用 LLVM 15 头文件编译,但运行时加载的是 LLVM 14 的 clang,就会触发 LLVM ERROR: Plugin API version mismatch 并退出进程。
另一个隐形坑是 PassBuilder 构造时机:它在 Clang 的 CodeGenAction 初始化阶段才创建,因此插件中不能在全局作用域访问 PassBuilder 对象,所有注册逻辑必须收束在回调函数内。曾有人把 static PassBuilder *GlobalPB 写在插件里,导致多线程下 clang -j4 编译时随机 crash。
最后提醒:NewPassManager 的调试输出默认关闭,加 -mllvm -debug-pass=Structure 才能看到 Pass 插入位置;而 LegacyPass 用 -debug-pass=Details 即可——别指望同一套调试开关通用。

















