美文网首页
LLVM IR 三部曲之一 --- IR语法

LLVM IR 三部曲之一 --- IR语法

作者: 好雨知时节浩宇 | 来源:发表于2020-04-26 21:32 被阅读0次

    IR基本组成部分

    IR主要有以下四部分组成:
    Module
    Function
    BasicBlock
    Instruction

    他们之间关系:(用图会描述的更加详细,稍后在贴上)
    Module -> Function ->BasicBlock ->Instruction

    IR中最为复杂的部分就是Instruction,IR中指令繁多,每个Instruction是做什么用、示例代码等在文档上都有详细解释,需要可以查阅文档!

    IR整体结构:
    IR中的指令介绍:LLVM Instruction

    IR语法之变/常量,数组

    这部分代码太多,暂时先不贴

    不同数据类型运算:

    #include <iostream>
    
    double dou() {
        double a,b;
        return a+b;
    }
    
    int main() {
        int c,d;
        return c+d;
    }
    

    上述代码转换为IR后内容如下:

    ; ModuleID = 'haoyu.cpp'
    source_filename = "haoyu.cpp"
    target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
    target triple = "x86_64-apple-macosx10.14.0"
    
    ; Function Attrs: noinline nounwind optnone ssp uwtable
    define double @_Z3douv() #0 {
      %1 = alloca double, align 8
      %2 = alloca double, align 8
      %3 = load double, double* %1, align 8
      %4 = load double, double* %2, align 8
      %5 = fadd double %3, %4  //add前加f,表示浮点数相加
      ret double %5
    }
    
    ; Function Attrs: noinline norecurse nounwind optnone ssp uwtable
    define i32 @main() #1 {
      %1 = alloca i32, align 4
      %2 = alloca i32, align 4
      %3 = alloca i32, align 4
      store i32 0, i32* %1, align 4
      %4 = load i32, i32* %2, align 4
      %5 = load i32, i32* %3, align 4 //将%3变量的指向的值加载到%5中
      %6 = add nsw i32 %4, %5
      ret i32 %6
    }
    //省略一部分不相关内容
    

    关于上述IR中相关命令的解释:

    alloca 是开辟内存空间指令
    load 是加载指令,即读出内容
    store 是写入指令。

    这之后是运算命令:

    Add是加
    Sub是减
    Mul是乘
    Div是除
    Rems是求余

    运算命令前头:
    f的是浮点运算;
    u的是返回无符号整型值(unsigned integer);
    s返回的是有符号的;

    ret i32 %6表示返回加的结果,如果是void型的函数,就ret void

    基本条件语句:

    int main() {
        int a,b,c;
        a=78;
        b=66;
        c=33;
        if( a > b) {
            c=1;
        }
        else {
            c=2;
        }
    //没有添加返回值,llvm会默认添加一个返回值,默认值为0
    }
    
    ; ModuleID = 'haoyu.cpp'
    source_filename = "haoyu.cpp"
    target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
    target triple = "x86_64-apple-macosx10.14.0"
    
    ; Function Attrs: noinline norecurse nounwind optnone ssp uwtable
    define i32 @main() #0 {
      %1 = alloca i32, align 4 //返回值
      %2 = alloca i32, align 4 //a
      %3 = alloca i32, align 4 //b
      %4 = alloca i32, align 4 //c
      store i32 0, i32* %1, align 4 //赋值操作
      store i32 78, i32* %2, align 4
      store i32 66, i32* %3, align 4
      store i32 33, i32* %4, align 4
      %5 = load i32, i32* %2, align 4
      %6 = load i32, i32* %3, align 4
      %7 = icmp sgt i32 %5, %6 //icmp
      br i1 %7, label %8, label %9 //br命令
    
    ; <label>:8:                                      ; preds = %0
      store i32 1, i32* %4, align 4
      br label %10       //无条件跳转指令
    
    ; <label>:9:                                      ; preds = %0
      store i32 2, i32* %4, align 4
      br label %10       //无条件跳转指令
    
    ; <label>:10:                                     ; preds = %9, %8
      %11 = load i32, i32* %1, align 4
      ret i32 %11
    }
    

    基本条件语句多了两个指令+一个数据类型
    icmp:比较命令(不同类型会有不同比较命令:icmp、fcmp)icmp Instruction
    语法规则如下:

    <result> = icmp <cond> <ty> <op1>, <op2>   ; yields i1 or <N x i1>:result
    //第一参数是:关键字 ,表示比较的规则 ex:大于、小于、大于等于.具体可选择可参考官方文档!
    //第二个参数是:类型。表示后面两个值的类型。
    //后面两个参数为要做比较的两个值
    

    br:跳转指令
    语法规则:

    br i1 <cond>, label <iftrue>, label <iffalse> //有条件跳转
    br label <dest>          ; Unconditional branch //无条件跳转
    

    可以看到br跳转包括无条件跳转有条件跳转 。即br i1br
    cond表示跳转条件:
    第一个lable表示如果条件为true要跳转到哪一个基本块的标签(用来标记该基本块的入口)
    第二个label表示如果比较条件false要跳转的基本块。

    以上面的示例为例:

     br i1 %7, label %8, label %9 
    

    如果局部变量%7的值为真,则跳转到标签为label %8的基本块执行,否则跳转到标签为label %9的基本块执行。

    至于无条件跳转br指令就很容易理解了,直接跳转至标签为dest的基本块执行。

      br label %10 
    

    label:标签
    严格的讲它也是一种数据类型(type),但它可以标识入口,相当于代码标签;

    我们再来看一个if-else if-else 结构的IR:

    int main() {
        int i = 0;
        int b = 0;
        if(i>0) {
            b = 5;
        }
        else if ( i == 0 ) {
            b = 10;
        }
        else if( i < 0 ) {
            b = 200;
        }
        return 0;
    }
    

    生成IR如下:

    target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
    target triple = "x86_64-apple-macosx10.14.0"
    
    ; Function Attrs: noinline norecurse nounwind optnone ssp uwtable
    define i32 @main() #0 {
      %1 = alloca i32, align 4
      %2 = alloca i32, align 4
      %3 = alloca i32, align 4
      store i32 0, i32* %1, align 4
      store i32 0, i32* %2, align 4
      store i32 0, i32* %3, align 4
      %4 = load i32, i32* %2, align 4
      %5 = icmp sgt i32 %4, 0
      br i1 %5, label %6, label %7
    
    ; <label>:6:                                      ; preds = %0
      store i32 5, i32* %3, align 4
      br label %17
    
    ; <label>:7:                                      ; preds = %0
      %8 = load i32, i32* %2, align 4
      %9 = icmp eq i32 %8, 0   //做一次是否相等的判断
      br i1 %9, label %10, label %11
    
    ; <label>:10:                                     ; preds = %7
      store i32 10, i32* %3, align 4
      br label %16
    
    ; <label>:11:                                     ; preds = %7
      %12 = load i32, i32* %2, align 4
      %13 = icmp slt i32 %12, 0   //做一次小于判断,判断是否小于0
      br i1 %13, label %14, label %15
    
    ; <label>:14:                                     ; preds = %11
      store i32 200, i32* %3, align 4
      br label %15
    
    ; <label>:15:                                     ; preds = %14, %11
      br label %16
    
    ; <label>:16:                                     ; preds = %15, %10
      br label %17
    
    ; <label>:17:                                     ; preds = %16, %6
      ret i32 0
    }
    

    我们可以看到还是运用到了icmp、br、label 命令和标签无其他特殊内容

    While循环

    int main() {
        int a = 10,i = 20;
        while (i < 10) {
            i=i+1;
            a=a*2;
        }
        return 0;
    }
    

    生成的IR如下:

    ; ModuleID = 'haoyu.cpp'
    source_filename = "haoyu.cpp"
    target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
    target triple = "x86_64-apple-macosx10.14.0"
    
    ; Function Attrs: noinline norecurse nounwind optnone ssp uwtable
    define i32 @main() #0 {
      %1 = alloca i32, align 4
      %2 = alloca i32, align 4 //a
      %3 = alloca i32, align 4 //i
      store i32 0, i32* %1, align 4
      store i32 10, i32* %2, align 4 
      store i32 20, i32* %3, align 4
      br label %4
    
    ; <label>:4:                                      ; preds = %7, %0 这里是一个if判断
      %5 = load i32, i32* %3, align 4
      %6 = icmp slt i32 %5, 10  //判断i是否小于10
      br i1 %6, label %7, label %12
    
    ; <label>:7:                                      ; preds = %4  这里是一个普通的分支循环
      %8 = load i32, i32* %3, align 4
      %9 = add nsw i32 %8, 1 // i + 1 操作
      store i32 %9, i32* %3, align 4
      %10 = load i32, i32* %2, align 4
      %11 = mul nsw i32 %10, 2 //a * 2 操作
      store i32 %11, i32* %2, align 4
      br label %4 //跳转到while开始的地方,构成循环调用
    
    ; <label>:12:                                     ; preds = %4
      ret i32 0
    }
    

    可以看到相较于if,while在IR中的实现几乎没有用到新的指令,可以说,所谓的循环语句while == if + 分支循环;

    for循环

    int main() {
        int a = 10, i = 20;
        
        for ( i = 0; i < 10; i++ ) {
            a = a *2;
        }
        return 0;
    }
    
    

    生成的IR如下:

    ; ModuleID = 'haoyu.cpp'
    source_filename = "haoyu.cpp"
    target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
    target triple = "x86_64-apple-macosx10.14.0"
    
    ; Function Attrs: noinline norecurse nounwind optnone ssp uwtable
    define i32 @main() #0 {
      %1 = alloca i32, align 4
      %2 = alloca i32, align 4
      %3 = alloca i32, align 4
      store i32 0, i32* %1, align 4
      store i32 10, i32* %2, align 4 //a
      store i32 20, i32* %3, align 4 //i 
      store i32 0, i32* %3, align 4
      br label %4
    
    ; <label>:4:                                      ; preds = %10, %0
      %5 = load i32, i32* %3, align 4
      %6 = icmp slt i32 %5, 10         //判断i是否小于10
      br i1 %6, label %7, label %13
    
    ; <label>:7:                                      ; preds = %4
      %8 = load i32, i32* %2, align 4
      %9 = mul nsw i32 %8, 2   // a *2 操作
      store i32 %9, i32* %2, align 4 //将* 2后的值重新赋值到a上
      br label %10
    
    ; <label>:10:                                     ; preds = %7
      %11 = load i32, i32* %3, align 4
      %12 = add nsw i32 %11, 1        //对 i 进行 ++ 操作
      store i32 %12, i32* %3, align 4
      br label %4  // i++完成后回到上一步
    
    ; <label>:13:                                     ; preds = %4
      ret i32 0
    }
    

    可以看到for循环同样也没有什么新的指令出现;它一样是条件判断+分支循环,只不过比while更高级的地方在于:它把用于判断是否继续循环的条件单独放进一个basicblock中,也因此比while循环多了一个basicBlock。

    switch操作:

    int main() {
        int a = 5, b = 20;
        switch(a)
        {
            case 0:
            {
                b = 1;
            }
            case 1:
            {
                b = 2;
            }
            case 5:
            {
                b = 3;
            }
        }
        return 0;
    }
    

    生成的IR如下:

    ; ModuleID = 'haoyu.cpp'
    source_filename = "haoyu.cpp"
    target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
    target triple = "x86_64-apple-macosx10.14.0"
    
    ; Function Attrs: noinline norecurse nounwind optnone ssp uwtable
    define i32 @main() #0 {
      %1 = alloca i32, align 4
      %2 = alloca i32, align 4
      %3 = alloca i32, align 4
      store i32 0, i32* %1, align 4
      store i32 5, i32* %2, align 4  //a
      store i32 20, i32* %3, align 4 //b
      %4 = load i32, i32* %2, align 4 //读取a的值用于switch判断
      switch i32 %4, label %8 [       //switch指令
        i32 0, label %5
        i32 1, label %6
        i32 5, label %7
      ]
    
    ; <label>:5:                                      ; preds = %0
      store i32 1, i32* %3, align 4
      br label %6      //没有添加break,执行完label 5后,会继续向下执行 label 6
    
    ; <label>:6:                                      ; preds = %0, %5
      store i32 2, i32* %3, align 4
      br label %7
    
    ; <label>:7:                                      ; preds = %0, %6
      store i32 3, i32* %3, align 4
      br label %8
    
    ; <label>:8:                                      ; preds = %7, %0
      ret i32 0
    }
    
    

    我们可以看到多了一个switch指令。
    swicth : switch Instruction
    语法规则:

    switch <intty> <value>, label <defaultdest> [ <intty> <val>, label <dest> ... ]
    //para1:int 类型
    //para2: 要匹配的值
    //para3:要跳转到的标签
    //[ ] 中罗列的就是固定结构的:int类型、具体值、对应的标签
    跟多更具体可参考官方文档!
    

    switchbr指令的加强版,可以产生多个(不止两个)程序分支;说白了跟c语言的switch机制差不多。

    另外我们可以看到switch的各个分支不是运行一个就完事了的,而是自上而下顺序依次运行的,如果你的条件变量的值触发了第N个程序分支,那么运行完第N个程序分支后switch会继续运行N+1,知道执行完所有label。

     switch i32 %4, label %8 [       //switch指令
        i32 0, label %5
        i32 1, label %6
        i32 5, label %7
      ]
    
    ; <label>:5:                                      ; preds = %0
      store i32 1, i32* %3, align 4
      br label %6      //没有添加break,执行完label 5后,会继续向下执行 label 6
    
    ; <label>:6:                                      ; preds = %0, %5
      store i32 2, i32* %3, align 4
      br label %7
    //添加了break后的执行如下:
    
    ; <label>:5:                                      ; preds = %0
      store i32 1, i32* %3, align 4
      br label %8       //直接跳到最后面的label 8上执行,不会继续向下执行。
    
    
    

    综上IR中除了switch指令外,没有什么新命令出现。是不是觉得很简单。

    OC文件编译出的IR

    上述都是C++代码编译的结果,那么我们OC语法编译出来的会有什么不同吗?其实本质上无太大不同,只是OC由于复杂的继承关系以及xitp0ng类库的调用,会使编译出来的结果全局、局部变量很多,另外很多隐式的函数也会出现在我们的IR中。

    先来看一下生成IR的命令:

    clang -fobjc-arc -emit-llvm HaoyuViewController.m -S -c -o haoyu.ll -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk
    

    -emiit-llvm :触发LLVM生成IR
    -S:生成刻度的汇编IR
    -fobjc-arc:指定使用ARC方式
    -isysroot:指定依赖的系统类库

    部分代码示例:(文件内容太多,不全部展示)

    %struct.__NSConstantString_tag = type { i32*, i32, i8*, i64 }
    %struct._objc_cache = type opaque
    %struct._class_t = type { %struct._class_t*, %struct._class_t*, %struct._objc_cache*, i8* (i8*, i8*)**, %struct._class_ro_t* }
    
    ...
    //默认继承的函数也会被添加到IR中
    ; Function Attrs: noinline optnone ssp uwtable
    define internal void @"\01-[UIViewController setView:]"(%0*, i8*, %2*) #0 {
      %4 = alloca %0*, align 8
      %5 = alloca i8*, align 8
      %6 = alloca %2*, align 8
      store %0* %0, %0** %4, align 8
      store i8* %1, i8** %5, align 8
      store %2* %2, %2** %6, align 8
      %7 = load %2*, %2** %6, align 8
      %8 = load %0*, %0** %4, align 8
      %9 = load i64, i64* @"OBJC_IVAR_$_UIViewController._view", align 8, !invariant.load !10
      %10 = bitcast %0* %8 to i8*
      %11 = getelementptr inbounds i8, i8* %10, i64 %9
      %12 = bitcast i8* %11 to %2**
      %13 = bitcast %2** %12 to i8**
      %14 = bitcast %2* %7 to i8*
      call void @llvm.objc.storeStrong(i8** %13, i8* %14) #1
      ret void
    }
    
    ; Function Attrs: noinline optnone ssp uwtable
    define internal %2* @"\01-[UIViewController viewIfLoaded]"(%0*, i8*) #0 {
      %3 = alloca %0*, align 8
      %8 = getelementptr inbounds i8, i8* %7, i64 %6
      %9 = bitcast i8* %8 to %2**
      %10 = load %2*, %2** %9, align 8
      ret %2* %10
    }
    
    
    ...
    
    //运行时函数也会被加入带这里面来
    ; Function Attrs: noinline optnone ssp uwtable
    define internal void @"\01-[UIViewController .cxx_destruct]"(%0*, i8*) #0 {
      %3 = alloca %0*, align 8
    ...
    }
    

    综上基本上常见的的语法我们都覆盖到了,还有一些更为具体的细节,我们可以参考这个LLVM中文文档
    可作为参考,更深入的了解LLVM。

    相关文章

      网友评论

          本文标题:LLVM IR 三部曲之一 --- IR语法

          本文链接:https://www.haomeiwen.com/subject/nswxwhtx.html