可以转载,请注明出处!
文章目录
5.1 终端指令(Terminator Instructions)
所有基本块的结束指令,都是终端指令,这在基本块的介绍中已经说过了。
ret指令
这个就是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
<type> <value>
返回值类型必须是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指令
这条指令用于将控制流程输送到不同的代码块,也就是一个跳转指令,包含条件跳转和无条件跳转。
语法:
br i1 <cond>, label <iftrue>, label <iffalse> ;条件跳转
br label <dest> ;无条件跳转
-
i1 <cond>
这是一个布尔值,之所以是i1是
因为true
在内存中是1,false
是0。在执行br
指令的时候,会对整个值进行计算,如果是true
就跳到iftrue
基本块,否则就跳到iffalse
。 -
label <iftrue>, label <iffalse>
这是两个label类型,分别对应两个基本块。
举例:
Test:
%cond = icmp eq i32 %a, %b
br i1 %cond, label %IfEqual, label %IfUnequal
IfEqual:
ret i32 1
IfUnequal:
ret i32 0
switch指令
switch
指令更像是一个升级版的br
,用于将控制流程输送到许多基本块中的某一个中。
语法:
switch <intty> <value>, label <defaultdest> [ <intty> <val>, label <dest> ... ]
<intty> <value>
这是一个整型值,决定着具体跳到要哪个分支。label <defaultdest>
这是默认的label,如果后面的分支块中没有与上面的值相匹配,就跳到这个分支。<intty> <val>, label <dest>
分支块,有若干个这样的分支。
举例:
; 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指令
indirectbr
指令在当前函数控制跳转到指定地址的基本块中,指定的地址必须是基本块地址(Addresses of Basic Blocks)常量。所有可能的目标块必须在标签列表中列出,否则此指令具有未定义行为。这意味着跳转到其他函数中定义的label也有未定义的行为,如果指定地址是poison
或undef
,该指令也具有未定义行为。
语法:
indirectbr <somety>* <address>, [ label <dest1>, label <dest2>, ... ]
<somety>* <address>
是要跳转的地址。label <dest1>, label <dest2>, ...
上面地址指向的全部可能。也就是说,上面要跳转的地址指向的肯定是这里某一个label
,同一个label
可以在这里重复出现,但是没啥意义。
举例:
indirectbr i8* %Addr, [ label %bb1, label %bb2, label %bb3 ]
br
指令也是块之间的跳转,那和这个有什么区别呢?br
指令仅局限于函数内部块之间的跳转,不能跨函数跳转,而indirectbr
指令可以实现跨函数块之间的跳转。这个指令我平时也没用过,等以后用到了再补充!
invoke指令
这个指令在llvm异常处理里面会用到,用来调用一个可能会产生异常的函数,可以看看llvm 异常处理,等以后有时间再来补充吧!
callbr等指令
终端指令还有很多,但那些不太常用,如果用到建议自己看官方文档,如果平时使用的过程中觉得那个重要了,我也会补上。
5.2 一元运算(Unary Operations)
fneg指令
fneg
指令返回操作数符号位翻转后的一个副本,也就是一个相反数。该指令还可以使用任意数量的fast-math flags
,这些是优化提示,以启用不安全的浮点(floating-point)优化。
语法:
<result> = fneg [fast-math flags]* <ty> <op1> ; yields ty:result
<ty> <op1>
是操作数类型,必须是浮点类型或者浮点向量。
举例:
<result> = fneg float %val ; yields float:result = -%var
5.3 二元运算(Binary Operations)
二元运算的两个操作数必须是相同类型,且返回的结果与操作数类型相同。
add指令
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
<ty> <op1>, <op2>
两个操作数的类型相同且必须是整型类型或者整型向量。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指令
fadd指令返回两个浮点值的和。
语法:
<result> = fadd [fast-math flags]* <ty> <op1>, <op2> ; yields ty:result
<ty> <op1>, <op2>
两个操作数的类型相同且必须是浮点类型或者浮点常量。
举例:
<result> = fadd float 4.0, %var ; yields float:result = 4.0 + %var
sub指令
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
<ty> <op1>, <op2>
两个操作数的类型相同且必须是整型类型或者整型向量。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
fsub指令
add指令返回两个浮点值的差。
语法:
<result> = fsub [fast-math flags]* <ty> <op1>, <op2> ; yields ty:result
<ty> <op1>, <op2>
两个操作数的类型相同且必须是浮点类型或者浮点常量。
举例:
<result> = fsub float 4.0, %var ; yields float:result = 4.0 - %var
<result> = fsub float -0.0, %val ; yields float:result = -%var
mul指令
mul
指令返回两个操作数的乘积,其他的与add
和sub
一样。
fmul指令
fmul
指令返回两个浮点值的乘积,其他的与fadd
和fsub
一样。
udiv指令
udiv指令返回两个无符号操作数的商,结果也为无符号。
语法:
<result> = udiv <ty> <op1>, <op2> ; yields ty:result
<result> = udiv exact <ty> <op1>, <op2> ; yields ty:result
<ty> <op1>, <op2>
两个操作数的类型相同且必须是整型类型或者整型向量,且操作数的值不能为零,如果是整型向量,向量任一元素都不能为零。exact
关键字出现,但是op1不是op2的倍数(这个等式成立就是倍数((a udiv exact b) mul b) == a)
,则产生的结果就是一个poison value
。
举例:
<result> = udiv i32 4, %var ; yields i32:result = 4 / %var
sdiv指令
sdiv指令返回两个有符号操作数的商,且四舍五入为零。
语法:
<result> = sdiv i32 4, %var ; yields i32:result = 4 / %var
<ty> <op1>, <op2>
两个操作数的类型相同且必须是整型类型或者整型向量,且操作数的值不能为零,如果是整型向量,向量任一元素都不能为零。exact
关键字出现,但是op1
不是op2
的倍数(这个等式成立就是倍数((a udiv exact b) mul b) == a)
,则产生的结果就是一个poison value
。
举例:
<result> = udiv i32 4, %var ; yields i32:result = 4 / %var
fdiv指令
fdiv
指令返回两个浮点数的商,其他的与fadd
和fsub
一样。
urem指令
urem
指令是两个无符号的操作数进行取余操作,结果也为无符号,其他的与udiv
一样。
srem指令
srem
指令是两个有符号的操作数进行取余操作,结果也为有符号,其他的与时sdiv
一样。
frem指令
frem
指令是两个浮点类型的操作数进行取余操作,其他的与fadd
和fsub
一样。
5.4 二进制位运算(Bitwise Binary Operations)
shl指令
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
<ty> <op1>, <op2>
,两个操作数的类型相同且必须是整型类型或者整型向量,<op2>
作为无符号值来处理。如果<op1>
是向量,<op2>
将会按照<op1>
的元素逐个移动。生成的值为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指令
lshr
指令是右移运算符,将opt2
向右按位移动opt1
位。如果opt2
的位数大于或等于opt1
的位数,该指令将返回一个poison value
。
语法:
<result> = lshr <ty> <op1>, <op2> ; yields ty:result
<result> = lshr exact <ty> <op1>, <op2> ; yields ty:result
<ty> <op1>, <op2>
,两个操作数的类型相同且必须是整型类型或者整型向量,作为无符号值来处理。如果是向量,将会按照的元素逐个移动。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指令
ashr
指令是算数右移运算符,将opt2
向右按位移动opt1
位。如果opt2
的位数大于或等于opt1
的位数,该指令将返回一个poison value
。
这里注意区分算数右移和逻辑右移的区别,算术右移在最高位要补符号位的,而逻辑右移最高位都是零。
语法:
<result> = ashr <ty> <op1>, <op2> ; yields ty:result
<result> = ashr exact <ty> <op1>, <op2> ; yields ty:result
<ty> <op1>, <op2>
,两个操作数的类型相同且必须是整型类型或者整型向量,作为无符号值来处理。如果是向量,将会按照的元素逐个移动。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指令
and
指令是按位逻辑与操作。
语法:
<result> = and <ty> <op1>, <op2> ; yields ty:result
<ty> <op1>, <op2>
,两个操作数的类型相同且必须是整型类型或者整型向量。
真值表:
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指令
or指令是按位逻辑或操作。
语法:
<result> = or <ty> <op1>, <op2> ; yields ty:result
<ty> <op1>, <op2>
,两个操作数的类型相同且必须是整型类型或者整型向量。
真值表:
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指令
xor指令是按位异或操作。
语法:
<result> = xor <ty> <op1>, <op2> ; yields ty:result
<ty> <op1>, <op2>
,两个操作数的类型相同且必须是整型类型或者整型向量。
真值表:
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
5.5 向量运算(Vector Operations)
extractelement指令
extractelement
指令从指定的向量中提取单个标量元素,有两个操作数,第一个是向量类型;第二个是一个向量索引。如果索引值不在向量的长度范围内,会返回一个poison value
。
语法:
<result> = extractelement <n x <ty>> <val>, <ty2> <idx> ; yields <ty>
<result> = extractelement <vscale x n x <ty>> <val>, <ty2> <idx>
<ty2> <idx>
索引值大于等于零,小于向量的长度。
举例:
<result> = extractelement <4 x i32> %vec, i32 0 ; yields i32
insertelement指令
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指令
shufflevector
指令从两个向量中抽取指定的一些元素,按照shuffle mask(调整掩码?)构成一个新的向量,新向量的类型与被抽取的向量相同,长度与shuffle mask相同。
举个例子通俗的解释就是:要从这两个向量中<4 x i32> %v1, <4 x i32> %v2
,抽取指定的元素构成一个新的向量。怎么抽呢?首先会给这两个向量的元素从左到右从0
开始编号,上面两个向量的编号就是0-7
;然后再根据调整掩码,选取指定的元素,<4 x i32> <i32 0, i32 4, i32 1, i32 5>
,比如这个掩码就会选取四个向量复制到新向量中,对应的编号分别是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向量常量,元素类型是i32类型或者未定义(undef)值。
<n x <ty>> <v1>, <n x <ty>> <v2>
两个输入向量,如果输入向量中未定义的元素被选中,则新向量对应的元素也是未定义。<m x i32> <mask>
调整掩码(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>
5.6 聚合运算(Aggregate Operations)
extractvalue指令
extractvalue
指令用于在聚合数据中提取指定索引处的字段。
语法:
<result> = extractvalue <aggregate type> <val>, <idx>{, <idx>}*
<aggregate type> <val>
聚合类型,数组或结构体。<idx>{, <idx>}*
常量索引值,提取指定索引处的元素。
举例:
<result> = extractvalue {i32, float} %agg, 0 ; yields i32
insertvalue指令
insertvalue
指令用于在聚合数据的指定索引处插入元素。
语法:
<result> = insertvalue <aggregate type> <val>, <ty> <elt>, <idx>{, <idx>}* ; yields <aggregate type>
<aggregate type> <val>
聚合类型,数组或结构体。<ty> <elt>
要插入的数据,<idx>{, <idx>}*
常量索引值,提取指定索引处的元素。
举例:
%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}}
5.7 内存访问和寻址(Memory Access and Addressing Operations)
alloca指令
alloca
指令用来申请内存。会在你当前执行函数所在的栈中砍一块内存,内存大小为sizeof(<type>)*NumElements
,等函数返回时(使用ret
或resume
指令),申请的这块内存就自动释放了。对象被分配的总是datalayout
指定的地址空间。
内存分配返回一个指针,分配的内存未初始化。从未初始化的内存中加载会产生未定义(undefined )的值;如果用于分配的堆栈空间不足,则操作本身也是未定义的。alloca
指令通常用于表示必须要有可用地址的自动变量。分配零字节是合法的,但是返回的指针并不一定相同,在堆栈中内存以何种方式分配并没有指定。
语法:
<result> = alloca [inalloca] <type> [, <ty> <NumElements>] [, align <alignment>] [, addrspace(<num>)]
<type>
指令返回的指针类型,可以是任何类型。<ty> <NumElements>
要分配的内存数量,分配的内存大小就是该数量*单个类型空间,如果没有指定,默认情况下该值为1。align <alignment>
内存对齐值。如果指定了对齐值,对齐值不能大于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指令
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("<target-scope>")] <ordering>, align <alignment> [, !invariant.group !<index>]
<ty> <value>
要存入的值,值的类型必须是已知长度的first class type。<ty>* <pointer>
是一个指针,指向一块存值的地址。
还有一些对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指令
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("<target-scope>")] <ordering>, align <alignment> [, !invariant.group !<index>]
!<index> = !{ i32 1 }
!<deref_bytes_node> = !{i64 <dereferenceable_bytes>}
!<align_node> = !{ i64 <value_alignment> }
这个语法结构看上去特别复杂,其实有用的也就下面这三个参数,其余的一般情况下都遇不到。
<result>
是一个变量,变量的值是从内存中加载出来的值。load <ty>
加载类型,也就是前面变量的类型。<ty>* <pointer>
是一个指针,指向要加载的内存,指针的类型必须是不包含不透明结构(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
getelementptr指令
官网对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>
- 第一个
<ty>
是第一个索引<idx>
使用的基本类型 - 第二个
<ty>*
表示其后的基地址ptrval
的类型。 <ty> <idx>
是第一组索引的类型和值, 可以出现多次,其后出现就是第二组、第三组等等索引的类型和值。
要注意索引的类型和索引使用的基本类型是不一样的,索引的类型一般为i32或i64,而索引使用的基本类型确定的是增加索引值时指针的偏移量。能理解这句话就说明已经把这个指令掌握了。
inbounds
对于这个关键字,官方解释了一大推,我看的也不是很明白,但是自动生成的代码上面都有这个关键字。
下面是隔壁组同事写的一个解释:
理解第一个索引
- 第一个索引不会改变返回的指针的类型,也就是说ptrval前面的
<ty>*
对应什么类型,返回就是什么类型 - 第一个索引的偏移量的是由第一个索引的值和第一个
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)。
getelementptr如何作用于结构体
只有一个索引情况下,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
}
5.8 转换操作(Conversion Operations)
这个部分内容中的指令是转换指令(类型转换),都使用一个操作数和一个类型,即将操作数转换成指定的类型。
trunc…to…指令
trunc...to...
指令截取目标值的高位,使剩余的部分转换成目标类型。
语法:
<result> = trunc <ty> <value> to <ty2> ; yields ty2
<ty>
类型的占位一定要比<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
5.9 其他运算(Other Operations)
icmp和fcmp指令
这两个指令根据比较规则,比较两个操作数的关系。
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
<op1>, <op2>
这两个是操作数。<cond>
这是比较规则,icmp
和fcmp
指令的比较规则不一样。
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
节点。在运行时,phi
指令根据在当前block之前执行的是哪一个predecessor(前导) block来得到相应的值。
phi
指令必须在basic block的最前面,也就是在一个basic block中,在phi
指令之前不允许有非phi
指令,但是可以有多个phi
指令。
语法:
<result> = phi [fast-math-flags] <ty> [ <val0>, <label0>], ...
[ <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指令
select
指令根据条件选择一个值。有点像br指令,只不过这个是选择一个值。
语法:
<result> = select [fast-math flags] selty <cond>, <ty> <val1>, <ty> <val2>
selty <cond>
是一个i1类型的值,如果这个值是1(true),选择后面第一个参数,否则选择第二个参数。<ty> <val1>, <ty> <val2>
这两个参数是要被选择的值,两个参数的类型要相同,且都为first class type。
举例:
%X = select i1 true, i8 17, i8 42 ; yields i8:17
call指令
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 标记提供以下保证:- 1)如果该调用是调用图中的递归循环的一部分,则该调用将不会导致堆栈无限增长。
- 2)具有
inalloca
或preallocated
属性的参数将就地转发。 - 3)如果
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(<num>)
属性可用于指示被调用函数的地址空间,如果未指定,将使用data layout
(高级结构里面有这个的介绍,翻译成了数据布局)字符串中的程序地址空间 -
<ty>
是调用指令本身的类型,也是返回值的类型,没有返回值的函数被标记为void
。 -
<fnty>
是被调用函数的签名。参数类型必须匹配此签名所隐含的类型,如果函数不是可变参数,则可以省略此类型。 -
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
landingpad指令
这个指令也在llvm异常处理里面会用到,硬翻译的话可以称为着陆场,该指令用来接收异常信息,感兴趣的话可以看看llvm 异常处理,等以后有时间再来补充!
cleanuppad、catchpad指令
这都是用在异常处理机制中的指令,不写llvm异常一般用不到