LLVM-tutor 学习笔记2: OpcodeCounter

本文详细介绍了OpcodeCounter,一个用于统计LLVMIR模块中函数内不同opcode使用的分析pass,以及配套的打印passOpcodeCounterPrinter。文章还涵盖了如何作为FunctionAnalysis运行,以及如何在编译器优化中注册和使用该pass,包括与vectorization优化的关系。
摘要由CSDN通过智能技术生成

Basic

OpcodeCounter 是一个 analysis pass, 可以统计输入文件对应的 module 中每个函数使用的各个 LLVM IR opcode 的数量。

为了可视化 OpcodeCounter 的分析结果,引入了一个专门输出结果的 transformation pass: OpcodeCounterPrinter。 按照惯例, 这样的 printing pass 命令行选项为 print<analysis-pass-name>。 所以这里的printing pass 命令行选项为print<OpcodeCounter>

OpcodeCounter 类

作为一个 analysis pass ,需要继承 AnalysisInfoMixin

struct OpcodeCounter : public llvm::AnalysisInfoMixin<OpcodeCounter> {
...
};

属性

private

static llvm::AnalysisKey Key;类似于 ID, 可以认证这个特殊的 analysis pass 类型

注意这个 Key 要在 cpp 文件中初始化。 如果不初始化, 则会出现类似以下报错:

LLVM ERROR: Could not load library './lib/libOpcodeCounter.dylib': dlopen(./lib/libOpcodeCounter.dylib, 0x0009): symbol not found in flat namespace '__ZN13OpcodeCounter3KeyE'

generateOpcodeMap()

用于统计一个函数的 opcode 数量的辅助函数。

OpcodeCounter::Result OpcodeCounter::generateOpcodeMap(llvm::Function &Func) {
  OpcodeCounter::Result OpcodeMap;

  for (auto &BB : Func) {
    for (auto &Inst : BB) {
      StringRef Name = Inst.getOpcodeName();

      if (OpcodeMap.find(Name) == OpcodeMap.end()) {
        OpcodeMap[Inst.getOpcodeName()] = 1;
      } else {
        OpcodeMap[Inst.getOpcodeName()]++;
      }
    }
  }

  return OpcodeMap;
}

返回值类型实质上是 llvm::StringMap<unsigned>, 即字符串 string 到 unsigned 映射。

这个函数的逻辑还是很清楚的。 值得注意的是对 Function 直接做范围循环就可以遍历所有BasicBlock, 对 BasicBlock 直接做范围循环就可以遍历所有 Instrument。

run()

pass 的入口函数, 这里将 OpcodeCounter 实现为了一个 Function Analysis。

OpcodeCounter::Result OpcodeCounter::run(llvm::Function &Func,
                                         llvm::FunctionAnalysisManager &) {
  return generateOpcodeMap(Func);
}

OpcodeCounterPrinter 类

作为一个 transformer pass 继承了 PassInfoMixin

class OpcodeCounterPrinter : public llvm::PassInfoMixin<OpcodeCounterPrinter> {
...
};

属性

private

  llvm::raw_ostream &OS;   对外输入流的引用

run()

OpcodeCounterPrinter 同样实现为了 function transformer pass。

PreservedAnalyses OpcodeCounterPrinter::run(Function &Func,
                                            FunctionAnalysisManager &FAM) {
  auto &OpcodeMap = FAM.getResult<OpcodeCounter>(Func);

  // In the legacy PM, the following string is printed automatically by the
  // pass manager. For the sake of consistency, we're adding this here so that
  // it's also printed when using the new PM.
  OS << "Printing analysis 'OpcodeCounter Pass' for function '"
     << Func.getName() << "':\n";

  printOpcodeCounterResult(OS, OpcodeMap);
  return PreservedAnalyses::all();
}

使用 FunctionAnalysisManager 的 getResult 方法获取 analysis 结果。之后更是输出结果逻辑, 比较简单, 不分析了。

注册 pass plugin

使用了与 HelloWorld 一致的注册写法, 先写一个辅助的 cpp 函数处理注册时逻辑, 再实现 llvmGetPassPluginInfo 这个纯 C 函数接口。

lvm::PassPluginLibraryInfo getOpcodeCounterPluginInfo() {
  return {
    LLVM_PLUGIN_API_VERSION, "OpcodeCounter", LLVM_VERSION_STRING,
        // 回调函数
          }
        };
}

extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo() {
  return getOpcodeCounterPluginInfo();
}

getOpcodeCounterPluginInfo()

llvm::PassPluginLibraryInfo getOpcodeCounterPluginInfo() {
  return {
    LLVM_PLUGIN_API_VERSION, "OpcodeCounter", LLVM_VERSION_STRING,
        [](PassBuilder &PB) {
          // #1 对应命令行中使用 "opt -passes=print<opcode-counter>" 的情况
          // 注册为 registerPipelineParsingCallback 的回调函数
          PB.registerPipelineParsingCallback(
              [&](StringRef Name, FunctionPassManager &FPM,
                  ArrayRef<PassBuilder::PipelineElement>) {
                if (Name == "print<opcode-counter>") {
                  FPM.addPass(OpcodeCounterPrinter(llvm::errs()));
                  return true;
                }
                return false;
              });
          // #2 对应命令行中使用 "-O{1|2|3|s}" pipeline 的情况
          // 注册为 registerVectorizerStartEPCallback 的回调函数
          // 这意着 OpcodeCounterPrinter 会在使用 vectoriser (矢量化) 时被调用 
          // ( 也就是在命令行中调用 -O{1|2|3|s} 的情况下 )
          PB.registerVectorizerStartEPCallback(
              [](llvm::FunctionPassManager &PM,
                 llvm::OptimizationLevel Level) {
                PM.addPass(OpcodeCounterPrinter(llvm::errs()));
              });
          // #3 向 FunctionAnalysisManager 注册 OpcodeCounter 的构造函数
          // 这样向 FunctionAnalysisManager 调用 FAM.getResult<OpcodeCounter>(Func)时才能正确处理

          PB.registerAnalysisRegistrationCallback(
              [](FunctionAnalysisManager &FAM) {
                FAM.registerPass([&] { return OpcodeCounter(); });
              });
          }
        };
}

相关类

AnalysisInfoMixin


/// 继承了PassInfoMixin, 使用 CRTP 混合技术
/// 为 analysis pass 提供相比普通 pass 额外的必要的 API: ID()
template <typename DerivedT>
struct AnalysisInfoMixin : PassInfoMixin<DerivedT> {
  /// 为该 analysis type 提供独立的 ID
  /// 该 ID 是一个指针类型
  ///  要求子类提供一个静态的 AnalysisKey 名为 Key

  static AnalysisKey *ID() {
    static_assert(std::is_base_of<AnalysisInfoMixin, DerivedT>::value,
                  "Must pass the derived type as the template argument!");
    return &DerivedT::Key;
  }
};

MISC

vectoriser 矢量化 from ChatGPT

在计算机编程领域,"vectorization"(翻译为中文可能是"矢量化")通常是指使用向量(或数组)操作来替代标量操作,以便能够在并行处理的硬件上获得更好的性能。这在许多编程语言和编译器中都是一个重要的优化技术。

"编译中的vectorizer"指的是编译器中的一个优化器,其主要任务是将程序中的标量操作转换为向量操作,以充分利用硬件的并行性。这种优化可以在现代CPU和GPU等硬件上提高程序的执行效率。

例如,在循环中执行的一系列标量操作可能会被编译器自动转换为使用SIMD(Single Instruction, Multiple Data)指令执行的向量操作。这样可以同时处理多个数据,提高计算效率。

不同的编程语言和编译器可能有不同的实现方式和优化策略,但总体思想是通过矢量化操作来提高程序性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值