可以转载,请注明出处!
对于type system,官方文档中并没有列举太多的例子,就只是说了一下他的结构,要想明白,还是要在工作与学习中反复练习,才能彻底掌握。对于有c基础的,理解起来相对会很轻松一点,比如指针和结构体,在以后的使用过程中,细品llvm的指针和c的指针有什么异同!
3.1 Void Type
Void类型不代表任何值,也没有大小。跟c、java中的void一样,在需要的地方起占位作用。
3.2 函数类型(Function Type)
Function
类型可以看做是函数的签名,它包括了函数的返回类型和形参列表。
语法:
<returntype> (<parameter list>)
returntype
是函数的返回类型,必须是一个void
类型或者first class
类型(不包含label
和metadata
类型)。Parameter list
是参数列表,多个参数之间用逗号分开。
例如:
i32 (i32)
接受一个i32
类型,返回一个i32
类型
float (i16, i32 *) *
函数指针(注意指针函数和函数指针的区别),该函数接受一个i16
类型和一个指向i32
的指针,返回一个float
类型
i32 (i8*, ...)
一个可变参数函数,至少有一个指向i8
(对应C语言中的char
)的指针,它返回一个i32
。这是LLVM中printf
函数的签名
{i32, i32} (i32)
接受一个i32
,返回一个结构体
3.3 一级类型(First Class Type)
first class type
是IR中最最最重要的类型,所有的指令都只能产生这个类型的值。First class type
包含下面五种类型。
3.3.1 单值类型(Single Value Types)
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_mmx Type
x86_mmx
类型表示在x86机器上的MMX寄存器中保存的值。允许的操作相当有限:作为参数和返回值,load
和store
以及bitcast
指令操作。用户指定的MMX指令表示为具有参数and/or
此类型结果的内部调用或asm
调用。没有这种类型的数组、向量或常量。
4)指针类型(Pointer Type)
指针类型用于指定内存位置,通常用于引用内存中的对象。指针类型可以有一个可选的地址空间属性,用于定义指向对象所在的编号地址空间。默认地址空间为0,非零地址空间的语义是特定于目标的。需要注意的是,没有指向void
和label
类型的指针,即不存在void*
和label*
,使用i8*
代替。
【注】 这里的指针同C语言中的指针还是有区别,在llvm里,值是值,指针是指针,值存到指针里就store
,从指针取值就load
,从值是取不到指针的(个人理解)。
5)向量类型(Vector Type)
向量类型是表示向量元素的简单派生类型,当使用单个指令(SIMD)并行操作多个原始数据(primitive data
)时,将使用向量类型。向量类型由一个表示元素数量的整数值和一个原始数据类型组成,在不确切知道编译环境的硬件长度时,需要用vscale
去表示向量具有可扩展性(感觉我对可扩展向量理解的不到位,最好是自己看一下文档)。
语法:
< <# elements> x <elementtype> > ; 定长向量
< vscale x <# elements> x <elementtype> > ; 可扩展向量
elements
表示向量元素的数量,取值要大于0。elementtype
表示向量元素的基本类型,可以是整数、浮点、指针类型。
这是对可扩展向量的解释,我怕我解释错。For scalable vectors, the total number of elements is a constant multiple (called vscale) of the specified number of elements; vscale is a positive integer that is unknown at compile time and the same hardware-dependent constant for all scalable vectors at run time. The size of a specific scalable vector type is thus constant within IR, even if the exact size in bytes cannot be determined until run time.
例如:
<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.3.2 标签类型(Label Type)
官方文档对这个的解释特别简单,就一句表示代码的标签,没了。我觉得更确切的解释应该是,表示basicblock
进入的标签。
语法:
label %变量名
例如:
%cmp = icmp eq i32 %rem, 0 ;icmp是比较指令,这里比较%rem与0是否相等
;br指令是跳转指令,这里根据上一步比较出来的变量%cmp的值决定跳转到那个分支
;label类型的作用就体现在这里
br i1 %cmp, label %if.then, label %if.else
if.then: ; preds = %entry
store i32 0, i32* %retval, align 4
br label %return
if.else: ; preds = %entry
store i32 1, i32* %retval, align 4
br label %return
从上面的用例中可以看出,跳转指令中声明的label类型变量,并不参与计算,而是作为一个代码块(basicblock)的开始标签。声明时label
后面的变量名可以随便起,但是必须要一一对应上,如label %if.then
要和if.then:
对应。
3.3.3 (标记类型)Token Type
当一个值与一条指令相关联时便可采取token
类型,但是这个值不能试图去反思(introspect)或模糊(obscure)它。所以具有phi
或select
指令的token
类型是不合适的。
语法:
token
后续对这个类型掌握的比较好了再来补!
3.3.4 元数据类型(Metadata Type)
元数据类型表示嵌入的元数据。除函数参数外,不得从元数据创建派生类型。
语法:
metadata
3.3.5 聚合类型(Aggregate Types)
1)数组类型(ArrayType)
在内存中砍了一块空间,将元素按顺序放进去。一个数组类型需要表示元素数量的值和基础数据类型两个属性。
语法:
[<# elements> x <elementtype>]
elements
整型常量值,即constant integer value。elementtype
可以是任何类型
例如:
[40 x i32] 由40个i32值组成的数组
[12 x [10 x float]] 12x10 的二维数组,基础类型是float
[2 x [3 x [4 x i16]]] 2x3x4 的三维数组,基础类型是i16
下面这句话没有理解,只读了个大概意思,说是llvm中可以有一维的变长数组,比如说这样就是一个变长数组[0 x float]
。
There is no restriction on indexing beyond the end of the array implied by a static type (though there are restrictions on indexing beyond the bounds of an allocated object in some cases). This means that single-dimension ‘variable sized array’ addressing can be implemented in LLVM with a zero length array type. An implementation of ‘pascal style arrays’ in LLVM could use the type “{ i32, [0 x float]}”, for example.
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 Types)
不透明结构类型用于表示没有指定主体的命名结构类型,可以用于向前声明尚不可用的类型。
语法:
%X = type opaque
%52 = type opaque