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
語法規(guī)則如下:

<result> = icmp <cond> <ty> <op1>, <op2>   ; yields i1 or <N x i1>:result
//第一參數是:關鍵字 ,表示比較的規(guī)則 ex:大于袒哥、小于缩筛、大于等于.具體可選擇可參考官方文檔!
//第二個參數是:類型堡称。表示后面兩個值的類型歪脏。
//后面兩個參數為要做比較的兩個值

br:跳轉指令
語法規(guī)則:

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的基本塊執(zhí)行,否則跳轉到標簽為label %9的基本塊執(zhí)行豪硅。

至于無條件跳轉br指令就很容易理解了哩照,直接跳轉至標簽為dest的基本塊執(zhí)行。

  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循環(huán)

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  這里是一個普通的分支循環(huán)
  %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開始的地方稽穆,構成循環(huán)調用

; <label>:12:                                     ; preds = %4
  ret i32 0
}

可以看到相較于if冠王,while在IR中的實現幾乎沒有用到新的指令,可以說舌镶,所謂的循環(huán)語句while == if + 分支循環(huán)柱彻;

for循環(huán)

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循環(huán)同樣也沒有什么新的指令出現;它一樣是條件判斷+分支循環(huán)餐胀,只不過比while更高級的地方在于:它把用于判斷是否繼續(xù)循環(huán)的條件單獨放進一個basicblock中哟楷,也因此比while循環(huán)多了一個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否灾,執(zhí)行完label 5后卖擅,會繼續(xù)向下執(zhí)行 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
語法規(guī)則:

switch <intty> <value>, label <defaultdest> [ <intty> <val>, label <dest> ... ]
//para1:int 類型
//para2: 要匹配的值
//para3:要跳轉到的標簽
//[ ] 中羅列的就是固定結構的:int類型墨技、具體值磨镶、對應的標簽
跟多更具體可參考官方文檔!

switchbr指令的加強版健提,可以產生多個(不止兩個)程序分支琳猫;說白了跟c語言的switch機制差不多。

另外我們可以看到switch的各個分支不是運行一個就完事了的私痹,而是自上而下順序依次運行的脐嫂,如果你的條件變量的值觸發(fā)了第N個程序分支偎捎,那么運行完第N個程序分支后switch會繼續(xù)運行N+1率挣,知道執(zhí)行完所有l(wèi)abel式塌。

 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鹃愤,執(zhí)行完label 5后,會繼續(xù)向下執(zhí)行 label 6

; <label>:6:                                      ; preds = %0, %5
  store i32 2, i32* %3, align 4
  br label %7
//添加了break后的執(zhí)行如下:

; <label>:5:                                      ; preds = %0
  store i32 1, i32* %3, align 4
  br label %8       //直接跳到最后面的label 8上執(zhí)行绵载,不會繼續(xù)向下執(zhí)行蛔翅。


綜上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 :觸發(fā)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
...
}

綜上基本上常見的的語法我們都覆蓋到了焕阿,還有一些更為具體的細節(jié)咪啡,我們可以參考這個LLVM中文文檔
可作為參考,更深入的了解LLVM暮屡。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末撤摸,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子栽惶,更是在濱河造成了極大的恐慌愁溜,老刑警劉巖疾嗅,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件外厂,死亡現場離奇詭異,居然都是意外死亡代承,警方通過查閱死者的電腦和手機汁蝶,發(fā)現死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來论悴,“玉大人掖棉,你說我怎么就攤上這事“蚬溃” “怎么了幔亥?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長察纯。 經常有香客問我帕棉,道長,這世上最難降的妖魔是什么饼记? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任香伴,我火速辦了婚禮,結果婚禮上具则,老公的妹妹穿的比我還像新娘即纲。我一直安慰自己,他們只是感情好博肋,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布低斋。 她就那樣靜靜地躺著蜂厅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拔稳。 梳的紋絲不亂的頭發(fā)上葛峻,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機與錄音巴比,去河邊找鬼术奖。 笑死,一個胖子當著我的面吹牛轻绞,可吹牛的內容都是我干的采记。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼政勃,長吁一口氣:“原來是場噩夢啊……” “哼唧龄!你這毒婦竟也來了?” 一聲冷哼從身側響起奸远,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤既棺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后懒叛,有當地人在樹林里發(fā)現了一具尸體丸冕,經...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年薛窥,在試婚紗的時候發(fā)現自己被綠了胖烛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡诅迷,死狀恐怖佩番,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情罢杉,我是刑警寧澤趟畏,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站滩租,受9級特大地震影響赋秀,放射性物質發(fā)生泄漏。R本人自食惡果不足惜持际,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一沃琅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜘欲,春花似錦益眉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽年碘。三九已至,卻和暖如春展鸡,著一層夾襖步出監(jiān)牢的瞬間屿衅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工莹弊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留涤久,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓忍弛,卻偏偏與公主長得像响迂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子细疚,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內容