LLVM 项目博客

LLVM 项目新闻和来自底层的详细信息

LLVM 3.1 向量变化

英特尔在其众多产品中使用低级虚拟机 (LLVM),包括 Intel® OpenCL SDK。SDK 的隐式向量化模块生成使用向量类型的 LLVM-IR(中间表示)。

LLVM-IR 支持使用向量数据类型的操作,LLVM 代码生成器需要进行非平凡的工作才能将向量操作有效地编译成 SIMD 指令。最近,LLVM 代码生成方面有一些变化,使向量操作的代码生成更加高效。除了许多低级优化之外,本文还将介绍两项重大变化:向量选择实现和对指针向量 的支持。

LLVM-IR 选择指令

LLVM IR 的 'select' 指令用于根据条件选择一个值。如果条件计算结果为 'True',则该指令返回第一个值参数;否则,它将返回第二个值参数。例如
  %X = select i1 true, i8 17, i8 42          ; yields i8:17
'select' 指令还支持向量数据类型,其中条件是布尔数据类型的向量。如果条件是布尔向量(见上文),则选择将按元素进行。

向量选择指令对于向量化编译器非常有用,向量化编译器使用它们来 '屏蔽' 不活动的 SIMD 通道。直到最近,LLVM 代码生成器才支持带有向量数据类型的条件。启用它们需要增强代码生成器的其他几个方面。

SSE 混合

英特尔的 SSE4.1 指令集包含 PBLENDVB 指令。该指令使用 XMM0 中每个字节的高位指定的掩码,从寄存器 XMM1 和 XMM2 中选择字节值,并将这些值存储到 XMM1 中。还有一些其他指令用于处理更大数据类型,例如 32 位整数等。选择位位于高位可能看起来很奇怪,但向量比较机器指令也会设置高位,因此比较和混合指令可以有效地协同工作。

如前所述,LLVM-IR 的 'select' 指令将掩码表示为布尔向量,需要将其转换为每个 SIMD 向量元素的高位。这种转换由 LLVM 代码生成器中的类型合法化阶段完成。

类型合法化

类型合法化是一个代码生成阶段,它将 LLVM-IR 表示的任何任意数据类型的操作转换为使用目标机器支持的类型的操作。例如,在 x86 架构上,通用寄存器支持类型 i8、i16、i32 和 i64。这些类型是 '合法的',因为它们适合机器寄存器。类型 i24 是 '不合法的',因为它与本机 x86 机器寄存器不匹配。类型合法化有一套复杂的规则用于合法化不同的类型,在许多情况下,类型合法化需要多个步骤。类型合法化有许多策略可以处理不合法的向量类型
  • 扩展 - 类型合法化可以通过添加额外的元素来扩展向量。例如,类型 <3 x float> 将扩展到合法类型 '<4 x float>'。
  • 拆分 - 类型合法化可以将大向量拆分为更小的类型。例如,<8 x float> 类型的值可以拆分为两个合法类型 '<4 x float>' 的值。
  • 标量化 - 类型合法化可以将向量分解为多个标量。例如,<2 x i64> 类型的操作可以使用通用寄存器在两个 64 位标量上完成。
请注意,以上策略都不能将类型 '<4 x i1>' 转换为寄存器大小的类型 '<4 x i32>'。为了支持向量选择的代码生成,我们添加了一种新的合法化类型,该类型可以支持向量中每个元素的提升,而不是增加向量中的元素数量。

LLVM 已经将小的标量整数提升为更大的整数。例如,类型 i8 会在不支持小于 32 位类型的处理器上提升为 i32。一旦实现了新的类型合法化技术,添加对 select 指令的支持就变得容易了。与其他指令类似,TD 文件中的一个简单模式为 Intel 架构 (SSE4.1、AVX 和 AVX2) 的不同代添加了对不同 '混合' 指令的支持。不支持 '混合' 指令的处理器会将向量选择 IR 降低为一系列 AND、XOR、OR,并具有可接受的性能。

元素提升优化

用于向量的新类型合法化方法和新的向量选择实现为新的优化打开了大门。例如,考虑将 <4 x i8> 类型的向量保存到内存中的问题。这种类型的内存表示是四个连续的字节,但向量的寄存器表示是 '<4 x i32>'。如果没有额外的优化,保存向量的朴素方法是将每个字节提取到通用寄存器中,并逐个将它们保存到内存中。我们最近添加的优化之一是将所有保存的字节混洗到向量的较低部分,并将四个字节使用单个标量 32 位存储保存到内存中。

指针向量

直到最近,LLVM 的向量类型只包含整数或浮点数元素。这种抽象与常见的 SIMD 指令集相匹配,并为许多处理器实现了高效的代码生成。在许多情况下,向量化编译器希望表示一个指针向量,主要用于实现散布/收集内存操作。IR 中缺乏支持使一些向量化编译器通过将指针转换为整数来规避限制。此解决方案要求向量化编译器手动实现地址计算,并增加了软件的复杂性。

为了解决这个问题,LLVM 现在支持指针向量类型,以及操作它的指令。与其他向量指令类似,指针向量可以使用 'insertelement'、'extractelement' 和 'shufflevector' 指令创建和修改。但是,如果没有向量 'getelementptr' 指令,指针向量类型将没有那么有用。我们将 LLVM 的 GEP 指令扩展为支持指针向量。新的指针向量抽象使代码生成更加高效,即使对于不支持显式收集/散布指令的处理器也是如此,因为地址计算现在是在向量上完成的。以下代码现在在 LLVM 中是合法的

define i32 @foo(<4 x i32*> %base, <4 x i32> %offset) nounwind { 
entry:
%A2 = getelementptr <4 x i32*> %base, <4 x i32> %offset
%k = extractelement <4 x i32*> %A2, i32 3
%v = load i32* %k
ret i32 %v
}
我们目前只支持指向基本类型的指针向量。将来,我们可能会添加其他功能和优化。

结论

Intel® OpenCL SDK 包含一个隐式向量化模块,该模块使用 LLVM 编译器工具包进行代码生成。我们正在继续改进 LLVM 对向量的代码生成支持,以便支持未来的英特尔架构。

LLVM 3.1 将包含许多变化,这些变化将使向量化编译器能够生成更好的代码。