本文基于LLVM 12官方文档的
LLVM Language Reference Manual
。以学习笔记为主。所以本文会摘录一些常见/常用的指令。对于一些更加深层次的指令属性/特性,待我对LLVM有更深的理解再单独写文章记录。
1. LLVM IR简介
LLVM IR可以理解为LLVM平台的汇编语言,所以官方也是以语言参考手册(Language Reference Manual)的形式给出LLVM IR的文档说明。既然是汇编语言,那么就和传统的CUP类似,有特定的汇编指令集。但是它又与传统的特定平台相关的指令集(x86, ARM, RISC-V等)不一样,它定位为平台无关的汇编语言。也就是说,LLVM IR是一种相对于CUP指令集高级,但是又是一种低级的代码中间表示(比抽象语法树等高级表示更加低级)。
1. 1 LLVM IR的基本特点
为:
SSA
。在指令集层面,以SSA的形式表示。指令集中存在Phi指令。类型安全
。它在指令集层面,是有类型的,也就是说指令是需要有特定的类型作为约束的。低级操作
。指令集相对比较底层。灵活
。能够很利索地表示“所有”的高级语言。也叫Universal IR。- 它是
LLVM编译各个阶段通用的IR
。
1.2 LLVM IR的表示形式
:
存在形式
内存
。基于LLVM开发需要用到许多的类。include "llvm/IR/XX"
磁盘(二进制)
。一般以.bc
的形式存在。方便JIT编译器快速加载。磁盘(文本)
。可读的汇编语言表示,一般以.ll
的形式存在,本文研究的就是这种形式的LLVM IR
特点
- 轻量
- 低级
- 富有表达性
- 有类型的。利于指针分析
- 可扩展性的
良好的格式
- LLVM提供了验证格式正确性的Verification Pass。它不仅仅是语法上的格式验证,还从语义上进行验证。比如说
%x = add i32 1, %x在语法上符合,但是它并不是SSA形式的,所以会验证格式错误
- LLVM提供了验证格式正确性的Verification Pass。它不仅仅是语法上的格式验证,还从语义上进行验证。比如说
1.3 LLVM IR中的标识符(Identifier)
LLVM IR中有两种标识符类型
:
全局标识
。以@开头的- 函数
- 全局变量
局部标识
。以%开头- 寄存器名
- 类型名
LLVM IR中有3种标识符格式
:
-
有名字的值(Named Values)
- 格式:[%@][-azA-Z . ] [ − a − z A − Z ._][-a-zA-Z .][−a−zA−Z._0-9]*
- 例如
- %foo
- @DivisionByZero
- %a.really.long.identifier
-
无名字的值(Unnamed Values)
- 格式:用无符号数值作为它们的前缀
- 例如
- %12
- @2
- %44
-
常量(后文再讲)
2. LLVM整体结构
官方文档罗列得非常多,这里暂时只列出几个我觉得比较重要的
。
2.1 Module
LLVM程序是由若干个Module组成。而Module中由如下三个部分组成:
函数
全局变量
符号表项
其中函数/全局变量都可以认为是全局值(即全局函数和全局变量);
而全局值能够用一个指向内存位置的指针标识。其中
- 全局变量通过指向char数组的指针标识。
- 函数通过指向函数的指针标识。
全局值都有链接类型
。链接,即它们可能会结合LLVM Linker,合并每个Module的函数/全局变量定义。并resolve前向声明,合并符号表项
。
2.2 链接类型
全局值(全局变量/函数)会有一种如下的链接类型(这里只简单了解2中类型):
private
- 只能在当前module被访问
链接private全局值到另外一个模块中,可能会导致此private对象被重命名以防止名字冲突
- 只能在当前module被访问
internal
- 类似private,全局值在object file中被当成一个local符号
- 与C语言中的static关键字相关
- available_externally
- linkonce
- weak
- common
- appending
- extern_weak
- linkonce_odr,
- weak_odr
- external
- extern_weak
2.2 调用惯例
LLVM支持如下几种调用模式。(这里只简单了解,后续深入再添加)
ccc
The C calling convention
支持变长参数的函数调用,容忍一些函数声明和实现的不匹配(C里面也是这样)fastcc
coldcc
cc 10
cc 11
webkit_jscc
anyregcc
preserve_mostcc
preserve_allcc
cxx_fast_tlscc
swiftcc
tailcc
cfguard_checkcc
cc <n>
更多可能会在未来添加
…
3.类型系统
类型系统是LLVM IR中最重要的特性之一。它带来好处:
有类型让许多优化能够直接在IR上进行(一般的三地址码不行,它没类型)
强类型使得LLVM IR更加可读
3.1 Void类型
void就是通常编程语言中的void,空。
3.2 Function类型
函数类型,它的表示成:
<return type> (<parameter list>)
例如:
i32 (i32)
: 它表示的函数为:i32类型作为函数入参,i32类型作为函数返回值float (i16, i32 *) *
: 它表示指向函数的指针,此函数以i16,i32指针类型作为入参类型,float作为返回值类型。i32 (i8*, ...)
: 变长参数的函数,固定长度的入参为i8类型,返回值为i32类型{i32, i32} (i32)
: i32,i32为结构体的前两个字段类型,此结构体类型作为函数的返回值类型,函数入参为i32类型。
3.3 First Class类型
First Class表示一大类的类型。包括:
Single Value
Label
Token
Metadata
Aggregate
3.3.1 Single Value Type
Integer(整数)
- 表示为: iN, 其中N正整数,如i8,i32
Float-Point(浮点)
- half
- bfloat
- float
- double
- fp128
- x86_fp80
- ppc_fp128
x86_mmx
Pointer(指针类型)
- 表示形式为:
<type> *
- 表示形式为:
Vector(向量)
Fiexed-length vector(定长向量)
:< <# elements> x <elementtype> >
Scalable vector(变长向量)
:< vscale x <# elements> x <elementtype> >
- <vscale x 4 x i32>
Label
Token
Metadata
Aggregate(聚合类型)
Array(数组)
:- 表示: [<# elements> x ]
Structure(结构体)
- Identified normal struct type
%T1 = type { \<type list\> }
{ i32, i32, i32}
: 4字节三元组{ float, i32 (i32) * }
: 第一个元素float,第二个元素函数指针,函数为4字节入参,返回4字节类型
- Identified packed struct type
%T2 = type <{ \<type list\> }>
<{ i8, i32 }>
: 只知道5字节大小
- Identified normal struct type
Opaque Structure
- 没有结构体,类似C中的结构体类型前向声明
- %X = type opaque
- %52 = type opaque
- 没有结构体,类似C中的结构体类型前向声明
4. 常量
- 简单常量
- 布尔常量:i1类型,’true’ 或者 ’false’
- 整数常量: 4
- 浮点常量: 123.421, 1.23421e+2,
- Null指针常量: ‘null’
- Token常量: ‘none’等
- 复杂常量
- 结构体常量:{ i32 4, float 17.0, i32* @G }
- 数组常量:[ i32 42, i32 11, i32 74 ]
- 向量常量:< i32 42, i32 11, i32 74, i32 100 >
- Zero初始化:'zeroinitializer’能够用来初始化任何类型为零值
- Metadata node:!{!0, !{!2, !0}, !“test”}, !{!0, i32 0, i8* @global, i64 (i64)* @function, !“str”}
- 全局变量和函数的地址
- 全局变量和函数的地址隐式地为链接时常量
- 这些常量当它们的标识被使用时,会被显示地引用,并且是指针类型
- Undefined值
- Poison值
- Well-Defined值
- 基本块的地址
- DSO Local Equivalent
- 常量表达式
5. 指令集
指令集分为如下几大类,这里会挑一些指令列举,随着后续对指令更加深入,再对指令的具体内容进行补充。
- 终止指令
- 单目运算
- 二元运算
- 二元位运算
- 向量操作
- 聚合操作
- 访问/操作内存
- 强转操作
- 其它操作
5.1 终止指令
-
ret
- ret <type> <value>
- ret void
- 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 , label , label
- br label ; Unconditional branch
- 例子
Test: %cond = icmp eq i32 %a, %b br i1 %cond, label %IfEqual, label %IfUnequal IfEqual: ret i32 1 IfUnequal: ret i32 0
-
switch
- switch , 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
- indirectbr <somety>* <address>, [ label <dest1>, label <dest2>, … ]
-
invoke
- <result> = invoke [cconv] [ret attrs] [addrspace(<num>)] | (<function args>) [fn attrs]
[operand bundles] to label <normal label> unwind label <exception label> - 说明
- normal label对应于ret
- exception label对应于resume或者其它异常处理机制
- 这个指令对高级语言的catch实现很重要
- 例子
%retval = invoke i32 @Test(i32 15) to label %Continue unwind label %TestCleanup ; i32:retval set %retval = invoke coldcc i32 %Testfnptr(i32 15) to label %Continue unwind label %TestCleanup ; i32:retval set`
- <result> = invoke [cconv] [ret attrs] [addrspace(<num>)] | (<function args>) [fn attrs]
-
callbr
- <result> = callbr [cconv] [ret attrs] [addrspace(<num>)] <ty>|<fnty> <fnptrval>(<function args>) [fn attrs] [operand bundles] to label <fallthrough label> [indirect labels]
-
resume
- resume <type> <value>
-
catchswitch
- <resultval> = catchswitch within <parent> [ label <handler1>, label <handler2>, … ] unwind to caller
- <resultval> = catchswitch within <parent> [ label <handler1>, label <handler2>, … ] unwind label <default>
-
catchret
- catchret from <token> to label <normal>
-
cleanupret
- cleanupret from <value> unwind label <continue>
- cleanupret from <value> unwind to caller
-
unreachable
5.2 单目运算
fneg
- <result> = fneg [fast-math flags]* <ty> <op1> ; yields ty:result
- 浮点数或者浮点向量的负
5.3 二元运算
-
add
- <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
- No Unsigned Wrap
- nsw
- No Signed Wrap
- 如果无符号/有符号溢出,nuw/nsw设置,得到poison value
-
fadd
- <result> = fadd [fast-math flags]* <ty> <op1>, <op2> ; yields ty:result
- 约束
- 作用相等类型的浮点/浮点向量上
-
sub
- <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
- No Unsigned Wrap
- nsw
- No Signed Wrap
- 如果无符号/有符号溢出,nuw/nsw设置,得到poison value
- <result> = sub
-
fsub
- <result> = fsub [fast-math flags]* <ty> <op1>, <op2> ; yields ty:result
- 约束
- 作用相等类型的浮点/浮点向量上
-
mul
- <result> = mul <ty> <op1>, <op2> ; yields ty:result
- <result> = mul nuw <ty> , <op2> ; yields ty:result
- <result> = mul nsw <ty> <op1>, <op2> ; yields ty:result
- <result> = mul nuw nsw <ty> <op1>, <op2> ; yields ty:result
- 约束
- 作用到整数或者整数向量,两个操作数都必须是相同类型
- nuw
- No Unsigned Wrap
- nsw
- No Signed Wrap
- 如果无符号/有符号溢出,nuw/nsw设置,得到poison value
-
fmul
- <result> = fmul [fast-math flags]* <ty> <op1>, <op2> ; yields ty:result
- 约束
- 作用相等类型的浮点/浮点向量上
-
udiv
- <result> = udiv <ty> <op1>, <op2> ; yields ty:result
- <result> = udiv exact <ty> <op1>, <op2> ; yields ty:result
- 约束
- 作用到整数或者整数向量,两个操作数都必须是相同类型
- 无符号整数相除
- 除0行为未知
- 使用exact时,如果((%op1 udiv exact %op2) mul %op2) != %op1, 则udiv exact得到的结果为poison value
-
sdiv
- <result> = sdiv <ty> <op1>, <op2> ; yields ty:result
- <result> = sdiv exact <ty> <op1>, <op2> ; yields ty:result
- 约束
- 作用到整数或者整数向量,两个操作数都必须是相同类型
- 有符号整数相除
- 除0行为未知
- 溢出行为未知
- 如果exact使用,结果被舍入,则结果为poison value
-
fdiv
- <result> = fdiv [fast-math flags]* <ty> <op1>, <op2> ; yields ty:result
- 约束
- 作用相等类型的浮点/浮点向量上
-
urem
- <result> = urem <ty> <op1>, <op2> ; yields ty:result
- 约束
- 作用到整数或者整数向量,两个操作数都必须是相同类型
- 无符号整数除法的余数
- op2为0,行为未知
- 例如
= urem i32 4, %var ; yields i32:result = 4 % %var
-
srem
- <result> = srem <ty> <op1>, <op2> ; yields ty:result
- 约束
- 作用到整数或者整数向量,两个操作数都必须是相同类型
- 有符号整数除法的余数
- 结果要么为0,要么与op1有相同的符号
- op2为0,行为未知
- 溢出行为未知
-
frem
- <result> = frem [fast-math flags]* <ty> <op1>, <op2> ; yields ty:result
- 约束
- 作用相等类型的浮点/浮点向量上
- 与被除数相同符号
5.4 二元位运算
-
shl
- <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左移特定数量的位(op2为无符号)
- 结果为(op1 * 2^(op2) ) % 2^n
-
lshr
- <result> = lshr <ty> <op1>, <op2> ; yields ty:result
- <result> = lshr exact <ty> <op1>, <op2> ; yields ty:result
- 说明
- 逻辑右移
-
ashr
- <result> = ashr <ty> <op1>, <op2> ; yields ty:result
- <result> = ashr exact <ty> <op1>, <op2> ; yields ty:result
- 说明
- 算数右移
-
and
- <result> = and <ty> <op1>, <op2> ; yields ty:result
-
or
- <result> = or <ty> <op1>, <op2> ; yields ty:result
-
xor
- <result> = xor <ty> <op1>, <op2> ; yields ty:result
5.5 向量操作
-
extractelement
- <result> = extractelement <n x <ty>> <val>, <ty2> <idx> ; yields <ty>
- <result> = extractelement <vscale x n x <ty>> <val>, <ty2> <idx> ; yields <ty>
- 例子
- <result> = extractelement <4 x i32> %vec, i32 0 ; yields i32
-
insertelement
- <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
- <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>>
- 例子
- <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 访问/操作内存
-
alloca
- <result> = alloca [inalloca] <type> [, <ty> <NumElements>] [, align <alignment>] [, addrspace(<num>)] ; yields type addrspace(num)*:result
- 说明
- 在当前正执行函数的栈帧分配内存,当函数返回到调用点时自动释放此内存。对象分配内存空间会根据datalayout的指示来
- 例子
- %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
-
load
- <result> = load [volatile] <ty>, <ty>* <pointer>[, align <alignment>][, !nontemporal !<nontemp_node>][, !invariant.load !<empty_node>][, !invariant.group !<empty_node>][, !nonnull !<empty_node>][, !dereferenceable !<deref_bytes_node>][, !dereferenceable_or_null !<deref_bytes_node>][, !align !<align_node>][, !noundef !<empty_node>]
- <result> = load atomic [volatile] <ty>, <ty>* <pointer> [syncscope("<target-scope>")] <ordering>, align <alignment> [, !invariant.group !<empty_node>]
!<nontemp_node> = !{ i32 1 }
!<empty_node> = !{}
!<deref_bytes_node> = !{ i64 <dereferenceable_bytes> }
!<align_node> = !{ i64 <value_alignment> } - 说明
- 指定从哪个内存地址加载;指定的类型必须是已知first class类型(不包含opaque structural type)
- 例子
- %ptr = alloca i32 ; yields i32*:ptr
- store i32 3, i32* %ptr ; yields void
- %val = load i32, i32* %ptr ; yields i32:val = i32 3
-
store
- store [volatile] <ty> <value>, <ty>* <pointer>[, align <alignment>][, !nontemporal !<nontemp_node>][, !invariant.group !<empty_node>] ; yields void
- store atomic [volatile] <ty> <value>, <ty>* <pointer> [syncscope("<target-scope>")] <ordering>, align <alignment> [, !invariant.group !<empty_node>] ; yields void
!<nontemp_node> = !{ i32 1 }
!<empty_node> = !{} - 说明
- 被用来写内存
- 类型必须是的first-class类型
- 例子
- %ptr = alloca i32 ; yields i32*:ptr
- store i32 3, i32* %ptr ; yields void
- %val = load i32, i32* %ptr ; yields i32:val = i32 3
-
fence
- 用于实现happens-before
-
cmpxchg
- 用于自动修改内存(比较,然后修改)。
-
atomicrmw
- 用于自动修改内存
-
getelementptr
- <result> = getelementptr <ty>, * {, [inrange] <ty> <idx>}*
- <result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
- <result> = getelementptr <ty>, <ptr vector> <ptrval>, [inrange] <vector index type> <idx>
- 说明
- 获取aggregate数据结构的子元素的地址;它只执行地址计算不访问内存
- 例子
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]; }
%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.6 强转操作
trunc ... to
zext ... to
- <result> = zext <ty> <value> to <ty2> ; yields ty2
- 约束
- 两个ty必须是整数,或者相同数量的整数向量
- value的位数要比ty2类型的位数小
- 例子
- %X = zext i32 257 to i64 ; yields i64:257
- %Y = zext i1 true to i32 ; yields i32:1
- %Z = zext <2 x i16> <i16 8, i16 7> to <2 x i32> ; yields <i32 8, i32 7>
sext ... to
fptrunc ... to
fpext ... to
fptoui ... to
fptosi ... to
uitofp ... to
sitofp ... to
ptrtoint ... to
inttoptr ... to
bitcast ... to
addrspacecast ... to
5.7 其它操作
-
icmp
- <result> = icmp <cond> <ty> <op1>, <op2> ; yields i1 or <N x i1>:result
- <cond>
- eq: equal
- ne: not equal
- ugt: unsigned greater than
- uge: unsigned greater or equal
- ult: unsigned less than
- ule: unsigned less or equal
- sgt: signed greater than
- sge: signed greater or equal
- slt: signed less than
- sle: signed less or equal
- 说明
- 返回布尔值或者布尔值的向量
- op1和op2必须是integer/pointer或者interger vector
- 例子
- <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 [fast-math flags]* <cond> <ty> <op1>, <op2> ; yields i1 or <N x i1>:result
- <cond>
- false: no comparison, always returns false
- oeq: ordered and equal
- ogt: ordered and greater than
- oge: ordered and greater than or equal
- olt: ordered and less than
- ole: ordered and less than or equal
- one: ordered and not equal
- ord: ordered (no nans)
- ueq: unordered or equal
- ugt: unordered or greater than
- uge: unordered or greater than or equal
- ult: unordered or less than
- ule: unordered or less than or equal
- une: unordered or not equal
- uno: unordered (either nans)
- true: no comparison, always returns true
- 例子
- <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
- <result> = phi [fast-math-flags] <ty> [ <val0>, <label0>], …
- 说明
- 每个val对应一个label;
- 每个label对应一个basic block,此bb到phi节点的incoming value就是val
-
select
-
freeze
-
call
-
va_arg
-
landingpad
-
catchpad
-
cleanuppad