在设计新Pass时,您应该做的第一件事就是确定应该为Pass子类化的类。在Hello World示例中使用FunctionPass
类及其实现,但我们并没有讨论为什么或什么时候发生这种情况。在这里,我们讨论可用的类,从最一般的到最具体。
当您为您的Pass选择超类时,您应该选择最具体的类,同时仍然能够满足列出的要求。这为LLVM Pass Infrastructure提供了优化Pass运行所需的信息,因此生成的编译器不会非常慢。
ImmutablePass类
最简单和无聊的Pass类型是ImmutablePass
类。此传递类型用于不必运行的Pass,不更改状态,永远不需要更新。这不是正常类型的转换或分析,但可以提供有关当前编译器配置的信息。
尽管这种传Pass很少使用,但提供有关正在编译的当前目标机器的信息以及可能影响各种变换的其他静态信息非常重要。
ImmutablePass
es永远不会使其他转换失效,永远不会失效,永远不会“运行”。
ModulePass类
ModulePass
类是你可是使用的大多数类的超类。派生自ModulePass
表示您的Pass使用整个程序作为一个单元,以无法预测的顺序引用函数体,或添加和删除函数。因为对子ModulePass
类的行为一无所知,所以不能对它们的执行进行优化。
如果函数传递不需要任何模块或不可变传递,则模块传递可以使用函数级传递(例如,支dominators)使用getAnalysis
接口来提供检索分析结果的函数。请注意,这只能用于分析运行的函数,例如,在支配者的情况下,您应该只询问for函数定义,而不是声明。getAnalysis<DominatorTree>(llvm::Function *)``DominatorTree
要编写正确的ModulePass
子类,请使用以下签名派生ModulePass
并重载该runOnModule
方法:
runOnModule()方法:
virtual bool runOnModule(Module &M) = 0;
该runOnModule
方法执行通过的有趣工作。如果模块被转换修改它将返回true,否则应该返回false
。
CallGraphSCCPass类
CallGraphSCCPass
类由需要遍历上调用图(被调用者在调用者之前)的程序自下而上遍使用。派生CallGraphSCCPass
为构建和遍历提供了一些机制CallGraph
,但也允许系统优化CallGraphSCCPass
es的执行。如果您的通行证符合下面列出的要求,并且不符合FunctionPass
或BasicBlockPass
的要求 ,您应该从中获得CallGraphSCCPass
。
为了明确,CallGraphSCCPass子类是:
- … 不允许检查或修改
Function
除当前SCC以及SCC的直接呼叫者和直接被叫者之外的任何其他用户。 - … 需要保留当前
CallGraph
对象,更新它以反映对程序所做的任何更改。 - … 不允许在当前模块中添加或删除SCC,但它们可能会更改SCC的内容。
- … 允许在当前模块中添加或删除全局变量。
- … 允许在
runOnSCC
(包括全局数据)的调用中维护状态。
在某些情况下,实现CallGraphSccPass有点棘手,因为它必须处理具有多个节点的SCC。如果他们修改了程序,则下面描述的所有虚拟方法都应该返回true,反之,返回false。
doInitialization(CallGraph &)方法
virtual bool doInitialization(CallGraph &CG);
doInitialization
允许该方法执行大多数 CallGraphSCCPass
不允许执行的操作。它们可以添加和删除函数,获取函数指针等。该doInitialization
方法旨在执行不依赖于正在处理的SCC的简单初始化类型的东西。所述doInitialization
方法调用不被调度与任何其它通处决重叠(因此它应该是非常快)。
runOnSCC(CallGraphSCC &SCC)方法
virtual bool runOnSCC(CallGraphSCC &SCC) = 0;
该runOnSCC
方法执行pass的有趣工作,如果模块被转换修改则返回true
, 否则返回false
。
doFinalization(CallGraph &CG)方法
virtual bool doFinalization(CallGraph &CG);
该doFinalization
方法是一种不经常使用的方法,当Pass框架为正在编译的程序中的每个SCC 调用runOnSCC
时调用该方法。
FunctionPass类
与ModulePass
子类相比,FunctionPass
子类确实具有系统可以预期的可预测的本地行为。全部 FunctionPass
执行程序中的每个功能,独立于程序中的所有其他功能。 FunctionPass
es不要求它们按特定顺序执行,并且FunctionPass
不要修改外部函数。
为了明确,FunctionPass
子类不允许以下行为:
- 检查或修改
Function
当前正在处理的文件以外的文件。 Function
在当前添加或删除sModule
。- 从当前添加或删除全局变量
Module
。 - 跨
runOnFunction
调用(包括全局数据)维护状态。
实现FunctionPass
通常很简单(例如,请参阅Hello World Pass)。 FunctionPass
es可能会使三个虚拟方法超载以完成其工作。如果他们修改了程序则所有这些方法都应该返回true,否则返回false。
doInitialization(Module &M)方法
virtual bool doInitialization(Module &M);
doInitialization
允许该方法执行大多数 FunctionPass
不允许执行的操作。它们可以添加和删除函数,获取函数指针等。该doInitialization
方法旨在执行不依赖于正在处理的函数的简单初始化类型的东西。所述doInitialization
方法调用不被调度与任何其它通处决重叠(因此它应该是非常快)。
如何使用此方法的一个很好的例子是LowerAllocations
传递。该过程转换malloc
和free
指令转换成平台依赖malloc()
和free()
函数调用。它使用该doInitialization
方法获取对所需函数malloc
和free
函数的引用,必要时将原型添加到模块中。
runOnFunction(Function &F)方法
virtual bool runOnFunction(Function &F) = 0;
该runOnFunction
方法必须由您的子类实现,以执行传递的转换或分析工作。像往常一样,如果修改了函数,则应返回true。
doFinalization(Module &M)方法
virtual bool doFinalization(Module &M);
该doFinalization
方法是一种不经常使用的方法,当Pass框架为正在编译的程序中的每个函数调用runOnFunction
时调用该方法。
LoopPass类
全部LoopPass
在函数中的每个循环上执行,独立于函数中的所有其他循环。 LoopPass
以循环嵌套顺序处理循环,以便最后处理最外层循环。
LoopPass
允许子类使用LPPassManager
接口更新循环嵌套。实现循环传递通常很简单。 LoopPass
es可能会使三个虚拟方法超载以完成其工作。true
如果他们修改了程序,或者false
他们没有修改程序,那么所有这些方法都应返回。
LoopPass
子类其旨在作为主循环通管道的一部分运行子类需要维护所有被相同的功能分析,其他环路传递在其管道需要。为了使这更容易,getLoopAnalysisUsage
提供了一个功能LoopUtils.h
。它可以在子类的getAnalysisUsage
覆盖中调用,以获得一致和正确的行为。类似地,INITIALIZE_PASS_DEPENDENCY(LoopPass)
将初始化这组功能分析。
doInitialization(Loop *, LPPassManager &LPM)方法
virtual bool doInitialization(Loop *, LPPassManager &LPM);
该doInitialization
方法旨在完成不依赖于正在处理的函数的简单初始化类型的东西。所述 doInitialization
方法调用不被调度与任何其它通处决重叠(因此它应该是非常快)。 LPPassManager
接口应用于访问Function
或Module
级别分析信息。
runOnLoop(Loop *, LPPassManager &LPM)方法
virtual bool runOnLoop(Loop *, LPPassManager &LPM) = 0;
该runOnLoop
方法必须由您的子类实现,以执行传递的转换或分析工作。像往常一样,true
如果修改了函数,则应返回一个值。 LPPassManager
接口应该用于更新循环嵌套。
doFinalization()方法
virtual bool doFinalization();
该doFinalization
方法是一种不常用的方法,当pass框架为正在编译的程序中的每个循环调用runOnLoop
时调用该方法。
RegionPass类
RegionPass
类似于LoopPass
类,但在函数中的每个单个条目单个退出区域上执行。 RegionPass
以嵌套顺序处理区域,以便最后处理最外层区域。
RegionPass
允许子类通过使用RGPassManager
接口更新区域树 。您可以重载RegionPass
类的三种虚拟方法来实现您自己的区域传递。如果他们修改了程序那么所有这些方法都应返回true,反之返回false。
doInitialization(Region *, RGPassManager &RGM)方法
virtual bool doInitialization(Region *, RGPassManager &RGM);
doInitialization
方法旨在完成不依赖于正在处理的函数的简单初始化类型的东西。所述 doInitialization
方法调用不被调度与任何其它通处决重叠(因此它应该是非常快)。 RPPassManager
接口应用于访问Function
或Module
级别分析信息。
runOnRegion(Region *, RGPassManager &RGM)方法
virtual bool runOnRegion(Region *, RGPassManager &RGM) = 0;
runOnRegion
方法必须由您的子类实现,以执行传递的转换或分析工作。像往常一样,如果修改了区域,则应返回真值。 RGPassManager
接口应该用于更新区域树。
doFinalization()
virtual bool doFinalization();
该doFinalization
方法是一种不经常使用的方法,当pass框架为正在编译的程序中的每个区域调用runOnRegion
时调用该方法。
BasicBlockPass类
BasicBlockPass
es就像FunctionPass
一样,除了它们必须一次限制它们的检查和修改范围到一个基本块。因此,他们不会允许做任何以下内容:
- 修改或检查当前基本块之外的任何基本块。
- 在
runOnBasicBlock
调用中维护状态。 - 修改控制流图(通过更改终止符指令)
- 任何禁止使用
FunctionPasses
的东西。
BasicBlockPass
es对于传统的本地和“窥视孔”优化非常有用。它们可能会覆盖FunctionPass
所具有的相同的doInitialization(Module&
和doFinalization(Module&)
方法,但也可以实现以下虚拟方法:
doInitialization(Function &F)方法
virtual bool doInitialization(Function &F);
doInitialization
允许该方法执行大多数 BasicBlockPass
不允许执行的操作,但FunctionPass
可以执行此操作。该doInitialization
方法旨在进行简单的初始化,而不依赖于BasicBlock
正在处理的s。所述doInitialization
方法调用不被调度与任何其它通处决重叠(因此它应该是非常快)。
runOnBasicBlock(BasicBlock &BB)方法
virtual bool runOnBasicBlock(BasicBlock &BB) = 0;
重写此函数以完成工作BasicBlockPass
。此功能不允许检查或修改参数以外的基本块,也不允许修改CFG。true
如果修改了基本块,则必须返回一个值。
doFinalization(Function &F)方法
virtual bool doFinalization(Function &F);
该doFinalization
方法是一种不经常使用的方法,当pass框架完成为正在编译的程序中的每一个BasicBlock
调用runOnBasicBlock
方法。这可用于执行按功能完成。
MachineFunctionPass类
A MachineFunctionPass
是LLVM代码生成器的一部分,它在程序中每个LLVM函数的机器相关表示上执行。
代码生成器传递由TargetMachine::addPassesToEmitFile
类似的例程注册和初始化 ,因此通常不能从opt或bugpoint命令运行它们。
MachineFunctionPass
也是FunctionPass
,所以适用于a的所有限制FunctionPass
也适用于它。MachineFunctionPass
es还有其他限制。特别是,MachineFunctionPass
不允许es执行以下任何操作:
- 修改或创建任何LLVM IR
Instruction
s,BasicBlock
s,Argument
s,Function
s,GlobalVariable
sGlobalAlias
或esModule
。 - 修改
MachineFunction
当前正在处理的文件以外的其他文件。 - 在
runOnMachineFunction
(包括全局数据)的调用中维护状态。
runOnMachineFunction(MachineFunction &MF)方法
virtual bool runOnMachineFunction(MachineFunction &MF) = 0; |
runOnMachineFunction
可以被认为是一个主要的切入点 MachineFunctionPass
; 也就是说,你应该覆盖这个方法来完成你的工作MachineFunctionPass
。
在a中的runOnMachineFunction
每MachineFunction
一个 上调用该方法Module
,以便MachineFunctionPass
可以对函数的依赖于机器的表示执行优化。如果你想在LLVM Function
为MachineFunction
你工作,使用MachineFunction
的getFunction()
访问方法-但请记住,您不得修改LLVM Function
或者从它的内容MachineFunctionPass
。
注册Pass
在Hello World示例中,我们说明了传递注册的工作原理,并讨论了使用它的一些原因以及它的用途。在这里,我们讨论如何以及为什么注册Pass。
如上所述,Passes已使用RegisterPass模板注册。template参数是要在命令行上使用的传递的名称,用于指定应将传递添加到程序中(例如,使用 opt或bugpoint)。第一个参数是Pass的名称,用于 -help
程序的输出,以及-debug-pass
选项生成的调试输出。
如果您希望Pass易于转储,则应实现虚拟打印方法:
virtual void print(llvm::raw_ostream &O, const Module *M) const; |
print
方法必须通过“analyses”来实现,以便打印分析结果的人类可读版本。这对于调试分析本身以及其他人来确定分析如何工作非常有用。使用opt -analyze参数调用此方法。
llvm::raw_ostream
参数指定要写入结果的流,该Module参数提供指向已分析程序的顶级模块的指针。但请注意,此指针可能是NULL在某些情况下(例如Pass::dump()从调试器调用),因此它只应用于增强调试输出,不应该依赖它。
Pass之间的交互
PassManager
的其中一个主要职责是确保传递正确地相互交互。
因为PassManager尝试 优化传递的执行,它必须知道传递如何相互交互以及各个传递之间存在哪些依赖关系。为了跟踪这一点,每次传递都可以声明在当前Pass之前需要执行的Pass集,以及当前Pass导致失效的Pass。
通常,此功能用于要求在运行传递之前计算分析结果。运行任意转换过程可以使计算的分析结果无效,这是失效集指定的。如果Pass未实现getAnalysisUsage方法,则默认为没有任何先决条件Pass,并使所有其他Pass无效。
getAnalysisUsage方法
virtual void getAnalysisUsage(AnalysisUsage &Info) const; |
通过实现getAnalysisUsage方法,可以为转换指定必需和无效的集合。实现应该在AnalysisUsage对象中填写有关哪些传递是必需的而不是无效的信息。为此,传递可以在AnalysisUsage对象上调用以下任何方法:
AnalysisUsage::addRequired<> and AnalysisUsage::addRequiredTransitive<>方法
如果您的传递需要执行上一个传递(例如分析),它可以使用这些方法之一来安排在传递之前运行它。LLVM具有许多不同类型的分析和传递,可以是必需的,范围从DominatorSet到BreakCriticalEdges。BreakCriticalEdges例如,要求 保证在您的通行证运行时CFG中没有关键边缘。
一些分析链接到其他分析来完成它们的工作。例如,需要 AliasAnalysis
AnalysisUsage::addPreserved<>方法
PassManager其中一项工作是优化分析的运行方式和时间。特别是,它试图避免重新计算数据,除非它需要。出于这个原因,允许传递声明它们保留(即,它们不会使现有分析无效)(如果可用)。例如,简单的常量折叠传递不会修改CFG,因此它不可能影响支配者分析的结果。默认情况下,假定所有传递都使所有其他传递无效。
AnalysisUsage类提供了一些方法,它们是在涉及到某些情况下非常有用addPreserved。特别是,setPreservesAll可以调用该 方法来指示传递根本不修改LLVM程序(对于分析也是如此),并且该 setPreservesCFG方法可以由改变程序中的指令但不修改CFG的转换使用。终止符指令(请注意,此属性是为BasicBlockPass es 隐式设置的 )。
addPreserved对于像这样的转换特别有用 BreakCriticalEdges。这个过程知道如何更新一小组循环和支配者相关的分析(如果它们存在),所以它可以保留它们,尽管它会破坏CFG。
getAnalysisUsage实现示例
// This example modifies the program, but does not modify the CFG |
getAnalysis<> and getAnalysisIfAvailable<>方法
该Pass::getAnalysis<>方法由您的类自动继承,使您可以访问使用getAnalysisUsage 方法声明的声明。它需要一个模板参数来指定所需的传递类,并返回对该传递的引用。例如:
bool LICM::runOnFunction(Function &F) { |
此方法调用返回对所需传递的引用。如果尝试获取未在getAnalysisUsage实现中声明的分析,则可能会导致运行时断言失败。此方法可以由run*方法实现调用,也可以由方法调用的任何其他本地方法调用run*。
模块级别传递可以使用此接口使用功能级别分析信息。例如:
bool ModuleLevelPass::runOnModule(Module &M) { |
在上面的示例中,runOnFunctionfor DominatorTree返回对所需传递的引用之前由传递管理器调用。
如果您的传递能够更新分析( BreakCriticalEdges如上所述),则可以使用该 getAnalysisIfAvailable方法,该方法返回指向分析的指针(如果它处于活动状态)。例如:
if (DominatorSet *DS = getAnalysisIfAvailable<DominatorSet>()) { |
实现分析组
现在我们已经了解了如何定义Pass,如何使用它们以及如何从其他Pass中获得Pass的基础知识,现在是时候让它变得更有趣了。到目前为止我们看到的所有Pass关系都非常简单:一次传递依赖于另一个特定的传递,在它可以运行之前运行。对于许多应用来说,这很好,对于其他应用,需要更大的灵活性。
特别是,定义了一些分析,使得分析结果只有一个简单的界面,但有多种计算方法。例如,考虑别名分析。最琐碎的别名分析为任何别名查询返回“may alias”。最复杂的分析是流量敏感的,上下文敏感的过程间分析,可能需要花费大量的时间来执行(显然,这两个极端之间存在很大的空间用于其他实现)。为了干净地支持这种情况,LLVM Pass Infrastructure支持分析组的概念。
分析组概念
分析组是一个简单的界面,可以通过多个不同的Pass来实现。分析组可以像Pass一样给出人类可读的名称,但与Pass不同,它们不需要从Pass 类中派生。分析组可以具有一个或多个实现,其中之一是“默认”实现。
客户端传递使用分析组,就像其他传递一样: AnalysisUsage::addRequired()和Pass::getAnalysis()方法。为了解决此要求,PassManager会扫描可用的传递以查看是否有任何分析组的实现可用。如果没有,则为要使用的传递创建默认实现。传球之间相互作用的所有标准规则仍然适用。
虽然Pass Registration对于正常传递是可选的,但是必须注册所有分析组实现,并且必须使用INITIALIZE_AG_PASS模板来加入实现池。此外,必须 使用RegisterAnalysisGroup注册接口的默认实现。
作为分析组的具体实例,请考虑 AliasAnalysis 分析组。别名分析界面(basicaa pass)的默认实现只做了一些简单的检查,不需要进行大量的计算分析(例如:两个不同的全局变量永远不能互为别名等)。使用AliasAnalysis接口的传递(例如gvn传递)不关心实际提供别名分析的实现,它们只使用指定的接口。
从用户的角度来看,命令就像正常一样工作。发出该命令将导致该类被实例化并添加到传递序列中。发出命令将导致传递使用别名分析(实际上并不存在,这只是一个假设的例子)。opt -gvn …basicaaopt -somefancyaa -gvn …gvnsomefancyaa
使用RegisterAnalysisGroup
该RegisterAnalysisGroup模板用于注册分析组本身,而INITIALIZE_AG_PASS用于将传递实现添加到分析组。首先,应该注册一个分析组,并为其提供一个人类可读的名称。与传递注册不同,没有为分析组接口本身指定命令行参数,因为它是“抽象的”:
static RegisterAnalysisGroup<AliasAnalysis> A("Alias Analysis"); |
注册分析后,pass可以使用以下代码声明它们是接口的有效实现:
namespace { |
这里我们展示如何指定默认实现(使用INITIALIZE_AG_PASS模板的最后一个参数)。对于要使用的分析组,必须始终只有一个默认实现可用。只有默认实现可以派生自ImmutablePass。这里我们声明BasicAliasAnalysis传递是接口的默认实现。