LLVM’s Analysis and Transform Passes

1.概述

本文档是LLVM提供的优化功能的高级摘要。优化是作为一个过程来实现的,该过程遍历程序的某些部分,以收集信息或转换程序。下表将LLVM提供的过程分为三类。分析过程计算其他过程可以使用或用于调试或程序可视化目的的信息。转换过程可以使用(或使分析过程无效)。转换以某种方式传递所有的转换程序。实用程序传递提供了一些实用程序,但不适合分类。例如,将函数提取到位码或将模块写入位码的过程既不是分析过程,也不是转换过程。上面的目录提供了每个过程的快速摘要,并链接到文档后面更完整的过程描述。

2.Analysis Passes

本节介绍LLVM分析过程。

2.1.-aa-eval:详尽的别名分析精确度评估器
这是一个简单的N ^ 2别名分析精度评估器。 基本上,对于程序中的每个函数,它只是查询以查看别名分析实现如何回答函数中每对指针之间的别名查询。

这是由Naveen Neelakantam,Francesco Spadini和Wojciech Stryjewski编写的。

2.2.-basicaa:基本别名分析(无状态AA impl)
实现身份的基本别名分析传递(两个不同的全局变量不能别名等),但不进行有状态分析。

2.3.-basiccg:基本的CallGraph构造
还有待写。

2.4.-count-aa:计算别名分析查询响应
一个传递,可用于计算正在进行的别名查询的数量以及使用的别名分析实现如何响应。

2.5.-da:依赖性分析
依赖性分析框架,用于检测内存访问中的依赖性。

2.6.-debug-aa:AA使用调试器
这个简单的传递检查别名分析用户,以确保如果他们创建新值,他们不会在没有通知值的情况下查询AA。 它可以作为你想要的任何其他AA传球的垫片。

是的,跟踪程序中的每个值都很昂贵,但这是一个调试过程。

2.7.-domfrontier:前向支配者构造
这个过程是一个简单的支配者构造算法,用于寻找前向支配者边界。

2.8.-domtree:统治者树建设
这个过程是一个简单的支配者构造算法,用于寻找前向支配者。

2.9.-dot-callgraph:将Call Graph打印到“dot”文件
此过程仅在opt中可用,将调用图打印到.dot图中。然后可以使用“dot”工具处理该图形,以将其转换为postscript或其他一些合适的格式。

2.10.-dot-cfg:将函数的CFG打印到“点”文件
此过程仅在opt中可用,将控制流图打印到.dot图中。然后可以使用dot工具处理该图形以将其转换为postscript或一些其他合适的格式。

2.11.-dot-cfg-only:将函数的CFG打印到“点”文件(没有函数体)
此过程仅在opt中可用,将控制流图打印到.dot图中,省略了函数体。然后可以使用dot工具处理该图形以将其转换为postscript或一些其他合适的格式。

2.12.-dot-dom:将函数的支配树打印到“点”文件
此传递仅在opt中可用,将支配树打印到.dot图形中。然后可以使用dot工具处理该图形以将其转换为postscript或一些其他合适的格式。

2.13.-dot-dom-only:将函数的优势树打印到“点”文件(没有函数体)
此传递仅在opt中可用,将支配树打印到.dot图中,省略了函数体。然后可以使用点工具处理该图形以将其转换为postscript或一些其他合适的格式。

2.14.-dot-postdom:将函数的后向支配树打印到“点”文件
此传递仅在opt中可用,将后向支配者树打印为.dot图。然后可以使用dot工具处理该图形以将其转换为postscript或一些其他合适的格式。

2.15.-dot-postdom-only:将函数的后向支配树打印到“dot”文件(没有函数体)
此过程仅在opt中可用,将后置支配器树打印成.dot图,省略了函数体。然后可以使用dot工具处理该图形以将其转换为postscript或一些其他合适的格式。

2.16.-globalsmodref-aa:全局变量的简单mod / ref分析
这个简单的传递为没有获取地址的全局值提供了别名和mod / ref信息,并跟踪函数是读还是写内存(“纯”)。对于这个简单(但很常见)的情况,我们可以提供非常准确和有用的信息。

2.17.-instcount:计算各种类型的指令
此过程收集所有指令的计数并报告它们。

2.18. -intervals:区间分区构造
此分析计算并表示函数的间隔分区或预先存在的间隔分区。以这种方式,间隔分区可以用于将流程图减少到其简并单节点间隔分区(除非它是不可简化的)。

2.19.-iv-users:归纳变量用户
对从归纳变量计算的表达式的“有趣”用户进行簿记。

2.20.-lazy-value-info:惰性值信息分析
用于延迟计算值约束信息的接口。

2.21.-libcall-aa:LibCall别名分析
LibCall别名分析。

2.22.-lint:静态lint-checks LLVM IR
此过程静态检查在LLVM IR中产生未定义或可能非预期行为的常见且易于识别的构造。

它不是两种方式的正确性保证。首先,它并不全面。有些检查可以静态完成,但尚未实施。其中一些由TODO评论表明,但这些也不全面。其次,许多条件无法静态检查。此过程没有动态检测,因此无法检查所有可能的问题。

另一个限制是它假定所有代码都将被执行。通过永远不会到达的基本块中的空指针存储是无害的,但是这个传递无论如何都会警告它。

优化过程可能会使此过程检查的条件或多或少变得明显。如果优化过程似乎引入了警告,则优化过程可能仅仅是暴露代码中的现有条件。

此代码可以在instcombine之前运行。在许多情况下,instcombine检查相同类型的事物并将具有未定义行为的指令转换为无法访问(或等效)。正因为如此,这个过程做了一些努力来通过bitcasts等来看。

2.23.-loops:自然循环信息
该分析用于识别自然循环并确定CFG的各个节点的循环深度。请注意,标识的循环实际上可能是几个共享相同标头节点的自然循环...而不仅仅是一个自然循环。

2.24.-memdep:记忆依赖性分析
对于给定的内存操作,分析确定它依赖于前面的内存操作。它建立在别名分析信息的基础上,并尝试为常见的别名信息查询提供惰性缓存接口。

2.25.-module-debuginfo:解码模块级调试信息
此过程解码模块中的调试信息元数据,并以(充分准备的)人类可读形式打印。

例如,从opt运行此传递以及-analyze选项,它将打印到标准输出。

2.26.-postdomfrontier:后支配边境建设
这个过程是一个简单的后支配者构造算法,用于寻找后支配者边界。

2.27.-postdomtree:后统治者树建设
这个过程是一个简单的后支配者构造算法,用于寻找后支配者。

2.28.-print-alias-sets:别名设置打印机
还有待写。

2.29.-print-callgraph:打印一个调用图
此过程仅在opt中可用,以人类可读的形式将调用图打印到标准错误。

2.30.-print-callgraph-sccs:打印调用图的SCC
此通道仅在opt中可用,以人类可读的形式将调用图的SCC打印到标准错误。

2.31.-print-cfg-sccs:打印每个功能CFG的SCC
此通道仅在opt中可用,将每个功能CFG的SCC打印到人类可读形式中的标准错误。

2.32.-print-dom-info:Dominator Info Printer
支配者信息打印机。

2.33.-print-externalfnconstants:打印外部fn调用传递的常量
此pass仅在opt中可用,将调用站点打印到使用常量参数调用的外部函数。这在查找标准库函数时非常有用,我们应该在别名分析中对其进行常量折叠或处理。

2.34.-print-function:打印到stderr的功能
PrintFunctionPass类设计为与其他FunctionPasses流水线化,并在处理模块时打印出模块的功能。

2.35.-print-module:将模块打印到stderr
该pass只是在执行时打印出整个模块。

2.36.-print-used-types:查找使用的类型
此过程用于查找程序使用的所有类型。请注意,此分析显式不包括仅由符号表使用的类型。

2.37.-regions:检测单个条目单个退出区域
RegionInfo过程检测函数中的单个条目单个出口区域,其中区域定义为仅在两个点处连接到剩余图形的任何子图形。此外,构建了分层区域树。

2.38.-scalar-evolution:标量进化分析
ScalarEvolution分析可用于分析和分类循环中的标量表达式。它专门识别一般的归纳变量,用抽象和不透明的SCEV类来表示它们。鉴于此分析,可以获得循环的跳闸计数和其他重要属性。

该分析主要用于诱导变量替换和强度降低。

2.39.-scev-aa:基于ScalarEvolution的别名分析
根据ScalarEvolution查询实现的简单别名分析。

这与传统的循环依赖性分析的不同之处在于,它测试循环的单个迭代内的依赖性,而不是不同迭代之间的依赖性。

与BasicAliasAnalysis的ad-hoc分析集合相比,ScalarEvolution对指针算法的理解更为全面。

 2.40.-stack-safety:堆栈安全分析
StackSafety分析可用于确定堆栈分配的变量是否可以被认为是安全的内存访问错误。

该分析的主要目的是由sanitizers使用,以避免对安全变量进行不必要的检测。

2.41.-targetdata:目标数据布局
提供其他传递访问信息,以获取有关各种数据类型的目标ABI所需的大小和对齐方式的信息。

3.Transform Passes

本节介绍LLVM转换passes。

3.1.-adce:积极的死代码消除
ADCE积极尝试消除代码。此过程类似于DCE,但它假定值已经死亡,直到证明不是这样。这类似于SCCP,除了应用于值的活跃性。

3.2.-always-inline:内联器用于always_inline函数
自定义内联器,仅处理标记为“始终内联”的函数。

3.3.-argpromotion:向scalars推广“引用”参数
此过程将“通过引用”参数提升为“按值”参数。实际上,这意味着要查找具有指针参数的内部函数。如果它可以通过使用别名分析证明只加载了一个参数,那么它可以将值传递给函数而不是值的地址。这可以导致代码的递归简化并导致分配的消除(特别是在像STL这样的C++模板代码中)。

pass还处理传递给函数的聚合参数,如果仅加载聚合的元素则对它们进行标量化。请注意,它拒绝扩散聚合,这需要将超过三个操作数传递给函数,因为为大型数组或结构传递数千个操作数是无利可图的!

请注意,也可以对仅存储到(返回值)的参数执行此转换,但当前不会。当LLVM开始支持函数的多个返回值时,最好处理这种情况。

3.4.-bb-vectorize:基本块矢量化
该过程将基本块内的指令组合在一起形成向量指令。它迭代每个基本块,尝试配对兼容指令,重复此过程,直到没有选择其他对进行矢量化。当某些兼容指令对的输出被一些其他兼容指令用作输入时,这些对是潜在矢量化链的一部分。当指令对是长于某个阈值长度的链的一部分时,它们仅被融合到向量指令中。此外,传递尝试为每对兼容指令找到最佳可能链。这些启发式方法旨在防止在不会导致结果代码性能提高的情况下进行矢量化。

3.5.-block-placement:配置文件引导基本块放置
此过程是一个非常简单的配置文件引导基本块放置算法。我们的想法是在函数开始时将频繁执行的块放在一起,并希望增加掉落条件分支的数量。如果没有特定函数的配置文件信息,则此过程基本上按深度优先顺序排序块。

3.6.-break-crit-edges:打破CFG中的关键边缘
通过插入虚拟基本块来中断CFG中的所有关键边缘。它可能是“无法处理关键边缘的通行证”所要求的。这种转换显然使CFG无效,但可以更新前向支配者(集合,直接支配者,树和边界)信息。

3.7.-codegenprepare:优化代码生成
此传递将输入函数中的代码进行清除,以便更好地为基于SelectionDAG的代码生成做好准备。这解决了其基本块一次性方法的局限性。它应该最终被删除。

3.8.-constmerge:合并重复的全局常量
将重复的全局常量合并为一个共享的常量。这很有用,因为有些传递(即TraceValues)会将大量字符串常量插入到程序中,无论现有字符串是否可用。

3.9.-constprop:简单常数传播

该过程实现了常量传播和合并。它查找仅涉及常量操作数的指令,并用常量值而不是指令替换它们。例如:add i32 1, 2转变为i32 3,注意:这个过程习惯使定义死亡。在运行此传递后的某个时间运行死亡指令消除传递是个好主意。

3.10.-dce:死代码消除
死代码消除类似于死指令消除,但它会重新检查已删除指令使用的指令,以查看它们是否已新死。

3.11.-deadargelim:死亡论据消除
此传递从内部函数中删除死参数。死参数消除删除直接死的参数,以及仅作为其他函数的死参数传递给函数调用的参数。此过程也以类似的方式删除死参数。

这个过程通常用作清理过程,以便在激进的过程间过程后运行,这会增加可能死亡的参数。

3.12.-deadtypeelim:死亡类型消除
此过程用于清除GCC的输出。它使用find used types pass消除整个翻译单元中未使用的类型的名称。

3.13.-die:死亡教学消除
死指令消除对函数执行单次传递,删除明显无效的指令。

3.14.-dse:死店消除
一个简单的死存储库消除,只考虑基本块本地冗余存储。

3.15.-functionattrs:Deduce函数属性
一个简单的过程间传递,它遍历调用图,寻找不访问或只读取非本地内存的函数,并将它们标记为readnone / readonly。此外,如果对函数的调用没有创建比调用更长的指针值的任何副本,它会标记函数参数(指针类型)“nocapture”。这或多或少意味着指针仅被解除引用,并且不从函数返回或存储在全局中。此过程实现为调用图的自底向上遍历。

3.16.-globaldce:消除死亡的全局变量
此转换旨在消除程序中无法访问的内部全局变量。它使用一种积极的算法,搜索已知存在的全局变量。在找到所需的所有全局变量后,它会删除剩余的全部变量。这允许它删除无法访问的程序的递归块。

3.17.-globalopt:全局变量优化器
此pass转换从未获取其地址的简单全局变量。如果显然是真的,它将读/写全局变量标记为常量,删除仅存储到的变量等。

3.18.-gvn:全局值编号
此pass执行全局值编号以消除完全和部分冗余的指令。它还执行冗余负载消除。

3.19.-indvars:规范化感应变量
该转换分析并将诱导变量(以及从它们导出的计算)转换成适合于后续分析和转换的更简单形式。

此转换使用可识别的归纳变量对每个循环进行以下更改:

  • 所有循环都被转换为具有单个规范的归纳变量,该变量从零开始并逐步进行。
  • 规范的归纳变量保证是循环报头块中的第一个PHI节点。
  • 引发任何指针算术重复以使用数组下标。

如果循环的行程计数是可计算的,则pass也会进行以下更改:

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

在完成所有期望的环转换之后,该转换之后应该强度降低。 此外,在有利可图的目标上,可以将循环转换为倒计时到零(“循环”优化)。

3.20.-inline:函数集成/内联
自下而上将函数内联到callees中。

3.21.-instcombine:组合冗余指令
结合使用说明,形成更简单的指令。 此过程不会修改CFG。 这个pass是代数简化发生的地方。这个传递结合了以下内容:%Y = add i32 %X, 1 %Z = add i32 %Y, 1 into:%Z = add i32 %X, 2

这是一个简单的工作列表驱动算法。

此pass保证在程序上执行以下规范化:

  • 如果二元运算符具有常量操作数,则将其移动到右侧。
  • 具有常量操作数的按位运算符始终被分组,以便首先执行移位,然后执行ors,然后执行ands,然后执行xors。
  • 如果可能,比较指令从<,>,≤或≥转换为=或≠。
  • 所有关于布尔值的cmp指令都将替换为逻辑运算。
  • 添加X,X表示为mul X,2⇒shlX,1
  • 具有恒定的二次幂参数的乘法被转换为移位。
  • ...等

该pass还可以简化对特定的已知函数调用的调用(例如,运行时库函数)。 例如,在main()函数内发生的调用exit(3)可以转换为简单return 3.是否简化了库调用是由-functionattrs传递和LLVM对不同目标上的库调用的知识控制的。

3.22.-aggressive-instcombine:组合表达式模式
将表达式模式组合在一起,形成表达式较少,简单的指令。此过程不会修改CFG。

例如,此pass将树干支配的表达式的宽度减小到适用的较小宽度。

它与instcombine pass的不同之处在于它包含需要比O(1)更高复杂度的模式优化,因此,它应该比instcombine pass运行的次数少。

3.23.-internalize:内化全局符号
此循环遍历输入模块中的所有函数,寻找主函数。如果找到主函数,则所有其他函数和具有初始值设定项的所有全局变量都标记为内部。

3.24.-ipconstprop:过程间常量传播
该过程实现了极其简单的过程间常量传播过程。它肯定可以通过许多不同的方式得到改进,例如使用工作清单。这个过程使参数死亡,但不会删除它们。应该在此之后运行现有的死亡参数消除通道来清理混乱。

3.25.-ipsccp:过程间稀疏条件常数传播
稀疏条件常数传播的过程间变量。

3.26.-jump-threading:跳线程
跳转线程尝试查找通过基本块运行的控制流的不同线程。 此过程查看具有多个前驱和多个后继的块。 如果可以证明该块的一个或多个前任总是导致跳转到其中一个后继者,我们通过复制该块的内容将前一个边缘转发到后继者。

可能发生这种情况的一个例子是这样的代码:

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

在这种情况下,第一个if的末尾的无条件分支可以被反映到第二个if的假侧。

3.27.-lcssa:Loop-Closed SSA表格通行证

此过程通过将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; 额外的phi节点纯粹是多余的,并且可以通过InstCombine轻松消除。 这种转换的主要好处是它使许多其他循环优化,例如LoopUnswitching,更简单。

3.28.-licm:循环不变代码运行

此pass执行循环不变代码运行,尝试从循环体中删除尽可能多的代码。它通过将代码提升到预读器块中,或者通过将代码下沉到出口块(如果它是安全的)来实现。此pass还促使循环中的必须别名的内存位置存在于寄存器中,从而提升和下沉“不变”的加载和存储。

此pass使用别名分析有两个目的:

  1. 移动循环不变加载和调用循环。如果我们可以确定循环内的加载或调用永远不会存储任何存储的内容,我们可以像任何其他指令一样将其提升或下沉。
  2. 标量促进记忆。如果循环内部存在存储指令,我们会尝试将存储移动到循环之后而不是循环内部。只有在几个条件成立时才会发生这种情况:1.通过存储的指针是循环不变的。2.循环中没有存储或加载可能使指针变为别名。在循环中没有调用/修改指针的调用。

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

3.29.-loop-deletion:删除死循环
该文件实现了Dead Loop Deletion Pass。此过程负责消除具有无限可计算跳闸计数的循环,这些循环没有副作用或易失性指令,并且不会有助于计算函数的返回值。

3.30.-loop-extract:将循环提取到新函数中
ExtractLoop()标量转换的pass包装器,将每个顶级循环提取到自己的新函数中。如果循环是给定函数中唯一的循环,则不会触及它。这是一个通过bugpoint进行调试最有用的pass。

3.31.-loop-extract-single:将最多一个循环提取到一个新函数中
类似于将循环提取到新函数中,此过程从程序中提取一个自然循环,如果可以的话。这是由bugpoint使用的。

3.32.-loop-reduced:减少环路强度
此pass对循环内的数组引用执行强度降低,循环中包含一个或多个组件的循环归纳变量。这是通过创建一个新值来保存第一次迭代的数组访问的初始值,然后在循环中创建一个新的GEP指令以将值递增适当的量来实现的。

3.33.-loop-rotate:旋转循环
一个简单的循环旋转变换。

3.34.-loop-simplified:规范化自然循环
此过程执行多次转换,将自然循环转换为更简单的形式,这使后续分析和转换更简单,更有效。

循环预标头插入可确保从循环外部到循环标头存在单个非关键入口边缘。这简化了许多分析和转换,例如LICM。

循环出口块插入保证循环中的所有出口块(循环内部具有前导的循环之外的块)仅具有来自循环内部的前导(因此由循环头控制)。这简化了LICM中内置的存储沉降等转换。

此过程还保证循环将只有一个备份。

请注意,simplifycfg pass将清除被拆分但最终不必要的块,因此使用此pass不应使生成使我们失望的代码。

这个过程显然修改了CFG,但更新了循环信息和支配者信息。

3.35.-loop-unroll:展开循环
此过程实现了一个简单的循环展开器。当循环通过indvars传递规范化时,它最有效,允许它轻松确定循环的行程计数。

3.36.-loop-unroll-and-jam:展开和阻塞循环
此过程实现了一个简单的展开和阻塞经典循环优化过程。 它转换循环:

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:非开关循环
此过程转换在循环不变条件下包含分支的循环以具有多个循环。 例如,它将左侧变为正确的代码:

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

这可以以指数方式增加代码的大小(每次循环未切换时将其加倍),因此我们仅在结果代码小于阈值时才切换。
这个过程期望LICM在它之前运行以将不变条件提升到循环之外,以使明显的非开关机会。

3.38.-loweratomic:将原子内在性降低到非原子形式

此过程将原子内在函数降低为非原子形式,以用于已知的非可抢占环境。

该过程不验证环境是不可抢占的(通常这需要知道程序的整个调用图,包括可能不以bitcode形式提供的任何库); 它简单地降低了每个原子内在物。

3.39.-LowerInvoke:将invoke指令降低为call指令,用于不可靠的代码生成器

此转换旨在供尚未支持堆栈展开的代码生成器使用。此pass将invoke指令转换为call指令,以便任何异常处理的登陆块块变为死代码(可以通过之后运行-simplifycfg pass来删除)。

3.40.-lowerswitch:将Switch指令降低到分支
用一系列分支指令重写switch指令,这允许目标在方便之前不执行切换指令而逃脱。

3.41.-mem2reg:提升内存至寄存器
此文件将内存引用提升为寄存器引用。它促进了仅具有加载和存储作为用途的alloca指令。通过使用支配前沿来放置phi节点来转换alloca,然后以深度优先的顺序遍历函数以适当地重写加载和存储。这只是构建“修剪”SSA形式的标准SSA构造算法。

3.42.-memcpyopt:MemCpy优化
此过程执行与消除memcpy调用或将存储集转换为memset相关的各种转换。

3.43.-mergefunc:合并函数
此pass查找可以合并并折叠它们的等效函数。

在函数集中引入了总排序:我们定义了对每个函数的答案进行比较,这两个函数中的哪个函数更大。它允许将函数排列到二叉树中。

对于每个新函数,我们在树中检查等效函数。

如果等价,我们折叠这些函数。如果两个函数都可以覆盖,我们将功能移动到一个新的内部函数中,并为其留下两个可覆盖的thunks。

如果没有等价物,那么我们将此函数添加到树中。

查找例程具有O(log(n))复杂度,而整个合并过程具有O(n * log(n))的复杂度。

阅读本文了解更多详情。(http://llvm.org/docs/MergeFunctions.html)

3.44.-mergereturn:统一函数退出节点
确保函数中最多只有一条ret指令。此外,它还跟踪哪个节点是CFG的新退出节点。

3.45.-partial-inliner:部分内联
此pass执行部分内联,通常通过在函数体周围插入if语句。

3.46.-prune-eh:删除未使用的异常处理信息
此文件实现了一个简单的程序间pass,它遍历调用图,当且仅当被调用者不能抛出异常时,才将invoke指令转换为call指令。它将此实现为调用图的自底向上遍历。

3.47.-reassociate:重新关联表达式
此过程按照旨在促进更好的常量传播,GCSE,LICM,PRE等的顺序重新关联交换表达式。

例如:4 +(x + 5)⇒x+(4 + 5)

在该算法的实现中,常量被赋值为rank = 0,函数参数为rank = 1,其他值被赋予对应于当前函数的反向后序遍历的等级(从2开始),这有效地给出深循环中的值 比不在循环中的值更高的等级。

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

此文件将所有寄存器降级为内存引用。它的目的是与mem2reg相反。通过转换为加载指令,基本块中唯一存在的值是alloca指令和phi节点之前的加载指令。这样做的目的是使CFG黑客攻击更加容易。为了便于以后的黑客攻击,入口块被分成两部分,这样所有引入的alloca指令(以及其他任何指令)都在入口块中。

3.49.-sroa:标量替换聚合

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

3.50.-sccp:稀疏条件常量传播
稀疏条件常量传播和合并,可以概括为:

  • 除非另有证明,否则假设值是常数
  • 除非另有证明,否则假设BasicBlocks已经死亡
  • 证明值是常量,并用常量替换它们
  • 证明条件分支是无条件的

请注意,此过程习惯使定义失效。在运行此传递后的某个时间运行DCEpass是个好主意。

3.51.-simplifycfg:简化CFG
执行死代码消除和基本块合并。特别:

  • 删除没有前辈的基本块。
  • 如果只有一个基本块并且前一个块只有一个后继块,则将基本块合并到其前一个块中。
  • 使用单个前驱者消除基本块的PHI节点。
  • 消除仅包含无条件分支的基本块。

3.52.-sink:代码下沉
此过程在可能的情况下将指令移动到后继块中,以便它们不会在不需要其结果的路径上执行。

3.53.-strip:从模块中删除所有符号
执行代码剥离。此转换可以删除:

  • 虚拟寄存器的名称
  • 内部全局变量和函数的符号
  • 调试信息

请注意,此转换使代码的可读性降低,因此只应在将使用条带实用程序的情况下使用,例如减少代码大小或使反向工程代码变得更难。

3.54.-strip-dead-debug-info:删除未使用符号的调试信息
执行代码剥离。这个转换可以删除:

  • 虚拟寄存器的名称
  • 内部全局变量和函数的符号
  • 调试信息

请注意,此转换使代码的可读性降低,因此只应在使用条带实用程序的情况下使用,例如减少代码大小或使代码更难反向工程。

3.55.-strip-dead-prototypes:剥离未使用的函数原型
此循环遍历输入模块中的所有函数,查找死声明并删除它们。死声明是没有可用实现的函数声明(即未使​​用的库函数的声明)。

3.56.-strip-debug-declare:去除所有llvm.dbg.declare内在函数
此pass实现代码剥离。具体来说,它可以删除:

  • 虚拟寄存器的名称
  • 内部全局变量和函数的符号
  • 调试信息

请注意,此转换使代码的可读性降低,因此只应在使用“strip”实用程序的情况下使用,例如减少代码大小或使代码反向工程变得更难。

3.57.-strip-nondebug:从模块中删除除dbg符号之外的所有符号
此过程实现代码剥离。具体来说,它可以删除:

  • 虚拟寄存器的名称
  • 内部全局变量和函数的符号
  • 调试信息

请注意,此转换使代码的可读性降低,因此只应在使用“strip”实用程序的情况下使用,例如减少代码大小或使代码反向工程变得更难。

3.58.-tailcallelim:尾部呼叫消除
此文件转换当前函数的调用(自递归),然后是返回指令,其中包含函数入口的分支,从而创建循环。此过程还实现了对基本算法的以下扩展:

  • 调用和返回之间的简单指令不会阻止转换发生,尽管目前分析不支持移动任何真正有用的指令(只有死的指令)。
  • 此过程转换由关联表达式阻止被尾递归的函数以使用累加器变量,从而将典型的朴素因子或者fib实现编译成有效的代码。
  • 如果函数返回void,则执行TRE,如果返回的结果是调用的,或者函数在函数的所有出口上返回运行时常量。虽然不太可能,但返回返回其他内容(如常量0)是可能的,并且仍然可以是TRE'd。如果函数中的所有其他返回指令返回完全相同的值,则可以是TRE。
  • 如果它可以证明callees不访问其他调用者堆栈帧,则它们被标记为有资格进行尾调用消除(通过代码生成器)。

4.Utility Passes
本节介绍LLVM实用程序通过。

4.1.-deadarghaX0r:死亡参数黑客攻击(仅限BUGPOINT使用;请勿使用)
与死参数消除相同,但删除外部函数的参数。 这仅供bugpoint使用。

4.2.-extract-blocks:从模块中提取基本块(用于bugpoint)
bugpoint使用此过程将模块中的所有块提取到它们自己的函数中。

4.3.-instnamer:为匿名指令分配名称
这是一个提供指令名称的小实用程序传递,这在区分优化效果时非常有用,因为删除未命名的指令可以更改所有其他指令编号,使diff非常嘈杂。

4.4.-verify:模块验证程序
验证LLVM IR代码。这对于正在进行测试的优化之后运行很有用。请注意,llvm-as在发出bitcode之前验证其输入,并且该格式错误的bitcode可能会导致LLVM崩溃。因此,鼓励所有语言前端在执行优化转换之前验证其输出。

  • 二元运算符的参数都是相同的类型。
  • 验证mem访问指令的索引是否与其他操作数匹配。
  • 验证算术和其他事情仅在第一类类型上执行。验证移位和逻辑仅发生在积分f.e.
  • switch语句中的所有常量都是正确的类型。
  • 代码采用有效的SSA格式。
  • 将标签放入任何其他类型(如结构)或返回一个标签是非法的。
  • 只有phi节点可以自引用:%x = add i32%x,%x无效。
  • PHI节点必须具有每个前任的条目,没有额外的内容。
  • PHI节点必须是基本块中的第一个东西,所有节点都组合在一起。
  • PHI节点必须至少有一个条目。
  • 所有基本块应仅以终结器inst结束,不包含它们。
  • 函数的入口节点不能有前导。
  • 所有指令必须嵌入基本块中。
  • 函数不能采用void类型参数。
  • 验证函数的参数列表是否与其声明的类型一致。
  • 为void值指定名称是非法的。
  • 拥有没有初始化程序的内部全局值是违法的。
  • 具有返回与函数返回值类型不一致的值的ret指令是非法的。
  • 函数调用参数类型与函数原型匹配。
  • 断言测试的所有其他内容都会传播代码。
  • 请注意,这不提供完整的安全性验证(如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/zhang14916/article/details/89244487
今日推荐