文章目录
前言
llvm 的类型体系是IR语法中很重要的一部分,中间代码类型化可以使得许多优化在IR层面进行,不需要在转换之前做额外的分析,强类型系统可以更加轻松地读取生成的代码,并支持对正常的三地址码表示以及新颖的分析和转换。
每个三地址码指令,都可以分解为四个元祖:运算符、运算对象1、运算对象2和结果。因为每个称述都包含了三个变量,所以它被称为三地址码
1. void 类型
void类型没有值,也没有大小,只是起到占位作用。
2.Function 类型
函数类型可以认为是函数的签名,它包括了一个返回值类型和形参列表,函数的返回值类型是void类型或者first class类型——除label和metadata之外。
<returntype> (<parameter list>)
形参列表用逗号分隔,形参列表可以包括一个类型...
,表示该函数采用可变数量的参数(像python中的**kwargs)。
例如:
- i32(i32) function 传入一个i32类型,返回一个i32类型
- i32 (i8*, …) 一个可变参数函数,它至少接收一个指向i8(对应C中的char)的指针,返回一个整数(i32),这是LLVM中
printf
函数的签名。 - float (i16, i32 *) * 函数指针(注意指针函数和函数指针的区别),该函数接受一个i16类型和一个指向i32的指针,返回一个float类型
- {i32, i32} (i32) 接收一个
i32
,返回一个结构体。
3. first class 类型
the first class type是IR类型中最重要的,所有的指令(instruction)都只能产生这个类型的值。包含五种类型:
3.1 单值类型(single value Type)
从CodeGen的角度来看,这些类型在寄存器中都是有效的。
1 整型类型(Integer Type)
整型类型非常简单,格式如:iN
,简单描述了所需数大小的位宽N
,可以表示的范围是1~(2^23)-1
。i32
就表示整数位宽为32位,占用4字节大小,对应C语言中的int
类型。
2. 浮点类型(Floating-Point Types)
half 16位浮点值
float 32位浮点值
double 64位浮点值
fp128 128位浮点值(112位尾数)
x86_fp80 80位浮点值(X87)
ppc_fp128 128位浮点值(两个64位)
half 16位浮点值
float 32位浮点值
其中half
,float
,double
和fp128
的二进制格式分别对应于binary16
,binary32
,binary64
和binary128
的IEEE-754-2008规范。
3. X86_amx Type
x86_amx
类型表示在x86机器上的AMX寄存器中保存的值。允许的操作非常有限,只有不多几个内联函数被允许:跨步加载和存储、零和点积。没有这种类型的任何指令、参数、数组、指针、向量或常量。
4. X86_mmx Type
x86_mmx
类型表示在x86机器上的MMX寄存器中保存的值。允许的操作相当有限:作为参数和返回值,load
和store
以及bitcast
指令操作。用户指定的MMX指令表示为具有参数and/or
此类型结果的内部调用或asm
调用。没有这种类型的数组、向量或常量。
5. 指针类型(Pointer Type)
指针类型通常被用于引用内存中的对象。指针类型可以有一个可选的地址空间属性,用于定义指向对象所在的编号地址空间。默认地址空间为0,非零地址空间的语义是特定于目标的。需要注意的是,没有指向void
和label
类型的指针。
6. 目标扩展类型(Target Extension Type)
目标扩展类型表示必须通过优化保留的类型,但通常对编译器不透明。它们可以用作函数参数或自变量,也可以用在phi或select指令中。某些类型也可以在alloca指令中使用或用作全局值,相应地,对它们使用加载和存储指令是合法的。这些类型的完整语义由目标定义
目标扩展类型可能具有的唯一常量是 zeroinitializer、undef 和 poison。目标扩展类型的其他可能值可能来自特定于目标的内在函数和函数。
7. 矢量类型(Vector Type)
向量类型是表示向量元素的简单派生类型,当单个指令(SIMD)并行操作多个原始数据时,将使用向量类型。矢量类型需要一个大小(元素数量)、一个基础原数据类型和一个可伸缩属性来表示在编译时不知道确切的硬件矢量长度的矢量。
通常来说,矢量元素在内存中的存放方式和数组类似。前提是矢量元素是字节大小,这样的类比讲得通(在内存中存放方式和数组类似),然而,如果矢量元素不是字节大小,它的存放会变得稍稍复杂。一种描述内存布局的方式是将类似于这样的向量转换为一个N*M位的整数类型,然后遵循将此整数类型存放在内存中的规则,进而存放该矢量。
矢量类型转为标量类型(整数),可以看到矢量中的元素被打包在一起(没有填充)。元素插入到整数的顺序取决于小端存储还是大端存储。对于小端,元素零放在整数的最低有效位,而对于大端,元素零存放在最高有效位。
<4 x i32> 4个i32类型值的向量
<8 x float> 8个float类型值的向量
<4 x i64*> 4个i64类型指针值的向量
<vscale x 4 x i32> 4个i32类型值整数倍的向量(8个、12个、16个)
3.2 标签类型(Label Type)
The label type represents code labels.
label
3.3 Token 类型(Token Type)
当一个值与一条指令相关联时便可采取token
类型,但是这个值不能试图去反思(introspect
)或模糊(obscure
)它。所以具有phi
或select
指令的token类型
是不合适的。
3.4 元数据类型(Metadata Type)
元数据类型表示嵌入的元数据。除了函数参数外,不得从元数据创建派生类型。
语法:
metadata
3.5 聚合类型(Aggregate Type)
聚合类型是派生类型的子集,它可以包含多个成员类型。
注意:Vectors不是派生类型
1. 数组类型(Array Type)
数组类型是一种非常简单的派生类型,它将元素按序排列在内存中。数组类型需要元素的数量和基础数据类型这两个参数。
语法:
[<# elements> x <elementtype>]
例如:
[40 x i32] 由40个i32值组成的数组
[12 x [10 x float]] 12x10 的二维数组,基础类型是float
[2 x [3 x [4 x i16]]] 2x3x4 的三维数组,基础类型是i16
2. 结构体(Structure Type)
结构体类型用于表示内存中的数据成员集合,结构体的元素可以是任何具有大小的类型。通过使用getelementptr
指令获取一个指向字段的指针,使用load
和store
访问内存中的结构体。使用extractvalue
和insertvalue
指令访问寄存器中的结构体。
结构体可以选择压缩(packed)结构,这表示结构的对齐方式是一个字节,并且元素之间没有填充。在非压缩(non-packed)结构体中,字段类型之间的填充将按照模块(module)中DataLayout
字符串的定义插入,且该模块需要与基础代码生成器的预期相匹配。
结构体可以是字面量(literal)或者标识符(identified)。字面量结构与其它类型(例如{i32, i32}*
)一起定义,而标识符类型始终在顶层使用名称定义。字面量类型被其类型所独占,因为没有办法写入(write)它们,所以永远不会递归或不透明。标识符的类型可以是递归的,可以是不透明的,并且永远不会被分离。
语法:
%T1 = type { <type list> } ; 普通标识符结构类型
%T2 = type <{ <type list> }> ; 压缩(packed)标识符结构类型
例如:
{ i32, i32, i32 } 3个i32值的结构体
{ float, i32 (i32) * } 第一个元素是float,第二个元素是一个指向函数的指针,该函数接受一个i32,返回一个i32
<{ i8, i32 }> 一个已知为5字节大小的压缩结构
3. 不透明结构体(Opaque Structure Type)
不透明结构类型用于表示没有指定主体的命名结构类型,可以用于向前声明尚不可用的类型。
语法:
%X = type opaque
%52 = type opaque