JIT Compiler编译器及指令集
JIT Compiler編譯器及指令集
LLVM一些編程語法語義特性
High Level Structure
Module Structure
LLVM 程序由Module’s組成,每個 's 是輸入程序的一個翻譯單元。每個模塊由函數,全局變量和符號表條目組成。模塊可與 LLVM 鏈接器組合在一起,后者合并函數(全局變量)定義,解析前向聲明,合并符號表條目。這是“hello world”模塊的示例:
; Declare the string constant as a global constant.
@.str=private unnamed_addr constant [13 x i8] c"hello world\0A\00"
; External declaration of the puts function
declare i32 @puts(i8* nocapture) nounwind
; Definition of main function
define i32 @main() { ; i32()*
; Convert [13 x i8]* to i8*…
%cast210=getelementptr [13 x i8],[13 x i8]* @.str,i64 0,i64 0
; Call puts function to write out the string to stdout.
call i32 @puts(i8* %cast210)
ret i32 0
}
; Named metadata
!0=!{i32 42,null,!“string”}
!foo=!{!0}
這個示例是由全局變量命名為“ .str”,在一個外部聲明“ puts”函數,函數定義為“main”,命名為元數據“ foo”。
通常,模塊由全局值列表組成(函數和全局變量都是全局值)。全局值由指向內存位置的指針(指向字符數組的指針和指向函數的指針)表示。
命名元數據
命名元數據是元數據的集合。元數據節點(不是元數據字符串)是命名元數據的唯一有效算子。
- 命名元數據表示為帶有元數據前綴的字符串。元數據名稱的規則與標識符相同,不允許引用名稱。"\xx"類型轉義仍然有效,允許任何字符成為名稱的一部分。
句法:
; Some unnamed metadata nodes,which are referenced by the named metadata.
!0=!{!“zero”}
!1=!{!“one”}
!2=!{!“two”}
; A named metadata.
!name=!{!0,!1,!2}
參數屬性
函數類型的返回類型和每個參數,可能有一組與之關聯的參數屬性。參數屬性用于傳達有關函數結果,或參數的附加信息。參數屬性被認為是函數的一部分,不是函數類型,具有不同參數屬性的函數,可具有相同的函數類型。
參數屬性是遵循指定類型的簡單關鍵字。如果需要多個參數屬性,空格分隔。例如:
declare i32 @printf(i8* noalias nocapture,…)
declare i32 @atoi(i8 zeroext)
declare signext i8 @returns_signed_char()
前綴數據
前綴數據是與函數關聯的數據,代碼生成器將在函數入口點前面立即發出數據。允許前端將特定語言的運行時元數據與特定函數相關聯,通過函數指針可用,允許調用函數指針。
要訪問給定函數的數據,程序可將函數指針位轉換為指向常量類型的指針,取消引用索引 -1。IR 符號剛好越過前綴數據的末尾。例如,一個用單i32個,注釋的函數為例。
define void @f() prefix i32 123 { … }
前綴數據可引用
%0=bitcast void* () @f to i32*
%a=getelementptr inbounds i32,i32* %0,i32 -1
%b=load i32,i32* %a
define void @f() prologue i8 144 { … }
序言數據
prologue屬性允許在函數體前面,插入任意代碼(編碼為字節)。用于啟用功能熱修補和檢測。
為了維護普通函數調用的語義,序言數據必須具有特定的格式。具體,必須以一個字節序列開始,這些字節序列解碼為一系列機器指令,對模塊的目標有效,這些指令將控制轉移到序言數據后的點,不執行任何可見的操作。允許內聯和通道推理函數定義的語義,無需推理序言數據。使得序言數據的格式高度依賴于目標。
x86 架構的有效序言數據的一個簡單示例,對指令進行編碼:i8 144nop
define void @f() prologue i8 144 { … }
通??赏ㄟ^編碼,跳過元數據的相對分支指令,形成序言數據,如x86_64架構的有效序言數據示例,前兩個字節編碼:jmp .+10
%0=type <{ i8,i8,i8* }>
define void @f() prologue %0 <{ i8 235,i8 8,i8* @md}> { … }
; Target-independent attributes:
attributes #0={ alwaysinline alignstack=4 }
; Target-dependent attributes:
attributes #1={ “no-sse” }
; Function @f has attributes: alwaysinline,alignstack=4,and “no-sse”.
define void @f() #0 #1 { … }
屬性組
屬性組是由 IR 內的對象引用的屬性組。對于保持.ll文件可讀很重要,許多函數將使用相同的屬性集。在.ll文件對應于單個.c文件的退化情況下,單個屬性組將捕獲用于構建文件的重要命令行標志。
屬性組是模塊級對象。要使用屬性組,對象引用屬性組的 ID(如#37)。一個對象可能引用多個屬性組。在這種情況下,不同組的屬性將合并。
下面是一個函數的屬性組示例,函數始終內聯,堆棧對齊為 4,不應使用 SSE 指令:
define void @f() noinline { … }
define void @f() alwaysinline { … }
define void @f() alwaysinline optsize { … }
define void @f() optsize { … }
去優化算子包
去優化算子包的特點是"deopt" 算子包標簽。這些算子包代表了所連接的調用站點的替代“安全”延續,可由合適的運行時使用,取消優化指定調用站點的編譯幀。最多可有一個"deopt"算子束,附加到調用站點。去優化的確切細節超出了語言參考的范圍,通常涉及將編譯幀重寫為一組解釋幀。
從編譯器的角度,去優化算子包,所連接的調用點,至少連接到readonly。通讀了所有指針類型的算子(即使沒有以其它方式轉義)和整個可見堆。去優化算子包不捕獲算子,除非在去優化期間,在這種情況下控制,不會返回到編譯幀。
內聯器知道如何通過具有去優化算子包的調用進行內聯。就像通過普通調用站點內聯涉及組合正常和異常延續一樣,通過具有去優化算子包的調用站點,內聯需要適當地組合“安全”去優化延續。內聯程序通過將父級的去優化延續,添加到內聯正文中的每個去優化延續前面,做到這一點。例如內聯@f到@g在下面的示例中
define void @f() {
call void @x() ;; no deopt state
call void @y() [“deopt”(i32 10)]
call void @y() [“deopt”(i32 10),“unknown”(i8* null)]
ret void
}
define void @g() {
call void @f() [“deopt”(i32 20)]
ret void
}
導致
define void @g() {
call void @x() ;; still no deopt state
call void @y() [“deopt”(i32 20,i32 10)]
call void @y() [“deopt”(i32 20,i32 10),“unknown”(i8* null)]
ret void
}
在每個需要 a 的規范上:,指定 對齊方式是可選的。如果省略,前面的省略,等于。
在為給定目標構建數據布局時,LLVM從一組默認規范開始,然后(可能)被datalayout關鍵字中的規范覆蓋。此列表中給出了默認規格:
? E - big endian
? p:64:64:64 - 64-bit pointers with 64-bit alignment.
? p[n]:64:64:64 - Other address spaces are assumed to be the same as the default address space.
? S0 - natural stack alignment is unspecified
? i1:8:8 - i1 is 8-bit (byte) aligned
? i8:8:8 - i8 is 8-bit (byte) aligned
? i16:16:16 - i16 is 16-bit aligned
? i32:32:32 - i32 is 32-bit aligned
? i64:32:64 - i64 has ABI alignment of 32-bits but preferred alignment of 64-bits
? f16:16:16 - half is 16-bit aligned
? f32:32:32 - float is 32-bit aligned
? f64:64:64 - double is 64-bit aligned
? f128:128:128 - quad is 128-bit aligned
? v64:64:64 - 64-bit vector is 64-bit aligned
? v128:128:128 - 128-bit vector is 128-bit aligned
? a:0:64 - aggregates are 64-bit aligned
用列order指令
用列指令,對每個用列的內存順序進行編碼,允許重重排序。是分配給引用值的使用的索引的逗號分隔列表。引用值的用列,實時按這些索引排序。
用列指令,可能出現在函數作用域,或全局作用域。不是指令,對 IR 的語義沒有影響。當處于函數作用域時,必須出現在最終基本塊的終止符后。
如果基本塊的地址,通過blockaddress()表達式獲取,uselistorder_bb從函數范圍外重新排序用列。
Syntax:
uselistorder ,{ }
uselistorder_bb @function,%block { }
Examples:
define void @foo(i32 %arg1,i32 %arg2) {
entry:
; … instructions …
bb:
; … instructions …
; At function scope.
uselistorder i32 %arg1,{ 1,0,2 }
uselistorder label %bb,{ 1,0 }
}
; At global scope.
uselistorder i32* @global,{ 1,2,0 }
uselistorder i32 7,{ 1,0 }
uselistorder i32 (i32) @bar,{ 1,0 }
uselistorder_bb @foo,%bb,{ 5,1,3,2,0,4 }
函數類型
概述:
函數類型可被認為是一個函數簽名。由一個返回類型和一個形參類型列表組成。函數類型的返回類型是 void 類型,或第一類類型——標簽和元數據類型除外。
句法:
()
… ’ ’ 是逗號分隔的類型說明符列表。參數列表可包括一個 type…,表明函數采用可變數量的參數??勺儏岛瘮凳褂每勺儏堤幚韮炔亢瘮翟L問參數。’’ 是除標簽和元數據外的任何類型。
示例
i32 (i32) 函數取一個i32,返回一個i32
float (i16,i32 *) * 指針,接受一個函數i16和一個指針i32,返回float。
i32 (i8*,…) 可變參數函數,至少一個指針到i8(在C炭),返回一個整數。這是printfLLVM 中的簽名。
{i32,i32} (i32) 一個函數采用i32,返回一個包含兩個值的結構i32
整數將占用的位數由N 值指定。
例示例
i1 一位整數。
i32 一個 32 位整數。
i1942652 一個超過 100 萬位的非常大的整數。
浮浮點類型
類型 描述
half 16 位浮點值
float 32 位浮點值
double 64 位浮點值
fp128 128 位浮點值(112 位尾數)
x86_fp80 80 位浮點值 (X87)
ppc_fp128 128 位浮點值(兩個 64 位)
指指針類型
概述:
指針類型用于指定內存位置。指針通常用于引用內存中的對象。
指針類型可能有一個可選的地址空間屬性,用于定義指向對象所在的編號地址空間。默認地址空間是數字零。非零地址空間的語義是特定目標的。
LLVM 不允許指向 void ( void*) 的指針,不允許指向標簽 ( label*) 的指針。使用i8* 代替。
句法:
<類型> *
示例
[4 x i32]* 指針到陣列的4個i32值。
i32 (i32*) * 一個指向函數的指針,接受一個i32*,返回一個i32。
i32 addrspace(5)* 指向i32位于地址空間#5值的指針。
矢向量類型
概述:
向量類型是表示元素向量的簡單派生類型。當使用單個指令 (SIMD),并行操作多個原始數據時,將使用向量類型。向量類型需要大小(元素數量)和底層原始數據類型。向量類型被認為是一類的。
< <# elements> x >
元素個數是一個大于0的常量整數值;elementtype 可是任何整數,浮點數或指針類型。不允許大小為零的向量。
示例:
<4 x i32> 4 個 32 位整數值的向量。
<8 x float> 8 個 32 位浮點值的向量。
<2 x i64> 2 個 64 位整數值的向量。
<4 x i64*> 4 個指向 64 位整數值的指針的向量。
數數組類型
概述:
數組類型是一種非常簡單的派生類型,在內存中按順序排列元素。數組類型需要大小(元素數)和基礎數據類型。
句法:
[<# elements> x ]
元素的數量是一個常量整數值;elementtype可是具有大小的任何類型。
示例:
[40 x i32] 40 個 32 位整數值的數組。
[41 x i32] 41 個 32 位整數值的數組。
[4 x i8] 4 個 8 位整數值的數組。
多維數組的一些示例:
[3 x [4 x i32]] 32 位整數值的 3x4 數組。
[12 x [10 x float]] 12x10 單精度浮點值數組。
[2 x [3 x [4 x i16]]] 2x3x4 16 位整數值數組。
對超出靜態類型所隱含的數組末尾的索引,沒有限制(在某些情況下對超出已分配對象范圍的索引有限制)。在具有零長度數組類型的 LLVM 中,實現一維“可變大小數組”尋址。例如,LLVM 中“pascal 樣式數組”的實現,可使用類型“{i32,[0 x float]}”。
結結構類型
概述:
結構類型用于表示內存中數據成員的集合。結構的元素可是具有大小的任何類型。
通過使用“ ”指令,獲取指向字段的指針,可使用“ load”和“ store”訪問內存中的結構getelementptr。使用“ extractvalue”和“ insertvalue”指令,訪問寄存器中的結構。
結構可選擇是“打包”結構,這表明結構的對齊是一個字節,元素之間沒有填充。在非壓縮結構中,字段類型之間的填充,按照模塊中 DataLayout 字符串的定義插入,這是匹配底層代碼生成器所期望的。
結構可是“文字”或“已知”。文字結構是與其它類型(如{i32,i32}*)內聯定義的,標識的類型總是在頂層定義一個名稱。文字類型因內容而唯一,永遠不會是遞歸或不透明的,無法編寫。已知類型可是遞歸的,可是不透明的,永遠不會是唯一的。
句法:
% T1 = type { <類型 列表> } ; 已知的 正常 結構體 類型
% T2 = type < { <類型 列表> } > ; 已知的 壓縮 結構 類型
示例:
{ i32,i32,i32 } 三個i32值的三元組
{ float,i32 (i32) * } 第一個元素是 float,第二個元素是指向一個函數的指針,函數接受一個i32,返回一個i32。
<{ i8,i32 }> 已知大小為 5 個字節的打包結構。
不透明結構類型
概述:
不透明結構類型,用于表示沒有指定主體的命名結構類型。對應于前向聲明結構的 C 概念。
句法:
%X = type opaque
%52 = type opaque
示例:
opaque 不透明類型。
全全局變量和函數地址
全局變量和函數的地址總是隱式有效(鏈接時)常量。這些常量在使用全局標識符時顯式引用,始終具有指針類型。例如,以下是一個合法的 LLVM 文件:
@X=global i32 17
@Y=global i32 42
@Z=global [2 x i32*] [i32* @X,i32* @Y]
未定義值
字符串 ’ undef’ 可用于任何需要常量的地方,指示值的用戶,可能會收到未指定的位模式。未定義的值可是任何類型(除了“ label”或“ void”),可在任何允許常量的地方使用。
未定義值很有用,向編譯器表明,無論使用什么值,程序都是定義良好的。這給了編譯器更多的優化自由。以下是一些有效(在偽 IR 中)轉換的示例:
%A=add %X,undef
%B=sub %X,undef
%C=xor %X,undef
Safe:
%A=undef
%B=undef
%C=undef
這是安全的,所有輸出位都受 undef 位影響。任何輸出位都可有01,具體取決于輸入位。
%A=or %X,undef
%B=and %X,undef
Safe:
%A=-1
%B=0
Safe:
%A=%X ;; By choosing undef as 0
%B=%X ;; By choosing undef as -1
Unsafe:
%A=undef
%B=undef
這些邏輯運算的位,不總是受輸入影響。例如,如果%X有一個零位,無論 ’ undef’的相應位是什么,對于位’and ’ 操作的輸出,始終為零。優化或假設“ and”的結果 為“ undef”是不安全的。但是,可安全地假設 ’ undef’ 的所有位都為 0,將 ’ and’優化為 0。同樣,可安全地假設 ’ undef’ 算子的所有位設置為’or’,從而將’or’設置為-1。
%A=select undef,%X,%Y
%B=select undef,42,%Y
%C=select %X,%Y,undef
Safe:
%A=%X (or %Y)
%B=42 (or %Y)
%C=%Y
Unsafe:
%A=undef
%B=undef
%C=undef這組示例表明,未定義的“ select”(和條件分支)條件,可采用任何一種方式,必須來自兩個算子之一。在%A示例中,如果%X和%Y是兩個已知具有明顯的低位,%A就必須有一個清除低位。但是,在%C示例中,優化器可假設“ undef”算子,可與 %Y相同,從而select可消除整個“ ”。
%A=xor undef,undef
%B=undef
%C=xor %B,%B
%D=undef
%E=icmp slt %D,4
%F=icmp gte %D,4
Safe:
%A=undef
%B=undef
%C=undef
%D=undef
%E=undef
%F=undef
此示例指出兩個“ undef”算子不一定相同??赡軙屓藗兏械襟@訝(匹配 C 語義),假設“ X^X”始終為零,即使 X未定義。出于多種原因,情況并非如此,但簡短的回答是undef“變量”,可在 “有效范圍”內,任意更改值。變量實際上沒有有效范圍。相反,值是在需要時,從恰好在鄰近的任意寄存器邏輯讀取的,值不一定隨時間保持一致。事實上,%A與 %C需要具有相同的語義,或核心LLVM概念,不會執行“替換所有用途”。
%A=fdiv undef,%X
%B=fdiv %X,undef
Safe:
%A=undef
b: unreachable
這些示例顯示了未定義值和未定義行為間的關鍵區別。undef允許未定義的值(如“ ”)具有任意位模式。%A 操作可常量折疊為“ undef”,“ undef”可能是 SNaN,fdiv(當前)未在 SNaN 上定義。
在第二個示例中,可做出更激進的假設:undef允許是任意值,可假設可能為零。除以零具有未定義的行為,可假設操作根本不執行。刪除除法后的所有代碼。未定義的操作“不可能發生”,優化器可假設發生在死代碼中。
a: store undef -> %X
b: store %X -> undef
Safe:
a:
b: unreachable
危危險值
危險值類似于undef 值,代表了這樣一個事實,即不能引起輔助的指令,或常量表達式,仍然檢測到導致未定義行為的條件。
目前沒有辦法在 IR 中表示危險值;當通過操作,如產生只存在附加與nsw標記。
危險值與undef值具有相同的行為,附加效果是任何依賴危險值的指令,都具有未定義的行為。
示例:
entry:
%poison=sub nuw i32 0,1 ; Results in a poison value.
%still_poison=and i32 %poison,0 ; 0,but also poison.
%poison_yet_again=getelementptr i32,i32* @h,i32 %still_poison
store i32 0,i32* %poison_yet_again ; memory at @h[0] is poisoned
store i32 %poison,i32* @g ; Poison value stored to memory.
%poison2=load i32,i32* @g ; Poison value loaded back from memory.
store volatile i32 %poison,i32* @g ; External observation; undefined behavior.
%narrowaddr=bitcast i32* @g to i16*
%wideaddr=bitcast i32* @g to i64*
%poison3=load i16,i16* %narrowaddr ; Returns a poison value.
%poison4=load i64,i64* %wideaddr ; Returns a poison value.
%cmp=icmp slt i32 %poison,0 ; Returns a poison value.
br i1 %cmp,label %true,label %end ; Branch to either destination.
true:
store volatile i32 0,i32* @g ; This is control-dependent on %cmp,so
; it has undefined behavior.
br label %end
end:
%p=phi i32 [0,%entry],[1,%true]
; Both edges into this PHI are
; control-dependent on %cmp,so this
; always results in a poison value.
store volatile i32 0,i32* @g ; This would depend on the store in %true
; if %cmp is true,or the store in %entry
; otherwise,so this is undefined behavior.
br i1 %cmp,label %second_true,label %second_end
; The same branch again,but this time the
; true block doesn’t have side effects.
second_true:
; No side effects!
ret void
second_end:
store volatile i32 0,i32* @g ; This time,the instruction always depends
; on the store in %end. Also,it is
; control-equivalent to %end,so this is
; well-defined (ignoring earlier undefined
; behavior in this example).
參考鏈接:
https://releases.llvm.org/6.0.0/docs/LangRef.html#module-structure
總結
以上是生活随笔為你收集整理的JIT Compiler编译器及指令集的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在Cuda上部署量化模型
- 下一篇: synopsys PCIE IP协议解析