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 i1
和br
粮呢。
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類型墨技、具體值磨镶、對應的標簽
跟多更具體可參考官方文檔!
switch
是br
指令的加強版健提,可以產生多個(不止兩個)程序分支琳猫;說白了跟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暮屡。