3. 实现指令计数Pass

实现指令计数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})

编译与运行

  1. 编译Pass:
    mkdir build && cd build
    cmake .. -DLLVM_DIR=$(llvm-config --cmakedir)
    make
    生成 InstCountPass.so。
  2. 准备测试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
    }
  3. 运行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适合需要跨函数全局信息(如调用图分析)的场景。