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与回调中的名称字符串。