LLVM IR / LLVM指令集入门

本文基于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形式的,所以会验证格式错误

1.3 LLVM IR中的标识符(Identifier)

LLVM IR中有两种标识符类型

  • 全局标识。以@开头的
    • 函数
    • 全局变量
  • 局部标识。以%开头
    • 寄存器名
    • 类型名

LLVM IR中有3种标识符格式

  • 有名字的值(Named Values)

    • 格式:[%@][-azA-Z . ] [ − a − z A − Z ._][-a-zA-Z .][azAZ._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对象被重命名以防止名字冲突
  • 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
  • 更多可能会在未来添加

3.类型系统

类型系统是LLVM IR中最重要的特性之一。它带来好处:

  • 有类型让许多优化能够直接在IR上进行(一般的三地址码不行,它没类型)
  • 强类型使得LLVM IR更加可读

3.1 Void类型

void就是通常编程语言中的void,空。

3.2 Function类型

函数类型,它的表示成:

 ()

例如:

  • 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(指针类型)
    • 表示形式为: *
  • Vector(向量)
    • Fiexed-length vector(定长向量): < <# elements> x >
    • Scalable vector(变长向量): < vscale x <# elements> x >
  • Label
  • Token
  • Metadata
  • Aggregate(聚合类型)
    • Array(数组):
      • 表示: [<# elements> x ]
    • Structure(结构体)
      • Identified normal struct type
        • %T1 = type { \ }
          • { i32, i32, i32}: 4字节三元组
          • { float, i32 (i32) * }: 第一个元素float,第二个元素函数指针,函数为4字节入参,返回4字节类型
      • Identified packed struct type
        • %T2 = type <{ \ }>
          • <{ i8, i32 }>: 只知道5字节大小
    • Opaque Structure
      • 没有结构体,类似C中的结构体类型前向声明
        • %X = type opaque
        • %52 = type opaque

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
    • 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 *
      , [ label , label , … ]
  • invoke

    • = invoke [cconv] [ret attrs] [addrspace()] | () [fn attrs]
      [operand bundles] to label unwind 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`
      
  • callbr

    • = callbr [cconv] [ret attrs] [addrspace()] | () [fn attrs] [operand bundles] to label [indirect labels]
  • resume

    • resume
  • catchswitch

    • = catchswitch within [ label , label , … ] unwind to caller
    • = catchswitch within [ label , label , … ] unwind label
  • catchret

    • catchret from to label
  • cleanupret

    • cleanupret from unwind label
    • cleanupret from unwind to caller
  • unreachable

5.2 单目运算

  • fneg
    • = fneg [fast-math flags]* ; yields ty:result
    • 浮点数或者浮点向量的负

5.3 二元运算

  • add

    • = add , ; yields ty:result
    • = add nuw , ; yields ty:result
    • = add nsw , ; yields ty:result
    • = add nuw nsw , ; yields ty:result
    • 约束
      • 作用到整数或者整数向量,两个操作数都必须是相同类型
      • nuw
        • No Unsigned Wrap
      • nsw
        • No Signed Wrap
      • 如果无符号/有符号溢出,nuw/nsw设置,得到poison value
  • fadd

    • = fadd [fast-math flags]* , ; yields ty:result
    • 约束
      • 作用相等类型的浮点/浮点向量上
  • sub

    • = sub > >, > ; yields ty:result
    • = sub nuw > >, > ; yields ty:result
    • = sub nsw > >, > ; yields ty:result
    • = sub nuw nsw > >, > ; yields ty:result
    • 约束
      • 作用到整数或者整数向量,两个操作数都必须是相同类型
      • nuw
        • No Unsigned Wrap
      • nsw
        • No Signed Wrap
      • 如果无符号/有符号溢出,nuw/nsw设置,得到poison value
  • fsub

    • = fsub [fast-math flags]* , ; yields ty:result
    • 约束
      • 作用相等类型的浮点/浮点向量上
  • mul

    • = mul , ; yields ty:result
    • = mul nuw , ; yields ty:result
    • = mul nsw , ; yields ty:result
    • = mul nuw nsw , ; yields ty:result
    • 约束
      • 作用到整数或者整数向量,两个操作数都必须是相同类型
      • nuw
        • No Unsigned Wrap
      • nsw
        • No Signed Wrap
      • 如果无符号/有符号溢出,nuw/nsw设置,得到poison value
  • fmul

    • = fmul [fast-math flags]* , ; yields ty:result
    • 约束
      • 作用相等类型的浮点/浮点向量上
  • udiv

    • = udiv , ; yields ty:result
    • = udiv exact , ; yields ty:result
    • 约束
      • 作用到整数或者整数向量,两个操作数都必须是相同类型
      • 无符号整数相除
      • 除0行为未知
      • 使用exact时,如果((%op1 udiv exact %op2) mul %op2) != %op1, 则udiv exact得到的结果为poison value
  • sdiv

    • = sdiv , ; yields ty:result
    • = sdiv exact , ; yields ty:result
    • 约束
      • 作用到整数或者整数向量,两个操作数都必须是相同类型
      • 有符号整数相除
      • 除0行为未知
      • 溢出行为未知
      • 如果exact使用,结果被舍入,则结果为poison value
  • fdiv

    • = fdiv [fast-math flags]* , ; yields ty:result
    • 约束
      • 作用相等类型的浮点/浮点向量上
  • urem

    • = urem , ; yields ty:result
    • 约束
      • 作用到整数或者整数向量,两个操作数都必须是相同类型
      • 无符号整数除法的余数
      • op2为0,行为未知
    • 例如
      = urem i32 4, %var ; yields i32:result = 4 % %var
  • srem

    • = srem , ; yields ty:result
    • 约束
      • 作用到整数或者整数向量,两个操作数都必须是相同类型
      • 有符号整数除法的余数
      • 结果要么为0,要么与op1有相同的符号
      • op2为0,行为未知
      • 溢出行为未知
  • frem

    • = frem [fast-math flags]* , ; yields ty:result
    • 约束
      • 作用相等类型的浮点/浮点向量上
      • 与被除数相同符号

5.4 二元位运算

  • shl

    • = shl , ; yields ty:result
    • = shl nuw , ; yields ty:result
    • = shl nsw , ; yields ty:result
    • = shl nuw nsw , ; yields ty:result
    • 说明
      • op1左移特定数量的位(op2为无符号)
      • 结果为(op1 * 2^(op2) ) % 2^n
  • lshr

    • = lshr , ; yields ty:result
    • = lshr exact , ; yields ty:result
    • 说明
      • 逻辑右移
  • ashr

    • = ashr , ; yields ty:result
    • = ashr exact , ; yields ty:result
    • 说明
      • 算数右移
  • and

    • = and , ; yields ty:result
  • or

    • = or , ; yields ty:result
  • xor

    • = xor , ; yields ty:result

5.5 向量操作

  • extractelement

    • = extractelement > , ; yields
    • = extractelement > , ; yields
    • 例子
      • = extractelement <4 x i32> %vec, i32 0 ; yields i32
  • insertelement

    • = insertelement > , , ; yields >
    • = insertelement > , , ; yields >
    • 例子
      • = insertelement <4 x i32> %vec, i32 1, i32 0 ; yields <4 x i32>
  • shufflevector

    • = shufflevector > , > , ; yields >
    • = shufflevector > , > v2, ; yields >
    • 例子
      • = shufflevector <4 x i32> %v1, <4 x i32> %v2, <4 x i32> ; yields <4 x i32>
      • = shufflevector <4 x i32> %v1, <4 x i32> undef, <4 x i32> ; yields <4 x i32> - Identity shuffle.
      • = shufflevector <8 x i32> %v1, <8 x i32> undef, <4 x i32> ; yields <4 x i32>
      • = shufflevector <4 x i32> %v1, <4 x i32> %v2, <8 x i32> ; yields <8 x i32>

5.6 访问/操作内存

  • alloca

    • = alloca [inalloca] [, ] [, align ] [, addrspace()] ; 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

    • = load [volatile] , * [, align ][, !nontemporal !][, !invariant.load !][, !invariant.group !][, !nonnull !][, !dereferenceable !][, !dereferenceable_or_null !][, !align !][, !noundef !]
    • = load atomic [volatile] , * [syncscope("")] , align [, !invariant.group !]
      ! = !{ i32 1 }
      ! = !{}
      ! = !{ i64 }
      ! = !{ i64 }
    • 说明
      • 指定从哪个内存地址加载;指定的类型必须是已知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] , * [, align ][, !nontemporal !][, !invariant.group !] ; yields void
    • store atomic [volatile] , * [syncscope("")] , align [, !invariant.group !] ; yields void
      ! = !{ i32 1 }
      ! = !{}
    • 说明
      • 被用来写内存
      • 类型必须是的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

    • = getelementptr , * {, [inrange] }*
    • = getelementptr inbounds , * {, [inrange] }*
    • = getelementptr , , [inrange]
    • 说明
      • 获取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
    • = zext to ; 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> to <2 x i32> ; yields
  • 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

    • = icmp , ; yields i1 or :result
      • 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
    • 例子
      • = icmp eq i32 4, 5 ; yields: result=false
      • = icmp ne float* %X, %X ; yields: result=false
      • = icmp ult i16 4, 5 ; yields: result=true
      • = icmp sgt i16 4, 5 ; yields: result=false
      • = icmp ule i16 -4, 5 ; yields: result=false
      • = icmp sge i16 4, 5 ; yields: result=false
  • fcmp

    • = fcmp [fast-math flags]* , ; yields i1 or :result
      • 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
    • 例子
      • = fcmp oeq float 4.0, 5.0 ; yields: result=false
      • = fcmp one float 4.0, 5.0 ; yields: result=true
      • = fcmp olt float 4.0, 5.0 ; yields: result=true
      • = fcmp ueq double 1.0, 2.0 ; yields: result=false
  • phi

    • = phi [fast-math-flags] [ , ], …
    • 说明
      • 每个val对应一个label;
    • 每个label对应一个basic block,此bb到phi节点的incoming value就是val
  • select

  • freeze

  • call

  • va_arg

  • landingpad

  • catchpad

  • cleanuppad

你可能感兴趣的:(Clang/LLVM,编译原理)