核心流水线

全局指令选择(GlobalISel)的核心流水线是

../_images/pipeline-overview.png

图中所示的四个阶段包括

IRTranslator

LLVM-IR 转换为 gMIR(通用 MIR)。这在很大程度上是一个直接的转换,并且很少进行目标自定义。它有点类似于 SelectionDAGBuilder,但构建的是一种名为 gMIR 的 MIR 变体,而不是专门的表示形式。gMIR 使用与 MIR 完全相同的数据结构,但具有更宽松的约束。例如,虚拟寄存器可以被约束到特定类型,而无需将其约束到特定的寄存器类。

Legalizer

用支持的操作替换不支持的操作。换句话说,它将 gMIR 塑造成后端可以支持的形式。目标需要支持的操作集非常小,但除此之外,目标可以根据需要塑造 MIR。

寄存器组选择器

将虚拟寄存器绑定到寄存器组。此阶段旨在通过将 MIR 的部分聚类在一起,从而最大程度地减少跨寄存器组的复制。

指令选择

使用 gMIR 选择目标指令。此时,gMIR 已经受到足够的约束,使其成为 MIR。

虽然我们倾向于将它们视为不同的阶段,但需要注意的是,这里有很大的灵活性,并且允许某些操作比下面描述的更早发生。例如,Legalizer 直接将内联函数合法化为目标指令的情况并不少见。具体的约束是,在每个阶段之后,以下附加约束都必须得到保留。

IRTranslator

在此阶段之后,表示形式必须是 gMIR、MIR 或两者的混合。通常,一开始大部分将是 gMIR,但后面的阶段将逐渐将 gMIR 转换为 MIR。

Legalizer

在此阶段之后,不得保留或引入任何非法操作。

寄存器组选择器

在此阶段之后,所有虚拟寄存器都必须分配寄存器组。

指令选择

在此阶段之后,不得保留或引入任何 gMIR。换句话说,我们必须完成从 gMIR 到 MIR 的转换。

除了这些阶段之外,还有一些可选的阶段执行优化。当前的可选阶段包括

Combiner

用更好的替代方案替换指令模式。通常,这意味着通过用更快的替代方案替换指令来提高运行时性能,但 Combiner 也可以关注代码大小或其他指标。

可以插入更多此类阶段来支持更高的优化级别或目标特定的需求。一个可能的流水线是

../_images/pipeline-overview-with-combiners.png

当然,Combiner 也可以插入到其他位置。只要其任务完成,就可以完全替换阶段,如以下(更自定义的)示例流水线所示。

../_images/pipeline-overview-customized.png

MachineVerifier

阶段方法使我们能够使用 MachineVerifier 来强制执行流水线某些点之后所需的特性。例如,具有 legalized 属性的函数可以由 MachineVerifier 强制执行不允许出现非法指令。类似地,regBankSelected 函数可能不允许存在未分配寄存器组的虚拟寄存器。

注意

由于分层的原因,MachineVerifier 无法成为全局指令选择中的唯一验证器。目前,一些阶段也在执行验证,同时我们正在寻找解决此问题的方法。

主要问题是全局指令选择是一个单独的库,因此我们无法从代码生成中直接引用它。

测试

与 SelectionDAG 相比,全局指令选择的测试能力有了显著提高。SelectionDAG 有点像黑盒,内部有很多操作。这使得编写可靠地测试其特定行为方面的测试变得困难。作为比较,请参见下图

../_images/testing-pass-level.png

每个灰色框都表示一个序列化当前状态并在流水线中两点之间测试行为的机会。可以使用 -stop-before-stop-after 序列化当前状态,并使用 -start-before-start-after-run-pass 加载。

我们还可以更进一步,因为全局指令选择的许多阶段都易于进行单元测试。

../_images/testing-unit-level.png

可以创建一个虚拟目标,例如在 LegalizerHelperTest.cpp 中,并执行算法的单个步骤并检查结果。可以使用字符串嵌入 MIR 和 FileCheck 指令,因此您仍然可以访问 llvm-lit 中提供的便利功能。

调试

一种被证明特别有价值的调试技术是使用 BlockExtractor 将基本块提取到新的函数中。这可以用来追踪正确性错误,也可以用来追踪性能回归。它还可以与函数属性结合使用,以对一个或多个提取的函数禁用全局指令选择。

../_images/block-extract.png

执行提取的命令是

./bin/llvm-extract -o - -S -b ‘foo:bb1;bb4’ <input> > extracted.ll

此特定示例从名为 foo 的函数中提取两个基本块。然后可以修改新的 LLVM-IR,以便向包含 bb4 的提取函数添加 failedISel 属性,以使该函数使用 SelectionDAG。

这可能会阻止某些优化,因为全局指令选择通常能够一次处理单个函数。可以对基本块的不同组合重复此技术,直到您确定导致错误的关键块。

确定关键块后,您可以通过将块从

bb1:
  ... instructions group 1 ...
  ... instructions group 2 ...

拆分为

bb1:
  ... instructions group 1 ...
  br %bb2

bb2:
  ... instructions group 2 ...

然后对新块重复此过程,进一步提高关键指令的分辨率。

也可以以一种模式使用此技术,其中主函数使用全局指令选择编译,而提取的基本块使用 SelectionDAG(或反之亦然)编译,以利用其他代码生成器的现有质量来追踪错误。此技术也可用于在追踪性能回归时提高快速代码和慢速代码之间的相似性,并帮助您确定回归的特定原因。