通用机器 IR

通用 MIR (gMIR) 是一种中间表示,它与机器 IR (MIR)共享相同的数据结构,但具有更宽松的约束。随着编译管道的进行,这些约束逐渐收紧,直到 gMIR 成为 MIR。

本文档的其余部分将假设您熟悉机器 IR (MIR)中的概念,并将重点介绍 MIR 和 gMIR 之间的差异。

通用机器指令

注意

本节扩展了 MIR 语言参考中机器指令

虽然 MIR 主要处理目标指令,并且只有一小部分目标无关的操作码,例如COPYPHIREG_SEQUENCE,但 gMIR 定义了一个丰富的通用操作码集合,它们是目标无关的,并描述了目标通常支持的操作。一个例子是G_ADD,它是整数加法的通用操作码。有关每个通用操作码的更多信息,请参阅通用操作码

MachineIRBuilder 类包装了MachineInstrBuilder,并提供了一种方便的方式来创建这些通用指令。

通用虚拟寄存器

注意

本节扩展了 MIR 语言参考中寄存器

通用虚拟寄存器类似于虚拟寄存器,但它们未分配寄存器类约束。相反,通用虚拟寄存器具有不太严格的约束,从低级类型开始,然后进一步约束到寄存器组。最终,它们将被约束到一个寄存器类,此时它们将成为正常的虚拟寄存器。

通用虚拟寄存器可以与MachineRegisterInfo提供的全部虚拟寄存器 API 一起使用。特别是,可以使用 def-use 链 API,而无需将它们与非通用虚拟寄存器区分开来。

为简单起见,大多数通用指令仅接受虚拟寄存器(通用和非通用)。但也有一些例外,但总的来说

  • 它们不使用立即数,而是使用由具体化立即数值的指令定义的通用虚拟寄存器(请参阅常量的转换)。通常是 G_CONSTANT 或 G_FCONSTANT。此规则的一个例外是 G_SEXT_INREG,其中必须使用立即数。

  • 它们不使用物理寄存器,而是使用一个通用虚拟寄存器,该寄存器要么由来自物理寄存器的COPY定义,要么由定义物理寄存器的COPY使用。

历史说明

我们最初使用了一种替代表示,其中 MRI 跟踪每个通用虚拟寄存器的尺寸,并且指令具有类型列表。这有两个缺点:类型和尺寸是冗余的,并且没有通用的方法来获取给定操作数的类型(因为指令类型和操作数之间没有 1:1 的映射)。我们考虑将类型放在 MCInstrDesc 的某个变体中:请参阅PR26576:[GlobalISel] 通用 MachineInstr 需要类型,但这会增加相关对象的内存占用

寄存器组

寄存器组是由目标定义的一组寄存器类。此定义相当宽松,因此让我们讨论一下它们可以实现什么。

假设我们有一个处理器有两个寄存器文件 A 和 B。它们在各个方面都相同,并以相同的成本支持相同的指令。它们只是物理上存储在不同的位置,并且每个指令只能访问 A 或 B 中的寄存器,而不能混合使用两者。如果我们要对存储在两个寄存器文件中的一部分数据执行操作,则必须首先将所有数据复制到单个寄存器文件中。

对于这样的处理器,我们将受益于将相关数据聚类到一个寄存器文件中,以便最大程度地减少在寄存器文件之间复制数据的成本,以满足所有指令的(可能冲突的)要求。寄存器组是一种方法,可以约束寄存器分配器使用特定寄存器文件来存储虚拟寄存器。

在实践中,寄存器文件 A 和 B 很少是相同的。它们通常可以存储相同的数据,但通常对每个寄存器文件可以执行哪些操作有一些限制。一个相当常见的模式是其中一个可用于整数运算,另一个可用于浮点运算。为了适应这一点,让我们将 A 和 B 重命名为 GPR(通用寄存器)和 FPR(浮点寄存器)。

现在我们有一些额外的约束限制了我们。像 G_FMUL 这样的操作必须在 FPR 中发生,而 G_ADD 必须在 GPR 中发生。但是,即使这规定了许多分配,我们仍然有一些自由度。G_LOAD 可以在 GPR 和 FPR 中发生,我们想要哪一个取决于谁将使用加载的数据。类似地,G_FNEG 可以在 GPR 和 FPR 中发生。如果我们将其分配给 FPR,那么我们将使用浮点求反。但是,如果我们将其分配给 GPR,那么我们可以等效地将符号位与 1 进行异或以对其求反。

总之,寄存器组是一种方法,可以根据在给定上下文中应用每个选择时的差异分析,来消除看似等效的选择之间的歧义。

举一些具体的例子

AArch64

AArch64 有三个主要的寄存器组。GPR 用于整数运算,FPR 用于浮点运算以及 NEON 矢量指令集。第三个是 CCR,它描述用于预测的条件码寄存器。

MIPS

MIPS 有五个主要的寄存器组,其中许多程序实际上只使用一两个。GPR 是用于整数运算的通用寄存器组。FGR 或 CP1 用于浮点运算以及 MSA 矢量指令和一些其他特定于应用程序的扩展。CP0 用于系统寄存器,很少有程序会使用它。CP2 和 CP3 用于芯片中可能存在的任何特定于应用程序的协处理器。可以说,还有一个用于 LO 和 HI 寄存器的第六个寄存器组,但这些寄存器仅用于少数操作的结果,并且将其与 GPR 区分开来是否值得怀疑。

X86

X86 可以看作有 3 个主要的寄存器组:通用、x87 和向量(可以进一步细分为每个域的寄存器组,用于单精度和双精度指令)。它看起来还有一些其他潜在的寄存器组,例如一个用于 AVX512 掩码寄存器的寄存器组。

寄存器组由目标提供的 API 描述,RegisterBankInfo

低级类型

此外,每个通用虚拟寄存器都具有一个类型,由LLT类的实例表示。

EVT/MVT/Type类似,它不区分无符号和有符号整数类型。此外,它也不区分整数和浮点类型:它主要传达绝对必要的信息,例如大小和向量通道数

  • sN 用于标量

  • pN 用于指针

  • <N x sM> 用于向量

LLT 旨在替换 SelectionDAG 中EVT 的用法。

以下是一些 LLT 示例及其EVTType 等价物

LLT

EVT

IR 类型

s1

i1

i1

s8

i8

i8

s32

i32

i32

s32

f32

float

s17

i17

i17

s16

N/A

{i8, i8} [1]

s32

N/A

[4 x i8] [1]

p0

iPTR

i8*, i32*, %opaque*

p2

iPTR

i8 addrspace(2)*

<4 x s32>

v4f32

<4 x float>

s64

v1f64

<1 x double>

<3 x s32>

v3i32

<3 x i32>

基本原理:指令已经编码了对类型的特定解释(例如,addfadd,或 sdivudiv)。在类型系统中也编码该信息需要引入没有真正优势的位转换。

指针类型按地址空间区分。这与 IR 匹配,而不是 SelectionDAG,在 SelectionDAG 中地址空间是操作的属性。此表示更好地支持指针根据其地址空间具有不同的尺寸。

注意

注意

这仍然正确吗?我以为我们已经去掉了 1 元素向量这个概念。假设它可能与标量不同,但我认为我们没有找到实际的出现情况。

目前,LLT 要求向量至少包含 2 个元素,但某些目标具有“1 元素向量”的概念。将它们表示为其底层标量类型是一种很好的简化方法。

脚注

通用操作码参考

可用的通用操作码在通用操作码 中描述。