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 文件非空,且函数确实包含基本块和指令。