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文本行数不一定相同。