LLVM IR(五)——IR指令介绍(Instruction Reference)

可以转载,请注明出处!

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也有未定义的行为,如果指定地址是poisonundef,该指令也具有未定义行为。

语法:

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指令返回两个操作数的乘积,其他的与addsub一样。

fmul指令

fmul指令返回两个浮点值的乘积,其他的与faddfsub一样。

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指令返回两个浮点数的商,其他的与faddfsub一样。

urem指令

urem指令是两个无符号的操作数进行取余操作,结果也为无符号,其他的与udiv一样。

srem指令

srem指令是两个有符号的操作数进行取余操作,结果也为有符号,其他的与时sdiv一样。

frem指令

frem指令是两个浮点类型的操作数进行取余操作,其他的与faddfsub一样。

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,等函数返回时(使用retresume指令),申请的这块内存就自动释放了。对象被分配的总是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>是一个指针,指向一块存值的地址。

还有一些对volatileatomic 等的解释,内容很多但是不常用,建议自己看一下文档。

举例:

%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>这是比较规则,icmpfcmp指令的比较规则不一样。

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 ]

这些参数比较特殊:

  • tailmusttail 标记指示优化器应执行尾(tail)调用优化。tail 标记是可以忽略的提示。 musttail 标记意味着该调用必须经过尾(tail)调用优化,以使程序正确。musttail 标记提供以下保证:

    • 1)如果该调用是调用图中的递归循环的一部分,则该调用将不会导致堆栈无限增长。
    • 2)具有inallocapreallocated属性的参数将就地转发。
    • 3)如果musttail调用出现在带有thunk属性的函数中,并且调用者和被调用者都具有可变参数,那么寄存器或内存中的任何非原型参数都将转发给被调用者。类似地,被调用者的返回值返回给调用者的调用者,即使使用了空返回类型。
  • notail 标记表示优化器不应该向调用添加tailmusttail标记,它用于防止对调用执行尾调用优化。

  • fast-math flags标记表示调用有一个或多个fast-math flags(高级结构里面有这个的介绍),这些fast-math flags是用于启用不安全的浮点优化的优化提示。fast-math flags仅对返回浮点标量或向量类型、浮点标量或向量数组(嵌套到任何深度)类型的调用有效。

  • cconv标记指示调用应该使用哪种调用约定(高级结构里面有调用约定的介绍),如果没有指定,调用默认使用C调用约定。该调用约定必须匹配目标函数的调用约定,否则行为是未定义的。

  • ret attrs列出返回值的参数属性(高级结构里面有参数属性的介绍),这里只有zeroextsignextinreg属性有效。

  • 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异常一般用不到

  • 41
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yelvens

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值