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

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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容