本文档是LLVM提供的优化特性的高级总结。这些优化被作为Passes实现,它们遍历程序的某些部分以收集信息或转换程序。下面的列表将LLVM提供的Passes分为三类。分析Passes计算其他Passes可以使用或者用于调试或者程序可视化目的的信息。转换Passes可以使用分析Passes(或使分析Passes无效)。转换Passes都以某种方式改变程序。实用程序Passes提供了一些实用程序,但不适合分类。例如,将函数提取为bitcode或将一个Module写入bitcode的Passes既不是分析Passes,也不是转换Passes。上面的目录提供了每个pass的简洁摘要,并链接到文档后面更详细的pass描述。
本节描述LLVM分析Passes。
这是一个简单的N^2别名分析精确评估。基本上,对于程序中的每个函数,它只是简单的查询别名分析实现来看如何回答函数中每对指针之间的别名查询。
该代码的灵感和改编来自:Naveen Neelakantam、Francesco Spadini和Wojciech Stryjewski。
一个基本的别名分析pass,它实现特性(两个不同的全局变量不能别名,等等),但不进行有状态分析。
可用于计算正在进行的别名查询数量和正在使用的别名分析实现如何响应的一个pass。
依赖分析框架,用于检测内存访问中的依赖关系。
这个简单的pass检查别名分析用户,以确保如果他们创建了一个新值,他们不会在没有给AA赋值的情况下查询AA。
是的,跟踪程序中的每个值是很昂贵的,但是这是一个debug pass。
这是一个简单的支配边界构造算法,用于寻找前支配边界。
这是一个简单的支配树构造算法,用于寻找前支配树。
此pass(仅在opt中可用)将调用图打印到.dot图中。然后可以使用“dot”工具处理此图,将其转换为postscript或其他合适的格式。
此pass(仅在opt中可用)将控制流图打印为.dot图。然后可以使用“dot”工具处理此图,将其转换为postscript或其他合适的格式。
此pass(仅在opt中可用)将控制流图打印为.dot图,省略了函数体。然后可以使用“dot”工具处理此图,将其转换为postscript或其他合适的格式。
此pass(仅在opt中可用)将支配树打印到.dot图中。然后可以使用“dot”工具处理此图,将其转换为postscript或其他合适的格式。
此pass(仅在opt中可用)将支配树打印到.dot图中,省略了函数体。然后可以使用“dot”工具处理此图,将其转换为postscript或其他合适的格式。
此pass(仅在opt中可用)将后支配树打印到.dot图中。然后可以使用“dot”工具处理此图,将其转换为postscript或其他合适的格式。
此pass(仅在opt中可用)将后支配树打印到.dot图中,省略了函数体。然后可以使用“dot”工具处理此图,将其转换为postscript或其他合适的格式。
这个简单的pass为没有取地址的全局值提供别名和mod/ref信息,并跟踪函数的读或写内存(“pure”)。对于这种简单(但非常常见)的情况,我们可以提供相当准确和有用的信息。
此pass收集所有指令的计数并报告它们。
该分析计算并表示函数的区间分区,或预先存在的区间分区。
通过这种方式,区间分区可以用于将流图简化为退化的单节点区间分区(除非它是不可约的)。
为从归纳变量表达式计算的“感兴趣”用户记账。
用于lazy计算值约束信息的接口。
这将静态检查常见且容易识别的构造,这些构造在LLVM IR中产生未定义或可能意外的行为。
从两个方面来说,它不能保证正确性。首先,它并不全面。有些检查可以静态执行,但还没有实现。TODO的评论指出了其中一些问题,但这些问题也不全面。其次,许多condition不能静态检查。此pass不执行动态检测,因此不能检查所有可能的问题。
另一个限制是,它假定所有代码都将被执行。在基本块中通过一个空指针进行存储是无害的,但是无论如何,这个pass都会发出警告。
优化passes可能使此pass检查的条件或多或少变得明显。如果一个优化pass引入了警告,那么这个优化pass可能只是暴露了代码中一个存在的condition。
这段代码可以在instcombine之前运行。在许多情况下,对相同类型的东西进行instcombine检查,并将具有未定义行为的指令转换为不可访问的(或等效的)指令。正因为如此,这个pass将花一些功夫来查看bitcasts等等。
该分析用于识别自然循环,并确定CFG各节点的循环深度。请注意,所标识的循环实际上可能是多个共享同一头节点的自然循环,而不仅仅是一个自然循环。
一种分析,对于给定的一种内存操作,用于确定它所依赖的前置内存操作。它建立在别名分析信息的基础上,并试图为一种常见的别名信息查询提供一个lazy,缓存接口。
此pass将解码一个Module中的调试信息元数据,并以(充分准备好的)人类可读的形式打印。
例如,从opt和-analyze选项一起运行这个pass,它将打印到标准输出。
这是一个简单的后支配边界算法,用于寻找后支配边界。
这是一个简单的后支配树算法,用于寻找后支配树。
此pass仅在opt中可用,它将调用图以人类可读的形式打印到standard error。
此pass仅在opt中可用,它将调用图的SCCs以人类可读的形式打印到standard error。
此pass仅在opt中可用,它将每个函数CFG的SCCs以人类可读的形式打印到standard error。
此pass仅在opt中可用,它将输出调用站点到使用常量参数调用的外部函数。这在寻找标准库函数时很有用,我们应该在别名分析中常量折叠或处理这些函数。
这个PrintFunctionPass类被设计为与其他FunctionPasses管道连接,并在处理Module时打印出它们的函数。
此pass当它在执行时简单的打印出整个Module。
此pass用于查找程序使用的所有类型。请注意,此分析显式地不包括仅由符号表使用的类型。
RegionInfo pass检测函数中的单个入口和单个出口区域,其中一个区域定义为仅在两个位置连接到其余图的任何子图。在此基础上,建立了层次区域树。
ScalarEvolution分析可以用来分析和分解循环中的标量表达式。它专门识别一般的归纳变量,用抽象和不透明的SCEV类表示它们。通过这种分析,可以得到循环的trip次数等重要性质。该分析主要用于归纳变量替换和强度降低。
根据ScalarEvolution查询实现简单的别名分析。
这与传统的循环依赖分析不同,因为它测试循环的单个迭代中的依赖关系,而不是不同迭代之间的依赖关系。
相对于BasicAliasAnalysis 的ad-hoc分析的集合,ScalarEvolution对指针运算有更全面的理解。
StackSafety分析可用于确定是否可以认为分配给堆栈的变量不受内存访问错误的影响。
这项分析的主要目的是被sanitizers使用,以避免不必要的安全变量检测。
提供其他passes访问有关目标ABI对各种数据类型所需的大小和对齐方式的信息。
本节描述LLVM转换Passes。
ADCE积极地试图消除代码。此pass类似于DCE,但它假定值是死的,除非得到其他证明。这类似于SCCP,除了用于值的活动性。
自定义内联程序,只处理标记为“always_inline”的函数。
scalar:标量类型
此Pass将“by reference”参数提升为“by value”参数。实际上,这意味着寻找具有指针参数的内部函数。如果它可以通过别名分析证明只加载了参数(没有对参数进行改变),那么它就可以将值传递给函数,而不是传递值的地址。这可能导致代码的递归简化,并导致取消allocas(尤其是在STL之类的c++模板代码中)。(alloca是向栈申请内存)
此pass还处理传递给函数的聚合参数,如果只加载聚合元素,则对它们进行scalarize。注意,它拒绝将需要向函数传递三个以上操作数的聚合进行scalarize,因为为一个大数组或结构传递数千个操作数是没有益处的!
注意,对于仅存储到(而不是返回值)但当前没有存储到的参数,也可以执行此转换。这种情况最好在LLVM开始支持函数的多个返回值时处理。
此pass将基本块中的指令组合成向量指令。它遍历每个基本块,试图对兼容的指令进行配对,重复这个过程,直到没有为向量化选择额外的配对。当某对相容指令的输出被另一对相容指令用作输入时,这些对就是潜在向量化链的一部分。只有当指令对是长度超过某个阈值的链的一部分时,才会融合到向量指令中。此外,pass尝试为每对兼容指令找到可能的最佳链。这些启发式方法旨在防止向量化在不会提高结果代码性能的情况下发生。
这个pass是一个非常简单的配置文件引导基本块放置算法。其思想是在函数开始时将经常执行的块放在一起,并希望增加跳过条件分支的数量。如果没有特定函数的配置文件信息,则这个pass基本上按深度优先顺序对块进行排序。
通过插入一个假的基本块打破CFG中的所有关键边。它可能是“必需的”对于不能处理关键边的passes。这种转换显然会使CFG失效,但是可以更新前支配(集合、立即支配 、树和边界)信息。
这个Pass将输入函数中的代码进行合并,以便更好地为基于SelectionDAG的代码生成做好准备。这解决了basic-block-at-a-time方法的局限性。它最终应该被移除。
将重复的全局常量合并到一个共享的常量中。这是有用的,因为一些passes(即TraceValues)在程序中插入许多字符串常量,不管现有字符串是否可用。
此pass实现常量传播和合并。它寻找只包含常量操作数的指令,并用常量值代替指令替换它们。例如:
add i32 1, 2
变成i32 3
注意:此pass有使定义失效的习惯。在运行此pass之后运行死指令消除pass是一个好主意。
死代码消除类似于死指令消除,但它会重新检查已删除指令所使用的指令,以确定它们是否为新的死指令。
此pass从内部函数中删除死参数。死参数消除删除直接死的参数,以及仅作为其他函数的死参数传递给函数调用的参数。此pass还以类似的方式删除死参数。
此pass通常作为清除pass非常有用,可以在主动的进程间passes之后运行,后者可能会添加死参数。
此pass用于清除GCC的输出。它使用find used types pass消除了整个转换单元中未使用的类型的名称。
死指令消除对函数执行一次遍历,删除明显已死的指令。
一个只考虑基本块本地冗余存储的简单死存储消除。
一个简单的过程间pass,它遍历调用图,寻找不访问或只读取非本地内存的函数,并将它们标记为readnone/readonly。此外,如果对函数的调用没有创建指针值的任何副本,则它将函数参数(指针类型的)标记为“nocapture”。这或多或少意味着指针只是取消引用,而不是从函数返回或存储在全局变量中。此pass被实现为调用图的自底向上遍历。
此转换旨在从程序中消除不可访问的内部全局变量。它使用一种主动的算法,搜索已知存在的全局变量。在找到所有需要的全局变量之后,删除剩下的所有变量。这允许它删除程序中不可访问的递归块。
此pass将转换从未获取其地址的简单全局变量。如果明显为真,则将读/写全局变量标记为常量,删除仅存储到的变量,等等。
此pass执行全局值编号,以消除全部和部分冗余指令。它还执行冗余负载消除。
此转换将分析和转换归纳变量(以及由此派生的计算),并将其转换为适合于后续分析和转换的更简单的形式。
此转换对每个循环进行以下更改,其中包含一个可识别的归纳变量:
如果循环的trip计数是可计算的,那么这个pass也会进行以下更改:
for (i = 7; i*i < 1000; ++i)
转换为 for (i = 0; i != 25; ++i)
在执行了所有所需的循环转换之后,应该进行强度降低。此外,对于有利可图的目标,可以将循环转换为倒数为零(“do loop”优化)。
自底向上将函数内联到调用程序中。
将指令组合成更少、更简单的指令。此pass不修改CFG。这个pass就是代数化简的过程。
这个pass结合了以下内容:
%Y = add i32 %X, 1
%Z = add i32 %Y, 1
转换为 %Z = add i32 %X, 2
这是一个简单的工作列表驱动算法。
此pass确保程序执行以下规范化:
mul X, 2 ⇒ shl X, 1
将表达式模式组合起来,用更少的简单指令形成表达式。此pass不修改CFG。
例如,在适用时,此pass将由TruncInst后支配的表达式的宽度减小到更小的宽度。
它与instcombine pass的不同之处在于,它包含的模式优化要求比O(1)更高的复杂性,因此,它应该比instcombine pass运行更少的次数。
这个pass循环遍历输入模块中的所有函数,寻找一个main函数。如果找到一个main函数,所有其他函数和所有带有初始化器的全局变量都被标记为内部变量。
此pass实现一个非常简单的过程间常量传播传递。它当然可以通过许多不同的方式进行改进,比如使用工作列表。此pass使参数死亡,但不删除它们。在此之后,应该运行现有的死参数消除pass来清理混乱。
稀疏条件常数传播的过程间变体。
跳转线程试图在基本块中找到不同的控制流线程。此pass查看具有多个前置和多个后继的块。如果可以证明块的一个或多个前置总是导致跳转到其中一个后继,则通过复制此块的内容将边从前置转发到后继。
发生这种情况的一个例子的代码如下:
if () { ...
X = 4;
}
if (X < 3) {
在这种情况下,第一个if末尾的无条件分支可以被重新定向到第二个if的false端。
这个pass通过将phi节点放置在循环的末尾来对循环边界上的所有值进行循环转换。例如,它把左边变成右边的代码:
for (...) for (...)
if (c) if (c)
X1 = ... X1 = ...
else else
X2 = ... X2 = ...
X3 = phi(X1, X2) X3 = phi(X1, X2)
... = X3 + 4 X4 = phi(X3)
... = X4 + 4
这仍然是有效的LLVM;额外的节点是完全冗余的,通过InstCombine可以很容易地消除。这种转换的主要好处是,它简化了许多其他循环优化,比如loop unswitch。
此pass执行循环不变的代码移动,尝试从循环体中删除尽可能多的代码。它可以将代码提升到preheader块中,或者如果安全的话,将代码下沉到exit块中。此pass还促进循环中必须别名的内存位置驻留在寄存器中,从而提升和降低“不变”负载和存储。
此pass使用别名分析有两个目的:
如果这些条件为真,我们可以提升指针循环中的加载和存储,以使用临时alloca 'd变量。然后,我们使用mem2reg功能为变量构造适当的SSA表单。
此文件实现死循环删除pass。此pass负责消除具有非无限可计算trip计数的循环,这些循环没有副作用或易变指令,并且不参与函数返回值的计算。
此pass封装ExtractLoop()标量转换,用于将每个顶级循环提取到它自己的新函数中。如果循环是给定函数中的唯一循环,则不会触及它。这是通过bugpoint进行调试时最有用的一种方法。
类似于将循环提取到新函数中,如果可以,此pass将从程序中提取一个自然循环到函数中。这是由bugpoint使用的。
此pass对循环内部的数组引用执行强度降低,这些循环中有一个或多个组件,即循环诱导变量。这是通过创建一个新值来保存第一次迭代的数组访问的初始值,然后在循环中创建一个新的GEP指令来将值增加适当的数量来实现的。
一个简单的循环旋转变换。
此pass执行几个转换,将自然循环转换为更简单的形式,从而使后续的分析和转换更简单、更有效。
循环pre-header插入确保从循环外部到循环header只有一个非关键的入口边。这简化了许多分析和转换,比如LICM。
循环exit-block插入保证了循环中的所有退出块(位于循环外部的块,其前置位于循环内部)只有来自循环内部的前置(因此由循环header支配)。这简化了内置于LICM中的转换,比如存储下沉(store-sinking)。
此pass还确保循环将恰好有一个backedge。
注意simplifycfg pass将清理被分割出来但最终没有必要的块,因此使用该pass不应该使生成的代码变得悲观。
此pass显然修改了CFG,但更新了循环信息和支配信息。
此pass实现一个简单的循环展开器。当循环被indvars pass规范化时,它的工作效率最高,这使得它可以很容易地确定循环的trip计数。
该pass实现了一个简单的展开和阻塞经典循环优化pass。它将转换循环如下:
for i.. i+= 1 for i.. i+= 4
for j.. for j..
code(i, j) code(i, j)
code(i+1, j)
code(i+2, j)
code(i+3, j)
remainder loop
这可以看作是将展开外部循环和“阻塞”(融合)内部循环合并成一个。当变量或负载可以在新的内部循环中共享时,这可以导致显著的性能改进。它使用依赖分析来证明转换是安全的。
此pass将包含循环不变条件上分支的循环转换为多个循环。例如,它把左边变成右边的代码:
for (...) if (lic)
A for (...)
if (lic) A; B; C
B else
C for (...)
A; C
这可以成倍地增加代码的大小(每次循环unswitched时将其翻倍),所以只有当最终代码小于阈值时,我们才会unswitch。
此pass预期在将不变条件从循环中取出之前运行LICM,以使unswitch机会变得明显。
此pass将原子内嵌原语降低到非原子形式,以便在已知的不可抢占环境中使用。
此pass不验证环境是不可抢占的(一般来说,这需要了解程序的整个调用图,包括任何可能无法以bitcode形式提供的库);它只是降低了每一个原子内嵌原语。
这种转换是为还不支持堆栈展开的代码生成器而设计的。此pass将invoke指令转换为call指令,因此任何异常处理landingpad块都将成为死代码(可以通过稍后运行-simplifycfg pass来删除)。
用一系列branches重写switch指令,这使得目标在没有实现switch指令时逃脱,直到它空闲。
这个文件将内存引用促进为寄存器引用。它促进alloca指令,其中只有load和store作为使用。alloca的转换是通过使用支配边界来放置phi节点,然后按深度优先顺序遍历函数来重写load和store。这只是标准的SSA构造算法来构造“修剪”的SSA形式。
此pass执行与消除memcpy调用或将存储集转换为memset相关的各种转换。
此pass查找可合并的等价函数并折叠它们。
在函数集中引入了total-ordering:我们定义了对应于每两个函数中哪个更大的比较。它允许将函数排列到二叉树中。
对于每一个新函数,我们都要在树中检查其等价性。
如果等价存在,我们折叠这些函数。如果这两个函数都是可覆盖的,那么我们将该函数移动到一个新的内部函数中,并给它留下两个可覆盖的重击。
如果没有相等的,那么我们将这个函数添加到树中。
查找例程复杂度为O(log(n)),而整个合并过程复杂度为O(n*log(n))。
阅读本文了解更多细节。
确保函数中最多有一个ret指令。此外,它还跟踪哪个节点是CFG的新退出节点。
此pass执行部分内联,通常通过内联包围函数主体的if语句。
这个文件实现了一个简单的过程间pass,它遍历调用图,当且仅当被调用方不能抛出异常时,将invoke指令转换为call指令。它将此实现为调用图的自底向上遍历。
此pass将交换表达式重新组合,以促进更好的常量传播、GCSE、LICM、PRE等。
例如:4 + (x + 5)
⇒ x + (4 + 5)
在这个算法的实现,常量分配rank = 0,函数参数是rank = 0,其他值分配rank对应当前函数的反向后序遍历(从2开始),有效地赋予深循环中的值比非循环中的值更高的rank。
该文件将所有寄存器引用降级为内存引用。它是mem2reg的反转。通过转换为load指令,基本块之间的唯一值是phi节点之前的alloca指令和load指令。这样做的目的是使CFG hack更加容易。为了使以后的hack更容易,入口块被分成两个,这样所有引入的alloca指令(没有其他内容)都在入口块中。
众所周知的聚合变换的标量替换。如果可能,这个转换将聚合类型(结构或数组)的alloca指令分解为每个成员的单独alloca指令。然后,如果可能,它将单个alloca指令转换为漂亮的纯标量SSA形式。
稀疏条件常数的传播与合并,可概括为:
执行死代码消除和基本块合并。具体地说:
如果可能的话,此pass将指令移动到后继块中,这样它们就不会在不需要结果的路径上执行。
执行代码剥离。这个转换可以删除:
执行代码剥离。这个转换可以删除:
这个循环遍历输入模块中的所有函数,寻找死声明并删除它们。死声明是没有实现的函数的声明(即未使用库函数的声明)。
此pass实现代码剥离。具体来说,它可以删除:
此pass实现代码剥离。具体来说,它可以删除:
该文件将当前函数(自递归)的调用转换为函数入口的返回指令(带有分支),从而创建一个循环。此pass还实现了对基本算法的以下扩展:
本节描述LLVM实用程序Passes。
与死参数消除相同,但删除外部函数的参数。这仅供bugpoint使用。
此pass被bugpoint使用将模块中的所有块提取到它们自己的函数中。
这是一个提供指令名称的小实用程序pass,这在差异化优化效果时非常有用,因为删除未命名的指令可以更改所有其他指令编号,使差异非常大。
验证一个LLVM IR代码。这对于正在进行测试的优化之后运行非常有用。请注意,在LLVM -as发送bitcode之前验证其输入,而且不正确的bitcode很可能导致LLVM崩溃。因此,鼓励所有语言前端在执行优化转换之前验证它们的输出。
使用GraphViz工具显示控制流图。
使用GraphViz工具显示控制流图,但省略了函数体。
使用GraphViz工具显示支配树。
使用GraphViz工具显示支配树,但省略了函数体。
使用GraphViz工具显示后支配树。
使用GraphViz工具显示后支配树,但省略了函数体。
发出关于尚未应用强制转换的警告(例如来自#pragma omp simd
)。