LLVM的分析和转换Passes

LLVM的分析和转换Passes

1 简介

本文档是LLVM提供的优化特性的高级总结。这些优化被作为Passes实现,它们遍历程序的某些部分以收集信息或转换程序。下面的列表将LLVM提供的Passes分为三类。分析Passes计算其他Passes可以使用或者用于调试或者程序可视化目的的信息。转换Passes可以使用分析Passes(或使分析Passes无效)。转换Passes都以某种方式改变程序。实用程序Passes提供了一些实用程序,但不适合分类。例如,将函数提取为bitcode或将一个Module写入bitcode的Passes既不是分析Passes,也不是转换Passes。上面的目录提供了每个pass的简洁摘要,并链接到文档后面更详细的pass描述。

2 分析Passes

本节描述LLVM分析Passes。

2.1 -aa-eval:详细的别名分析精确评估

这是一个简单的N^2别名分析精确评估。基本上,对于程序中的每个函数,它只是简单的查询别名分析实现来看如何回答函数中每对指针之间的别名查询。
该代码的灵感和改编来自:Naveen Neelakantam、Francesco Spadini和Wojciech Stryjewski。

2.2 -basicaa:基本别名分析(无状态别名分析实现)

一个基本的别名分析pass,它实现特性(两个不同的全局变量不能别名,等等),但不进行有状态分析。

2.3 -basiccg:基本的CallGraph结构
2.4 -count-aa:统计别名分析查询响应

可用于计算正在进行的别名查询数量和正在使用的别名分析实现如何响应的一个pass。

2.5 -da:依赖关系分析

依赖分析框架,用于检测内存访问中的依赖关系。

2.6 -debug-AA:使用debugger的别名分析

这个简单的pass检查别名分析用户,以确保如果他们创建了一个新值,他们不会在没有给AA赋值的情况下查询AA。
是的,跟踪程序中的每个值是很昂贵的,但是这是一个debug pass。

2.7 -domfrontier:支配边界构建

这是一个简单的支配边界构造算法,用于寻找前支配边界。

2.8 -domtree:支配树构建

这是一个简单的支配树构造算法,用于寻找前支配树。

2.9 -dot-callgraph:将调用图打印到“dot”文件中

此pass(仅在opt中可用)将调用图打印到.dot图中。然后可以使用“dot”工具处理此图,将其转换为postscript或其他合适的格式。

2.10 -dot-cfg:将函数的CFG打印到“dot”文件中

此pass(仅在opt中可用)将控制流图打印为.dot图。然后可以使用“dot”工具处理此图,将其转换为postscript或其他合适的格式。

2.11 -dot-cfg-only:将函数的CFG打印到“dot”文件中(没有函数体)

此pass(仅在opt中可用)将控制流图打印为.dot图,省略了函数体。然后可以使用“dot”工具处理此图,将其转换为postscript或其他合适的格式。

2.12 -dot-dom:将函数的支配树打印到“dot”文件

此pass(仅在opt中可用)将支配树打印到.dot图中。然后可以使用“dot”工具处理此图,将其转换为postscript或其他合适的格式。

2.13 -dot-dom-only:将函数的支配树打印到“dot”文件(没有函数体)

此pass(仅在opt中可用)将支配树打印到.dot图中,省略了函数体。然后可以使用“dot”工具处理此图,将其转换为postscript或其他合适的格式。

2.14 -dot-postdom:将函数的后支配树打印到“dot”文件

此pass(仅在opt中可用)将后支配树打印到.dot图中。然后可以使用“dot”工具处理此图,将其转换为postscript或其他合适的格式。

2.15 -dot-postdom-only:将函数的后支配树打印到“dot”文件(没有函数体)

此pass(仅在opt中可用)将后支配树打印到.dot图中,省略了函数体。然后可以使用“dot”工具处理此图,将其转换为postscript或其他合适的格式。

2.16 -globalsmodref-aa:对全局变量进行简单的mod/ref分析

这个简单的pass为没有取地址的全局值提供别名和mod/ref信息,并跟踪函数的读或写内存(“pure”)。对于这种简单(但非常常见)的情况,我们可以提供相当准确和有用的信息。

2.17 -instcount:对各种类型的指令计数

此pass收集所有指令的计数并报告它们。

2.18 -intervals:区间分区构建

该分析计算并表示函数的区间分区,或预先存在的区间分区。
通过这种方式,区间分区可以用于将流图简化为退化的单节点区间分区(除非它是不可约的)。

2.19 -iv-users:归纳变量Users

为从归纳变量表达式计算的“感兴趣”用户记账。

2.20 -lazy-value-info:Lazy值信息分析

用于lazy计算值约束信息的接口。

2.21 -libcall-aa:LibCall别名分析
2.22 -lint:静态lint-check LLVM IR

这将静态检查常见且容易识别的构造,这些构造在LLVM IR中产生未定义或可能意外的行为。
从两个方面来说,它不能保证正确性。首先,它并不全面。有些检查可以静态执行,但还没有实现。TODO的评论指出了其中一些问题,但这些问题也不全面。其次,许多condition不能静态检查。此pass不执行动态检测,因此不能检查所有可能的问题。
另一个限制是,它假定所有代码都将被执行。在基本块中通过一个空指针进行存储是无害的,但是无论如何,这个pass都会发出警告。
优化passes可能使此pass检查的条件或多或少变得明显。如果一个优化pass引入了警告,那么这个优化pass可能只是暴露了代码中一个存在的condition。
这段代码可以在instcombine之前运行。在许多情况下,对相同类型的东西进行instcombine检查,并将具有未定义行为的指令转换为不可访问的(或等效的)指令。正因为如此,这个pass将花一些功夫来查看bitcasts等等。

2.23 -loops:自然循环信息

该分析用于识别自然循环,并确定CFG各节点的循环深度。请注意,所标识的循环实际上可能是多个共享同一头节点的自然循环,而不仅仅是一个自然循环。

2.24 -memdep:内存依赖分析

一种分析,对于给定的一种内存操作,用于确定它所依赖的前置内存操作。它建立在别名分析信息的基础上,并试图为一种常见的别名信息查询提供一个lazy,缓存接口。

2.25 -module-debuginfo:解码Module级调试信息

此pass将解码一个Module中的调试信息元数据,并以(充分准备好的)人类可读的形式打印。
例如,从opt和-analyze选项一起运行这个pass,它将打印到标准输出。

2.26 -postdomfrontier:后支配边界构建

这是一个简单的后支配边界算法,用于寻找后支配边界。

2.27 -postdomtree:后支配树构建

这是一个简单的后支配树算法,用于寻找后支配树。

2.28 -print-alias-sets:别名集打印
2.29 -print-callgraph:打印调用图

此pass仅在opt中可用,它将调用图以人类可读的形式打印到standard error。

2.30 -print-callgraph-sccs:打印调用图的SCCs

此pass仅在opt中可用,它将调用图的SCCs以人类可读的形式打印到standard error。

2.31 -print-cfg-sccs:打印每个函数CFG的SCCs

此pass仅在opt中可用,它将每个函数CFG的SCCs以人类可读的形式打印到standard error。

2.32 -print-dom-info:支配树信息打印
2.33 -print-externalfnconstants:打印外部fn调用站点传递的常量

此pass仅在opt中可用,它将输出调用站点到使用常量参数调用的外部函数。这在寻找标准库函数时很有用,我们应该在别名分析中常量折叠或处理这些函数。

2.34 -print-function:将函数打印到stderr

这个PrintFunctionPass类被设计为与其他FunctionPasses管道连接,并在处理Module时打印出它们的函数。

2.35 -print-module:将Module打印到stderr

此pass当它在执行时简单的打印出整个Module。

2.36 -print-used-types:查找使用过的类型

此pass用于查找程序使用的所有类型。请注意,此分析显式地不包括仅由符号表使用的类型。

2.37 -regions:检测单个入口和单个出口区域

RegionInfo pass检测函数中的单个入口和单个出口区域,其中一个区域定义为仅在两个位置连接到其余图的任何子图。在此基础上,建立了层次区域树。

2.38 -scalar-evolution:标量进化分析

ScalarEvolution分析可以用来分析和分解循环中的标量表达式。它专门识别一般的归纳变量,用抽象和不透明的SCEV类表示它们。通过这种分析,可以得到循环的trip次数等重要性质。该分析主要用于归纳变量替换和强度降低。

2.39 -scev-aa:基于ScalarEvolution的别名分析

根据ScalarEvolution查询实现简单的别名分析。
这与传统的循环依赖分析不同,因为它测试循环的单个迭代中的依赖关系,而不是不同迭代之间的依赖关系。
相对于BasicAliasAnalysis 的ad-hoc分析的集合,ScalarEvolution对指针运算有更全面的理解。

2.40 -stack-safety:堆栈安全分析

StackSafety分析可用于确定是否可以认为分配给堆栈的变量不受内存访问错误的影响。
这项分析的主要目的是被sanitizers使用,以避免不必要的安全变量检测。

2.41 -targetdata:目标数据布局

提供其他passes访问有关目标ABI对各种数据类型所需的大小和对齐方式的信息。

3 转换Passes

本节描述LLVM转换Passes。

3.1 -adce:积极的死代码消除

ADCE积极地试图消除代码。此pass类似于DCE,但它假定值是死的,除非得到其他证明。这类似于SCCP,除了用于值的活动性。

3.2 -always-inline::always_inline函数的内联程序

自定义内联程序,只处理标记为“always_inline”的函数。

3.3 -argpromotion:将“by reference”参数提升到scalars实现

scalar:标量类型
此Pass将“by reference”参数提升为“by value”参数。实际上,这意味着寻找具有指针参数的内部函数。如果它可以通过别名分析证明只加载了参数(没有对参数进行改变),那么它就可以将值传递给函数,而不是传递值的地址。这可能导致代码的递归简化,并导致取消allocas(尤其是在STL之类的c++模板代码中)。(alloca是向栈申请内存)
此pass还处理传递给函数的聚合参数,如果只加载聚合元素,则对它们进行scalarize。注意,它拒绝将需要向函数传递三个以上操作数的聚合进行scalarize,因为为一个大数组或结构传递数千个操作数是没有益处的!
注意,对于仅存储到(而不是返回值)但当前没有存储到的参数,也可以执行此转换。这种情况最好在LLVM开始支持函数的多个返回值时处理。

3.4 -bb-vectorize:基本块向量化

此pass将基本块中的指令组合成向量指令。它遍历每个基本块,试图对兼容的指令进行配对,重复这个过程,直到没有为向量化选择额外的配对。当某对相容指令的输出被另一对相容指令用作输入时,这些对就是潜在向量化链的一部分。只有当指令对是长度超过某个阈值的链的一部分时,才会融合到向量指令中。此外,pass尝试为每对兼容指令找到可能的最佳链。这些启发式方法旨在防止向量化在不会提高结果代码性能的情况下发生。

3.5 -block-placement:配置文件引导的基本块放置

这个pass是一个非常简单的配置文件引导基本块放置算法。其思想是在函数开始时将经常执行的块放在一起,并希望增加跳过条件分支的数量。如果没有特定函数的配置文件信息,则这个pass基本上按深度优先顺序对块进行排序。

3.6 -break-crit-edges:断开CFG中的关键边

通过插入一个假的基本块打破CFG中的所有关键边。它可能是“必需的”对于不能处理关键边的passes。这种转换显然会使CFG失效,但是可以更新前支配(集合、立即支配 、树和边界)信息。

3.7 -codegenprepare:优化代码生成

这个Pass将输入函数中的代码进行合并,以便更好地为基于SelectionDAG的代码生成做好准备。这解决了basic-block-at-a-time方法的局限性。它最终应该被移除。

3.8 -constmerge:合并重复的全局常量

将重复的全局常量合并到一个共享的常量中。这是有用的,因为一些passes(即TraceValues)在程序中插入许多字符串常量,不管现有字符串是否可用。

3.9 -constprop:简单的常量传播

此pass实现常量传播和合并。它寻找只包含常量操作数的指令,并用常量值代替指令替换它们。例如:
add i32 1, 2变成i32 3
注意:此pass有使定义失效的习惯。在运行此pass之后运行死指令消除pass是一个好主意。

3.10 -dce:死代码消除

死代码消除类似于死指令消除,但它会重新检查已删除指令所使用的指令,以确定它们是否为新的死指令。

3.11 -deadargelim:死参数消除

此pass从内部函数中删除死参数。死参数消除删除直接死的参数,以及仅作为其他函数的死参数传递给函数调用的参数。此pass还以类似的方式删除死参数。
此pass通常作为清除pass非常有用,可以在主动的进程间passes之后运行,后者可能会添加死参数。

3.12 -deadtypeelim:死类型消除

此pass用于清除GCC的输出。它使用find used types pass消除了整个转换单元中未使用的类型的名称。

3.13 -die:死指令消除

死指令消除对函数执行一次遍历,删除明显已死的指令。

3.14 -dse:死存储消除

一个只考虑基本块本地冗余存储的简单死存储消除。

3.15 -functionattrs:推断函数属性

一个简单的过程间pass,它遍历调用图,寻找不访问或只读取非本地内存的函数,并将它们标记为readnone/readonly。此外,如果对函数的调用没有创建指针值的任何副本,则它将函数参数(指针类型的)标记为“nocapture”。这或多或少意味着指针只是取消引用,而不是从函数返回或存储在全局变量中。此pass被实现为调用图的自底向上遍历。

3.16 -globaldce:死全局变量消除

此转换旨在从程序中消除不可访问的内部全局变量。它使用一种主动的算法,搜索已知存在的全局变量。在找到所有需要的全局变量之后,删除剩下的所有变量。这允许它删除程序中不可访问的递归块。

3.17 -globalopt:全局变量优化:

此pass将转换从未获取其地址的简单全局变量。如果明显为真,则将读/写全局变量标记为常量,删除仅存储到的变量,等等。

3.18 -gvn:全局值编号

此pass执行全局值编号,以消除全部和部分冗余指令。它还执行冗余负载消除。

3.19 -indvars:规范化归纳变量

此转换将分析和转换归纳变量(以及由此派生的计算),并将其转换为适合于后续分析和转换的更简单的形式。
此转换对每个循环进行以下更改,其中包含一个可识别的归纳变量:

  • 所有循环都被转换为一个从0开始,按1步执行的规范归纳变量。
  • 正则归纳变量保证是循环头块中的第一个PHI节点。
  • 任何指针算术递归都将使用数组下标。

如果循环的trip计数是可计算的,那么这个pass也会进行以下更改:

  • 循环的退出条件被规范化,以便将归纳值与退出值进行比较。循环转换如下:
    for (i = 7; i*i < 1000; ++i) 转换为 for (i = 0; i != 25; ++i)
  • 从indvar派生的表达式在循环之外的任何使用都将被更改为计算循环之外的派生值,从而消除了对归纳变量的退出值的依赖。如果循环的唯一目的是计算某个派生表达式的出口值,则此转换将使循环死亡。

在执行了所有所需的循环转换之后,应该进行强度降低。此外,对于有利可图的目标,可以将循环转换为倒数为零(“do loop”优化)。

3.20 -inline:集成/内联函数

自底向上将函数内联到调用程序中。

3.21 -instcombine:合并冗余指令

将指令组合成更少、更简单的指令。此pass不修改CFG。这个pass就是代数化简的过程。
这个pass结合了以下内容:

%Y = add i32 %X, 1
%Z = add i32 %Y, 1

转换为 %Z = add i32 %X, 2
这是一个简单的工作列表驱动算法。
此pass确保程序执行以下规范化:

  1. 如果一个二进制操作符有一个常量操作数,它就被移到右边。
  2. 具有常量操作数的位操作符总是被分组,以便首先执行移位,然后执行or、ands和xors。
  3. 如果可能,比较指令从<、>、≤或≥转换为=或≠。
  4. 所有关于布尔值的cmp指令都替换为逻辑操作。
  5. 加上X, X表示为mul X, 2 ⇒ shl X, 1
  6. 以常数的二次方参数乘被转换成移位。
  7. …等等。
    此pass还可以简化对特定已知函数调用的调用(例如运行时库函数)。例如,main()函数中的调用exit(3)可以转换为简单的return 3。库调用是否简化由-functionattrs pass和LLVM对不同目标库调用的机制控制。
3.22 -aggressive-instcombine:组合表达式模式

将表达式模式组合起来,用更少的简单指令形成表达式。此pass不修改CFG。
例如,在适用时,此pass将由TruncInst后支配的表达式的宽度减小到更小的宽度。
它与instcombine pass的不同之处在于,它包含的模式优化要求比O(1)更高的复杂性,因此,它应该比instcombine pass运行更少的次数。

3.23 -internalize:内部化全局符号

这个pass循环遍历输入模块中的所有函数,寻找一个main函数。如果找到一个main函数,所有其他函数和所有带有初始化器的全局变量都被标记为内部变量。

3.24 -ipconstprop:过程间常量传播

此pass实现一个非常简单的过程间常量传播传递。它当然可以通过许多不同的方式进行改进,比如使用工作列表。此pass使参数死亡,但不删除它们。在此之后,应该运行现有的死参数消除pass来清理混乱。

3.25 -ipsccp:过程间稀疏条件常数传播

稀疏条件常数传播的过程间变体。

3.26 -jump-threading:跳转线程

跳转线程试图在基本块中找到不同的控制流线程。此pass查看具有多个前置和多个后继的块。如果可以证明块的一个或多个前置总是导致跳转到其中一个后继,则通过复制此块的内容将边从前置转发到后继。
发生这种情况的一个例子的代码如下:

if () { ...
  X = 4;
}
if (X < 3) {

在这种情况下,第一个if末尾的无条件分支可以被重新定向到第二个if的false端。

3.27 -lcssa:Loop-Closed SSA Form Pass

这个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。

3.28 -licm:循环不变的代码移动

此pass执行循环不变的代码移动,尝试从循环体中删除尽可能多的代码。它可以将代码提升到preheader块中,或者如果安全的话,将代码下沉到exit块中。此pass还促进循环中必须别名的内存位置驻留在寄存器中,从而提升和降低“不变”负载和存储。
此pass使用别名分析有两个目的:

  1. 将循环不变的加载和调用移出循环。如果我们能够确定循环内的加载或调用不会对存储的任何东西进行别名,我们就可以像其他指令一样提升或降低它。
  2. 内存的标量提升。如果在循环内部有一个存储指令,我们将尝试将存储移动到循环之后,而不是在循环内部。只有在以下几个条件为真时才会发生这种情况:
    1. 所存储的指针是循环不变的。
    2. 循环中没有可能别名指针的存储或加载。循环中没有对指针进行mod/ref的调用。

如果这些条件为真,我们可以提升指针循环中的加载和存储,以使用临时alloca 'd变量。然后,我们使用mem2reg功能为变量构造适当的SSA表单。

3.29 -loop-deletion:删除死循环

此文件实现死循环删除pass。此pass负责消除具有非无限可计算trip计数的循环,这些循环没有副作用或易变指令,并且不参与函数返回值的计算。

3.30 -loop-extract:将循环提取到新函数中

此pass封装ExtractLoop()标量转换,用于将每个顶级循环提取到它自己的新函数中。如果循环是给定函数中的唯一循环,则不会触及它。这是通过bugpoint进行调试时最有用的一种方法。

3.31 -loop-extract-single:将最多一个循环提取到一个新函数中

类似于将循环提取到新函数中,如果可以,此pass将从程序中提取一个自然循环到函数中。这是由bugpoint使用的。

3.32 -loop-reduce:循环强度降低

此pass对循环内部的数组引用执行强度降低,这些循环中有一个或多个组件,即循环诱导变量。这是通过创建一个新值来保存第一次迭代的数组访问的初始值,然后在循环中创建一个新的GEP指令来将值增加适当的数量来实现的。

3.33 -loop-rotate:旋转循环

一个简单的循环旋转变换。

3.34 -loop-simplify:将自然循环规范化

此pass执行几个转换,将自然循环转换为更简单的形式,从而使后续的分析和转换更简单、更有效。
循环pre-header插入确保从循环外部到循环header只有一个非关键的入口边。这简化了许多分析和转换,比如LICM。
循环exit-block插入保证了循环中的所有退出块(位于循环外部的块,其前置位于循环内部)只有来自循环内部的前置(因此由循环header支配)。这简化了内置于LICM中的转换,比如存储下沉(store-sinking)。
此pass还确保循环将恰好有一个backedge。
注意simplifycfg pass将清理被分割出来但最终没有必要的块,因此使用该pass不应该使生成的代码变得悲观。
此pass显然修改了CFG,但更新了循环信息和支配信息。

3.35 -loop-unroll:展开循环

此pass实现一个简单的循环展开器。当循环被indvars pass规范化时,它的工作效率最高,这使得它可以很容易地确定循环的trip计数。

3.36 -loop-unroll-and-jam: 展开和阻塞循环

该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

这可以看作是将展开外部循环和“阻塞”(融合)内部循环合并成一个。当变量或负载可以在新的内部循环中共享时,这可以导致显著的性能改进。它使用依赖分析来证明转换是安全的。

3.37 -loop-unswitch:Unswitch循环

此pass将包含循环不变条件上分支的循环转换为多个循环。例如,它把左边变成右边的代码:

for (...)                  if (lic)
    A                          for (...)
    if (lic)                       A; B; C
        B                  else
    C                          for (...)
                                   A; C

这可以成倍地增加代码的大小(每次循环unswitched时将其翻倍),所以只有当最终代码小于阈值时,我们才会unswitch。
此pass预期在将不变条件从循环中取出之前运行LICM,以使unswitch机会变得明显。

3.38 -loweratomic:降低原子内嵌原语到非原子形式

此pass将原子内嵌原语降低到非原子形式,以便在已知的不可抢占环境中使用。
此pass不验证环境是不可抢占的(一般来说,这需要了解程序的整个调用图,包括任何可能无法以bitcode形式提供的库);它只是降低了每一个原子内嵌原语。

3.39 -lowerinvoke:降低invokes到calls,用于unwindless代码生成器

这种转换是为还不支持堆栈展开的代码生成器而设计的。此pass将invoke指令转换为call指令,因此任何异常处理landingpad块都将成为死代码(可以通过稍后运行-simplifycfg pass来删除)。

3.40 -lowerswitch:降低SwitchInsts到branches

用一系列branches重写switch指令,这使得目标在没有实现switch指令时逃脱,直到它空闲。

3.41 -mem2reg:促进内存引用为寄存器引用

这个文件将内存引用促进为寄存器引用。它促进alloca指令,其中只有load和store作为使用。alloca的转换是通过使用支配边界来放置phi节点,然后按深度优先顺序遍历函数来重写load和store。这只是标准的SSA构造算法来构造“修剪”的SSA形式。

3.42 -memcpyopt:MemCpy优化

此pass执行与消除memcpy调用或将存储集转换为memset相关的各种转换。

3.43 -mergefunc:合并函数

此pass查找可合并的等价函数并折叠它们。
在函数集中引入了total-ordering:我们定义了对应于每两个函数中哪个更大的比较。它允许将函数排列到二叉树中。
对于每一个新函数,我们都要在树中检查其等价性。
如果等价存在,我们折叠这些函数。如果这两个函数都是可覆盖的,那么我们将该函数移动到一个新的内部函数中,并给它留下两个可覆盖的重击。
如果没有相等的,那么我们将这个函数添加到树中。
查找例程复杂度为O(log(n)),而整个合并过程复杂度为O(n*log(n))。
阅读本文了解更多细节。

3.44 -mergereturn:统一函数退出节点

确保函数中最多有一个ret指令。此外,它还跟踪哪个节点是CFG的新退出节点。

3.45 -partial-inliner:部分内联

此pass执行部分内联,通常通过内联包围函数主体的if语句。

3.46 -prune-eh:删除未使用的异常处理信息

这个文件实现了一个简单的过程间pass,它遍历调用图,当且仅当被调用方不能抛出异常时,将invoke指令转换为call指令。它将此实现为调用图的自底向上遍历。

3.47 -reassociate:重新组合表达式

此pass将交换表达式重新组合,以促进更好的常量传播、GCSE、LICM、PRE等。
例如:4 + (x + 5)x + (4 + 5)
在这个算法的实现,常量分配rank = 0,函数参数是rank = 0,其他值分配rank对应当前函数的反向后序遍历(从2开始),有效地赋予深循环中的值比非循环中的值更高的rank。

3.48 -reg2mem:将所有值降级到堆栈槽

该文件将所有寄存器引用降级为内存引用。它是mem2reg的反转。通过转换为load指令,基本块之间的唯一值是phi节点之前的alloca指令和load指令。这样做的目的是使CFG hack更加容易。为了使以后的hack更容易,入口块被分成两个,这样所有引入的alloca指令(没有其他内容)都在入口块中。

3.49 -sroa:聚合的标量替换

众所周知的聚合变换的标量替换。如果可能,这个转换将聚合类型(结构或数组)的alloca指令分解为每个成员的单独alloca指令。然后,如果可能,它将单个alloca指令转换为漂亮的纯标量SSA形式。

3.50 -sccp:稀疏条件常数传播

稀疏条件常数的传播与合并,可概括为:

  • 假设值是常数,除非另有证明
  • 假定基本块已死,除非另有证明
  • 证明值是常量,并用常量替换它们
  • 证明条件分支是无条件的
    注意,此pass有一个使定义失效的特点,在运行此pass之后运行DCE pass是一个好主意。
3.51 -simplifycfg:简化CFG

执行死代码消除和基本块合并。具体地说:

  • 删除没有前置的基本块。
  • 如果一个基本块只有一个前置块,并且该前置块只有一个后继块,则将该基本块合并到其前置块中。
  • 为只有一个前置的基本块消除PHI节点。
  • 消除只包含无条件分支的基本块。
3.52 -sink:代码下沉

如果可能的话,此pass将指令移动到后继块中,这样它们就不会在不需要结果的路径上执行。

3.53 -strip:从一个模块中剥离所有符号

执行代码剥离。这个转换可以删除:

  • 虚拟寄存器的名称
  • 内部全局值和函数的符号
  • 调试信息
    注意,这种转换使代码可读性差得多,因此只应该在使用剥离实用程序的情况下使用它,例如减小代码大小或使逆向工程代码更加困难。
3.54 -strip-dead-debug-info:剥离未使用符号的调试信息

执行代码剥离。这个转换可以删除:

  • 虚拟寄存器的名称
  • 内部全局值和函数的符号
  • 调试信息
    注意,这种转换使代码可读性差得多,因此只应该在使用剥离实用程序的情况下使用它,例如减小代码大小或使逆向工程代码更加困难。
3.55 -strip-dead-prototypes:剥离未使用的函数原型

这个循环遍历输入模块中的所有函数,寻找死声明并删除它们。死声明是没有实现的函数的声明(即未使用库函数的声明)。

3.56 -strip-debug-declare:剥离所有llvm.dbg.declare内嵌原语

此pass实现代码剥离。具体来说,它可以删除:

  • 虚拟寄存器的名称
  • 内部全局值和函数的符号
  • 调试信息
    注意,这种转换使代码可读性差得多,因此只应该在使用剥离实用程序的情况下使用它,例如减小代码大小或使逆向工程代码更加困难。
3.57 -strip-nondebug:从模块中剥离除dbg符号外的所有符号

此pass实现代码剥离。具体来说,它可以删除:

  • 虚拟寄存器的名称
  • 内部全局值和函数的符号
  • 调试信息
    注意,这种转换使代码可读性差得多,因此只应该在使用剥离实用程序的情况下使用它,例如减小代码大小或使逆向工程代码更加困难。
3.58 -tailcallelim:尾部调用消除

该文件将当前函数(自递归)的调用转换为函数入口的返回指令(带有分支),从而创建一个循环。此pass还实现了对基本算法的以下扩展:

  1. 调用和返回之间的简单指令并不能阻止转换的发生,尽管目前的分析不能支持移动任何真正有用的指令(只有死指令)。
  2. 此pass将关联表达式阻止尾部递归的函数转换为使用累加器变量,从而将典型的简单阶乘或fib实现编译为高效代码。
  3. 如果函数返回void,如果返回返回调用返回的结果,或者如果函数返回函数所有出口的运行时常量,则执行TRE。有可能,尽管不太可能,返回返回一些其他的东西(比如常量0),并且仍然可以被执行。如果函数中的所有其他返回指令返回完全相同的值,则可以重新设置。
  4. 如果它能证明被调用者不访问它们的调用者堆栈帧,它们就被标记为有资格进行尾部调用消除(由代码生成器)。

4 实用程序Passes

本节描述LLVM实用程序Passes。

4.1 -deadarghaX0r:死参数hack(仅用于bugpoint使用;不要使用)

与死参数消除相同,但删除外部函数的参数。这仅供bugpoint使用。

4.2 -extract-blocks:从模块中提取基本块(用于bugpoint使用)

此pass被bugpoint使用将模块中的所有块提取到它们自己的函数中。

4.3 -instnamer:为匿名指令指定名称

这是一个提供指令名称的小实用程序pass,这在差异化优化效果时非常有用,因为删除未命名的指令可以更改所有其他指令编号,使差异非常大。

4.4 -verify:模块验证器

验证一个LLVM IR代码。这对于正在进行测试的优化之后运行非常有用。请注意,在LLVM -as发送bitcode之前验证其输入,而且不正确的bitcode很可能导致LLVM崩溃。因此,鼓励所有语言前端在执行优化转换之前验证它们的输出。

  1. 二进制操作符的两个参数都具有相同的类型。
  2. 验证mem访问指令的索引是否匹配其他操作数。
  3. 验证算术和其他操作只在第一类类型上执行。验证移位和逻辑只发生在积分f.e上。
  4. switch语句中的所有常量都是正确的类型。
  5. 代码是有效的SSA形式。
  6. 将标签放入任何其他类型(如结构)或返回一个标签都是非法的。
  7. 只有phi节点可以是自引用的:%x =添加i32% x, %x无效。
  8. PHI节点必须为每个前辈都有一个条目,没有额外的条目。
  9. 节点必须是基本块中的第一个元素,它们都被分组在一起。
  10. 节点必须至少有一个元素。
  11. 所有基本块都应该以终止符insts结束,而不是包含它们。
  12. 函数的入口节点必须没有前项。
  13. 所有指令都必须嵌入到一个基本块中。
  14. 函数不能接受空类型参数。
  15. 验证函数的参数列表是否与其声明的类型一致。
  16. 为空值指定名称是非法的。
  17. 没有初始化器的内部全局值是非法的。
  18. 使用ret指令返回与函数返回值类型不一致的值是非法的。
  19. 函数调用参数类型匹配函数原型。
  20. 由断言测试的所有其他内容都与代码有关。
    注意,这并没有提供完整的安全验证(如Java),而只是尝试确保代码格式良好。
4.5 -view-cfg:查看函数的CFG

使用GraphViz工具显示控制流图。

4.6 -view-cfg-only:查看函数的CFG(没有函数体)

使用GraphViz工具显示控制流图,但省略了函数体。

4.7 -view-dom:查看函数的支配树

使用GraphViz工具显示支配树。

4.8 -view-dom-only:查看函数的支配树(没有函数体)

使用GraphViz工具显示支配树,但省略了函数体。

4.9 -view-postdom:查看函数的后支配树

使用GraphViz工具显示后支配树。

4.10 -view-postdom-only:查看函数的后支配树(没有函数体)

使用GraphViz工具显示后支配树,但省略了函数体。

4.11 -transform-warning:报告遗漏强制转换

发出关于尚未应用强制转换的警告(例如来自#pragma omp simd)。

猜你喜欢

转载自blog.csdn.net/qq_23599965/article/details/88344459