核心流水线¶
GlobalISel 的核心流水线是

图中所示的四个Pass包括:
将 LLVM-IR 转换为 gMIR (通用 MIR)。这很大程度上是一个直接的翻译,几乎没有目标定制。它有点类似于 SelectionDAGBuilder,但构建的是一种名为 gMIR 的 MIR,而不是专门的表示形式。gMIR 使用与 MIR 完全相同的数据结构,但约束更宽松。例如,虚拟寄存器可以被约束为特定类型,而无需将其约束为特定的寄存器类。
将不支持的操作替换为支持的操作。换句话说,它塑造 gMIR 以适应后端可以支持的内容。目标需要支持的操作集非常小,除此之外,目标可以根据需要塑造 MIR。
将虚拟寄存器绑定到寄存器堆。此pass旨在通过将 MIR 的各个部分聚类在一起来最大限度地减少跨寄存器堆的复制。
使用 gMIR 选择目标指令。此时,gMIR 已被充分约束,变成了 MIR。
虽然我们倾向于将它们视为不同的pass,但应该注意的是,这里有很大的灵活性,并且有些事情发生得比下面描述的更早是可以的。例如,合法化器直接将 intrinsic 合法化为目标指令并不少见。具体的要求是,在每个pass之后,以下附加约束都得到保留
IRTranslator
在此pass之后,表示形式必须是 gMIR、MIR 或两者的混合。大多数情况下,开始时将是 gMIR,但后面的pass将逐渐将 gMIR 转换为 MIR。
合法化器
在此pass之后,不得保留或引入任何非法操作。
寄存器堆选择器
在此pass之后,所有虚拟寄存器都必须分配一个寄存器堆。
指令选择
在此pass之后,不得保留或引入任何 gMIR。换句话说,我们必须完成从 gMIR 到 MIR 的转换。
除了这些pass之外,还有一些可选的pass执行优化。当前的可选pass是
组合器
用更好的替代方案替换指令模式。通常,这意味着通过用更快的替代方案替换指令来提高运行时性能,但组合器也可以关注代码大小或其他指标。
可以插入此类附加pass以支持更高的优化级别或特定于目标的需要。可能的流水线是

当然,组合器也可以插入到其他位置。此外,只要它们的任务完成,pass也可以完全替换,如这个(更自定义的)示例流水线所示。

机器验证器¶
pass方法使我们能够使用 MachineVerifier
来强制执行在流水线的某些点之后需要的约束。例如,具有 legalized
属性的函数可以使用 MachineVerifier
来强制执行不出现非法指令。类似地,regBankSelected
函数可能没有未分配寄存器堆的虚拟寄存器。
注意
由于分层原因,MachineVerifier
无法成为 GlobalISel 中唯一的验证器。目前,当我们找到解决此问题的方法时,某些pass也执行验证。
主要问题是 GlobalISel 是一个单独的库,因此我们无法直接从 CodeGen 引用它。
测试¶
与 SelectionDAG 相比,测试 GlobalISel 的能力得到了显着提高。SelectionDAG 有点像一个黑匣子,内部发生了很多事情。这使得编写一个可靠地测试其行为特定方面的测试变得困难。为了进行比较,请参见下图

每个灰色框都表示一个机会,可以序列化当前状态并测试流水线中两个点之间的行为。可以使用 -stop-before
或 -stop-after
序列化当前状态,并使用 -start-before
、-start-after
和 -run-pass
加载。
我们还可以更进一步,因为 GlobalISel 的许多pass都很容易进行单元测试

可以创建一个假想的目标,例如在 LegalizerHelperTest.cpp 中那样,执行算法的单个步骤并检查结果。MIR 和 FileCheck 指令可以使用字符串嵌入,因此您仍然可以访问 llvm-lit 中可用的便利性。
调试¶
一种已被证明特别有价值的调试技术是使用 BlockExtractor 将基本块提取到新函数中。这可以用于追踪正确性错误,也可以用于追踪性能下降。它还可以与函数属性结合使用,以禁用一个或多个提取函数的 GlobalISel。

执行提取的命令是
./bin/llvm-extract -o - -S -b ‘foo:bb1;bb4’ <input> > extracted.ll
这个特定的示例从名为 foo
的函数中提取了两个基本块。然后可以修改新的 LLVM-IR,将 failedISel
属性添加到包含 bb4 的提取函数,以使该函数使用 SelectionDAG。
这可以防止一些优化,因为 GlobalISel 通常一次只能在一个函数上工作。可以为基本块的不同组合重复此技术,直到您确定了 bug 中涉及的关键块。
一旦确定了关键块,您可以通过拆分块来进一步提高对关键指令的分辨率,例如从
bb1:
... instructions group 1 ...
... instructions group 2 ...
变为
bb1:
... instructions group 1 ...
br %bb2
bb2:
... instructions group 2 ...
然后为新块重复该过程。
还可以以这样一种模式使用此技术:主函数使用 GlobalISel 编译,提取的基本块使用 SelectionDAG 编译(或反之亦然),以利用另一个代码生成器的现有质量来追踪 bug。当追踪性能下降时,此技术还可以用于提高快速代码和慢速代码之间的相似性,并帮助您精确定位性能下降的特定原因。