1. 编写第一个LLVM Pass

编写第一个LLVM Pass 学习卡片

任务目标

实现一个LLVM FunctionPass,遍历函数内所有指令并打印每条指令的操作码名称,通过该最小示例掌握LLVM Pass的编写、注册与加载流程。

环境准备

需要LLVM开发库(版本≥14,本示例兼容至18)。Ubuntu/Debian安装:llvm-dev、llvm包;macOS使用Homebrew:llvm。确保llvm-config在PATH中。

完整实现代码

// OpcodePrinter.cpp
#include "llvm/IR/Function.h"
#include "llvm/IR/Instructions.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 OpcodePrinter : public PassInfoMixin<OpcodePrinter> {
  PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) {
    for (BasicBlock &BB : F) {
      for (Instruction &I : BB) {
        errs() << F.getName() << ":\t"
               << I.getOpcodeName() << "\n";
      }
    }
    return PreservedAnalyses::all();
  }
};

} // namespace

// 注册插件入口
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() {
  return {LLVM_PLUGIN_API_VERSION, "OpcodePrinter", LLVM_VERSION_STRING,
          [](PassBuilder &PB) {
            PB.registerPipelineParsingCallback(
                [](StringRef Name, FunctionPassManager &FPM,
                   ArrayRef<PassBuilder::PipelineElement>) {
                  if (Name == "print-opcodes") {
                    FPM.addPass(OpcodePrinter());
                    return true;
                  }
                  return false;
                });
          }};
}

编译与运行

编译Pass为共享库:

clang++ -shared -fPIC -fno-rtti OpcodePrinter.cpp \
    $(llvm-config --cxxflags --ldflags --libs) -o libOpcodePrinter.so

准备一个测试C文件test.c:

int add(int a, int b) { return a + b; }

生成LLVM IR并运行Pass:

clang -S -emit-llvm test.c -o test.ll
opt -load-pass-plugin=./libOpcodePrinter.so \
    -passes="print-opcodes" test.ll -o /dev/null

验证方法

运行后标准错误输出应包含类似以下内容(顺序可能因优化级别不同而异):

add:	alloca
add:	store
add:	load
add:	load
add:	add
add:	ret

判定通过条件:输出中add函数内包含add和ret操作码,且无编译错误。若使用-O0生成IR,指令序列与上述示例一致。

常见问题

  • 链接错误:确认llvm-config --cxxflags包含-fno-rtti,且LLVM版本与编译命令一致。
  • Pass未注册:检查插件加载路径是否正确,-passes="print-opcodes"名称需与代码中注册字符串完全匹配。
  • 输出为空:确认输入IR包含函数定义(非仅声明),可用cat test.ll检查。