2. 编写第一个LLVM Pass
任务目标
本案例要求实现一个继承自llvm::FunctionPass的LLVM Pass,其功能为遍历输入IR中的每个函数并打印函数名。该Pass需要能够被opt工具加载并执行,以验证Pass的编写、编译与集成流程。
环境准备
需要已安装LLVM(版本≥15)及其开发头文件。确认llvm-config可用:
llvm-config --version
完整实现代码
创建文件PrintFunctionNames.cpp:
#include "llvm/IR/Function.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Pass.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
namespace {
struct PrintFunctionNames : public FunctionPass {
static char ID;
PrintFunctionNames() : FunctionPass(ID) {}
bool runOnFunction(Function &F) override {
errs() << "Function: " << F.getName() << "\n";
return false; // 未修改IR
}
};
} // namespace
char PrintFunctionNames::ID = 0;
// 注册旧版Pass管理器(opt -print-funcs)
static RegisterPass<PrintFunctionNames> X("print-funcs",
"Print function names", false, false);
// 注册新Pass管理器插件(opt -load-pass-plugin)
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() {
return {LLVM_PLUGIN_API_VERSION, "PrintFunctionNames", LLVM_VERSION_STRING,
[](PassBuilder &PB) {
PB.registerPipelineParsingCallback(
[](StringRef Name, FunctionPassManager &FPM,
ArrayRef<PassBuilder::PipelineElement>) {
if (Name == "print-funcs") {
FPM.addPass(PrintFunctionNames());
return true;
}
return false;
});
}};
}
编译与运行
使用llvm-config获取编译参数并生成共享库:
g++ -shared -fPIC -fno-rtti PrintFunctionNames.cpp \
$(llvm-config --cxxflags --ldflags --libs) \
-o libPrintFunctionNames.so
准备测试IR文件test.ll:
define i32 @add(i32 %a, i32 %b) {
%sum = add i32 %a, %b
ret i32 %sum
}
define void @hello() {
ret void
}
运行Pass(两种方式均可):
# 方式一:旧版Pass管理器
opt -load ./libPrintFunctionNames.so -print-funcs test.ll -o /dev/null
# 方式二:新Pass管理器插件
opt -load-pass-plugin ./libPrintFunctionNames.so \
-passes=print-funcs test.ll -o /dev/null
验证方法与判定标准
运行后,标准错误输出应包含以下内容(顺序与IR中函数定义顺序一致):
Function: add
Function: hello
同时满足以下条件即判定通过:
- 命令退出码为0,无报错信息;
- 输出中每个函数名与
test.ll中定义的函数一一对应; - IR文件未被修改(
-o /dev/null仅丢弃输出,Pass返回false表示未变更IR)。
常见问题
- 链接错误:确认编译命令包含
-fno-rtti,否则LLVM类型信息冲突。 - 插件加载失败:检查
llvmGetPassPluginInfo的符号导出,确保使用extern "C"且编译为-shared。 - Pass名称冲突:若
-print-funcs已被占用,可修改RegisterPass与回调中的名称字符串。