JIT Compiler编译器及指令集
JIT Compiler編譯器及指令集
LLVM一些編程語法語義特性
High Level Structure
Module Structure
LLVM 程序由Module’s組成,每個 's 是輸入程序的一個翻譯單元。每個模塊由函數(shù),全局變量和符號表?xiàng)l目組成。模塊可與 LLVM 鏈接器組合在一起,后者合并函數(shù)(全局變量)定義,解析前向聲明,合并符號表?xiàng)l目。這是“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”函數(shù),函數(shù)定義為“main”,命名為元數(shù)據(jù)“ foo”。
通常,模塊由全局值列表組成(函數(shù)和全局變量都是全局值)。全局值由指向內(nèi)存位置的指針(指向字符數(shù)組的指針和指向函數(shù)的指針)表示。
命名元數(shù)據(jù)
命名元數(shù)據(jù)是元數(shù)據(jù)的集合。元數(shù)據(jù)節(jié)點(diǎn)(不是元數(shù)據(jù)字符串)是命名元數(shù)據(jù)的唯一有效算子。
- 命名元數(shù)據(jù)表示為帶有元數(shù)據(jù)前綴的字符串。元數(shù)據(jù)名稱的規(guī)則與標(biāo)識符相同,不允許引用名稱。"\xx"類型轉(zhuǎn)義仍然有效,允許任何字符成為名稱的一部分。
句法:
; Some unnamed metadata nodes,which are referenced by the named metadata.
!0=!{!“zero”}
!1=!{!“one”}
!2=!{!“two”}
; A named metadata.
!name=!{!0,!1,!2}
參數(shù)屬性
函數(shù)類型的返回類型和每個參數(shù),可能有一組與之關(guān)聯(lián)的參數(shù)屬性。參數(shù)屬性用于傳達(dá)有關(guān)函數(shù)結(jié)果,或參數(shù)的附加信息。參數(shù)屬性被認(rèn)為是函數(shù)的一部分,不是函數(shù)類型,具有不同參數(shù)屬性的函數(shù),可具有相同的函數(shù)類型。
參數(shù)屬性是遵循指定類型的簡單關(guān)鍵字。如果需要多個參數(shù)屬性,空格分隔。例如:
declare i32 @printf(i8* noalias nocapture,…)
declare i32 @atoi(i8 zeroext)
declare signext i8 @returns_signed_char()
前綴數(shù)據(jù)
前綴數(shù)據(jù)是與函數(shù)關(guān)聯(lián)的數(shù)據(jù),代碼生成器將在函數(shù)入口點(diǎn)前面立即發(fā)出數(shù)據(jù)。允許前端將特定語言的運(yùn)行時元數(shù)據(jù)與特定函數(shù)相關(guān)聯(lián),通過函數(shù)指針可用,允許調(diào)用函數(shù)指針。
要訪問給定函數(shù)的數(shù)據(jù),程序可將函數(shù)指針位轉(zhuǎn)換為指向常量類型的指針,取消引用索引 -1。IR 符號剛好越過前綴數(shù)據(jù)的末尾。例如,一個用單i32個,注釋的函數(shù)為例。
define void @f() prefix i32 123 { … }
前綴數(shù)據(jù)可引用
%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 { … }
序言數(shù)據(jù)
prologue屬性允許在函數(shù)體前面,插入任意代碼(編碼為字節(jié))。用于啟用功能熱修補(bǔ)和檢測。
為了維護(hù)普通函數(shù)調(diào)用的語義,序言數(shù)據(jù)必須具有特定的格式。具體,必須以一個字節(jié)序列開始,這些字節(jié)序列解碼為一系列機(jī)器指令,對模塊的目標(biāo)有效,這些指令將控制轉(zhuǎn)移到序言數(shù)據(jù)后的點(diǎn),不執(zhí)行任何可見的操作。允許內(nèi)聯(lián)和通道推理函數(shù)定義的語義,無需推理序言數(shù)據(jù)。使得序言數(shù)據(jù)的格式高度依賴于目標(biāo)。
x86 架構(gòu)的有效序言數(shù)據(jù)的一個簡單示例,對指令進(jìn)行編碼:i8 144nop
define void @f() prologue i8 144 { … }
通??赏ㄟ^編碼,跳過元數(shù)據(jù)的相對分支指令,形成序言數(shù)據(jù),如x86_64架構(gòu)的有效序言數(shù)據(jù)示例,前兩個字節(jié)編碼: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 內(nèi)的對象引用的屬性組。對于保持.ll文件可讀很重要,許多函數(shù)將使用相同的屬性集。在.ll文件對應(yīng)于單個.c文件的退化情況下,單個屬性組將捕獲用于構(gòu)建文件的重要命令行標(biāo)志。
屬性組是模塊級對象。要使用屬性組,對象引用屬性組的 ID(如#37)。一個對象可能引用多個屬性組。在這種情況下,不同組的屬性將合并。
下面是一個函數(shù)的屬性組示例,函數(shù)始終內(nèi)聯(lián),堆棧對齊為 4,不應(yīng)使用 SSE 指令:
define void @f() noinline { … }
define void @f() alwaysinline { … }
define void @f() alwaysinline optsize { … }
define void @f() optsize { … }
去優(yōu)化算子包
去優(yōu)化算子包的特點(diǎn)是"deopt" 算子包標(biāo)簽。這些算子包代表了所連接的調(diào)用站點(diǎn)的替代“安全”延續(xù),可由合適的運(yùn)行時使用,取消優(yōu)化指定調(diào)用站點(diǎn)的編譯幀。最多可有一個"deopt"算子束,附加到調(diào)用站點(diǎn)。去優(yōu)化的確切細(xì)節(jié)超出了語言參考的范圍,通常涉及將編譯幀重寫為一組解釋幀。
從編譯器的角度,去優(yōu)化算子包,所連接的調(diào)用點(diǎn),至少連接到readonly。通讀了所有指針類型的算子(即使沒有以其它方式轉(zhuǎn)義)和整個可見堆。去優(yōu)化算子包不捕獲算子,除非在去優(yōu)化期間,在這種情況下控制,不會返回到編譯幀。
內(nèi)聯(lián)器知道如何通過具有去優(yōu)化算子包的調(diào)用進(jìn)行內(nèi)聯(lián)。就像通過普通調(diào)用站點(diǎn)內(nèi)聯(lián)涉及組合正常和異常延續(xù)一樣,通過具有去優(yōu)化算子包的調(diào)用站點(diǎn),內(nèi)聯(lián)需要適當(dāng)?shù)亟M合“安全”去優(yōu)化延續(xù)。內(nèi)聯(lián)程序通過將父級的去優(yōu)化延續(xù),添加到內(nèi)聯(lián)正文中的每個去優(yōu)化延續(xù)前面,做到這一點(diǎn)。例如內(nèi)聯(lián)@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
}
導(dǎo)致
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 的規(guī)范上:,指定 對齊方式是可選的。如果省略,前面的省略,等于。
在為給定目標(biāo)構(gòu)建數(shù)據(jù)布局時,LLVM從一組默認(rèn)規(guī)范開始,然后(可能)被datalayout關(guān)鍵字中的規(guī)范覆蓋。此列表中給出了默認(rèn)規(guī)格:
? 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指令
用列指令,對每個用列的內(nèi)存順序進(jìn)行編碼,允許重重排序。是分配給引用值的使用的索引的逗號分隔列表。引用值的用列,實(shí)時按這些索引排序。
用列指令,可能出現(xiàn)在函數(shù)作用域,或全局作用域。不是指令,對 IR 的語義沒有影響。當(dāng)處于函數(shù)作用域時,必須出現(xiàn)在最終基本塊的終止符后。
如果基本塊的地址,通過blockaddress()表達(dá)式獲取,uselistorder_bb從函數(shù)范圍外重新排序用列。
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 }
函數(shù)類型
概述:
函數(shù)類型可被認(rèn)為是一個函數(shù)簽名。由一個返回類型和一個形參類型列表組成。函數(shù)類型的返回類型是 void 類型,或第一類類型——標(biāo)簽和元數(shù)據(jù)類型除外。
句法:
()
… ’ ’ 是逗號分隔的類型說明符列表。參數(shù)列表可包括一個 type…,表明函數(shù)采用可變數(shù)量的參數(shù)??勺儏?shù)函數(shù)使用可變參數(shù)處理內(nèi)部函數(shù)訪問參數(shù)?!?是除標(biāo)簽和元數(shù)據(jù)外的任何類型。
示例
i32 (i32) 函數(shù)取一個i32,返回一個i32
float (i16,i32 *) * 指針,接受一個函數(shù)i16和一個指針i32,返回float。
i32 (i8*,…) 可變參數(shù)函數(shù),至少一個指針到i8(在C炭),返回一個整數(shù)。這是printfLLVM 中的簽名。
{i32,i32} (i32) 一個函數(shù)采用i32,返回一個包含兩個值的結(jié)構(gòu)i32
整數(shù)將占用的位數(shù)由N 值指定。
例示例
i1 一位整數(shù)。
i32 一個 32 位整數(shù)。
i1942652 一個超過 100 萬位的非常大的整數(shù)。
浮浮點(diǎn)類型
類型 描述
half 16 位浮點(diǎn)值
float 32 位浮點(diǎn)值
double 64 位浮點(diǎn)值
fp128 128 位浮點(diǎn)值(112 位尾數(shù))
x86_fp80 80 位浮點(diǎn)值 (X87)
ppc_fp128 128 位浮點(diǎn)值(兩個 64 位)
指指針類型
概述:
指針類型用于指定內(nèi)存位置。指針通常用于引用內(nèi)存中的對象。
指針類型可能有一個可選的地址空間屬性,用于定義指向?qū)ο笏诘木幪柕刂房臻g。默認(rèn)地址空間是數(shù)字零。非零地址空間的語義是特定目標(biāo)的。
LLVM 不允許指向 void ( void*) 的指針,不允許指向標(biāo)簽 ( label*) 的指針。使用i8* 代替。
句法:
<類型> *
示例
[4 x i32]* 指針到陣列的4個i32值。
i32 (i32*) * 一個指向函數(shù)的指針,接受一個i32*,返回一個i32。
i32 addrspace(5)* 指向i32位于地址空間#5值的指針。
矢向量類型
概述:
向量類型是表示元素向量的簡單派生類型。當(dāng)使用單個指令 (SIMD),并行操作多個原始數(shù)據(jù)時,將使用向量類型。向量類型需要大小(元素數(shù)量)和底層原始數(shù)據(jù)類型。向量類型被認(rèn)為是一類的。
< <# elements> x >
元素個數(shù)是一個大于0的常量整數(shù)值;elementtype 可是任何整數(shù),浮點(diǎn)數(shù)或指針類型。不允許大小為零的向量。
示例:
<4 x i32> 4 個 32 位整數(shù)值的向量。
<8 x float> 8 個 32 位浮點(diǎn)值的向量。
<2 x i64> 2 個 64 位整數(shù)值的向量。
<4 x i64*> 4 個指向 64 位整數(shù)值的指針的向量。
數(shù)數(shù)組類型
概述:
數(shù)組類型是一種非常簡單的派生類型,在內(nèi)存中按順序排列元素。數(shù)組類型需要大小(元素數(shù))和基礎(chǔ)數(shù)據(jù)類型。
句法:
[<# elements> x ]
元素的數(shù)量是一個常量整數(shù)值;elementtype可是具有大小的任何類型。
示例:
[40 x i32] 40 個 32 位整數(shù)值的數(shù)組。
[41 x i32] 41 個 32 位整數(shù)值的數(shù)組。
[4 x i8] 4 個 8 位整數(shù)值的數(shù)組。
多維數(shù)組的一些示例:
[3 x [4 x i32]] 32 位整數(shù)值的 3x4 數(shù)組。
[12 x [10 x float]] 12x10 單精度浮點(diǎn)值數(shù)組。
[2 x [3 x [4 x i16]]] 2x3x4 16 位整數(shù)值數(shù)組。
對超出靜態(tài)類型所隱含的數(shù)組末尾的索引,沒有限制(在某些情況下對超出已分配對象范圍的索引有限制)。在具有零長度數(shù)組類型的 LLVM 中,實(shí)現(xiàn)一維“可變大小數(shù)組”尋址。例如,LLVM 中“pascal 樣式數(shù)組”的實(shí)現(xiàn),可使用類型“{i32,[0 x float]}”。
結(jié)結(jié)構(gòu)類型
概述:
結(jié)構(gòu)類型用于表示內(nèi)存中數(shù)據(jù)成員的集合。結(jié)構(gòu)的元素可是具有大小的任何類型。
通過使用“ ”指令,獲取指向字段的指針,可使用“ load”和“ store”訪問內(nèi)存中的結(jié)構(gòu)getelementptr。使用“ extractvalue”和“ insertvalue”指令,訪問寄存器中的結(jié)構(gòu)。
結(jié)構(gòu)可選擇是“打包”結(jié)構(gòu),這表明結(jié)構(gòu)的對齊是一個字節(jié),元素之間沒有填充。在非壓縮結(jié)構(gòu)中,字段類型之間的填充,按照模塊中 DataLayout 字符串的定義插入,這是匹配底層代碼生成器所期望的。
結(jié)構(gòu)可是“文字”或“已知”。文字結(jié)構(gòu)是與其它類型(如{i32,i32}*)內(nèi)聯(lián)定義的,標(biāo)識的類型總是在頂層定義一個名稱。文字類型因內(nèi)容而唯一,永遠(yuǎn)不會是遞歸或不透明的,無法編寫。已知類型可是遞歸的,可是不透明的,永遠(yuǎn)不會是唯一的。
句法:
% T1 = type { <類型 列表> } ; 已知的 正常 結(jié)構(gòu)體 類型
% T2 = type < { <類型 列表> } > ; 已知的 壓縮 結(jié)構(gòu) 類型
示例:
{ i32,i32,i32 } 三個i32值的三元組
{ float,i32 (i32) * } 第一個元素是 float,第二個元素是指向一個函數(shù)的指針,函數(shù)接受一個i32,返回一個i32。
<{ i8,i32 }> 已知大小為 5 個字節(jié)的打包結(jié)構(gòu)。
不透明結(jié)構(gòu)類型
概述:
不透明結(jié)構(gòu)類型,用于表示沒有指定主體的命名結(jié)構(gòu)類型。對應(yīng)于前向聲明結(jié)構(gòu)的 C 概念。
句法:
%X = type opaque
%52 = type opaque
示例:
opaque 不透明類型。
全全局變量和函數(shù)地址
全局變量和函數(shù)的地址總是隱式有效(鏈接時)常量。這些常量在使用全局標(biāo)識符時顯式引用,始終具有指針類型。例如,以下是一個合法的 LLVM 文件:
@X=global i32 17
@Y=global i32 42
@Z=global [2 x i32*] [i32* @X,i32* @Y]
未定義值
字符串 ’ undef’ 可用于任何需要常量的地方,指示值的用戶,可能會收到未指定的位模式。未定義的值可是任何類型(除了“ label”或“ void”),可在任何允許常量的地方使用。
未定義值很有用,向編譯器表明,無論使用什么值,程序都是定義良好的。這給了編譯器更多的優(yōu)化自由。以下是一些有效(在偽 IR 中)轉(zhuǎn)換的示例:
%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
這些邏輯運(yùn)算的位,不總是受輸入影響。例如,如果%X有一個零位,無論 ’ undef’的相應(yīng)位是什么,對于位’and ’ 操作的輸出,始終為零。優(yōu)化或假設(shè)“ and”的結(jié)果 為“ undef”是不安全的。但是,可安全地假設(shè) ’ undef’ 的所有位都為 0,將 ’ and’優(yōu)化為 0。同樣,可安全地假設(shè) ’ undef’ 算子的所有位設(shè)置為’or’,從而將’or’設(shè)置為-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示例中,優(yōu)化器可假設(shè)“ 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 語義),假設(shè)“ X^X”始終為零,即使 X未定義。出于多種原因,情況并非如此,但簡短的回答是undef“變量”,可在 “有效范圍”內(nèi),任意更改值。變量實(shí)際上沒有有效范圍。相反,值是在需要時,從恰好在鄰近的任意寄存器邏輯讀取的,值不一定隨時間保持一致。事實(shí)上,%A與 %C需要具有相同的語義,或核心LLVM概念,不會執(zhí)行“替換所有用途”。
%A=fdiv undef,%X
%B=fdiv %X,undef
Safe:
%A=undef
b: unreachable
這些示例顯示了未定義值和未定義行為間的關(guān)鍵區(qū)別。undef允許未定義的值(如“ ”)具有任意位模式。%A 操作可常量折疊為“ undef”,“ undef”可能是 SNaN,fdiv(當(dāng)前)未在 SNaN 上定義。
在第二個示例中,可做出更激進(jìn)的假設(shè):undef允許是任意值,可假設(shè)可能為零。除以零具有未定義的行為,可假設(shè)操作根本不執(zhí)行。刪除除法后的所有代碼。未定義的操作“不可能發(fā)生”,優(yōu)化器可假設(shè)發(fā)生在死代碼中。
a: store undef -> %X
b: store %X -> undef
Safe:
a:
b: unreachable
危危險值
危險值類似于undef 值,代表了這樣一個事實(shí),即不能引起輔助的指令,或常量表達(dá)式,仍然檢測到導(dǎo)致未定義行為的條件。
目前沒有辦法在 IR 中表示危險值;當(dāng)通過操作,如產(chǎn)生只存在附加與nsw標(biāo)記。
危險值與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
總結(jié)
以上是生活随笔為你收集整理的JIT Compiler编译器及指令集的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在Cuda上部署量化模型
- 下一篇: synopsys PCIE IP协议解析