Function Pass适合处理单个函数内部逻辑,如插入日志、替换指令、常量传播或控制流平坦化;它天然隔离、并行安全、修改限于当前函数体,且不涉全局状态。

Function Pass 适合改单个函数内部逻辑
当你想对每个函数做独立处理,比如插入日志、替换特定指令、做常量传播或控制流混淆(如控制流平坦化),FunctionPass 是默认选择。它天然隔离:每个函数的 run 调用互不影响,IR 修改只限于当前函数体,不会意外触碰全局变量或跨函数跳转。
常见错误现象包括:在 FunctionPass 里尝试获取其他函数的地址、修改模块级元数据(如 @llvm.used)、或调用 Module::getFunction() 后直接操作——这些操作本身不报错,但后续优化可能因分析失效而跳过你的改动。
- 必须重写
runOnFunction(Function &F)(Legacy PM)或run(Function &F, FunctionAnalysisManager &AM)(New PM) - 不能直接访问
Module中未被当前函数引用的全局符号;若需,得通过F.getParent()拿到模块再查,但要小心生命周期和线程安全 - 性能影响小:LLVM 会并行调度多个
FunctionPass实例,适合做轻量、高频率的 per-function 变换
Module Pass 必须用于跨函数或全局视角操作
如果你的任务涉及函数间关系——比如决定哪些函数该内联、删掉未被调用的死函数、重写所有函数的入口桩、合并重复的全局常量、或构建整个模块的调用图(CallGraph)——那就只能用 ModulePass。它的执行粒度是整个 Module,一次遍历就能看到所有函数、全局变量、命名元数据和链接属性。
典型翻车点:把本该是 ModulePass 的逻辑硬塞进 FunctionPass,然后在循环里反复重建调用图或扫描全局变量。这不仅慢(O(N²)),还会导致分析结果不一致,因为每次 FunctionPass 运行时看到的模块状态可能已被前一个函数的修改所改变。
- Legacy PM 中,
ModulePass无法依赖FunctionPass的分析结果;New PM 中可通过ModuleAnalysisManager::getResult<SomeAnalysis>(M)获取,但要注意分析是否已注册 - 注册方式不同:
ModulePass用RegisterPass<MyModPass>或 New PM 的registerModuleAnalyses(),别和FunctionPass的注册混用 - 副作用强:一旦修改了全局结构(如删除函数、重命名全局变量),后续所有
FunctionPass都会看到新状态,务必确保顺序可控
Legacy PM 和 New Pass Manager 的注册行为差异很大
LLVM 11+ 默认启用 New Pass Manager(NPM),但很多旧教程和 OLLVM 衍生项目仍用 Legacy PM。两者对 FunctionPass 和 ModulePass 的调度逻辑、分析缓存、以及依赖声明完全不同。
最常被忽略的一点:在 NPM 下,FunctionPass 无法直接请求 ModulePass 的分析结果,而 ModulePass 却可以安全使用 FunctionAnalysisManager 提供的分析(如 DominatorTree、LoopInfo)。反向依赖在 Legacy PM 中会静默失败,在 NPM 中则编译不过。
- Legacy PM 注册
FunctionPass:用static RegisterPass<X> Y("name", "...") - New PM 注册
FunctionPass:需在插件registerPipelineParsingCallback中调用FPM.addPass(...) - 混淆类项目(如 OLLVM)多数基于 Legacy PM,直接迁移到 NPM 会导致
runOnModule不被触发、或分析管理器为空指针崩溃
控制流平坦化这类变换必须用 Function Pass
控制流平坦化(Control Flow Flattening)本质是重写单个函数的控制流结构:把原始基本块打散,统一接入一个 dispatcher switch,再用状态变量跳转。它不关心其他函数是否存在,也不需要修改模块级符号表或链接属性——所有操作都封闭在函数边界内。
强行用 ModulePass 实现,反而会引入冗余开销:你得手动遍历所有函数、逐个调用平坦化逻辑,还容易漏掉条件(比如跳过 optnone 函数),而 FunctionPass 天然提供这个遍历能力,并自动跳过被标记为不可优化的函数。
- 平坦化后,函数的 CFG 被彻底破坏,
DominatorTree和LoopInfo等分析失效;所以应在这些分析之后、代码生成之前插入FunctionPass - 不要在
FunctionPass中调用verifyFunction()——平坦化后的 IR 通常不满足 LLVM 验证规则,但这是预期行为 - 若需保留某些函数不被平坦化(如
main或__libc_start_main),应在run开头加白名单判断,而不是靠 Module 级过滤
ModulePass;不能,就老实用 FunctionPass。LLVM 的模块化设计不是为了让人纠结“哪个更高级”,而是让每种改动落在它自然归属的粒度上。

















