3. 实现指令计数Pass
任务目标
编写一个LLVM FunctionPass,遍历当前函数的所有基本块,累加每条指令的数量,将函数名与指令总数输出到标准错误(errs())。通过该实现,理解FunctionPass与ModulePass在遍历粒度上的差异。
环境与依赖
- LLVM版本:15.x(或兼容的后续版本)
- 构建工具:CMake >= 3.20
- 编译器:支持C++17的GCC/Clang
核心实现
创建文件 InstCountPass.cpp,完整代码如下:
#include "llvm/IR/Function.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 InstCountPass : public FunctionPass {
static char ID;
InstCountPass() : FunctionPass(ID) {}
bool runOnFunction(Function &F) override {
unsigned instCount = 0;
for (BasicBlock &BB : F) {
instCount += BB.size(); // 每条指令占一个元素
}
errs() << "Function: " << F.getName()
<< " has " << instCount
<< " instructions\n";
return false; // 未修改函数
}
};
}
char InstCountPass::ID = 0;
static RegisterPass<InstCountPass> X("inst-count", "Count instructions per function");
// 自动注册到 opt(可选,便于直接使用)
static void registerInstCountPass(const PassManagerBuilder &,
legacy::PassManagerBase &PM) {
PM.add(new InstCountPass());
}
static RegisterStandardPasses
RegisterInstCount(PassManagerBuilder::EP_EarlyAsPossible,
registerInstCountPass);
构建配置
创建 CMakeLists.txt:
cmake_minimum_required(VERSION 3.20)
project(InstCountPass)
find_package(LLVM REQUIRED CONFIG)
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
add_library(InstCountPass MODULE InstCountPass.cpp)
target_include_directories(InstCountPass PRIVATE ${LLVM_INCLUDE_DIRS})
target_compile_definitions(InstCountPass PRIVATE ${LLVM_DEFINITIONS})
target_link_libraries(InstCountPass PRIVATE ${LLVM_LIBRARIES})
编译与运行
- 编译Pass:
生成mkdir build && cd build cmake .. -DLLVM_DIR=$(llvm-config --cmakedir) makeInstCountPass.so。 - 准备测试IR(
test.ll):define i32 @add(i32 %a, i32 %b) { %sum = add i32 %a, %b ret i32 %sum } define i32 @main() { %r = call i32 @add(i32 1, i32 2) ret i32 %r } - 运行Pass:
opt -load ./InstCountPass.so -inst-count < test.ll > /dev/null-inst-count是注册的Pass名称。
验证方法
观察标准错误输出,应包含两行:
Function: add has 2 instructions
Function: main has 2 instructions
判定标准:每个函数名后输出的指令数与test.ll中该函数的指令条数一致(add函数含add和ret共2条;main含call和ret共2条)。若输出匹配,则Pass实现正确。
ModulePass与FunctionPass的区别
本案例使用FunctionPass,其runOnFunction对每个函数独立调用一次,无需手动遍历模块中的函数集合。若改用ModulePass,则需在runOnModule中通过M.begin()/M.end()遍历所有Function,再对每个函数内部的基本块和指令进行双重循环。两种方式均能实现统计,但FunctionPass由LLVM Pass管理器自动按函数粒度调度,代码更简洁且天然支持后续基于函数分析的优化流水线;ModulePass适合需要跨函数全局信息(如调用图分析)的场景。