可以转载,请注明出处!
所有基本块的结束指令,都是终端指令,这在基本块的介绍中已经说过了。
这个就是return
,要么返回一个值,要么返回void
,返回void
就是只起到了改变控制流程的作用。
当执行ret
指令时,控制流程将返回到调用函数的上下文。如果是通过call
指令调用,则在指令调用后继续执行。如果是通过invoke
指令调用,则在normal
目标块(destination block,看一下invoke指令的语法就能理解这句话的意思了)的开头继续执行。如果指令返回一个值,该值将设置为call
或者invoke
指令的返回值。
语法:
ret <type> <value> ; Return a value from a non-void function
ret void ; Return from void function
返回值类型必须是first class类型。举例:
ret i32 5 ; Return an integer value of 5
ret void ; Return from a void function
ret { i32, i8 } { i32 4, i8 2 } ; Return a struct of values 4 and 2
这条指令用于将控制流程输送到不同的代码块,也就是一个跳转指令,包含条件跳转和无条件跳转。
语法:
br i1 <cond>, label <iftrue>, label <iffalse> ;条件跳转
br label <dest> ;无条件跳转
i1
这是一个布尔值,之所以是i1是
因为true
在内存中是1,false
是0。在执行br
指令的时候,会对整个值进行计算,如果是true
就跳到iftrue
基本块,否则就跳到iffalse
。
label
这是两个label类型,分别对应两个基本块。
举例:
Test:
%cond = icmp eq i32 %a, %b
br i1 %cond, label %IfEqual, label %IfUnequal
IfEqual:
ret i32 1
IfUnequal:
ret i32 0
switch
指令更像是一个升级版的br
,用于将控制流程输送到许多基本块中的某一个中。
语法:
switch <intty> <value>, label <defaultdest> [ <intty> <val>, label <dest> ... ]
这是一个整型值,决定着具体跳到要哪个分支。label
这是默认的label,如果后面的分支块中没有与上面的值相匹配,就跳到这个分支。 , label
分支块,有若干个这样的分支。举例:
; Emulate a conditional br instruction
%Val = zext i1 %value to i32
switch i32 %Val, label %truedest [ i32 0, label %falsedest ]
; Emulate an unconditional br instruction
switch i32 0, label %dest [ ]
; Implement a jump table:
switch i32 %val, label %otherwise [ i32 0, label %onzero
i32 1, label %onone
i32 2, label %ontwo ]
indirectbr
指令在当前函数控制跳转到指定地址的基本块中,指定的地址必须是基本块地址(Addresses of Basic Blocks)常量。所有可能的目标块必须在标签列表中列出,否则此指令具有未定义行为。这意味着跳转到其他函数中定义的label也有未定义的行为,如果指定地址是poison
或undef
,该指令也具有未定义行为。
语法:
indirectbr <somety>* <address>, [ label <dest1>, label <dest2>, ... ]
*
是要跳转的地址。label , label , ...
上面地址指向的全部可能。也就是说,上面要跳转的地址指向的肯定是这里某一个label
,同一个label
可以在这里重复出现,但是没啥意义。举例:
indirectbr i8* %Addr, [ label %bb1, label %bb2, label %bb3 ]
br
指令也是块之间的跳转,那和这个有什么区别呢?br
指令仅局限于函数内部块之间的跳转,不能跨函数跳转,而indirectbr
指令可以实现跨函数块之间的跳转。这个指令我平时也没用过,等以后用到了再补充!
这个指令在llvm异常处理里面会用到,用来调用一个可能会产生异常的函数,可以看看llvm 异常处理,等以后有时间再来补充吧!
终端指令还有很多,但那些不太常用,如果用到建议自己看官方文档,如果平时使用的过程中觉得那个重要了,我也会补上。
fneg
指令返回操作数符号位翻转后的一个副本,也就是一个相反数。该指令还可以使用任意数量的fast-math flags
,这些是优化提示,以启用不安全的浮点(floating-point)优化。
语法:
<result> = fneg [fast-math flags]* <ty> <op1> ; yields ty:result
是操作数类型,必须是浮点类型或者浮点向量。举例:
<result> = fneg float %val ; yields float:result = -%var
二元运算的两个操作数必须是相同类型,且返回的结果与操作数类型相同。
add
指令返回两个操作数的和,如果和有无符号(unsigned)溢出,则返回的结果模是2^n
,其中n是结果的位宽。Because LLVM integers use a two’s complement representation(是二进制补码还是二的补码?应该是二进制补码),所以这个指令既适用有符号整数也适用无符号整数。
语法:
<result> = add <ty> <op1>, <op2> ; yields ty:result
<result> = add nuw <ty> <op1>, <op2> ; yields ty:result
<result> = add nsw <ty> <op1>, <op2> ; yields ty:result
<result> = add nuw nsw <ty> <op1>, <op2> ; yields ty:result
,
两个操作数的类型相同且必须是整型类型或者整型向量。nuw nsw
,nuw和nsw分别代表No Unsigned Wrap和No Signed Wrap。如果nuw或nsw关键字存在,如果分别发生无符号溢出或有符号溢出,则add指令的的结果值是poison value
。举例:
<result> = add i32 4, %var ; yields i32:result = 4 + %var
fadd指令返回两个浮点值的和。
语法:
<result> = fadd [fast-math flags]* <ty> <op1>, <op2> ; yields ty:result
,
两个操作数的类型相同且必须是浮点类型或者浮点常量。举例:
<result> = fadd float 4.0, %var ; yields float:result = 4.0 + %var
sub
指令返回两个操作数的差,如果和有无符号(unsigned)溢出,则返回的结果模是2^n
,其中n是结果的位宽。Because LLVM integers use a two’s complement representation(是二进制补码还是二的补码?应该是二进制补码),所以这个指令既适用有符号整数也适用无符号整数。
语法:
<result> = sub <ty> <op1>, <op2> ; yields ty:result
<result> = sub nuw <ty> <op1>, <op2> ; yields ty:result
<result> = sub nsw <ty> <op1>, <op2> ; yields ty:result
<result> = sub nuw nsw <ty> <op1>, <op2> ; yields ty:result
,
两个操作数的类型相同且必须是整型类型或者整型向量。nuw nsw
nuw和nsw分别代表No Unsigned Wrap和No Signed Wrap。如果分别发生无符号溢出或有符号溢出,则add指令的的结果值是poison value
。举例:
<result> = sub i32 4, %var ; yields i32:result = 4 - %var
<result> = sub i32 0, %val ; yields i32:result = -%var
add指令返回两个浮点值的差。
语法:
<result> = fsub [fast-math flags]* <ty> <op1>, <op2> ; yields ty:result
,
两个操作数的类型相同且必须是浮点类型或者浮点常量。举例:
<result> = fsub float 4.0, %var ; yields float:result = 4.0 - %var
<result> = fsub float -0.0, %val ; yields float:result = -%var
mul
指令返回两个操作数的乘积,其他的与add
和sub
一样。
fmul
指令返回两个浮点值的乘积,其他的与fadd
和fsub
一样。
udiv指令返回两个无符号操作数的商,结果也为无符号。
语法:
<result> = udiv <ty> <op1>, <op2> ; yields ty:result
<result> = udiv exact <ty> <op1>, <op2> ; yields ty:result
,
两个操作数的类型相同且必须是整型类型或者整型向量,且操作数的值不能为零,如果是整型向量,向量任一元素都不能为零。exact
关键字出现,但是op1不是op2的倍数(这个等式成立就是倍数((a udiv exact b) mul b) == a)
,则产生的结果就是一个poison value
。举例:
<result> = udiv i32 4, %var ; yields i32:result = 4 / %var
sdiv指令返回两个有符号操作数的商,且四舍五入为零。
语法:
<result> = sdiv i32 4, %var ; yields i32:result = 4 / %var
,
两个操作数的类型相同且必须是整型类型或者整型向量,且操作数的值不能为零,如果是整型向量,向量任一元素都不能为零。exact
关键字出现,但是op1
不是op2
的倍数(这个等式成立就是倍数((a udiv exact b) mul b) == a)
,则产生的结果就是一个poison value
。举例:
<result> = udiv i32 4, %var ; yields i32:result = 4 / %var
fdiv
指令返回两个浮点数的商,其他的与fadd
和fsub
一样。
urem
指令是两个无符号的操作数进行取余操作,结果也为无符号,其他的与udiv
一样。
srem
指令是两个有符号的操作数进行取余操作,结果也为有符号,其他的与时sdiv
一样。
frem
指令是两个浮点类型的操作数进行取余操作,其他的与fadd
和fsub
一样。
shl
指令是左移运算符,将op1
向左按位移动opt2
位。如果opt2
的位数大于或等于opt1
的位数,该指令将返回一个poison value
。
语法:
<result> = shl <ty> <op1>, <op2> ; yields ty:result
<result> = shl nuw <ty> <op1>, <op2> ; yields ty:result
<result> = shl nsw <ty> <op1>, <op2> ; yields ty:result
<result> = shl nuw nsw <ty> <op1>, <op2> ; yields ty:result
,
,两个操作数的类型相同且必须是整型类型或者整型向量,
作为无符号值来处理。如果
是向量,
将会按照
的元素逐个移动。生成的值为op1 * 2^op2 mod 2n
,其中n为结果的宽度。nuw
关键字出现,移出任何非零位,都会产生一个poison value
。nsw
关键字出现,移出任何与结果的符号位不一致的位,都会产生一个poison value
。举例:
<result> = shl i32 4, %var ; yields i32: 4 << %var
<result> = shl i32 4, 2 ; yields i32: 16
<result> = shl i32 1, 10 ; yields i32: 1024
<result> = shl i32 1, 32 ; undefined
<result> = shl <2 x i32> < i32 1, i32 1>, < i32 1, i32 2> ; yields: result=<2 x i32> < i32 2, i32 4>
lshr
指令是右移运算符,将opt2
向右按位移动opt1
位。如果opt2
的位数大于或等于opt1
的位数,该指令将返回一个poison value
。
语法:
<result> = lshr <ty> <op1>, <op2> ; yields ty:result
<result> = lshr exact <ty> <op1>, <op2> ; yields ty:result
,
,两个操作数的类型相同且必须是整型类型或者整型向量,作为无符号值来处理。如果是向量,将会按照的元素逐个移动。exact
关键字出现,移出任何非零位,都会产生一个poison value
。举例:
<result> = lshr i32 4, 1 ; yields i32:result = 2
<result> = lshr i32 4, 2 ; yields i32:result = 1
<result> = lshr i8 4, 3 ; yields i8:result = 0
<result> = lshr i8 -2, 1 ; yields i8:result = 0x7F
<result> = lshr i32 1, 32 ; undefined
<result> = lshr <2 x i32> < i32 -2, i32 4>, < i32 1, i32 2> ; yields: result=<2 x i32> < i32 0x7FFFFFFF, i32 1>
ashr
指令是算数右移运算符,将opt2
向右按位移动opt1
位。如果opt2
的位数大于或等于opt1
的位数,该指令将返回一个poison value
。
这里注意区分算数右移和逻辑右移的区别,算术右移在最高位要补符号位的,而逻辑右移最高位都是零。
语法:
<result> = ashr <ty> <op1>, <op2> ; yields ty:result
<result> = ashr exact <ty> <op1>, <op2> ; yields ty:result
,
,两个操作数的类型相同且必须是整型类型或者整型向量,作为无符号值来处理。如果是向量,将会按照的元素逐个移动。exact
关键字出现,移出任何非零位,都会产生一个poison value。举例:
<result> = ashr i32 4, 1 ; yields i32:result = 2
<result> = ashr i32 4, 2 ; yields i32:result = 1
<result> = ashr i8 4, 3 ; yields i8:result = 0
<result> = ashr i8 -2, 1 ; yields i8:result = -1
<result> = ashr i32 1, 32 ; undefined
<result> = ashr <2 x i32> < i32 -2, i32 4>, < i32 1, i32 3> ; yields: result=<2 x i32> < i32 -1, i32 0>
and
指令是按位逻辑与操作。
语法:
<result> = and <ty> <op1>, <op2> ; yields ty:result
,
,两个操作数的类型相同且必须是整型类型或者整型向量。真值表:
In0 In1 Out
0 0 0
0 1 0
1 0 0
1 1 1
举例:
<result> = and i32 4, %var ; yields i32:result = 4 & %var
<result> = and i32 15, 40 ; yields i32:result = 8
<result> = and i32 4, 8 ; yields i32:result = 0
or指令是按位逻辑或操作。
语法:
<result> = or <ty> <op1>, <op2> ; yields ty:result
,
,两个操作数的类型相同且必须是整型类型或者整型向量。真值表:
In0 In1 Out
0 0 0
0 1 1
1 0 1
1 1 1
举例:
<result> = or i32 4, %var ; yields i32:result = 4 | %var
<result> = or i32 15, 40 ; yields i32:result = 47
<result> = or i32 4, 8 ; yields i32:result = 12
xor指令是按位异或操作。
语法:
<result> = xor <ty> <op1>, <op2> ; yields ty:result
,
,两个操作数的类型相同且必须是整型类型或者整型向量。真值表:
In0 In1 Out
0 0 0
0 1 1
1 0 1
1 1 0
举例:
<result> = xor i32 4, %var ; yields i32:result = 4 ^ %var
<result> = xor i32 15, 40 ; yields i32:result = 39
<result> = xor i32 4, 8 ; yields i32:result = 12
<result> = xor i32 %V, -1 ; yields i32:result = ~%V
extractelement
指令从指定的向量中提取单个标量元素,有两个操作数,第一个是向量类型;第二个是一个向量索引。如果索引值不在向量的长度范围内,会返回一个poison value
。
语法:
<result> = extractelement <n x <ty>> <val>, <ty2> <idx> ; yields <ty>
<result> = extractelement <vscale x n x <ty>> <val>, <ty2> <idx>
索引值大于等于零,小于向量的长度。举例:
<result> = extractelement <4 x i32> %vec, i32 0 ; yields i32
insertelement
指令向向量的指定位置中插入一个元素,有三个操作数,第一个是向量类型;第二个是要插入的值,值的类型要与向量的元素类型匹配;第三个是向量索引。如果索引值不在向量的长度范围内,会返回一个poison value
。
语法:
<result> = insertelement <n x <ty>> <val>, <ty> <elt>, <ty2> <idx> ; yields <n x <ty>>
<result> = insertelement <vscale x n x <ty>> <val>, <ty> <elt>, <ty2> <idx> ; yields <vscale x n x <ty>>
举例:
<result> = insertelement <4 x i32> %vec, i32 1, i32 0 ; yields <4 x i32>
shufflevector
指令从两个向量中抽取指定的一些元素,按照shuffle mask(调整掩码?)构成一个新的向量,新向量的类型与被抽取的向量相同,长度与shuffle mask相同。
举个例子通俗的解释就是:要从这两个向量中<4 x i32> %v1, <4 x i32> %v2
,抽取指定的元素构成一个新的向量。怎么抽呢?首先会给这两个向量的元素从左到右从0
开始编号,上面两个向量的编号就是0-7
;然后再根据调整掩码,选取指定的元素,<4 x i32>
,比如这个掩码就会选取四个向量复制到新向量中,对应的编号分别是0、4、1、5
。所以这就能理解为什么新向量的类型与被抽取的向量相同,长度与shuffle mask相同了。
语法:
<result> = shufflevector <n x <ty>> <v1>, <n x <ty>> <v2>, <m x i32> <mask> ; yields <m x <ty>>
<result> = shufflevector <vscale x n x <ty>> <v1>, <vscale x n x <ty>> v2, <vscale x m x i32> <mask> ; yields <vscale x m x <ty>> ;这是长度可变的向量
> , >
两个输入向量,如果输入向量中未定义的元素被选中,则新向量对应的元素也是未定义。
调整掩码(shuffle mask),如果调整掩码是未定义的,则新向量就是未定义的。举例:
<result> = shufflevector <4 x i32> %v1, <4 x i32> %v2,
<4 x i32> <i32 0, i32 4, i32 1, i32 5> ; yields <4 x i32>
<result> = shufflevector <4 x i32> %v1, <4 x i32> undef,
<4 x i32> <i32 0, i32 1, i32 2, i32 3> ; yields <4 x i32> - Identity shuffle.
<result> = shufflevector <8 x i32> %v1, <8 x i32> undef,
<4 x i32> <i32 0, i32 1, i32 2, i32 3> ; yields <4 x i32>
<result> = shufflevector <4 x i32> %v1, <4 x i32> %v2,
<8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7 > ; yields <8 x i32>
extractvalue
指令用于在聚合数据中提取指定索引处的字段。
语法:
<result> = extractvalue <aggregate type> <val>, <idx>{, <idx>}*
聚合类型,数组或结构体。{, }*
常量索引值,提取指定索引处的元素。举例:
<result> = extractvalue {i32, float} %agg, 0 ; yields i32
insertvalue
指令用于在聚合数据的指定索引处插入元素。
语法:
<result> = insertvalue <aggregate type> <val>, <ty> <elt>, <idx>{, <idx>}* ; yields <aggregate type>
聚合类型,数组或结构体。
要插入的数据,{, }*
常量索引值,提取指定索引处的元素。举例:
%agg1 = insertvalue {i32, float} undef, i32 1, 0 ; yields {i32 1, float undef}
%agg2 = insertvalue {i32, float} %agg1, float %val, 1 ; yields {i32 1, float %val}
%agg3 = insertvalue {i32, {float}} undef, float %val, 1, 0 ; yields {i32 undef, {float %val}}
alloca
指令用来申请内存。会在你当前执行函数所在的栈中砍一块内存,内存大小为sizeof(
,等函数返回时(使用ret
或resume
指令),申请的这块内存就自动释放了。对象被分配的总是datalayout
指定的地址空间。
内存分配返回一个指针,分配的内存未初始化。从未初始化的内存中加载会产生未定义(undefined )的值;如果用于分配的堆栈空间不足,则操作本身也是未定义的。alloca
指令通常用于表示必须要有可用地址的自动变量。分配零字节是合法的,但是返回的指针并不一定相同,在堆栈中内存以何种方式分配并没有指定。
语法:
<result> = alloca [inalloca] <type> [, <ty> <NumElements>] [, align <alignment>] [, addrspace(<num>)]
指令返回的指针类型,可以是任何类型。
要分配的内存数量,分配的内存大小就是该数量*单个类型空间,如果没有指定,默认情况下该值为1。align
内存对齐值。如果指定了对齐值,对齐值不能大于1 << 29,且保证分配的值结果至少与该边界对齐;如果没有指定或者对齐值为零,目标可以选择在任何与类型兼容的范围内对齐分配。举例:
%ptr = alloca i32 ; yields i32*:ptr
%ptr = alloca i32, i32 4 ; yields i32*:ptr
%ptr = alloca i32, i32 4, align 1024 ; yields i32*:ptr
%ptr = alloca i32, align 1024 ; yields i32*:ptr
store
指令用于将值写入到指定的内存中。如果写入的值是标量类型(scalar type),则值占用的字节数不能超越容纳类型所需要的最小字节数。比如说i24
类型最多只能存3个字节大小的数字,如果数字的值大于3个字节,没有确切说会发生什么,但通常都会将值覆盖,所以不能这么搞。
语法:
store [volatile] <ty> <value>, <ty>* <pointer>[, align <alignment>][, !nontemporal !<index>][, !invariant.group !<index>]
store atomic [volatile] <ty> <value>, <ty>* <pointer> [syncscope("" )] <ordering>, align <alignment> [, !invariant.group !<index>]
要存入的值,值的类型必须是已知长度的first class type。*
是一个指针,指向一块存值的地址。还有一些对volatile
、atomic
等的解释,内容很多但是不常用,建议自己看一下文档。
举例:
%ptr = alloca i32 ; yields i32*:ptr
store i32 3, i32* %ptr ; yields void
%val = load i32, i32* %ptr ; yields i32:val = i32 3
load
指令用于从指定的内存中读取一个值,也可以说加载一个值。如果读取的值是一个标量类型(scalar type ),则读取值的字节数不能超越容纳类型所需要的最小字节数。比如说load i24, i32* %ptr
,不管指针所指的内存中存放多大字节的值,这里最多只能加载三个字节。当加载值的大小不是整数字节的类型(如i20
)时,如果该值最初不是使用相同的类型(i20
)写入(store
指令)的,则加载的结果是未定义的(undefined)。
语法:
<<result> = load [volatile] <ty>, <ty>* <pointer>[, align <alignment>][, !nontemporal !<index>][, !invariant.load !<index>][, !invariant.group !<index>][, !nonnull !<index>][, !dereferenceable !<deref_bytes_node>][, !dereferenceable_or_null !<deref_bytes_node>][, !align !<align_node>]
<result> = load atomic [volatile] <ty>, <ty>* <pointer> [syncscope("" )] <ordering>, align <alignment> [, !invariant.group !<index>]
!<index> = !{ i32 1 }
!<deref_bytes_node> = !{i64 <dereferenceable_bytes>}
!<align_node> = !{ i64 <value_alignment> }
这个语法结构看上去特别复杂,其实有用的也就下面这三个参数,其余的一般情况下都遇不到。
是一个变量,变量的值是从内存中加载出来的值。load
加载类型,也就是前面变量的类型。*
是一个指针,指向要加载的内存,指针的类型必须是不包含不透明结构(opaque structural type)的first class type类型。举例:
%ptr = alloca i32 ; yields i32*:ptr
store i32 3, i32* %ptr ; yields void
%val = load i32, i32* %ptr ; yields i32:val = i32 3
官网对gep做了一个单独的详细解释,如果看完下面你还有困惑,可以看看这个:getelementptr指令详解
getelementptr
指令来获得指向数组的元素和指向结构体成员的指针,所以这个指令产生的结果是一个指针。它只执行地址计算,不访问内存,该指令也可用于计算此类地址的向量。
语法:
<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
<result> = getelementptr <ty>, <ptr vector> <ptrval>, [inrange] <vector index type> <idx>
是第一个索引
使用的基本类型*
表示其后的基地址ptrval
的类型。
是第一组索引的类型和值, 可以出现多次,其后出现就是第二组、第三组等等索引的类型和值。要注意索引的类型和索引使用的基本类型是不一样的,索引的类型一般为i32或i64,而索引使用的基本类型确定的是增加索引值时指针的偏移量。能理解这句话就说明已经把这个指令掌握了。
inbounds
对于这个关键字,官方解释了一大推,我看的也不是很明白,但是自动生成的代码上面都有这个关键字。
下面是隔壁组同事写的一个解释:
理解第一个索引
*
对应什么类型,返回就是什么类型ty
指定的基本类型共同确定的。上图中第一个索引所使用的基本类型是[6 x i8]
,值是1,所以返回的值相对基址@a_gv
前进了6个字节。由于只有一个索引,所以返回的指针也是[6 x i8]*
类型。
理解后面的索引
后面的索引是在 Aggregate Types
内进行索引每增加一个索引,就会使得该索引使用的基本类型和返回的指针的类型去掉一层,下面看个例子
我们看%elem_ptr = getelementptr [6 x i8], [6 x i8]* @a_gv, i32 0, i32 0
这一句,第一个索引值是0,使用的基本类型[6 x i8]
, 因此其使返回的指针先前进0 x 6
个字节,也就是不前进,第二个索引的值是1,使用的基本类型就是i8
([6 x i8]
去掉左边的6),因此其使返回的指针前进一个字节,返回的指针类型为i8*
([6 x i8]*
去掉左边的6)。
只有一个索引情况下,GEP作用于结构体与作用于数组的规则相同,%new_ptr = getelementptr %MyStruct*, %MyStruct* @a_gv, i32 1
使得%new_ptr
相对@a_gv
偏移一个结构体%MyStruct
的大小。
在有两个索引的情况下,第二个索引对返回指针的影响跟结构体的成员类型有关。譬如说在上图中,第二个索引值是1,那么返回的指针就会偏移到第二个成员,也就是偏移1个字节,由于第二个成员是i32
类型,因此返回的指针是i32*
。
-如果结构体的本身也有Aggregate Type
的成员,就会出现超过两个索引的情况。第三个索引将会进入这个Aggregate Type
成员进行索引。譬如说上图中的第二个索引是2,指针先指向第三个成员,第三个成员是个数组。再看第三个索引是0,因此指针就指向该成员的第一个元素,指针类型也变成了i32*
。
官网举例:这是一段c的代码:
struct RT {
char A;
int B[10][20];
char C;
};
struct ST {
int X;
double Y;
struct RT Z;
};
int *foo(struct ST *s) {
return &s[1].Z.B[5][13];
}
这是对应的llvm代码:
%struct.RT = type { i8, [10 x [20 x i32]], i8 }
%struct.ST = type { i32, double, %struct.RT }
define i32* @foo(%struct.ST* %s) nounwind uwtable readnone optsize ssp {
entry:
%arrayidx = getelementptr inbounds %struct.ST, %struct.ST* %s, i64 1, i32 2, i32 1, i64 5, i64 13
ret i32* %arrayidx
}
这个部分内容中的指令是转换指令(类型转换),都使用一个操作数和一个类型,即将操作数转换成指定的类型。
trunc...to...
指令截取目标值的高位,使剩余的部分转换成目标类型。
语法:
<result> = trunc <ty> <value> to <ty2> ; yields ty2
类型的占位一定要比
类型多,比如前面是32位,后面最多只能有31位,这样才叫截取嘛。
举例:
%X = trunc i32 257 to i8 ; yields i8:1
解释一下上面这个例子:257
换成二进制是(...0001 0000 0001
),现在要转成i8
,就从低位开始截取8
位,将截取出来的高位扔掉,剩下的低位(0000 0001
),就是咋们的目标结果。
%Y = trunc i32 123 to i1 ; yields i1:true
%Z = trunc i32 122 to i1 ; yields i1:false
%W = trunc <2 x i16> <i16 8, i16 7> to <2 x i8> ; yields <i8 8, i8 7>
【注】关于截取的指令总共有十三个,都没有太难的语法或语义限制,建议自己看看文档。这些指令会经常用的,也很重要,比如:
int a = 1 + 1;
float b = a + 1;
上面是C的代码,下面是IR代码。
%a = alloca i32, align 4
%b = alloca float, align 4
store i32 2, i32* %a, align 4
%0 = load i32, i32* %a, align 4
%add = add nsw i32 %0, 1
%conv = sitofp i32 %add to float
store float %conv, float* %b, align 4
这两个指令根据比较规则,比较两个操作数的关系。
icmp
指令的操作数类型是整型或整型向量、指针或指针向量。对于指针或指针向量,在做比较运算的时候,都会将其指向的地址值作为整型数值去比较,所以归根结底也还是整型。
fcmp
指令要求操作数是浮点值或者浮点向量,这个没有指针类型。
语法:
<result> = icmp <cond> <ty> <op1>, <op2> ; yields i1 or <N x i1>:result
<result> = fcmp [fast-math flags]* <cond> <ty> <op1>, <op2> ; yields i1 or <N x i1>:result
,
这两个是操作数。
这是比较规则,icmp
和fcmp
指令的比较规则不一样。举例:
icmp:
<result> = icmp eq i32 4, 5 ; yields: result=false
<result> = icmp ne float* %X, %X ; yields: result=false
<result> = icmp ult i16 4, 5 ; yields: result=true
<result> = icmp sgt i16 4, 5 ; yields: result=false
<result> = icmp ule i16 -4, 5 ; yields: result=false
<result> = icmp sge i16 4, 5 ; yields: result=false
fcmp:
<result> = fcmp oeq float 4.0, 5.0 ; yields: result=false
<result> = fcmp one float 4.0, 5.0 ; yields: result=true
<result> = fcmp olt float 4.0, 5.0 ; yields: result=true
<result> = fcmp ueq double 1.0, 2.0 ; yields: result=false
phi
指令用于实现PHI
节点。在运行时,phi
指令根据在当前block之前执行的是哪一个predecessor(前导) block来得到相应的值。
phi
指令必须在basic block的最前面,也就是在一个basic block中,在phi
指令之前不允许有非phi
指令,但是可以有多个phi
指令。
语法:
<result> = phi [fast-math-flags] <ty> [ <val0>, <label0>], ...
[
是一个列表,列表中的每个元素是一个value/label 对,每个label
表示一个当前block的predecessor block,phi 指令就是根据 label
选相应的 value。
举例:
Loop: ; Infinite loop that counts from 0 on up...
%indvar = phi i32 [ 0, %LoopHeader ], [ %nextindvar, %Loop ]
%nextindvar = add i32 %indvar, 1
br label %Loop
以上面示例中的 phi
指令为例,如果当前block之前执行的是LoopHeader
,则该 phi
指令的值是 0,而如果是从Loop label
过来的,则该 phi 指令的值是 %nextindvar
。
【注】下面将要说的这部分内容,官方文档只是提了一句,并没有细说,我只是查阅的非官方资料外加自己的理解,感觉这部分比较重要,所以展开说一下:
phi
指令的目的是为了实现SSA(静态单一赋值)。在IR中SSA是一个非常重要的属性(对所有中间语言应该都重要),它要求每个变量只分配一次,并且每个变量在使用之前定义。比如说下面这个代码就不是SSA,变量a
被分配了两次值,程序必须执行可达定义分析(reaching definition analysis)才能确定变量b
被分配的值是多少。
a = 1; //第一次分配值
a = 2; //第二次分配值
b = a;
如果要让这段代码符合SSA,改成下面这样就可以。
a1 = 1;
a2 = 2;
b = a2;
再比如说下面这个代码就不是SSA,变量b
被分配了多次值,虽然只能走一个分支,但是不行不行就不行。
if(a > 0)
b = 1;
else if (a < 0)
b = -1;
else
b = 0;
如果要让这段代码符合SSA,改成下面这样就可以。
if(a > 0)
b1 = 1;
else if (a < 0)
b2 = -1;
else
b3 = 0;
这个时候phi
指令的作用就来了,上面这个例子对应到IR中肯定是三个基本块中的某一个基本块传来预期的值,咋们只需要用phi
指令接受值就OK,这样就能实现SSA。
select
指令根据条件选择一个值。有点像br指令,只不过这个是选择一个值。
语法:
<result> = select [fast-math flags] selty <cond>, <ty> <val1>, <ty> <val2>
selty
是一个i1类型的值,如果这个值是1(true),选择后面第一个参数,否则选择第二个参数。 ,
这两个参数是要被选择的值,两个参数的类型要相同,且都为first class type。举例:
%X = select i1 true, i8 17, i8 42 ; yields i8:17
call
指令用于调用一个函数。该指令使控制流转移到指定的函数,其入参值绑定到被调函数的形参。 在被调用函数中执行ret
指令后,控制流程将会回到该指令上,并且背调函数的返回值将绑定到结果参数。对下面的属性不知道没关系,只要知道他是调用函数的指令,以及如何调用就OK!
语法:
<result> = [tail | musttail | notail ] call [fast-math flags] [cconv] [ret attrs] [addrspace(<num>)]<ty>|<fnty> <fnptrval>(<function args>) [fn attrs] [ operand bundles ]
这些参数比较特殊:
tail
和musttail
标记指示优化器应执行尾(tail)调用优化。tail 标记是可以忽略的提示。 musttail 标记意味着该调用必须经过尾(tail)调用优化,以使程序正确。musttail 标记提供以下保证:
inalloca
或preallocated
属性的参数将就地转发。musttail
调用出现在带有thunk属性的函数中,并且调用者和被调用者都具有可变参数,那么寄存器或内存中的任何非原型参数都将转发给被调用者。类似地,被调用者的返回值返回给调用者的调用者,即使使用了空返回类型。notail
标记表示优化器不应该向调用添加tail
或musttail
标记,它用于防止对调用执行尾调用优化。
fast-math flags
标记表示调用有一个或多个fast-math flags
(高级结构里面有这个的介绍),这些fast-math flags
是用于启用不安全的浮点优化的优化提示。fast-math flags
仅对返回浮点标量或向量类型、浮点标量或向量数组(嵌套到任何深度)类型的调用有效。
cconv
标记指示调用应该使用哪种调用约定(高级结构里面有调用约定的介绍),如果没有指定,调用默认使用C调用约定。该调用约定必须匹配目标函数的调用约定,否则行为是未定义的。
ret attrs
列出返回值的参数属性(高级结构里面有参数属性的介绍),这里只有zeroext
、signext
和inreg
属性有效。
addrspace(
属性可用于指示被调用函数的地址空间,如果未指定,将使用data layout
(高级结构里面有这个的介绍,翻译成了数据布局)字符串中的程序地址空间
是调用指令本身的类型,也是返回值的类型,没有返回值的函数被标记为void
。
是被调用函数的签名。参数类型必须匹配此签名所隐含的类型,如果函数不是可变参数,则可以省略此类型。
fnptrval
是一个LLVM值,包含要调用的函数的指针(定义一个函数时的函数名)。在大多数情况下,这是一个直接的函数调用,但是间接调用则尽可能调用任意指向函数值的指针。
function args
是函数参数列表,有参数类型和参数属性,所有的参数都是first class type
。如果函数签名表明函数接受可变数量的参数,则可以指定额外的参数。
fn attrs
函数属性。
operand bundles
操作数集。
举例:
%retval = call i32 @test(i32 %argc)
call i32 (i8*, ...)* @printf(i8* %msg, i32 12, i8 42) ; yields i32
%X = tail call i32 @foo() ; yields i32
%Y = tail call fastcc i32 @foo() ; yields i32
call void %foo(i8 97 signext)
%struct.A = type { i32, i8 }
%r = call %struct.A @foo() ; yields { i32, i8 }
%gr = extractvalue %struct.A %r, 0 ; yields i32
%gr1 = extractvalue %struct.A %r, 1 ; yields i8
%Z = call void @foo() noreturn ; indicates that %foo never returns normally
%ZZ = call zeroext i32 @bar() ; Return value is %zero extended
这个指令也在llvm异常处理里面会用到,硬翻译的话可以称为着陆场,该指令用来接收异常信息,感兴趣的话可以看看llvm 异常处理,等以后有时间再来补充!
这都是用在异常处理机制中的指令,不写llvm异常一般用不到