4. 遍历基本块和指令

任务目标

编写一个 LLVM FunctionPass,遍历函数中的每个基本块(BasicBlock)以及其中的每条指令(Instruction),并打印每条指令的操作码名称。该 Pass 不修改 IR,仅做遍历与输出,用于理解 LLVM IR 的层次结构。

环境与依赖

需要 LLVM 开发库(版本 10 或更高,示例基于 LLVM 10 API)。确保 llvm-config 可用,并已安装对应版本的开发头文件。

完整实现代码

// BasicBlockInstrPrinter.cpp
#include "llvm/IR/Function.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Instruction.h"
#include "llvm/Pass.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

namespace {
    struct BasicBlockInstrPrinter : public FunctionPass {
        static char ID;
        BasicBlockInstrPrinter() : FunctionPass(ID) {}

        bool runOnFunction(Function &F) override {
            errs() << "Function: " << F.getName() << "\n";

            for (BasicBlock &BB : F) {
                errs() << "  BasicBlock: " << BB.getName() << "\n";

                for (Instruction &I : BB) {
                    errs() << "    Opcode: " << I.getOpcodeName()
                           << "  (opcode #" << I.getOpcode() << ")\n";
                }
            }
            return false; // 不修改 IR
        }
    };
}

char BasicBlockInstrPrinter::ID = 0;
static RegisterPass<BasicBlockInstrPrinter> X(
    "bb-instr-printer",
    "Print opcodes of every instruction in each basic block",
    false /* 非仅查看 Pass */,
    false /* 非分析 Pass */);

// 自动注册到 opt(可选,便于直接使用)
static void registerBasicBlockInstrPrinter(const PassManagerBuilder &,
                                           legacy::PassManagerBase &PM) {
    PM.add(new BasicBlockInstrPrinter());
}
static RegisterStandardPasses
    RegisterMyPass(PassManagerBuilder::EP_EarlyAsPossible,
                   registerBasicBlockInstrPrinter);

编译与运行

# 编译为共享库(以 LLVM 10 为例)
clang++ -shared -fPIC -fno-rtti BasicBlockInstrPrinter.cpp \
    $(llvm-config --cxxflags --ldflags --libs) -o BBInstrPrinter.so

# 生成测试用 LLVM IR(以简单 C 代码为例)
cat > test.c << 'EOF'
int add(int a, int b) {
    int c = a + b;
    if (c > 10)
        return c;
    else
        return 0;
}
EOF
clang -S -emit-llvm test.c -o test.ll

# 运行 Pass
opt -load ./BBInstrPrinter.so -bb-instr-printer test.ll -o /dev/null

验证方法与预期输出

运行后,终端应输出每个函数的名称、每个基本块的名称(若未命名则为空),以及该基本块内每条指令的操作码名称和操作码编号。对于上述 test.c,预期输出片段如下(实际名称可能因 LLVM 版本略有差异):

Function: add
  BasicBlock: entry
    Opcode: alloca  (opcode #26)
    Opcode: alloca  (opcode #26)
    Opcode: store   (opcode #28)
    Opcode: store   (opcode #28)
    Opcode: load    (opcode #27)
    Opcode: load    (opcode #27)
    Opcode: add     (opcode #10)
    Opcode: store   (opcode #28)
    Opcode: load    (opcode #27)
    Opcode: icmp    (opcode #52)
    Opcode: br      (opcode #2)
  BasicBlock: if.then
    Opcode: load    (opcode #27)
    Opcode: ret     (opcode #1)
  BasicBlock: if.else
    Opcode: ret     (opcode #1)

判定通过条件:输出中能看到 add 函数包含 3 个基本块(entry、if.then、if.else),且每条指令均打印了对应的操作码名称与编号,程序正常退出无报错。

常见问题

  • 链接错误:确保 llvm-config --cxxflags 包含正确的头文件路径,且编译时使用 -fno-rtti(LLVM 默认禁用 RTTI)。
  • Pass 未注册:若使用 opt 加载时提示找不到 Pass,检查 RegisterPass 的 ID 字符串是否与命令行参数一致。
  • 输出为空:确认输入 IR 文件非空,且函数确实包含基本块和指令。