http://blog.csdn.net/banyao2006/article/details/7045216
摘要
該文檔是LLVM匯編語言的參考指南。LLVM是基于表示的靜態單賦值(SSA),該表示提供類型安全、低層級操作,靈活性,及簡潔表示所有高層級語言的能力。這是貫穿各方面LLVM編譯策略的通用代碼表示。
簡介
LLVM代碼表示用于三個不同形式:作為在內存(in-memory)編譯器IR、磁盤比特碼表示(適合即時編譯器的快速加載),以及人類可讀的匯編語言表示。這讓LLVM為高效編譯器轉換和分析提供強大的中間表示的同時,提供調試和轉換可視化的自然手段。LLVM的三種不同形式是等價的。該文檔描述了人類可讀的表示和注解。
LLVM表示旨在具有易于表達、類型化和擴展性的同時保持輕量級和低層級。它通過處于可清晰映射高層想法的足夠低的層級(類似于微處理器允許許多源語言映射其上而成為通用IR),使其成為通用IR。通過提供類型信息,LLVM可用于優化的目的:例如,通過指針分析,可證明C自動變量永遠不會訪問當前函數以外的部分...允許其使用簡單的SSA以代替內存位置。
規范化
記住,該文檔描述的是規范化的LLVM匯編語言,這很重要。語法分析器的接受范圍與規范化之間有所差異。例如,下列指令句法通順,但不夠規范化:
%x = add i32 1, %x
因為%x的定義不能控制自身的所有使用。LLVM基礎架構提供驗證LLVM模塊是否規范化的驗證pass。在語法分析輸入的匯編后由語法分析器和在其輸出比特碼前由優化器自動運行該pass。驗證pass指出的違反行為指名了轉換pass或語法分析器輸入的bug。
標識符
LLVM標識符具有兩種基本類型:全局和局部。全局標識符(函數,全局變量)以'@'起始。局部標識符(寄存器名字,類型)以'%'起始。另外,標識符針對不同目的有三種不同格式:
1 命名值表示為字符串及其前綴。例如,%foo, @DivisionByZero, %a.really.long.identifier。實際的正則表達式是'[%@][a-zA-Z$._][a-zA-Z$._0-9]*'。在其名字里需要其它字符的標識符會被引用環繞。特殊字符可使用"\xx"表示,其中xx是字符ASCII碼的16進制表示。這樣,所有字符可用于命名值,甚至引用自身。
2 未命名值利用無符號數值及其前綴表示。例如,%12, @2, %44。
3 常量,在下面的章節“常量”中描述。
LLVM需要以前綴起始取值有兩個原因:編譯器無需擔心保留字的命名沖突,而且保留字的集合可在未來無懲罰地擴展。另外,未命名標識符允許編譯器無需刻意避免符號表沖突而快速使用臨時變量。
LLVM保留字與其它語言非常相似。關鍵字包括不同操作碼(add, bitcast,ret等),基本類型名(void,i32等)和其它。這些保留字不會與變量名沖突,因為前者都不以前綴字符('%'或'@')起始。
這是整數變量'%x'乘以8的LLVM代碼示例。
簡單方式:
%result = mul i32 %x, 8
簡化之后:
%result = shl i32 %x, i8 3
復雜方式:
%0 = add i32 %X, %X ; 生成 {i32}:%0
%1 = add i32 %0, %0 ; 生成 {i32}:%1
%result = add i32 %1, %1
最后一種實現%x乘以8的方式表明LLVM許多重要詞法特點:
1.注釋以';'界定,直到行尾。
2.當計算結果未賦值給命名變量時,創建未命名臨時變量。
3.未命名臨時變量順序編號。
還表明該文檔中遵循的習慣。當展示指令時,指令之后是定義生成值類型和命名的注釋。注釋以italic文本呈現。
高層結構
模塊結構
LLVM程序是由"模塊"組成的,每個模塊是輸入程序的一個轉換單元。每個模塊包括函數、全局變量和符號表入口。模塊可由LLVM鏈接器組合在一起,這將合并函數(全局變量)的定義,解析之后的聲明,并合并符號表入口。這是"hello world"模塊的示例:
; 聲明string常量作為全局常量
@.LC0 = internal constant [13 x i8] c"hello world\0A\00" ; [13 x i8]*
; puts函數的外部聲明
declare i32 @puts(i8 *) ; i32(i8 *)*
; main函數的定義
define i32 @main() { ; i32()*
; Convert [13 x i8]* to i8 *...
%cast210 = getelementptr [13 x i8]* @.LC0, i64 0, i64 0 ; i8 *
; Call puts function to write out the string to stdout...
call i32 @puts(i8 * %cast210) ; i32
ret i32 0
}
本例由全局變量".LC0",puts函數的外部聲明和main的函數定義組成。
通常地,模塊由一系列的全局值組成,其中函數和全局變量都是全局值。全局值以指向內存位置的指針(因此,指向字符數組的指針,指向函數的指針)表示,并具有下列鏈接類型的一種。
鏈接類型
所有全局變量和函數具有下列鏈接類型之一:
private 具有私有鏈接的全局值只能被當前模塊的對象直接訪問。特別地,具有私有全局值模塊的鏈接代碼可能在必要時導致對私有部分重命名,以避免沖突。由于該符號對模塊是私有的,所有引用會被更新。這并不會在目標文件的任何符號表有所顯示。
linker_private 類似私有鏈接,但該符號通過匯編器并由鏈接器在求值后移除。
internal 類似私有鏈接,但其值在目標文件中顯示為局部符號(ELF文件格式的STB_LOCAL)。這對應C語言的static關鍵字。
available_externally 具有"有效外部"鏈接的全局值不會引入到LLVM模塊對應的目標文件。它們的存在是為進行內聯和其它優化時告知位于模塊外的全局值定義具體位置。具有有效外部鏈接的全局值可隨意丟棄,否則與linkconce_odr鏈接類型一樣。該鏈接類型只能定義不能聲明。
linkonce 具有"僅鏈接一次"鏈接的全局值在鏈接發生時,與同名的其它全局值合并。這典型地用實現內聯函數,模板或在每個使用該鏈接的轉換單元中生成的其它代碼。未引用的linkonce全局值可丟棄。
weak "弱"鏈接與linkonce鏈接具有相同的合并語法,除未引用的弱鏈接全局值不丟棄外。這用于C源碼中聲明為"weak"的全局值。
common "通用"鏈與"弱"鏈接極其相似,但它們用于C的定義,例如全局作用域的"int X;"。"通用"鏈接的符號與weak符號一樣,以同樣的方式合,而且若未引用,該鏈接可能也不會刪除。通用符號可沒有顯式段,但必須零初始化,且可能未標記為'constant'。函數和別名可能沒有通用鏈接。
appending "附加"鏈接只用于指向數組類型的全局變量。兩個具有appending鏈接的全局變量鏈接在一起時,兩個全局數組附加在一起。這就是在鏈接.o文件時LLVM系統鏈接器將相同名稱的“段”附加在一起的類型安全和等性。
extern_weak 該鏈接的語法含義遵循ELF目標文件模型:該符號直到進行鏈接是才是weak類型,如果未鏈接,該符號會用null替代未定義的引用。
linkonce_odr
weak_odr 一些語言允許不同的全局值進行合并,例如不同語義的兩個函數。其它語言,例如C++,保證只有等價的全局值才能合并("一次定義規則"-"ODR")。這樣的語言可使用linkonce_odr和weak_odr鏈接類型以表明只有等價的全局值才能合并。這些鏈接類型在其它方面與非odr版本具有相同屬性。
externally 若以上標識符都沒有使用,全局值則是外部可見的,這意味全局值參與鏈接并可用于解析外部符號引用。
以下兩種鏈接類型僅針對微軟Windows平臺。它們為支持從DLL(Dynamic Link Library)引入符號(或導出符號至DLL)而設計。
dllimport "dllimport"鏈接導致編譯器通過由DLL導出符號建立的指向指針的全局指針,引用函數或變量。
dllexport "dllexport"鏈接導致編譯器提供DLL中指向指針的全局指針,以使該全局值可利用dllimport屬性引用。在微軟Windows目標平臺,指針通過聯合_imp_和函數或變量名的形式命名。
例如,".LC0"變量定義為interal,如果其它模塊定義了".LC0"變量且該變量被鏈接,兩者之一將被重命名以避免沖突。既然"main"和"puts"是external類型(比如缺少鏈接聲明),它們可在當前模塊外被訪問。
除了"外部可見",dllimport或extern_weak外,函數聲明的其它鏈接類型都是不合法的。
別名僅有external, internal, weak或weak_odr鏈接。
調用約定
LLVM的函數,call和invoke都可為指定的調用設定可選的調用約定。動態調用者/被調用者配對的調用約定必須匹配,否則程序行為未定義。LLVM支持下列調用約定,且可能在將來有所增加。
"ccc" - C調用約定
該調用約定(如果沒有指定其它調用約定,默認使用該調用約定)匹配目標平臺的C調用約定。它支持可變參數(varargs)函數調用,并容忍函數原型聲明和聲明實現的一些誤配(就像正常C行為)。
"fastcc" - 快速調用約定
該調用約定試圖使調用盡可能快(例如通過寄存器傳值)。它允許目標平臺使用任何小技巧以產生該平臺的快速代碼,而無需與外部指定的ABI(Application Binary Interface)保持一致。其實現支持任意的推后調用優化(tail call optimization)。它不支持可變參數,且要求所有被調用者的原型與函數定義的原型完全匹配。
"coldcc" - 生冷的調用約定
該調用約定基于調用不常執行這一假定,試圖使調用者的代碼盡可能有效。因此,這些調用常保護所有寄存器,以致調用不會破壞調用者邊界的任何數據區域。它不支持可變參數,且要求所有被調用者的原型與函數定義的原型完全匹配。
"cc <n>" - 編號約定
所有調用約定可通過編號指定,可用于目標平臺相關的調用約定。目標平臺相關的調用約定從64開始編號。
更多的調用約定可按需增加/定義,以支持Pascal約定或所有其它熟知的與目標平臺無關的約定。
可見性樣式
所有全局變量和函數具有下列可見性樣式之一:
"default" - 默認樣式
在使用ELF目標文件格式的目標平臺上,默認可見性意味著聲明對其它模塊可見,對于共享庫意味著聲明實體可被覆蓋(override)。對于Darwin平臺,默認可見性意味著聲明可對其它模塊可見。默認可見性對應語言的"外部鏈接"。
"hidden" - 隱藏樣式
如果具有隱藏樣式對象的兩個聲明位于同一共享目標文件時,它們引用同一目標。隱藏可見性通常表明,符號不會位于動態符號表,因此沒有其它模塊(可執行或共享庫)能直接引用該符號。
"protected" - 保護樣式
ELF的保護可見性表明,符號將會位于動態符號表,但在定義模塊內的引用會綁定為局部符號。也就是說,該符號不會被其它模塊覆蓋。
命名類型
LLVM IR允許對確定類型指定別名。這可使IR更可讀和更緊湊(condense)(特別是涉及遞歸類型時)。下面是命名規范的示例:
%mytype = type { %mytype*, i32 }
除"void"外可以給定所有類型命名。類型名字別名可用于任何識別到"%mytype"語法的地方。
記住,針對指定結構的類型進行類型名字別名,而且你還能對同一類型指定不同名字。當輸出.ll文件時,這常導致混淆的行為。既然LLVM IR使用結構的類型,名字就不是類型的一部分。當輸出LLVM IR時,打印器(printer)將挑選一個名字以返回(render)特有形狀的所有類型。這意味著,如果對以同一LLVM類型結尾的不同源類型進行編碼,輸出器(dumper)有時輸出"錯誤"或意外的類型。這是重要的設計觀點,且不會改變。
全局變量
全局變量定義了在編譯期而非運行期的內存分配區域。它可被任意值初始化,可位于顯式段(section),還具有可選的顯式指定對齊。變量可定義為"thread_local",這意味它不會被線程共享(每個線程將具有它的獨立拷貝)。變量也可定義為全局"常量",這表明它的內容永不會被修改(允許更好的優化,可將全局數據位于執行代碼的只讀段等)。記住,需要在運行時初始化的變量不能標記為"常量",是因為它有存儲空間。
LLVM顯式允許全局變量的聲明標記為常量,甚至它的最終定義并非如此。這可用于使程序優化效果好一些,但需要語言定義以保證基于'constantness'的優化對并不包含定義的轉換單元有效。
如SSA賦值,全局變量定義程序中具有所有基本塊作用域(例如指針指向的)的指針值。它總定義至其"內容"類型的指針,是因為后者描述了內存區域和可通過指針訪問的所有LLVM內存目標。
全局變量可聲明位于目標平臺相關的計數地址空間。對于支持以上操作的目標平臺,地址空間可能會影響優化器的執行和/或可訪問該變量的目標平臺指令。默認的地址空間是零。地址空間的限制必須處于其它屬性之前。
LLVM允許指定針對全局值的顯式段。如果目標平臺支持的話,LLVM將發射全局值至指定段。
還可對全局值指定顯式的對齊。如果沒設置,或是對齊設為零,全局值的對齊將設置為目標平臺方便的值。如果指定了顯式對齊,全局值強制具有至少指定的對齊值。所有對齊必須為2的冪。
例如,下列定義了位于計數地址空間的全局值,具有初始器,段和對齊:
@G = addrspace(5) constant float 1.0, section "foo", align 4
函數
LLVM函數定義由"define"關鍵字、可選的鏈接類型、可選的可見樣式、可選的調用慣例、返回類型、可選的返回類型參數屬性、函數名稱、(可能為空的)參數列表(每一個參數都有可選的參數屬性)、可選的函數屬性、可選的段、可選的對齊、可選的垃圾回收器、打開的花括號、基本塊列表和封閉的花括號組成。
LLVM函數聲明由"declare"關鍵字、可選的鏈接類型、可選的可見樣式、可選的調用慣例、返回類型、可選的返回類型參數屬性、函數名稱、可能為空的參數列表、可選的對齊和可選的垃圾回收器組成。
函數定義包含基本塊列表以形成該函數的CFG(控制流圖)。每個基本塊可選地以標號(給出基本塊的符號表入口)起始,包含指令列表,并以終止指令(例如跳轉或函數返回)結束。
函數的第一個基本塊有兩點特殊性:進入函數時直接執行,且不允許具有前趨基本塊(例如,函數入口的塊不能有任何分支)。因為該塊沒有前趨,它同樣沒有任何PHI節點。
LLVM允許為函數指定顯式段。如果目標平臺支持的話,LLVM將函數置于指定的段。
對函數可指定顯式的對齊。如果未設置,或是對齊設置為零,函數的對齊會設置為目標平臺方便的值。如果指定顯式的對齊,函數強制至少具有指定的對齊。所有對齊必須是2的冪。
語法:
define [linkage] [visibility]
[cconv] [ret attrs]
<ResultType> @<FunctionName> ([argument list])
[fn Attrs] [section "name"] [align N]
[gc] { ... }
別名
別名作為被別名值(the aliasee value)(可為函數、全局變量、另一個別名或全局值的位轉換)的"第二名稱"。別名可具有可選的鏈接類型和可選的可見樣式。
語法:
@<Name> = alias [Linkage] [Visibility] <AliaseeTy> @<Aliasee>
參數屬性
返回類型和函數類型的每個參數可具有與其相關的參數屬性集合。參數屬性用于表達函數結果或參數的額外信息。參數屬性被認為是函數而非函數類型的一部分,因此具有不同參數屬性的函數可具有同一函數類型。
參數屬性是遵循指定類型的簡單關鍵字。如果需要多個函數屬性,則以空格分隔。例如:
declare i32 @printf(i8* noalias nocapture, ...)
declare i32 @atoi(i8 zeroext)
declare signext i8 @returns_signed_char()
記住,函數結果(nounwind,只讀)的任何屬性直接位于參數列表后。
當前,只定義了下列參數屬性:
zeroext
這表明,代碼生成器中調用者或被調用者分別將參數或返回值零擴展為32位值。
signext
這表明,代碼生成器中調用者或被調用者分別將參數或返回值符號擴展為32位值。
inreg
這表明,在函數調用或返回生成代碼期間(通常,將變量置于與內存相對的寄存器中,但是目標平臺使用這種方式以區別寄存器的兩種不同類型),參數或返回值被視為處于特殊的目標平臺無關方式。該屬性的使用是目標平臺相關的。
byval
這表明,指針參數應以傳值方式傳至函數。該屬性指出,調用者和被調用者間將導致被指向單元(pointee)的隱形復制,因此被調用者不能修改調用者的值(原文有筆誤:so the callee is unable to modify the value in the callee,應為in the caller)。該屬性只對LLVM指針參數有效。這通常用于傳值方式傳遞結構和數組,但對指向標量的指針同樣有效。上述復制被認為是屬于調用者而非被調用者(例如,只讀函數不應寫具有byval屬性的參數)。這不是返回值的有效屬性。byval屬性也支持由align屬性指定的對齊。這對代碼生成器有與目標平臺相關的影響,這通常表明生成的棧槽(stack slot)需要對齊。
sret
這表明,指針參數指定了源程序的函數返回值結構的地址。該指針必須由調用者保證有效:加載和存儲該結構假定被調用者不會進入陷阱(trap)。這只可能應用與第一個參數。這不是返回值的有效屬性。
noalias
這表明,該指針不能別名一切全局值或其它任何參數。調用者有責任保證符合上述情況。對于函數返回值,noalias還表明指針不能別名為其它任何對調用者可見的指針。更多的細節請參見別名分析中NoAlias回復的討論。
nocapture
這表明,被調用者不產生比被調用者本身生命周期長的指針的任何復制。這不是返回值的有效屬性。
nest
這表明,指針參數可通過使用trampoline intrinsics引用。這不是返回值的有效屬性。
垃圾回收器名稱
每個函數可指定垃圾回收器名稱,該名稱是簡單的字符串:
define void @f() gc "name" { ... }
編譯器聲明name的支持值。指定將導致編譯器改變其輸出的回收器,是為了支持有名的垃圾回收算法。
函數屬性
函數屬性為增加函數的附加信息而設置。函數屬性被視為函數而非函數類型的一部分,因此具有不同參數屬性的函數可具有同一函數類型。
函數屬性是遵循指定類型的簡單關鍵字。如果需要多個函數屬性,則以空格分隔。例如:
define void @f() noinline { ... }
define void @f() alwaysinline { ... }
define void @f() alwaysinline optsize { ... }
define void @f() optsize
alwaysinline
該屬性表明,內聯器試圖盡可能將函數內聯至調用者,而忽略該調用者的任何活躍的內聯大小閾值。
noinline
該屬性表明,內聯器任何情況都不會將函數內聯。該屬性可能不能與alwaysinline屬性一起使用。
optsize
該屬性建議,優化pass和代碼生成器pass在保持函數少代碼量還是執行特定優化以減少代碼大小間做決定。
noreturn