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 别名分析准确性评估器。基本上,对于程序中的每个函数,它只是查询别名分析实现如何回答函数中每对指针之间的别名查询。

此 Pass 受以下人员的代码启发并改编而来: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: 归纳变量使用者

对从归纳变量计算出的表达式的“有趣”使用者进行簿记。

lazy-value-info: 惰性值信息分析

惰性计算值约束信息的接口。

lint: 静态 lint 检查 LLVM IR

此 Pass 静态检查 LLVM IR 中产生未定义或可能意外行为的常见且易于识别的结构。

它不能保证正确性,有两个方面。首先,它不全面。可以静态执行的检查尚未实现。其中一些由 TODO 注释指示,但这些注释也不全面。其次,许多条件无法静态检查。此 Pass 不执行动态检测,因此无法检查所有可能的问题。

另一个限制是它假设所有代码都将执行。通过永远不会到达的基本块中的空指针进行存储是无害的,但此 Pass 仍会发出警告。

优化 Pass 可能会使此 Pass 检查的条件变得或多或少明显。如果优化 Pass 似乎正在引入警告,则可能是因为优化 Pass 仅仅是在暴露代码中已存在的条件。

此代码可能在 instcombine 之前运行。在许多情况下,instcombine 会检查相同类型的事物并将具有未定义行为的指令转换为不可到达(或等效)指令。因此,此 Pass 尽力查看位转换等。

loops: 自然循环信息

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

memdep: 内存依赖性分析

一种分析,它确定对于给定的内存操作,它依赖于哪些前面的内存操作。它建立在别名分析信息的基础上,并试图为一种常见的别名信息查询类型提供惰性缓存接口。

postdomtree: 后支配树构建

此 Pass 是一种简单而后支配者构建算法,用于查找后支配者。

function(print): 将函数打印到标准错误

PrintFunctionPass 类旨在与其他 FunctionPasses 结合使用,并在处理模块的函数时将其打印出来。

module(print): 将模块打印到标准错误

此 Pass 仅在执行时打印出整个模块。

regions: 检测单入口单出口区域

RegionInfo Pass 检测函数中的单入口单出口区域,其中区域定义为仅在两个位置连接到其余图的任何子图。此外,还构建了一个分层的区域树。

scalar-evolution: 标量演化分析

ScalarEvolution 分析可用于分析和分类循环中的标量表达式。它专门用于识别通用归纳变量,并使用抽象且不透明的 SCEV 类来表示它们。有了此分析,就可以获得循环的循环次数和其他重要属性。

此分析主要用于归纳变量替换和强度削弱。

scev-aa: 基于标量演化的别名分析

根据 ScalarEvolution 查询实现的简单别名分析。

这与传统的循环相关性分析不同,因为它测试循环的单个迭代内的相关性,而不是不同迭代之间的相关性。

ScalarEvolution 对指针算术的理解比 BasicAliasAnalysis 的特定分析集合更完整。

stack-safety: 堆栈安全分析

StackSafety 分析可用于确定是否可以将堆栈分配的变量视为不受内存访问错误影响的安全变量。

此分析的主要目的是供消毒剂使用,以避免对安全变量进行不必要的检测。

转换 Pass

本节介绍 LLVM 转换 Pass。

adce: 积极的死代码消除

ADCE 积极尝试消除代码。此 Pass 类似于 DCE,但它假设值是死的,除非证明并非如此。这类似于 SCCP,只是应用于值的活性。

always-inline: 用于 always_inline 函数的内联器

一个自定义内联器,它只处理标记为“always inline”的函数。

argpromotion: 将“按引用”参数提升为标量

此 Pass 将“按引用”参数提升为“按值”参数。在实践中,这意味着查找具有指针参数的内部函数。如果它可以通过使用别名分析来证明参数被加载,那么它可以将值传递给函数而不是值的地址。这可能导致代码的递归简化并导致消除 alloca(尤其是在像 STL 这样的 C++ 模板代码中)。

此 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 节点。

  • 任何指针算术递归都提升为使用数组下标。

如果循环的循环次数可计算,此过程还会进行以下更改

  • 循环的退出条件被规范化为将归纳值与退出值进行比较。这将像这样的循环转换为

    for (i = 7; i*i < 1000; ++i)
    
    into
    
    for (i = 0; i != 25; ++i)
    
  • 将循环外部对从 indvar 派生的表达式的任何使用更改为在循环外部计算派生值,从而消除对归纳变量的退出值的依赖。如果循环的唯一目的是计算某些派生表达式的退出值,则此转换将使循环变为死循环。

此转换应在执行所有所需的循环转换后,再进行强度削弱。此外,在有利的目标上,可以将循环转换为从零倒数(“do 循环”优化)。

inline:函数集成/内联

自底向上将函数内联到被调用者中。

instcombine:合并冗余指令

合并指令以形成更少、更简单的指令。此过程不会修改 CFG。此过程是代数简化的发生地。

此过程将以下内容合并:

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

进入

%Z = add i32 %X, 2

这是一个简单的基于工作列表的算法。

此过程保证对程序执行以下规范化

  1. 如果二元运算符具有常量操作数,则将其移动到右侧。

  2. 具有常量操作数的按位运算符始终分组,以便先执行移位,然后是or,然后是and,然后是xor

  3. 如果可能,将比较指令从<>转换为=

  4. 所有布尔值上的cmp指令都替换为逻辑运算。

  5. add X, X表示为mul X, 2shl X, 1

  6. 具有常量 2 的幂参数的乘法转换为移位。

  7. … 等等。

此过程还可以简化对特定知名函数调用的调用(例如运行时库函数)。例如,在main()函数中出现的exit(3)调用可以转换为简单的return 3。是否简化库调用由-function-attrs传递和 LLVM 对不同目标上库调用的了解控制。

aggressive-instcombine:合并表达式模式

合并表达式模式以形成具有更少、更简单指令的表达式。

例如,此过程在适用时将 TruncInst 后支配的表达式的宽度减少到较小的宽度。

它与 instcombine 过程的不同之处在于它可以修改 CFG 并包含比 O(1) 复杂度更高的模式优化,因此,它应该比 instcombine 过程运行的次数更少。

internalize:内部化全局符号

此过程循环遍历输入模块中的所有函数,查找主函数。如果找到主函数,则所有其他函数以及所有具有初始化程序的全局变量都标记为内部。

ipsccp:过程间稀疏条件常量传播

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

jump-threading:跳转线程化

跳转线程化尝试查找通过基本块运行的不同控制流线程。此过程查看具有多个前驱和多个后继的块。如果可以证明该块的一个或多个前驱始终导致跳转到其中一个后继,则我们通过复制此块的内容将前驱到后继的边转发。

这可能发生的情况的一个示例如下所示

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

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

lcssa:循环闭包 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轻松消除。此转换的主要好处是它使许多其他循环优化(例如LoopUnswitch)变得更简单。您可以在循环术语部分了解有关 LCSSA 形式的更多信息

licm:循环不变代码移动

此过程执行循环不变代码移动,尝试尽可能多地从循环体中删除代码。它通过将代码提升到预头块中,或者如果安全则将其下沉到退出块中来实现此目的。此过程还将循环中必须别名的内存位置提升到寄存器中,从而提升和下沉“不变”加载和存储。

将操作提升出循环是规范化转换。它启用并简化了中间端中的后续优化。提升指令的重新物化以减少寄存器压力是后端的责任,后端拥有关于寄存器压力更准确的信息,并且还处理比 LICM 更多的增加生命范围的优化。

此过程将别名分析用于两个目的

  1. 将循环不变加载和调用移出循环。如果我们可以确定循环内的加载或调用从不与存储的任何内容发生别名,则可以像任何其他指令一样提升或下沉它。

  2. 标量内存提升。如果循环内存在存储指令,我们尝试将存储移动到循环之后而不是循环内部发生。只有在满足以下几个条件时才能发生这种情况

    1. 存储的指针是循环不变的。

    2. 循环中没有可能与指针发生别名的存储或加载。循环中没有修改/引用指针的调用。

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

loop-deletion:删除死循环

此文件实现死循环删除过程。此过程负责消除具有非无限可计算循环次数且没有副作用或易失性指令的循环,并且不会影响函数返回值的计算。

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

围绕ExtractLoop()标量转换的过程包装器,用于将每个顶级循环提取到其自己的新函数中。如果循环是给定函数中**唯一**的循环,则不会对其进行处理。对于通过 bugpoint 进行调试,此过程非常有用。

loop-reduce:循环强度削弱

此过程对循环内部的数组引用执行强度削弱,这些数组引用将其一个或多个组件作为循环归纳变量。这是通过创建一个新值来保存第一次迭代的数组访问的初始值,然后在循环中创建一个新的 GEP 指令来按适当数量递增该值来完成的。

loop-rotate:旋转循环

一个简单的循环旋转转换。可以在旋转循环的循环术语中找到其摘要。

loop-simplify:规范化自然循环

此过程执行多个转换,以将自然循环转换为更简单的形式,这使得后续分析和转换更简单且更有效。可以在循环术语,循环简化形式中找到其摘要。

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

循环退出块插入保证了所有来自循环的退出块(位于循环外部且具有循环内部的前驱块的块)仅具有来自循环内部的前驱块(因此受循环头的支配)。这简化了 LICM 中内置的存储下沉等转换。

此过程还保证循环将恰好有一个回边。

请注意,simplifycfg 过程将清理被拆分但最终变得不必要的块,因此使用此过程不应使生成的代码性能下降。

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

loop-unroll: 展开循环

此过程实现了一个简单的循环展开器。当循环已被 indvars 过程规范化时,它效果最佳,从而使其能够轻松确定循环的循环次数。

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

这可以看作是展开外层循环并将内层循环“合并”(融合)成一个。当新内层循环中可以共享变量或加载时,这可以带来显着的性能提升。它使用 依赖性分析 来证明转换是安全的。

lower-global-dtors: 降低全局析构函数

此过程通过创建包装函数来降低全局模块析构函数(llvm.global_dtors),这些包装函数在 llvm.global_ctors 中注册为全局构造函数,并且包含对 __cxa_atexit 的调用以注册其析构函数。

lower-atomic: 将原子内联函数降低到非原子形式

此过程将原子内联函数降低到非原子形式,以便在已知的非抢占环境中使用。

此过程不验证环境是否是非抢占的(通常这需要了解程序的整个调用图,包括可能以位码形式不可用的任何库);它只是降低每个原子内联函数。

lower-invoke: 将 invoke 降低为 call,用于无展开代码生成器

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

lower-switch: 将 SwitchInst 降低为分支

使用一系列分支重写 switch 指令,这允许目标避免实现 switch 指令,直到方便为止。

mem2reg: 将内存提升到寄存器

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

memcpyopt: MemCpy 优化

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

mergefunc: 合并函数

此过程查找可以合并的等效函数并折叠它们。

在函数集中引入了全序关系:我们定义了比较,用于回答每两个函数中哪个更大。它允许将函数排列成二叉树。

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

如果存在等效项,则我们折叠这些函数。如果两个函数都是可覆盖的,我们将功能移动到一个新的内部函数中,并留下两个可覆盖的存根指向它。

如果没有等效项,则我们将此函数添加到树中。

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

阅读 这篇文章 以了解更多详细信息。

mergereturn: 统一函数退出节点

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

partial-inliner: 部分内联器

此过程执行部分内联,通常通过内联围绕函数体周围的 if 语句。

reassociate: 重关联表达式

此过程以旨在促进更好的常量传播、GCSE、LICM、PRE 等的顺序重关联交换表达式。

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

在此算法的实现中,常量的等级被分配为 0,函数参数的等级被分配为 1,其他值的等级被分配为对应于当前函数的反向后序遍历(从 2 开始),这有效地使深度循环中的值比不在循环中的值具有更高的等级。

rel-lookup-table-converter: 相对查找表转换器

此过程将查找表转换为 PIC 友好的相对查找表。

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

此文件将所有寄存器降级为内存引用。它旨在作为 mem2reg 的逆过程。通过转换为 load 指令,跨越基本块存活的唯一值是 alloca 指令和 load 指令(在 phi 节点之前)。它旨在使 CFG 篡改变得更容易。为了使以后的篡改更容易,入口块被分成两个,这样所有引入的 alloca 指令(以及其他任何指令)都在入口块中。

sroa: 聚合体的标量替换

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

sccp: 稀疏条件常量传播

稀疏条件常量传播和合并,可以概括为

  • 假设值是常量,除非证明并非如此。

  • 假设基本块是死的,除非证明并非如此。

  • 证明值为常量,并用常量替换它们。

  • 证明条件分支是无条件的。

请注意,此过程习惯于使定义失效。在运行此过程后,最好运行 DCE 过程。

simplifycfg: 简化 CFG

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

  • 删除没有前驱块的基本块。

  • 如果只有一个前驱块并且前驱块只有一个后继块,则将基本块合并到其前驱块中。

  • 消除具有单个前驱块的基本块的 PHI 节点。

  • 消除仅包含无条件分支的基本块。

sink: 代码下沉

此过程将指令移动到后继块中(如果可能),以便它们不会在不需要其结果的路径上执行。

simple-loop-unswitch: 分离循环

此过程将包含循环不变条件的分支的循环转换为多个循环。例如,它将左侧代码转换为右侧代码

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

这可能会使代码大小呈指数级增长(每次分离循环时都会将其加倍),因此我们仅在结果代码小于阈值时才分离循环。

此过程期望在运行它之前运行 LICM 以将不变条件提升到循环之外,以使分离机会变得明显。

strip: 从模块中剥离所有符号

执行代码剥离。此转换可以删除

  • 虚拟寄存器的名称

  • 内部全局变量和函数的符号

  • 调试信息

请注意,此转换使代码的可读性大大降低,因此仅应在需要使用 strip 实用程序的情况下使用,例如减小代码大小或使其更难以反向工程。

strip-dead-debug-info: 剥离未使用的符号的调试信息

执行代码剥离。类似于 strip,但仅剥离未使用的符号的调试信息。

strip-dead-prototypes: 剥离未使用的函数原型

此过程遍历输入模块中的所有函数,查找已删除的声明并将其删除。已删除的声明是指没有可用实现的函数的声明(即,未使用的库函数的声明)。

strip-debug-declare: 剥离所有 llvm.dbg.declare 内联函数和 #dbg_declare 记录。 ——————————————————————-

执行代码剥离。类似于 strip,但仅剥离 llvm.dbg.declare 内联函数。

strip-nondebug: 从模块中剥离所有符号,但 dbg 符号除外

执行代码剥离。类似于 strip,但保留 dbg 信息。

tailcallelim: 尾调用消除

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

  1. 调用和返回之间的简单指令不会阻止转换发生,尽管目前分析无法支持移动任何真正有用的指令(仅限于死指令)。

  2. 此过程将因关联表达式而无法进行尾递归的函数转换为使用累加器变量,从而将典型的朴素阶乘或斐波那契实现编译成高效的代码。

  3. 如果函数返回 void,如果返回返回调用返回的结果,或者如果函数在所有退出函数时返回运行时常量,则执行 TRE。返回返回其他内容(如常量 0)是可能的,尽管不太可能,并且仍然可以进行 TRE。如果函数中的所有其他返回指令返回完全相同的值,则可以对其进行 TRE。

  4. 如果可以证明被调用者不访问其调用者堆栈帧,则将其标记为符合尾调用消除的条件(由代码生成器)。

实用程序过程

本节介绍 LLVM 实用程序过程。

deadarghaX0r: 死参数黑客(仅供 BUGPOINT 使用;请勿使用)

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

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

此过程由 bugpoint 用于将模块中的所有块提取到其自己的函数中。

instnamer: 为匿名指令分配名称

这是一个小的实用程序过程,它为指令命名,这在对优化效果进行差异比较时非常有用,因为删除未命名的指令会更改所有其他指令编号,从而使差异变得非常嘈杂。

verify: 模块验证器

验证 LLVM IR 代码。这在经过测试的优化之后运行非常有用。请注意,llvm-as 在发出位代码之前会验证其输入,并且格式错误的位代码可能会导致 LLVM 崩溃。因此,鼓励所有语言前端在执行优化转换之前验证其输出。

  1. 二元运算符的两个参数都是相同类型。

  2. 验证内存访问指令的索引是否与其他操作数匹配。

  3. 验证算术和其他操作是否仅在第一类类型上执行。验证移位和逻辑运算是否仅发生在整数上,例如。

  4. switch 语句中的所有常量都具有正确的类型。

  5. 代码处于有效的 SSA 形式。

  6. 不允许将标签放入任何其他类型(如结构)或返回标签。

  7. 只有 phi 节点可以自引用: %x = add i32 %x%x 无效。

  8. PHI 节点必须为每个前驱体提供一个条目,并且不能有额外的条目。

  9. PHI 节点必须是基本块中的第一件事,并且全部组合在一起。

  10. PHI 节点必须至少有一个条目。

  11. 所有基本块都只能以终止指令结尾,而不能包含终止指令。

  12. 函数的入口节点不能有前驱体。

  13. 所有指令都必须嵌入到基本块中。

  14. 函数不能接受 void 类型的参数。

  15. 验证函数的参数列表是否与其声明的类型一致。

  16. 不允许为 void 值指定名称。

  17. 不允许内部全局值没有初始化程序。

  18. 不允许 ret 指令返回与函数返回值类型不一致的值。

  19. 函数调用参数类型与函数原型匹配。

  20. 代码中散布的断言测试的所有其他内容。

请注意,这不会提供完整的安全验证(如 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)。