从零开始教你写一个LLVM Pass
失业在家闲来无事,不如写些文章回馈下社区。
如果你已经有一定基础,这里是建议您直接看 LLVM的加Pass的 官方文档1 官方文档2。可能限制于自身经验以及专有名词语言的障碍,您可以看下我这篇文章,希望对您有所帮助,如果有不清楚的欢迎留言或查看官方文档。
本文以LLVM Release 15.0.7: Jan 2023 中的 AMDGPUResourceUsageAnalysisPass
为例,对应的commit id是8dfdcc7, 和你一起剖析他是怎么加进去的
一、什么是Pass
LLVM Pass是针对LLVM编译器基础设施的插件式组件,用于执行编译器优化或代码分析。
1、所有的LLVM Pass都是Pass的子类
我们 AMDGPUResourceUsageAnalysis
是这样定义的,llvm/lib/Target/AMDGPU/AMDGPUResourceUsageAnalysis.h:27,其继承于 ModulePass
struct AMDGPUResourceUsageAnalysis : public ModulePass {
static char ID;
我们可以在 llvm/include/llvm/Pass.h:248 中看到 ModulePass 是基于 Pass 的子类
//===----------------------------------------------------------------------===//
/// ModulePass class - This class is used to implement unstructured
/// interprocedural optimizations and analyses. ModulePasses may do anything
/// they want to the program.
///
class ModulePass : public Pass {
public:
AMDGPUResourceUsageAnalysis 这个Pass是为了完成栈、寄存器等相关信息的计算,待到指令打印的时候用的。
ModulePass 是用于在整个编译单元(Module)级别执行优化和分析操作,作用于编译单元的中间表示(IR),而不是单个函数或基本块。
2、Pass的目的各种各样
在LLVM中,编译过程可以分为多个阶段,每个阶段都可以通过插件式的方式添加和修改。其可以用于多种目的,包括但不限于:
-
优化: 这些Pass用于改进生成的机器码的性能和效率,例如执行常量传播、循环优化、内联等。
-
分析: 这些Pass用于对代码进行静态分析,例如查找未使用的变量、检测内存泄漏、执行指令计数等。
-
代码转换: 这些Pass用于在中间表示的不同形式之间进行转换,例如将高级语言代码转换为中间表示,或者执行特定的代码重组。
除上文提到的 Pass子类 ModulePass 外,还有 CallGraphSCCPass 在用于在调用图(call graph)上从下至上的遍历程序,SCC是strongly connected(强连通分量,转换DAG用),LoopPass在每个每个循环上执行,RegionPass在函数中的每个单入口单出口region执行,知乎文章参考
官方文档还有 LLVM’s Analysis and Transform Passes 清单
3、绝大多数Pass的输入是IR
IR中间表示是什么呢,就是经过LLVM前端处理后生成的一种抽象的、与具体硬件无关的表示,且可以展示为ISD,经过后端处理后Node可能会降级为目标平台特有的ISD。IR分类和解释详见 官方文档,或者看看这个大佬总结的也挺好。IR中涉及的Module、Function、Basic Block(BB) 如下图所示,我们后端往往拿到的就是其中的一个结构,他们是一个嵌套关系。
二、基础代码
1、build环境的问题
官方文档使用的是opt -load
库文件来跑的,由于我们是改在LLVM后端的,所以仅需在你需要修改的Target增加需要编译的文件即可,比如 AMDGPUResourceUsageAnalysis.cpp 可以在 llvm/lib/Target/AMDGPU/CMakeLists.txt:93 看到
2、写Pass需要一些头文件来引入库
你需要引入 llvm/Pass.h
文件,AMDGPUResourceUsageAnalysis 引入了 CallGraphSCCPass.h
, CallGraphSCCPass.h
内 引入了 llvm/Pass.h
llvm/lib/Target/AMDGPU/AMDGPUResourceUsageAnalysis.h:18
#ifndef LLVM_LIB_TARGET_AMDGPU_AMDGPURESOURCEUSAGEANALYSIS_H
#define LLVM_LIB_TARGET_AMDGPU_AMDGPURESOURCEUSAGEANALYSIS_H
#include "llvm/Analysis/CallGraphSCCPass.h"
#include "llvm/CodeGen/MachineModuleInfo.h"
namespace llvm {
3、定义这个Pass
选好你需要继承的Pass类,我们这个Pass要知道全局的信息,所以使用的ModulePass
。
这个Pass需要一个ID,并而且是static的,即只有一份
构造函数也是需要的,AMDGPUResourceUsageAnalysis() : ModulePass(ID) {}
还有runOnModule
函数需要重写,bool runOnModule(Module &M) override; ,这个往往具体实现在 .cpp 中
这里还重写了 doInitialization
去 做初始化
其他就是你需要定义的成员以及你抽象的接口了
//===- AMDGPUResourceUsageAnalysis.h ---- analysis of resources -*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
/// \file
/// \brief Analyzes how many registers and other resources are used by
/// functions.
///
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_AMDGPU_AMDGPURESOURCEUSAGEANALYSIS_H
#define LLVM_LIB_TARGET_AMDGPU_AMDGPURESOURCEUSAGEANALYSIS_H
#include "llvm/Analysis/CallGraphSCCPass.h"
#include "llvm/CodeGen/MachineModuleInfo.h"
namespace llvm {
class GCNSubtarget;
class MachineFunction;
class TargetMachine;
struct AMDGPUResourceUsageAnalysis : public ModulePass {
static char ID;
public:
// Track resource usage for callee functions.
struct SIFunctionResourceInfo {
// Track the number of explicitly used VGPRs. Special registers reserved at
// the end are tracked separately.
int32_t NumVGPR = 0;
int32_t NumAGPR = 0;
int32_t NumExplicitSGPR = 0;
uint64_t PrivateSegmentSize = 0;
bool UsesVCC = false;
bool UsesFlatScratch = false;
bool HasDynamicallySizedStack = false;
bool HasRecursion = false;
bool HasIndirectCall = false;
int32_t getTotalNumSGPRs(const GCNSubtarget &ST) const;
// Total number of VGPRs is actually a combination of AGPR and VGPR
// depending on architecture - and some alignment constraints
int32_t getTotalNumVGPRs(const GCNSubtarget &ST, int32_t NumAGPR,
int32_t NumVGPR) const;
int32_t getTotalNumVGPRs(const GCNSubtarget &ST) const;
};
AMDGPUResourceUsageAnalysis() : ModulePass(ID) {}
bool doInitialization(Module &M) override {
CallGraphResourceInfo.clear();
return ModulePass::doInitialization(M);
}
bool runOnModule(Module &M) override;
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.addRequired<MachineModuleInfoWrapperPass>();
AU.setPreservesAll();
}
const SIFunctionResourceInfo &getResourceInfo(const Function *F) const {
auto Info = CallGraphResourceInfo.find(F);
assert(Info != CallGraphResourceInfo.end() &&
"Failed to find resource info for function");
return Info->getSecond();
}
private:
SIFunctionResourceInfo analyzeResourceUsage(const MachineFunction &MF,
const TargetMachine &TM) const;
void propagateIndirectCallRegisterUsage();
DenseMap<const Function *, SIFunctionResourceInfo> CallGraphResourceInfo;
};
} // namespace llvm
#endif // LLVM_LIB_TARGET_AMDGPU_AMDGPURESOURCEUSAGEANALYSIS_H
4、runOnModule
或 runOnFunction
实现
后端的所有要做的事情其实就是一个个的Pass,由 PassManager 进行管理,可能是各种类型的Pass,ModulePass
是通过 llvm/lib/IR/LegacyPassManager.cpp:1545 LocalChanged |= MP->runOnModule(M);
来运行的,runOnModule 就是这个 Pass 的入口;FunctionPass
是通过 llvm/lib/IR/LegacyPassManager.cpp:1430 LocalChanged |= FP->runOnFunction(F);
来运行的。
llvm/lib/Target/AMDGPU/AMDGPUResourceUsageAnalysis.cpp:100 代码中 可以看到了其拿到了这个Module 的 CallGraph,然后进行后序遍历,也就是左右根,然后进行了analyzeResourceUsage
,这个函数里我们可以看到其会判断子call然后求大,即 CalleeFrameSize = std::max(I->second.PrivateSegmentSize, CalleeFrameSize);
,当前函数所用栈 = callee中的最大栈 + 当前函数的PrivateSegmentSize,而且你是后序遍历这个图的,也就是把子调用求完再求父的,不需要递归
bool AMDGPUResourceUsageAnalysis::runOnModule(Module &M) {
auto *TPC = getAnalysisIfAvailable<TargetPassConfig>();
if (!TPC)
return false;
MachineModuleInfo &MMI = getAnalysis<MachineModuleInfoWrapperPass>().getMMI();
const TargetMachine &TM = TPC->getTM<TargetMachine>();
bool HasIndirectCall = false;
CallGraph CG = CallGraph(M);
auto End = po_end(&CG);
for (auto IT = po_begin(&CG); IT != End; ++IT) {
Function *F = IT->getFunction();
if (!F || F->isDeclaration())
continue;
MachineFunction *MF = MMI.getMachineFunction(*F);
assert(MF && "function must have been generated already");
auto CI = CallGraphResourceInfo.insert(
std::make_pair(F, SIFunctionResourceInfo()));
SIFunctionResourceInfo &Info = CI.first->second;
assert(CI.second && "should only be called once per function");
Info = analyzeResourceUsage(*MF, TM);
HasIndirectCall |= Info.HasIndirectCall;
}
if (HasIndirectCall)
propagateIndirectCallRegisterUsage();
return false;
}
三、注入到后端中
1、初始化Pass
在 llvm/lib/Target/AMDGPU/AMDGPU.h:262 声明
void initializeAMDGPUResourceUsageAnalysisPass(PassRegistry &);
extern char &AMDGPUResourceUsageAnalysisID;
在 llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp:401 LLVMInitializeAMDGPUTarget 函数中调用
initializeAMDGPUResourceUsageAnalysisPass(*PR);
initializeGCNNSAReassignPass(*PR);
initializeGCNPreRAOptimizationsPass(*PR);
}
2、Pass 执行和信息使用
这个Pass是在指令打印时使用的,其通过 &getAnalysis
bool AMDGPUAsmPrinter::runOnMachineFunction(MachineFunction &MF) {
// Init target streamer lazily on the first function so that previous passes
// can set metadata.
if (!IsTargetStreamerInitialized)
initTargetStreamer(*MF.getFunction().getParent());
ResourceUsage = &getAnalysis<AMDGPUResourceUsageAnalysis>();
CurrentProgramInfo = SIProgramInfo();
需要注意的是其重写了 getAnalysisUsage
确保能拿到数据并执行正确
void AMDGPUAsmPrinter::getAnalysisUsage(AnalysisUsage &AU) const {
AU.addRequired<AMDGPUResourceUsageAnalysis>();
AU.addPreserved<AMDGPUResourceUsageAnalysis>();
AsmPrinter::getAnalysisUsage(AU);
}
其他情况呢,比如 AMDGPUAlwaysInlinePass 总是强制内联Pass 他是在 void AMDGPUPassConfig::addIRPasses() {
加入的,addIRPasses 表示他的阶段,当然这也是 重写的函数
还可以加在不同的阶段,当然你想知道当前Target用了哪些Pass 查看 TargetPassConfig 和 XXPassConfig 即可
class AMDGPUPassConfig : public TargetPassConfig {
public:
AMDGPUPassConfig(LLVMTargetMachine &TM, PassManagerBase &PM);
AMDGPUTargetMachine &getAMDGPUTargetMachine() const {
return getTM<AMDGPUTargetMachine>();
}
ScheduleDAGInstrs *
createMachineScheduler(MachineSchedContext *C) const override;
void addEarlyCSEOrGVNPass();
void addStraightLineScalarOptimizationPasses();
void addIRPasses() override;
void addCodeGenPrepare() override;
bool addPreISel() override;
bool addInstSelector() override;
bool addGCPasses() override;
另外可以根据用户不同的编译选项添加不同的Pass
if (getOptLevel() == CodeGenOpt::Aggressive)
addPass(createGVNPass());
llvm/lib/Transforms 文件夹下更是Pass集中地,标量优化在Scalar目录,过程间优化在 IPO 目录。比如 死代码消除 ADCELegacyPass在 llvm/lib/Passes/PassBuilderPipelines.cpp:561 我们可以看到其是默认启用的。
本文来自博客园,作者:暴力都不会的蒟蒻,转载请注明原文链接:https://www.cnblogs.com/BobHuang/p/17640378.html