LLVM 的分析和转换 Pass¶
简介¶
警告
本文档不会经常更新,Pass 列表很可能不完整。可以使用 opt -print-passes
命令列出 opt 工具已知的 Pass。
本文档作为 LLVM 提供的优化功能的高级摘要。优化以 Pass 的形式实现,Pass 遍历程序的一部分以收集信息或转换程序。下表将 LLVM 提供的 Pass 分为三类。分析 Pass 计算其他 Pass 可以使用的信息,或用于调试或程序可视化目的。转换 Pass 可以使用(或使失效)分析 Pass。所有转换 Pass 都会以某种方式改变程序。实用工具 Pass 提供一些实用功能,但不适合其他分类。例如,将函数提取到 bitcode 或将模块写入 bitcode 的 Pass 既不是分析 Pass 也不是转换 Pass。上面的目录提供了每个 Pass 的快速摘要,并链接到文档后面更完整的 Pass 描述。
分析 Pass¶
本节介绍 LLVM 分析 Pass。
aa-eval
: 详尽别名分析精度评估器¶
这是一个简单的 N^2 别名分析精度评估器。基本上,对于程序中的每个函数,它只是简单地查询别名分析实现如何回答函数中每对指针之间的别名查询。
这部分代码的灵感和改编来自:Naveen Neelakantam、Francesco Spadini 和 Wojciech Stryjewski。
basic-aa
: 基本别名分析(无状态 AA 实现)¶
一个基本的别名分析 Pass,它实现了标识(两个不同的全局变量不能互为别名等),但不执行有状态分析。
basiccg
: 基本调用图构建¶
待编写。
da
: 依赖分析¶
依赖分析框架,用于检测内存访问中的依赖关系。
domfrontier
: 支配边界构建¶
此 Pass 是一个简单的支配器构建算法,用于查找前向支配边界。
domtree
: 支配树构建¶
此 Pass 是一个简单的支配器构建算法,用于查找前向支配器。
dot-callgraph
: 将调用图打印到 “dot” 文件¶
此 Pass 仅在 opt
中可用,将调用图打印到 .dot
图形文件中。然后可以使用 “dot” 工具处理此图形,将其转换为 postscript 或其他合适的格式。
dot-cfg
: 将函数的 CFG 打印到 “dot” 文件¶
此 Pass 仅在 opt
中可用,将控制流图打印到 .dot
图形文件中。然后可以使用 dot 工具处理此图形,将其转换为 postscript 或其他合适的格式。此外,可以使用 -cfg-func-name=<substring>
选项来过滤要打印的函数。所有包含指定子字符串的函数都将被打印。
dot-cfg-only
: 将函数的 CFG 打印到 “dot” 文件(不包含函数体)¶
此 Pass 仅在 opt
中可用,将控制流图打印到 .dot
图形文件中,省略函数体。然后可以使用 dot 工具处理此图形,将其转换为 postscript 或其他合适的格式。此外,可以使用 -cfg-func-name=<substring>
选项来过滤要打印的函数。所有包含指定子字符串的函数都将被打印。
dot-dom
: 将函数的支配树打印到 “dot” 文件¶
此 Pass 仅在 opt
中可用,将支配树打印到 .dot
图形文件中。然后可以使用 dot 工具处理此图形,将其转换为 postscript 或其他合适的格式。
dot-dom-only
: 将函数的支配树打印到 “dot” 文件(不包含函数体)¶
此 Pass 仅在 opt
中可用,将支配树打印到 .dot
图形文件中,省略函数体。然后可以使用 dot 工具处理此图形,将其转换为 postscript 或其他合适的格式。
dot-post-dom
: 将函数的后支配树打印到 “dot” 文件¶
此 Pass 仅在 opt
中可用,将后支配树打印到 .dot
图形文件中。然后可以使用 dot 工具处理此图形,将其转换为 postscript 或其他合适的格式。
dot-post-dom-only
: 将函数的后支配树打印到 “dot” 文件(不包含函数体)¶
此 Pass 仅在 opt
中可用,将后支配树打印到 .dot
图形文件中,省略函数体。然后可以使用 dot 工具处理此图形,将其转换为 postscript 或其他合适的格式。
globals-aa
: 全局变量的简单 mod/ref 分析¶
这个简单的 Pass 为未取地址的全局值提供别名和 mod/ref 信息,并跟踪函数是否读取或写入内存(是否为“纯”函数)。对于这种简单(但非常常见)的情况,我们可以提供相当准确和有用的信息。
instcount
: 计数各种类型的 Instruction
¶
此 Pass 收集所有指令的计数并报告它们。
iv-users
: 归纳变量用户¶
用于记录从归纳变量计算的表达式的“有趣”用户的簿记。
kernel-info
: GPU 内核信息¶
报告为 GPU 编译的代码的各种统计信息。此 Pass 在 单独的文档 中进行了描述。
lazy-value-info
: 延迟值信息分析¶
用于延迟计算值约束信息的接口。
lint
: 静态 lint 检查 LLVM IR¶
此 Pass 静态检查 LLVM IR 中常见的且易于识别的构造,这些构造会产生未定义的或可能意外的行为。
它不能保证正确性,原因有二。首先,它不是全面的。有些静态检查可以完成,但尚未实现。其中一些在 TODO 注释中指出,但这些注释也不是全面的。其次,许多条件无法静态检查。此 Pass 不执行动态 instrumentation,因此它无法检查所有可能的问题。
另一个限制是它假设所有代码都将被执行。通过永不访问的基本块中的空指针进行的存储是无害的,但此 Pass 仍然会发出警告。
优化 Pass 可能会使此 Pass 检查的条件更加或更不明显。如果优化 Pass 似乎引入了警告,则可能是优化 Pass 仅仅暴露了代码中已存在的条件。
此代码可以在 instcombine 之前运行。在许多情况下,instcombine 会检查相同类型的事情,并将具有未定义行为的指令转换为 unreachable(或等效项)。因此,此 Pass 尽力查看 bitcast 等。
loops
: 自然循环信息¶
此分析用于识别自然循环并确定 CFG 各个节点的循环深度。请注意,识别的循环实际上可能是共享同一标头节点的多个自然循环……而不仅仅是单个自然循环。
memdep
: 内存依赖分析¶
一种分析,用于确定对于给定的内存操作,它依赖于哪些先前的内存操作。它建立在别名分析信息之上,并尝试为常见的别名信息查询提供延迟、缓存的接口。
print<module-debuginfo>
: 解码模块级调试信息¶
此 Pass 解码模块中的调试信息元数据,并以(充分准备的)人类可读的形式将其打印到标准输出。
postdomtree
: 后支配树构建¶
此 Pass 是一个简单的后支配器构建算法,用于查找后支配器。
print-alias-sets
: 别名集打印器¶
待编写。
print-callgraph
: 打印调用图¶
此 Pass 仅在 opt
中可用,以人类可读的形式将调用图打印到标准错误输出。
print-callgraph-sccs
: 打印调用图的 SCC¶
此 Pass 仅在 opt
中可用,以人类可读的形式将调用图的 SCC 打印到标准错误输出。
print-cfg-sccs
: 打印每个函数 CFG 的 SCC¶
此 Pass 仅在 opt
中可用,以人类可读的形式将每个函数 CFG 的 SCC 打印到标准错误输出。
function(print)
: 将函数打印到 stderr¶
PrintFunctionPass
类设计为与其他 FunctionPasses
管道化,并在处理模块的函数时打印出来。
module(print)
: 将模块打印到 stderr¶
此 Pass 只是在执行时打印出整个模块。
regions
: 检测单入口单出口区域¶
RegionInfo
Pass 检测函数中的单入口单出口区域,其中区域定义为仅在两个位置连接到剩余图的任何子图。此外,还构建了分层区域树。
scalar-evolution
: 标量演化分析¶
ScalarEvolution
分析可用于分析和分类循环中的标量表达式。它专门识别通用归纳变量,并使用抽象且不透明的 SCEV
类表示它们。给定此分析,可以获得循环的 trip count 和其他重要属性。
此分析主要用于归纳变量替换和强度缩减。
scev-aa
: 基于 ScalarEvolution 的别名分析¶
根据 ScalarEvolution
查询实现的简单别名分析。
这与传统的循环依赖性分析不同,因为它测试循环单次迭代内的依赖性,而不是不同迭代之间的依赖性。
ScalarEvolution
比 BasicAliasAnalysis
的特设分析集合更全面地理解指针算法。
stack-safety
: 栈安全分析¶
StackSafety
分析可用于确定堆栈分配的变量是否可以被认为是内存访问错误安全的。
此分析的主要目的是供 sanitizers 使用,以避免对安全变量进行不必要的检测。
转换 Pass¶
本节介绍 LLVM 转换 Pass。
adce
: 激进的死代码消除¶
ADCE 积极尝试消除代码。此 pass 类似于 DCE,但它假定值是死的,除非另有证明。这类似于 SCCP,只是应用于值的活跃性。
always-inline
: 用于 always_inline
函数的内联器¶
一个自定义内联器,仅处理标记为 “always inline” 的函数。
argpromotion
: 将 ‘按引用’ 参数提升为标量¶
此 pass 将 “按引用” 参数提升为 “按值” 参数。在实践中,这意味着查找具有指针参数的内部函数。如果它可以通过使用别名分析来证明参数仅被加载,那么它可以将值而不是值的地址传递给函数。这可能会导致代码的递归简化,并导致消除 allocas(尤其是在 C++ 模板代码(如 STL)中)。
此 pass 还处理传递给函数的聚合参数,如果聚合的元素仅被加载,则将它们标量化。请注意,它拒绝标量化需要向函数传递超过三个操作数的聚合,因为为大型数组或结构传递数千个操作数是不划算的!
请注意,这种转换也可以对仅存储到的参数进行(返回该值),但目前没有这样做。如果 LLVM 开始支持函数的多个返回值,这种情况最好处理。
block-placement
: 配置文件引导的基本块放置¶
此 pass 是一种非常简单的配置文件引导的基本块放置算法。其思想是将频繁执行的块放在函数开始处,并希望增加直通条件分支的数量。如果特定函数没有配置文件信息,则此 pass 基本上以深度优先顺序排列块。
break-crit-edges
: 打断 CFG 中的关键边¶
通过插入一个虚拟基本块来打断 CFG 中的所有关键边。这可能是不能处理关键边的 pass 所“需要”的。这种转换显然会使 CFG 无效,但可以更新前向支配器(集合、立即支配器、树和边界)信息。
codegenprepare
: 为代码生成优化¶
此 pass 修改输入函数中的代码,以便更好地为基于 SelectionDAG 的代码生成做准备。这可以解决其基本块一次处理方法的局限性。它最终应该被移除。
constmerge
: 合并重复的全局常量¶
将重复的全局常量合并为一个共享的常量。这很有用,因为某些 pass(例如,TraceValues)将大量字符串常量插入到程序中,而不管是否已存在字符串。
dce
: 死代码消除¶
死代码消除类似于死指令消除,但它会重新检查被删除指令使用的指令,以查看它们是否是新近死亡的。
deadargelim
: 死参数消除¶
此 pass 从内部函数中删除死参数。死参数消除会删除直接死的参数,以及仅作为其他函数的死参数传递到函数调用的参数。此 pass 也以类似的方式删除死参数。
此 pass 通常用作清理 pass,在激进的过程间 pass 之后运行,这些 pass 添加了可能死的参数。
dse
: 死存储消除¶
一个简单的死存储消除,仅考虑基本块本地的冗余存储。
function-attrs
: 推断函数属性¶
一个简单的过程间 pass,它遍历调用图,查找不访问或仅读取非本地内存的函数,并将它们标记为 readnone
/readonly
。此外,如果对函数的调用没有创建任何超出调用范围的指针值的副本,则将函数参数(指针类型)标记为 “nocapture
”。这或多或少意味着指针仅被解引用,而不是从函数返回或存储在全局变量中。此 pass 实现为调用图的自下而上遍历。
globaldce
: 死全局变量消除¶
此转换旨在从程序中消除无法访问的内部全局变量。它使用一种激进的算法,搜索已知为存活的全局变量。在找到所有需要的全局变量后,它会删除剩下的任何东西。这使其可以删除无法访问的程序的递归块。
globalopt
: 全局变量优化器¶
此 pass 转换从未获取其地址的简单全局变量。如果明显为真,它会将读/写全局变量标记为常量,删除仅存储到的变量等。
gvn
: 全局值编号¶
此 pass 执行全局值编号以消除完全和部分冗余指令。它还执行冗余加载消除。
indvars
: 规范化归纳变量¶
此转换分析和转换归纳变量(以及从它们派生的计算)为更简单的形式,以供后续分析和转换。
此转换对每个具有可识别归纳变量的循环进行以下更改
所有循环都转换为具有单个规范归纳变量,该变量从零开始并步长为一。
规范归纳变量保证是循环头块中的第一个 PHI 节点。
任何指针算术递归都会被提升为使用数组下标。
如果循环的 trip count 是可计算的,则此 pass 还会进行以下更改
循环的退出条件被规范化为将归纳值与退出值进行比较。这会将如下循环转换为
for (i = 7; i*i < 1000; ++i) into
for (i = 0; i != 25; ++i)
在循环外部使用任何从 indvar 派生的表达式都会更改为在循环外部计算派生值,从而消除对归纳变量退出值的依赖性。如果循环的唯一目的是计算某些派生表达式的退出值,则此转换将使循环变为死循环。
在执行所有期望的循环转换之后,应进行强度缩减。此外,在有利可图的目标上,循环可以转换为倒数到零(“do loop” 优化)。
inline
: 函数集成/内联¶
自下而上地将函数内联到被调用者中。
instcombine
: 合并冗余指令¶
合并指令以形成更少、更简单的指令。此 pass 不修改 CFG。此 pass 是代数简化发生的地方。
此 pass 合并如下内容
%Y = add i32 %X, 1
%Z = add i32 %Y, 1
成为
%Z = add i32 %X, 2
这是一个简单的工作列表驱动的算法。
此 pass 保证对程序执行以下规范化
如果二元运算符具有常量操作数,则将其移动到右侧。
具有常量操作数的位运算符始终分组,以便首先执行移位,然后是
or
,然后是and
,然后是xor
。比较指令从
<
、>
、≤
或≥
转换为=
或≠
(如果可能)。布尔值上的所有
cmp
指令都替换为逻辑运算。add X, X
表示为mul X, 2
⇒shl X, 1
与常量二次方参数的乘法转换为移位。
… 等。
此 pass 还可以简化对特定已知函数调用(例如,运行时库函数)的调用。例如,在 main()
函数中发生的调用 exit(3)
可以转换为简单的 return 3
。库调用是否被简化受 -function-attrs pass 和 LLVM 对不同目标上库调用的了解的控制。
aggressive-instcombine
: 合并表达式模式¶
合并表达式模式以形成具有更少、更简单指令的表达式。
例如,当适用时,此 pass 将由 TruncInst 后支配的表达式的宽度减小为更小的宽度。
它与 instcombine pass 的不同之处在于它可以修改 CFG,并且包含比 O(1) 需要更高复杂度的模式优化,因此,它应该比 instcombine pass 运行的次数更少。
internalize
: 内部化全局符号¶
此 pass 循环遍历输入模块中的所有函数,查找 main 函数。如果找到 main 函数,则所有其他函数和所有带有初始值设定项的全局变量都标记为内部。
ipsccp
: 过程间稀疏条件常量传播¶
一种 稀疏条件常量传播 的过程间变体。
ir-normalizer
: 将 IR 转换为更易于 diff 的规范形式¶
此 pass 旨在通过重新排序和重命名指令,同时保留相同的语义,将 LLVM 模块转换为规范形式。规范化器使在 diff 经历两个不同 pass 的两个模块时更容易发现语义差异。
jump-threading
: 跳转线程化¶
跳转线程化尝试查找通过基本块运行的不同控制流线程。此 pass 查看具有多个前驱和多个后继的块。如果可以证明块的一个或多个前驱总是导致跳转到后继之一,我们将通过复制此块的内容将边从前驱转发到后继。
这种情况发生的一个例子是这样的代码
if () { ...
X = 4;
}
if (X < 3) {
在这种情况下,第一个 if 结尾处的无条件分支可以重新定向到第二个 if 的 false 分支。
lcssa
: 循环封闭 SSA 形式 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;额外的 phi 节点是纯粹冗余的,并且将被 InstCombine
轻松消除。此转换的主要好处是,它使许多其他循环优化(例如 LoopUnswitch
ing)更简单。您可以在 LCSSA 形式的循环术语部分 中阅读更多内容。
licm
: 循环不变代码移动¶
此 pass 执行循环不变代码移动,尝试从循环体中删除尽可能多的代码。它通过将代码提升到前置头块中,或者在安全的情况下将代码下沉到出口块来实现这一点。此 pass 还将循环中必须别名的内存位置提升为驻留在寄存器中,从而提升和下沉 “不变” 的加载和存储。
将操作提升出循环是一种规范化转换。它启用并简化了中间端的后续优化。为了减少寄存器压力而重新物化提升的指令是后端的责任,后端具有更准确的关于寄存器压力的信息,并且还处理其他增加生存期的优化,而不是 LICM。
此 pass 将别名分析用于两个目的
将循环不变的加载和调用移出循环。如果我们能够确定循环内的加载或调用永远不会别名任何存储到的内容,我们可以像任何其他指令一样提升或下沉它。
内存的标量提升。如果循环内有存储指令,我们会尝试将存储移动到循环之后而不是循环内部发生。只有在满足以下几个条件时,才能发生这种情况
存储通过的指针是循环不变的。
循环中没有可能别名指针的存储或加载。循环中没有 mod/ref 指针的调用。
如果这些条件为真,我们可以提升循环中指针的加载和存储,以使用临时的 alloca 变量。然后,我们使用 mem2reg 功能为变量构建适当的 SSA 形式。
loop-deletion
: 删除死循环¶
此文件实现了死循环删除 Pass。此 pass 负责消除具有非无限可计算 trip count 的循环,这些循环没有副作用或易失性指令,并且不影响函数返回值的计算。
loop-extract
: 将循环提取到新函数中¶
围绕 ExtractLoop()
标量转换的 pass 包装器,用于将每个顶级循环提取到其自己的新函数中。如果循环是给定函数中唯一的循环,则不会触及它。这是一个对通过 bugpoint 进行调试最有用的 pass。
loop-reduce
: 循环强度缩减¶
此 pass 对循环内部的数组引用执行强度缩减,这些数组引用具有作为其一个或多个组成部分的循环归纳变量。这是通过创建一个新值来保存第一次迭代的数组访问的初始值,然后在循环中创建一个新的 GEP 指令来按适当的量递增该值来完成的。
loop-rotate
: 旋转循环¶
一个简单的循环旋转转换。可以在 旋转循环的循环术语 中找到它的摘要。
loop-simplify
: 规范化自然循环¶
此 pass 执行多个转换以将自然循环转换为更简单的形式,这使得后续的分析和转换更简单和更有效。可以在 循环术语,循环简化形式 中找到它的摘要。
循环前置头插入保证从循环外部到循环头的单个非关键入口边。这简化了许多分析和转换,例如 LICM。
循环出口块插入保证来自循环的所有出口块(循环外部的块,其前驱在循环内部)仅具有来自循环内部的前驱(因此由循环头支配)。这简化了诸如 store-sinking 之类的转换,这些转换内置在 LICM 中。
此 pass 还保证循环将恰好有一个反向边。
请注意,simplifycfg pass 将清理被拆分出来但最终变得不必要的块,因此使用此 pass 不应降低生成的代码的性能。
此 pass 显然会修改 CFG,但会更新循环信息和支配器信息。
loop-unroll
: 展开循环¶
此 pass 实现了一个简单的循环展开器。当循环已被 indvars pass 规范化时,效果最佳,这使其可以轻松确定循环的 trip count。
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
这可以看作是展开外循环并将内循环 “阻塞”(融合)为一个循环。当变量或加载可以在新的内循环中共享时,这可以带来显着的性能改进。它使用 依赖性分析 来证明转换是安全的。
lower-global-dtors
: 降低全局析构函数¶
此 pass 通过创建包装函数来降低全局模块析构函数 (llvm.global_dtors
),这些包装函数在 llvm.global_ctors
中注册为全局构造函数,并且包含对 __cxa_atexit
的调用以注册其析构函数。
lower-atomic
: 将原子内在函数降低为非原子形式¶
此 pass 将原子内在函数降低为非原子形式,以便在已知的非抢占式环境中使用。
该 pass 不验证环境是否为非抢占式的(通常这需要了解程序的整个调用图,包括任何可能以 bitcode 形式不可用的库);它只是降低每个原子内在函数。
lower-invoke
: 将 invokes 降低为 calls,用于无 unwind 代码生成器¶
此转换旨在供尚不支持堆栈展开的代码生成器使用。此 pass 将 invoke
指令转换为 call
指令,以便任何异常处理 landingpad
块都变成死代码(可以通过之后运行 -simplifycfg
pass 来删除)。
lower-switch
: 将 SwitchInst
s 降低为分支¶
使用一系列分支重写 switch 指令,这允许目标在方便之前不必实现 switch 指令。
mem2reg
: 将内存提升到寄存器¶
此文件将内存引用提升为寄存器引用。它提升仅将加载和存储作为用途的 alloca 指令。alloca
通过使用支配边界来放置 phi 节点来转换,然后以深度优先顺序遍历函数以根据需要重写加载和存储。这只是标准的 SSA 构造算法,用于构造 “修剪” 的 SSA 形式。
memcpyopt
: MemCpy 优化¶
此 pass 执行与消除 memcpy
调用或将存储集转换为 memset
相关的各种转换。
mergefunc
: 合并函数¶
此 pass 查找可合并的等效函数并折叠它们。
在函数集中引入了全序:我们定义了比较,该比较回答了每两个函数中哪个更大。它允许将函数排列到二叉树中。
对于每个新函数,我们检查树中是否存在等效函数。
如果存在等效函数,我们将折叠这些函数。如果两个函数都是可重写的,我们将功能移动到一个新的内部函数中,并为其留下两个可重写 thunk。
如果没有等效函数,那么我们将此函数添加到树中。
查找例程具有 O(log(n)) 复杂度,而整个合并过程的复杂度为 O(n*log(n))。
阅读 本文 了解更多详情。
mergereturn
: 统一函数出口节点¶
确保函数中最多只有一个 ret
指令。此外,它还跟踪哪个节点是 CFG 的新出口节点。
partial-inliner
: 部分内联器¶
此 pass 执行部分内联,通常通过内联包围函数体的 if
语句。
reassociate
: 重新关联表达式¶
此 pass 以旨在促进更好的常量传播、GCSE、LICM、PRE 等的顺序重新关联交换表达式。
例如:4 + (x + 5) ⇒ x + (4 + 5)
在此算法的实现中,常量被赋值 rank = 0,函数参数被赋值 rank = 1,其他值被赋值与当前函数的反向后序遍历相对应的 rank(从 2 开始),这有效地使深层循环中的值比不在循环中的值具有更高的 rank。
rel-lookup-table-converter
: 相对查找表转换器¶
此 pass 将查找表转换为 PIC 友好的相对查找表。
reg2mem
: 将所有值降级为堆栈槽¶
此文件将所有寄存器降级为内存引用。它旨在成为 mem2reg 的逆过程。通过转换为 load
指令,跨基本块存活的唯一值是 alloca
指令和 phi
节点之前的 load
指令。它旨在使以后的 hacking 更加容易。为了使以后的 hacking 更容易,入口块被分成两个,这样所有引入的 alloca
指令(以及其他任何指令)都在入口块中。
sroa
: 聚合的标量替换¶
众所周知的聚合标量替换转换。此转换将聚合类型(结构或数组)的 alloca
指令分解为每个成员的单独 alloca
指令(如果可能)。然后,如果可能,它将单独的 alloca
指令转换为简洁的标量 SSA 形式。
sccp
: 稀疏条件常量传播¶
稀疏条件常量传播和合并,可以概括为
假定值是常量,除非另有证明
假定基本块是死的,除非另有证明
证明值是常量,并用常量替换它们
证明条件分支是无条件的
请注意,此 pass 习惯于使定义变为死定义。最好在此 pass 运行后运行 DCE pass。
simplifycfg
: 简化 CFG¶
执行死代码消除和基本块合并。具体来说
删除没有前驱的基本块。
如果基本块只有一个前驱并且前驱只有一个后继,则将基本块合并到其前驱中。
消除具有单个前驱的基本块的 PHI 节点。
消除仅包含无条件分支的基本块。
sink
: 代码下沉¶
此趟 pass 将指令移动到可能的后继块中,以便它们不会在不需要其结果的路径上执行。
simple-loop-unswitch
:循环解开关¶
此趟 pass 转换包含循环不变量条件分支的循环,使其具有多个循环。例如,它将左侧代码转换为右侧代码
for (...) if (lic)
A for (...)
if (lic) A; B; C
B else
C for (...)
A; C
这可能会指数级地增加代码的大小(每次循环解开关时都会翻倍),因此我们仅在结果代码小于阈值时才进行解开关。
此趟 pass 期望在它之前运行 LICM 以将不变量条件提升出循环,从而使解开关的机会变得明显。
strip
:从模块中剥离所有符号¶
执行代码剥离。此转换可以删除
虚拟寄存器的名称
内部全局变量和函数的符号
调试信息
请注意,此转换会使代码的可读性大大降低,因此仅应在将使用 strip 实用程序的情况下使用,例如减小代码大小或使其更难进行逆向工程。
strip-dead-debug-info
:剥离未使用符号的调试信息¶
执行代码剥离。与 strip 类似,但仅剥离未使用符号的调试信息。
strip-dead-prototypes
:剥离未使用的函数原型¶
此趟 pass 遍历输入模块中的所有函数,查找未使用的声明并删除它们。未使用的声明是没有可用实现的函数的声明(即,未使用的库函数的声明)。
strip-debug-declare
:剥离所有 llvm.dbg.declare
内在函数和 #dbg_declare
记录。 ——————————————————————-
执行代码剥离。与 strip 类似,但仅剥离 llvm.dbg.declare
内在函数。
strip-nondebug
:从模块中剥离除 dbg 符号外的所有符号¶
执行代码剥离。与 strip 类似,但保留 dbg 信息。
tailcallelim
:尾调用消除¶
此文件转换对当前函数(自递归)的调用,后跟返回指令,并分支到函数的入口,从而创建一个循环。此趟 pass 还实现了对基本算法的以下扩展
调用和返回之间的琐碎指令不会阻止转换的发生,尽管目前分析无法支持移动任何真正有用的指令(仅限死指令)。
此趟 pass 转换那些因关联表达式而无法进行尾递归的函数,以使用累加器变量,从而将典型的朴素阶乘或斐波那契实现编译为高效代码。
如果函数返回 void,如果返回值返回调用的结果,或者如果函数在所有函数出口处返回运行时常量,则执行 TRE。返回其他内容(如常量 0)并且仍然可以进行 TRE 是可能的,尽管不太可能。如果函数中*所有其他*返回指令都返回完全相同的值,则可以进行 TRE。
如果它可以证明被调用者不访问其调用者堆栈帧,则它们将被标记为有资格进行尾调用消除(由代码生成器)。
实用工具 Pass¶
本节介绍 LLVM 实用工具 Pass。
deadarghaX0r
:死参数 Hack(仅限 BUGPOINT 使用;请勿使用)¶
与死参数消除相同,但删除外部函数的参数。这仅供 bugpoint 使用。
extract-blocks
:从模块中提取基本块(供 bugpoint 使用)¶
此趟 pass 供 bugpoint 使用,将模块中的所有块提取到它们自己的函数中。
instnamer
:为匿名指令分配名称¶
这是一个小实用工具 pass,用于为指令命名,这在比较优化的效果时非常有用,因为删除未命名的指令可能会更改所有其他指令编号,从而使差异非常嘈杂。
verify
:模块验证器¶
验证 LLVM IR 代码。这对于在正在测试的优化之后运行非常有用。请注意,llvm-as 在发出位代码之前会验证其输入,并且格式错误的位代码很可能导致 LLVM 崩溃。因此,鼓励所有语言前端在执行优化转换之前验证其输出。
二元运算符的两个参数类型相同。
验证内存访问指令的索引是否与其他操作数匹配。
验证算术和其他操作是否仅对第一类类型执行。验证移位和逻辑运算是否仅发生在整数上。
switch 语句中的所有常量都具有正确的类型。
代码采用有效的 SSA 形式。
将标签放入任何其他类型(如结构)或返回标签是非法的。
只有 phi 节点可以自引用:
%x = add i32 %x
,%x
无效。PHI 节点必须为每个前驱节点都有一个条目,并且没有额外的条目。
PHI 节点必须是基本块中的第一个事物,全部组合在一起。
PHI 节点必须至少有一个条目。
所有基本块都应仅以终止符指令结束,而不应包含它们。
函数的入口节点不得有前驱节点。
所有指令都必须嵌入到基本块中。
函数不能采用 void 类型的参数。
验证函数的参数列表与其声明的类型是否一致。
为 void 值指定名称是非法的。
拥有没有初始值设定项的内部全局值是非法的。
拥有
ret
指令返回的值与函数返回值类型不一致是非法的。函数调用参数类型与函数原型匹配。
代码中散布的断言测试的所有其他内容。
请注意,这不提供完整的安全验证(如 Java),而只是试图确保代码格式良好。
view-cfg
:查看函数的 CFG¶
使用 GraphViz 工具显示控制流程图。此外,可以使用 -cfg-func-name=<substring>
选项来过滤显示的函数。将显示包含指定子字符串的所有函数。
view-cfg-only
:查看函数的 CFG(不包含函数体)¶
使用 GraphViz 工具显示控制流程图,但省略函数体。此外,可以使用 -cfg-func-name=<substring>
选项来过滤显示的函数。将显示包含指定子字符串的所有函数。
view-dom
:查看函数支配树¶
使用 GraphViz 工具显示支配树。
view-dom-only
:查看函数支配树(不包含函数体)¶
使用 GraphViz 工具显示支配树,但省略函数体。
view-post-dom
:查看函数后支配树¶
使用 GraphViz 工具显示后支配树。
view-post-dom-only
:查看函数后支配树(不包含函数体)¶
使用 GraphViz 工具显示后支配树,但省略函数体。
transform-warning
:报告错过的强制转换¶
发出有关尚未应用的强制转换的警告(例如来自 #pragma omp simd
)。