LLVM New Pass Manager提供了一套严谨且高效的框架。通过PassInfoMixin实现 Pass 定义,通过PassPlugin完成注册,并结合IRBuilder和SplitBlock等辅助函数,开发者可以安全且高效地实现复杂的编译优化与插桩逻辑。理解PreservedAnalyses的机制是编写高性能Pass的关键,它能够最大限度地减少冗余计算,从而显著缩短编译时间。
LLVM New Pass Manager
An LLVM Pass is a modular unit of the LLVM compiler infrastructure that performs analysis or transformation on LLVM Intermediate Representation (IR). Passes are the primary mechanism for implementing optimizations and analyzing program properties.
LLVM Pass用于对LLVM IR执行分析或转换操作。Pass是实现优化和分析程序属性的核心机制。
可以把LLVM Pass与建模软件如Blender、Maya等中的Modifier作类比,如下图:
LLVM的New Pass Manager是为了解决旧版Pass Manager在扩展性、分析缓存失效和并行处理方面的限制而引入的。其核心设计哲学在于分析与转换的解耦以及基于模板的类型安全。
A Legacy LLVM Pass refers to a compiler pass written using the original LLVM Pass Framework that relies on inheritance-based polymorphism.
旧版Pass Manager依赖基于继承的多态机制,而新版Pass Manager基于分析声明与组合式范式。
Pass 的类型与继承结构
在New PM中,Pass主要分为两类:
分析 Pass: 继承自
AnalysisInfoMixin<DerivedPass>。它们负责计算某种 IR 属性(如支配树、别名分析),并返回一个结果对象。转换 Pass: 继承自
PassInfoMixin<DerivedPass>。它们负责修改 IR。
所有 Pass 必须实现run方法。对于FunctionPass,其签名通常为:
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
PreservedAnalyses:分析保留机制
这是 New PM 性能优化的基石。run方法返回的PreservedAnalyses对象告知管理器该Pass修改了哪些内容。
PreservedAnalyses::all():当前Pass未做任何修改,后续所有分析结果均有效。PreservedAnalyses::none():当前Pass破坏了所有分析结果。PA.preserve<DominatorTreeAnalysis>():明确指出保留了特定的分析。
编写与注册自定义 Pass
定义一个 Function Pass
以下是一个简单的Pass示例,它遍历函数中的指令:
#include "llvm/IR/PassManager.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
struct MyCustomPass : public llvm::PassInfoMixin<MyCustomPass> {
llvm::PreservedAnalyses run(llvm::Function &F, llvm::FunctionAnalysisManager &AM) {
// 获取分析结果示例
// auto &DT = AM.getResult<llvm::DominatorTreeAnalysis>(F);
for (auto &BB : F) {
for (auto &I : BB) {
// 执行转换逻辑
}
}
return llvm::PreservedAnalyses::all();
}
};
插件注册机制
New PM引入了动态插件机制,使得无需重编译整个LLVM即可加载外部Pass。
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() {
return {LLVM_PLUGIN_API_VERSION, "MyCustomPass", LLVM_VERSION_STRING,
[](llvm::PassBuilder &PB) {
PB.registerPipelineParsingCallback(
[](llvm::StringRef Name, llvm::FunctionPassManager &FPM,
llvm::ArrayRef<llvm::PassBuilder::PipelineElement>) {
if (Name == "my-custom-pass") {
FPM.addPass(MyCustomPass());
return true;
}
return false;
});
}};
}
CMake 配置
使用add_llvm_pass_plugin是现代的标准做法:
add_llvm_pass_plugin(MyCustomPass MyPass.cpp)
使用 IRBuilder 修改 IR
IRBuilder维护一个插入点(InsertPoint),所有创建的指令都会被放置在该点之后。
插入指令
创建一个IRBuilder实例,并将插入点设为指定基本块的末尾:
llvm::IRBuilder<> Builder(Context);
Builder.SetInsertPoint(BB, BB->getFirstInsertionPt());
auto *Add = Builder.CreateAdd(Val1, Val2, "sum");
替换指令
直接替换指令需要维护其Use-Def链:
// 将 oldInst 的所有使用者更新为 newInst
oldInst->replaceAllUsesWith(newInst);
// 删除旧指令
oldInst->eraseFromParent();
或者使用更底层的llvm::ReplaceInstWithInst工具函数。
拆分基本块
拆分基本块对于实现控制流注入非常重要。SplitBlock将基本块在指定指令处一分为二。
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
// 在 Instr 处拆分 BB,拆分后的第二部分为 NewBB
llvm::BasicBlock *NewBB = SplitBlock(BB, &*Instr);
构造条件分支
使用SplitBlockAndInsertIfThen处理所有的分支跳转、Phi 节点更新和基本块创建:
// 插入 if (Cond) { ... }
llvm::Value *Cond = ...;
llvm::Instruction *ThenTerm = nullptr;
llvm::SplitBlockAndInsertIfThen(Cond, InsertPoint, false, nullptr, nullptr, nullptr, &ThenTerm);
// 此时 InsertPoint 之后已经分出了一个 If 块,你可以获取该块并在其中添加逻辑
llvm::IRBuilder<> ThenBuilder(ThenTerm);
ThenBuilder.CreateCall(Func, Args);