(LLVM)中間語言(IR)基本語法簡介

(LLVM)中間語言(IR)基本語法簡介

轉自:http://blog.sina.com.cn/s/blog_49b6b6d001011gik.html

根據編譯原理知識,編譯器不是直接將源語言翻譯爲目標語言,而是翻譯爲一種“中間語言”,我們編譯器從業人員稱之爲“IR”--指令集,之後再由中間語言,利用後端程序和設備翻譯爲目標平臺的彙編語言;

無疑,不同編譯器的中間語言IR是不一樣的,而IR可以說是集中體現了這款編譯器的特徵—-他的算法,優化方式,彙編流程等等,想要完全掌握某種編譯器的工作和運行原理,分析和學習這款編譯器的中間語言無疑是重要手段,另外,由於中間語言相當於一款編譯器前端和後端的“橋樑”,如果我們想進行基於llvm的後端移植,無疑需要開發出對應目標平臺的編譯器後端,想要順利完成這一工作,透徹瞭解llvm的中間語言無疑是非常必要的工作。
Llvm相對於gcc的一大改進就是大大提高了中間語言的生成效率和可讀性,我個人感覺llvm的中間語言是一種介於c語言和彙編語言的格式,他既有高級語言的可讀性,又能比較全面地反映計算機底層數據的運算和傳輸的情況,精煉而又高效,相對而言,gcc的中間代碼有如科幻小說一般~


首先用vim命令創建一個新的c程序代碼文件try1.c:

int main()
{
int a,b;
return a+b;
}

Clang try1.c -o try1
生成可執行文件
Clang -emit-llvm try1.c -S -o try1.ll
生成中間代碼文件
Vim try1.ll
查看:

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
; ModuleID = ‘try1.c’
target datalayout = “e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128”
target triple = “i386-pc-linux-gnu”

define i32 @main() nounwind {
entry:
%retval = alloca i32, align 4
%a = alloca i32, align 4
%b = alloca i32, align 4
store i32 0, i32* %retval
%0 = load i32* %a, align 4
%1 = load i32* %b, align 4
�d = add nsw i32 %0, %1
ret i32 �d
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

根據llvm.org上的描述,@代表全局變量,%代表局部變量,那麼無疑,在llvm IR看來,int main這個函數,或者說他的函數返回值是個全局變量,其內部的a 和b是局部變量。

在這段代碼裏,我們找到了我們之前定義的a和b
%a = alloca i32, align 4
%b = alloca i32, align 4
那麼其他字符分別代表什麼操作呢?
Alloca
Alloca相當於變量聲明:

在llvm文檔上對於Alloca的解釋是:The ‘alloca’ instruction allocates memory on the stack frame of the currently executing function, to be automatically released when this function returns to its caller.
“alloca指令用於分配內存堆棧給當前執行的函數,當這個函數返回其調用者(我自己對於caller的翻譯)時自動釋放。”

感覺跟c語言裏的malloc差不多,不過當然,llvm更加“底層”。

i32:

可以得知這其實是在設置整數位長度 document裏說的很明白:i是幾這個整數就會佔幾位(bit),i32的話就是32位,4字節;i後面的數字可以隨意寫,這體現的就是llvm中間語言類似彙編的特徵;

align :
在Language Reference Manual似乎沒有這個關鍵字的註釋,align 的意思是“對齊”那麼這個對齊的意思究竟是什麼?

“對齊”的意義是:若一個結構中含有一個int,一個char,一個int則他應該佔用4*3=12字節,雖然char本身只佔用一個字節的空間,但由於要向4“對齊”所以其佔用內存空間仍爲4(根據大端小端分別存儲)

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

int main()
{
double a=128;

}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
它生成的中間代碼是這樣的:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

; ModuleID = ‘try5.c’
target datalayout = “e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128”
target triple = “i386-pc-linux-gnu”

define i32 @main() nounwind {
entry:
%retval = alloca i32, align 4
%a = alloca double, align 8
store i32 0, i32* %retval
store double 1.280000e+02, double* %a, align 8
%0 = load i32* %retval
ret i32 %0
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
%a = alloca double, align 8
可以看到align 後面跟的數字變成4而不是8了~~

i32, align 4的意義就應該是:向4對齊,即便數據沒有佔用4個字節,也要爲其分配4字節,這樣使得llvm IR在保證了數據格式一致性的前提條件下,定義數據型時非常靈活,不僅可以任意定義整形和浮點型的長度(iX,iXX,iXXX………),甚至還允許使用不同的數制,比如你需要使用64進制數字(?),那就只要i48, align 6即可。

這是a和b的情況,至於那個 %retval = alloca i32, align 4
中的retval,它無疑是return value 返回值的縮寫,但它很有意思,它存儲的值不一定就是返回值,它在上述return a+b的時候除了得到個0值之外根本不參與任何運算和傳輸,而且根據試驗情況,這個retval似乎只在main函數中出現,而且由於main的返回值必須是int,這個retval也總是“ %retval = alloca i32, align 4 ”事實上,當提供高優化等級之後,retval就不會再出現,這個變量可以被認爲是非必要的;

證明:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Try.c:
double dou()
{

double a,b;
return a+b;

}

int main()
{

int c,d;
return c+d;

}

…………………………………………………………………………………
Try.ll:

define double @dou() nounwind {
entry:
%a = alloca double, align 8
%b = alloca double, align 8
%0 = load double* %a, align 8
%1 = load double* %b, align 8
�d = fadd double %0, %1
ret double �d
}

define i32 @main() nounwind {
entry:
%retval = alloca i32, align 4
%c = alloca i32, align 4
%d = alloca i32, align 4
store i32 0, i32* %retval
%0 = load i32* %c, align 4
%1 = load i32* %d, align 4
�d = add nsw i32 %0, %1
ret i32 �d
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

我猜測這個retval可能是爲後端留的某個接口,因爲我是在x86下運行llvm所以默認數據型是int,但是這也僅僅是我的猜測,我自己並不知道retval是什麼,我在文檔上和網上也沒找到答案;

研究了以上這些後,之後的程序語句:
%0 = load i32* %a, align 4
%1 = load i32* %b, align 4
就好理解了;

它與alloca和store均屬於“Memory Access and Addressing Operations”
Load是“裝載”,即讀出內容,store則是寫入;

這之後是運算命令:

Add是加
Sub是減
Mul是乘
Div是除
Rems是求餘
前頭加f的是浮點運算,加u的是返回無符號整型值(unsigned integer)加s返回的是有符號的;

ret i32 �d表示返回加的結果,如果是void型的函數,就ret void;


4個c語言基本條件語句和循環語句:
If which for switch

If:
Try3.c:

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int main()
{

int a,b,c;

a=1;
b=2;
c=0;
if(a>b)
{c=1;}
else
{c=2;}

}

生成中間代碼文件:

; ModuleID = ‘try3.c’
target datalayout = “e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128”
target triple = “i386-pc-linux-gnu”

define i32 @main() nounwind {
entry:
%retval = alloca i32, align 4
%a = alloca i32, align 4
%b = alloca i32, align 4
%c = alloca i32, align 4
store i32 0, i32* %retval
store i32 1, i32* %a, align 4
store i32 2, i32* %b, align 4
store i32 0, i32* %c, align 4
%0 = load i32* %a, align 4
%1 = load i32* %b, align 4
%cmp = icmp sgt i32 %0, %1
br i1 %cmp, label %if.then, label %if.else

if.then: ; preds = %entry
store i32 1, i32* %c, align 4
br label %if.end

if.else: ; preds = %entry
store i32 2, i32* %c, align 4
br label %if.end

if.end: ; preds = %if.else, %if.then
%2 = load i32* %retval
ret i32 %2
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
在if的中間語言裏,主要有這麼幾個陌生關鍵字:
icmp
br
label

逐個分析:
Icmp:

Llvm.org explanation:

Syntax:

= icmp , ; yields {i1} or {}:result

Overview:

The ‘icmp’ instruction returns a boolean value or a vector of boolean values based on comparison of its two integer, integer vector, pointer, or pointer vector operands.
Icmp可以根據兩個整數值的比較(op1,op2)返回一個布爾類型的值或者布爾矢量(?)
比較規則由參數cond確定;
具體比較規則如下:
eq: yields true if the operands are equal, false otherwise. No sign interpretation is necessary or performed.
ne: yields true if the operands are unequal, false otherwise. No sign interpretation is necessary or performed.
ugt: interprets the operands as unsigned values and yields true if op1 is greater than op2.
uge: interprets the operands as unsigned values and yields true if op1 is greater than or equal to op2.
ult: interprets the operands as unsigned values and yields true if op1 is less than op2.
ule: interprets the operands as unsigned values and yields true if op1 is less than or equal to op2.
sgt: interprets the operands as signed values and yields true if op1 is greater than op2.
sge: interprets the operands as signed values and yields true if op1 is greater than or equal to op2.
slt: interprets the operands as signed values and yields true if op1 is less than op2.
sle: interprets the operands as signed values and yields true if op1 is less than or equal to op2.

sgt: interprets the operands as signed values and yields true if op1 is greater than op2.
也就是說:Sgt的意思就是若整數op1大於op2的話,cmp 就是true,否則就是false
無疑,icmp是用於判斷的指令;
但是僅僅判斷出結果來還不夠,仍需要根據判斷結果進行相應的選擇性操作,if語句才完整;

Br
Llvm.org explanation:

Syntax:

br i1 , label , label
br label ; Unconditional branch

Overview:

Llvm.org explanation:

The ‘br’ instruction is used to cause control flow to transfer to a different basic block in the current function. There are two forms of this instruction, corresponding to a conditional branch and an unconditional branch.
Br提供一個選擇分支結構,可根據cond的情況使程序轉向label 或label ;
另外br也有一種特殊形式:無條件分支(Unconditional branch):當在某種情況時;不必進行條件判斷而直接跳轉至某一個特定的程序入口標籤(label)處(感覺類似於一個“goto”);
如:
if.then: ; preds = %entry
store i32 1, i32* %c, align 4
br label %if.end
If then完事後,直接跳轉到if.end

label
嚴格的講它也是一種數據類型(type),但它可以標識入口,相當於代碼標籤;
綜上我們可知:
一個if工作的流程是:
1.開始
2.得到兩個操作數的值和比較條件;
3.開始比較,得到比較布爾值(true或者false)
4.根據布爾比較值使程序跳轉到分支入口去;

While:
例子:
Try7.c:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int main()
{
int a,i;
while (i<10)
{
i=i+1;
a=a*2;
}

}
生成的中間代碼:
; ModuleID = ‘try7.c’
target datalayout = “e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128”
target triple = “i386-pc-linux-gnu”

define i32 @main() nounwind {
entry:
%retval = alloca i32, align 4
%a = alloca i32, align 4
%i = alloca i32, align 4
store i32 0, i32* %retval
br label %while.cond

while.cond: ; preds = %while.body, %entry
%0 = load i32* %i, align 4
%cmp = icmp slt i32 %0, 10
br i1 %cmp, label %while.body, label %while.end

while.body: ; preds = %while.cond
%1 = load i32* %i, align 4
�d = add nsw i32 %1, 1
store i32 �d, i32* %i, align 4
%2 = load i32* %a, align 4
%mul = mul nsw i32 %2, 2
store i32 %mul, i32* %a, align 4
br label %while.cond

while.end: ; preds = %while.cond
%3 = load i32* %retval
ret i32 %3
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
可以看到相對於if,while在llvm IR中的實現幾乎沒有用到新的指令,可以說,所謂的循環語句while==if+分支循環;

While的運行流程是:首先跳到while.cond: 相關變量得到初始值後判斷是否滿足繼續循環條件,若滿足,就轉到while.body: 進行循環實際操作,一次實際操作運行完後再次跳到while.cond:進行條件判斷,如此循環~;若否,則直接跳到 while.end: 終止循環;

While~~~原來這麼簡單~~

For:
例子程序:
Try8.c:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Imt main()
{
Int a,i;
For(i=0;i<10;i++)
{
A=a*2;

}

}

生成中間:

; ModuleID = ‘try8.c’
target datalayout = “e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128”
target triple = “i386-pc-linux-gnu”

define i32 @main() nounwind {
entry:
%retval = alloca i32, align 4
%i = alloca i32, align 4
%a = alloca i32, align 4
store i32 0, i32* %retval
store i32 0, i32* %i, align 4
br label %for.cond

for.cond: ; preds = %for.inc, %entry
%0 = load i32* %i, align 4
%cmp = icmp slt i32 %0, 10
br i1 %cmp, label %for.body, label %for.end

for.body: ; preds = %for.cond
%1 = load i32* %a, align 4
%mul = mul nsw i32 %1, 2
store i32 %mul, i32* %a, align 4
br label %for.inc

for.inc: ; preds = %for.body
%2 = load i32* %i, align 4
%inc = add nsw i32 %2, 1
store i32 %inc, i32* %i, align 4
br label %for.cond

for.end: ; preds = %for.cond
%3 = load i32* %retval
ret i32 %3
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
可以看到for循環同樣也沒有什麼新的指令出現;它一樣是條件判斷+分支循環,只不過比while更高級的地方在於:它把用於判斷是否繼續循環的條件“集成”進了函數體,故而也比while多出了個“for.inc:”:用於處理For(i=0;i<10;i++)中“i”的運算;證明:
若把For(i=0;i<10;i++)
改爲:
For(i=0;i<10;i*2)
則for.inc:會變爲:

for.inc: ; preds = %for.body
%2 = load i32* %i, align 4
%mul1 = mul nsw i32 %2, 2
store i32 %mul1, i32* %i, align 4
br label %for.cond

Switch:
例子程序:
Try9.c:

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

int main()
{

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

轉換爲中間代碼:
; ModuleID = ‘try9.c’
target datalayout = “e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128”
target triple = “i386-pc-linux-gnu”

define i32 @main() nounwind {
entry:
%retval = alloca i32, align 4
%a = alloca i32, align 4
%b = alloca i32, align 4
store i32 0, i32* %retval
%0 = load i32* %a, align 4
switch i32 %0, label %sw.epilog [
i32 0, label %sw.bb
i32 1, label %sw.bb1
i32 2, label %sw.bb2
]

sw.bb: ; preds = %entry
store i32 1, i32* %b, align 4
br label %sw.bb1

sw.bb1: ; preds = %entry, %sw.bb
store i32 2, i32* %b, align 4
br label %sw.bb2

sw.bb2: ; preds = %entry, %sw.bb1
store i32 3, i32* %b, align 4
br label %sw.epilog

sw.epilog: ; preds = %sw.bb2, %entry
%1 = load i32* %retval
ret i32 %1
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
可以看到,switch就比較有意思了:
他的中間語言代碼形式與c語言代碼非常像,他並不是br的簡單多次重複,而是一個獨立的指令:
Llvm.org explanation:
Syntax:

switch , label [ , label … ]

Overview:

The ‘switch’ instruction is used to transfer control flow to one of several different places. It is a generalization of the ‘br’ instruction, allowing a branch to occur to one of many possible destinations.
Arguments:

The ‘switch’ instruction uses three parameters: an integer comparison value ‘value’, a default ‘label’ destination, and an array of pairs of comparison value constants and ‘label’s. The table is not allowed to contain duplicate constant entries.
Semantics:

The switch instruction specifies a table of values and destinations. When the ‘switch’ instruction is executed, this table is searched for the given value. If the value is found, control flow is transferred to the corresponding destination; otherwise, control flow is transferred to the default destination.

這就說得很明白了:switch是個獨立的命令,它是“br”的擴展版,可以產生多個(不止兩個)程序分支;說白了跟c語言的switch機制差不多;
這裏需要注意的是,根據IR代碼switch的各個分支不是運行一個就完事了的,而是自上而下順序運行的,如果你的條件變量的值觸發了第N個程序分支,那麼運行完第N個程序分支後switch會繼續運行N+1,N+2,N+3~~~~~~~~~,—-它是連成一串的:

sw.bb: ; preds = %entry
store i32 1, i32* %b, align 4
br label %sw.bb1

sw.bb1: ; preds = %entry, %sw.bb
store i32 2, i32* %b, align 4
br label %sw.bb2

sw.bb2: ; preds = %entry, %sw.bb1
store i32 3, i32* %b, align 4
br label %sw.epilog

sw.epilog: ; preds = %sw.bb2, %entry
%1 = load i32* %retval
ret i32 %1

這就是爲什麼正常寫c代碼使用switch時必須合理使用break;關鍵字的原因了~~一旦這個概念沒搞好,程序得出的結果往往都是錯的,想當年我們c語言期末考試還考過這個知識點呢,吼吼;

衆所周知,llvm起源自美國伊利諾伊大學香檳分校發起的一個開源計劃,目的是發展出一款模塊化的新興開源編譯器,使llvm擁有比現有編譯器更強的優化能力是該項目負責人Chris Lattner和Vikram Adve非常看重的一項技術指標;llvm的主要贊助人和支持者蘋果公司最看重的也是這一項;因爲在蘋果看來,如果能在程序編譯優化方面取得突破,那麼相當於一不用改良軟件編程語法,二不用更新硬件架構,就能使得程序運行性能和速度取得提升,從而優化用戶體驗,這樣的話簡直是太划算了;當然現在看來這種構想還只是鏡中花水中月,但是所有的偉大發明,最早都來自於狂野的幻想,我們程序員也沒有理由不去主動了解llvm這種致力於超越gcc的編譯環境,以期提高自己的專業素養,爭取在未來的競爭中立於不敗之地。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章