4. 遍历基本块
任务目标
编写一个LLVM FunctionPass,遍历函数内的所有基本块,打印每个基本块的标签(名称)及其包含的指令数量。该Pass作为独立模块编译并加载到opt中运行。
环境与依赖
- LLVM 15+(示例基于LLVM 15 API)
- CMake ≥ 3.20
- 已安装LLVM开发库(
llvm-dev或通过brew/源码构建)
完整实现
创建BlockCounter.cpp,实现一个继承FunctionPass的Pass。核心逻辑在runOnFunction中:遍历函数的基本块,对每个基本块调用size()获取指令数,并通过print输出标签名。若基本块无显式名称(如入口块),则使用地址标识。
// BlockCounter.cpp
#include "llvm/IR/Function.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Pass.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
using namespace llvm;
namespace {
struct BlockCounter : public FunctionPass {
static char ID;
BlockCounter() : FunctionPass(ID) {}
bool runOnFunction(Function &F) override {
errs() << "Function: " << F.getName() << "\n";
for (BasicBlock &BB : F) {
// 获取基本块标签:优先使用名称,否则用地址
std::string label;
if (BB.hasName()) {
label = BB.getName().str();
} else {
raw_string_ostream rso(label);
rso << &BB; // 输出地址作为标识
}
unsigned instCount = BB.size(); // 指令数
errs() << " Block " << label << ": " << instCount
<< " instructions\n";
}
return false; // 未修改函数
}
};
}
char BlockCounter::ID = 0;
static RegisterPass<BlockCounter> X("block-counter",
"Count instructions per basic block",
false /* Only looks at CFG */,
false /* Analysis Pass */);
// 自动注册以支持 opt -load 加载
static void registerBlockCounter(const PassManagerBuilder &,
legacy::PassManagerBase &PM) {
PM.add(new BlockCounter());
}
static RegisterStandardPasses
RegisterBlockCounter(PassManagerBuilder::EP_EarlySimpleOpts,
registerBlockCounter);
构建与运行
创建CMakeLists.txt:
cmake_minimum_required(VERSION 3.20)
project(BlockCounter)
find_package(LLVM REQUIRED CONFIG)
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})
add_library(BlockCounter MODULE BlockCounter.cpp)
target_link_libraries(BlockCounter PRIVATE LLVM)
编译与运行:
mkdir build && cd build
cmake .. -DLLVM_DIR=$(llvm-config --cmakedir)
make
# 生成测试用IR(示例)
cat > test.ll <<EOF
define i32 @main(i32 %argc) {
entry:
%cmp = icmp sgt i32 %argc, 1
br i1 %cmp, label %then, label %else
then:
ret i32 1
else:
ret i32 0
}
EOF
# 运行Pass
opt -load ./libBlockCounter.so -block-counter test.ll -o /dev/null
验证方法
预期输出(与代码逻辑严格一致):
Function: main
Block entry: 2 instructions
Block then: 1 instructions
Block else: 1 instructions
判定通过条件:输出中每个基本块的名称与test.ll中标签一致,指令数与IR内容匹配(entry包含icmp和br共2条,then/else各含1条ret)。若入口块无名称,则输出地址而非名称,属正常行为。
常见问题
- 链接错误:确保
LLVM_DIR指向正确的CMake配置目录,且LLVM版本与编译API匹配。 - Pass未注册:若
opt提示无法识别block-counter,检查是否使用-load指定了正确的.so路径。 - 指令数偏差:
BasicBlock::size()统计所有指令,包括PHI节点和终止指令,与IR文本行数不一定相同。