機器 IR (MIR) 格式參考手冊¶
警告
這是一份正在進行中的文件。
簡介¶
本文件是機器 IR (MIR) 序列化格式的參考手冊。MIR 是一種人類可讀的序列化格式,用於表示 LLVM 的 機器特定的中介表示。
MIR 序列化格式的設計目的是用於測試 LLVM 中的程式碼生成過程。
概述¶
MIR 序列化格式使用 YAML 容器。YAML 是一種標準的資料序列化語言,完整的 YAML 語言規範可以在 yaml.org 閱讀。
MIR 檔案被分割成一系列的 YAML 文件。第一個文件可以包含一個可選的嵌入式 LLVM IR 模組,其餘的文件包含序列化的機器函式。
MIR 測試指南¶
您可以透過兩種不同的方式使用 MIR 格式進行測試
您可以使用 llc 中的
-run-pass
選項編寫 MIR 測試,以呼叫單一程式碼生成過程。您可以將 llc 的
-stop-after
選項與現有的或新的 LLVM 組合語言測試一起使用,並檢查特定程式碼生成過程的 MIR 輸出。
測試個別程式碼生成過程¶
llc 中的 -run-pass
選項允許您建立 MIR 測試,以僅呼叫單一程式碼生成過程。使用此選項時,llc 將解析輸入的 MIR 檔案,執行指定的程式碼生成過程,並輸出產生的 MIR 程式碼。
您可以使用 llc 中的 -stop-after
或 -stop-before
選項來產生測試用的輸入 MIR 檔案。例如,如果您想為暫存器配置後偽指令展開過程編寫測試,您可以在 -stop-after
選項中指定機器複製傳播過程,因為它正好在我們要測試的過程之前運行
llc -stop-after=machine-cp bug-trigger.ll -o test.mir
如果同一個過程運行多次,可以在名稱後面加上逗號和運行索引。
llc -stop-after=dead-mi-elimination,1 bug-trigger.ll -o test.mir
產生輸入 MIR 檔案後,您必須添加一個使用 -run-pass
選項的運行行。為了在 X86-64 上測試暫存器配置後偽指令展開過程,可以使用如下所示的運行行
# RUN: llc -o - %s -mtriple=x86_64-- -run-pass=postrapseudos | FileCheck %s
MIR 檔案是目標相關的,因此它們必須放在目標特定的測試目錄中 (lib/CodeGen/TARGETNAME
)。它們還需要在運行行或嵌入式 LLVM IR 模組中指定目標三元組或目標架構。
簡化 MIR 檔案¶
-stop-after
/-stop-before
輸出的 MIR 代碼非常冗長;簡化後,測試更易於理解和面向未來
將
-simplify-mir
選項與 llc 一起使用。機器函數屬性通常具有預設值,或者測試在使用預設值時也能正常工作。典型的候選者有:alignment:、exposesReturnsTwice、legalized、regBankSelected、selected。如果函數中沒有特殊的框架使用,則通常不需要整個 frameInfo 部分。另一方面,對於一些關心區塊 livein 列表的過程來說,tracksRegLiveness 通常是必要的。
(全局)liveins: 列表通常只對早期的指令選擇過程有用,並且在測試後面的過程時可以刪除。另一方面,如果 tracksRegLiveness 為真,則需要每個區塊的 liveins:。
如果測試不依賴於區塊 successors: 列表中的分支概率數據,則可以將其刪除。例如:successors: %bb.1(0x40000000), %bb.2(0x40000000) 可以替換為 successors: %bb.1, %bb.2。
MIR 代碼包含整個 IR 模組。這是必要的,因為 MIR 中沒有全局變量、對外部函數的引用、函數屬性、元數據、調試信息的等效項。相反,一些 MIR 數據引用了 IR 構造。如果測試不依賴於它們,您通常可以刪除它們。
別名分析是在 IR 值上執行的。這些值由 MIR 中的內存操作數引用。例如::: (load 8 from %ir.foobar, !alias.scope !9)。如果測試不依賴於(良好的)別名分析,則可以刪除這些引用::: (load 8)
MIR 區塊可以引用 IR 區塊以進行調試打印、性能分析信息或調試位置。例如:MIR 中的 bb.42.myblock 引用了 IR 區塊 myblock。通常可以刪除 .myblock 引用,而直接使用 bb.42。
如果沒有記憶體運算元或區塊參照到 IR,則可以將 IR 函式替換為無參數的虛擬函式,例如 define @func() { ret void }。
如果 MIR 檔案的整個 IR 區段只包含虛擬函式(見上文),則可以將其刪除。在這種情況下,.mir 載入器會自動建立 IR 函式。
限制¶
目前 MIR 格式在可序列化的狀態方面有一些限制
目前尚未序列化目標特定
MachineFunctionInfo
子類別中的目標特定狀態。目前尚未序列化目標特定
MachineConstantPoolValue
子類別(在 ARM 和 SystemZ 後端中)。MCSymbol
機器運算元不支援暫時或局部符號。MachineModuleInfo
中的許多狀態尚未序列化 - 目前僅序列化來自 MMI 的 CFI 指令和變數除錯資訊。
這些限制對您可以使用 MIR 格式測試的內容施加了限制。目前,想要測試某些依賴於暫時或局部 MCSymbol
運算元狀態或 MMI 中的例外處理狀態的行為的測試無法使用 MIR 格式。此外,測試某些依賴於目標特定 MachineFunctionInfo
或 MachineConstantPoolValue
子類別狀態的行為的測試目前也無法使用 MIR 格式。
高階結構¶
嵌入式模組¶
當第一個 YAML 文件包含 YAML 區塊字面字串 時,MIR 解析器會將此字串視為表示嵌入式 LLVM IR 模組的 LLVM 組合語言字串。以下是一個包含 LLVM 模組的 YAML 文件範例
define i32 @inc(ptr %x) {
entry:
%0 = load i32, ptr %x
%1 = add i32 %0, 1
store i32 %1, ptr %x
ret i32 %1
}
機器函式¶
其餘的 YAML 文件包含機器函式。以下是一個此類 YAML 文件的範例
---
name: inc
tracksRegLiveness: true
liveins:
- { reg: '$rdi' }
callSites:
- { bb: 0, offset: 3, fwdArgRegs:
- { arg: 0, reg: '$edi' } }
body: |
bb.0.entry:
liveins: $rdi
$eax = MOV32rm $rdi, 1, _, 0, _
$eax = INC32r killed $eax, implicit-def dead $eflags
MOV32mr killed $rdi, 1, _, 0, _, $eax
CALL64pcrel32 @foo <regmask...>
RETQ $eax
...
上述文件由表示機器函式中各種屬性和資料結構的屬性組成。
屬性 name
是必需的,並且其值應該與此機器函式所基於的函式名稱相同。
屬性 body
是一個 YAML 區塊字面字串。其值表示函式的機器基本區塊及其機器指令。
屬性 callSites
是呼叫站點資訊的表示,用於追蹤呼叫指令和用於傳輸呼叫參數的暫存器。
機器指令格式參考¶
機器基本區塊及其指令使用自訂的、人類可讀的序列化語言表示。此語言用於對應於機器函式主體的 YAML 區塊字面字串 中。
使用此語言的來源字串包含機器基本區塊的清單,這些區塊將在下節中說明。
機器基本區塊¶
機器基本區塊是在單一區塊定義來源構造中定義的,其中包含區塊的 ID。以下範例定義了兩個 ID 為零和一的區塊
bb.0:
<instructions>
bb.1:
<instructions>
機器基本區塊也可以有名稱。應在區塊定義中的 ID 之後指定名稱
bb.0.entry: ; This block's name is "entry"
<instructions>
區塊的名稱應與此機器區塊所依據的 IR 區塊的名稱相同。
區塊參考¶
機器基本區塊由其 ID 號碼識別。個別區塊使用以下語法進行參考
%bb.<id>
範例
%bb.0
也支援以下語法,但前一種語法是區塊參考的首選語法
%bb.<id>[.<name>]
範例
%bb.1.then
後繼者¶
機器基本區塊的後繼者必須在任何指令之前指定
bb.0.entry:
successors: %bb.1.then, %bb.2.else
<instructions>
bb.1.then:
<instructions>
bb.2.else:
<instructions>
分支權重可以在後繼區塊之後的括號中指定。以下範例定義了一個區塊,它具有兩個後繼者,分支權重分別為 32 和 16
bb.0.entry:
successors: %bb.1.then(32), %bb.2.else(16)
活躍暫存器¶
機器基本區塊的活躍暫存器必須在任何指令之前指定
bb.0.entry:
liveins: $edi, $esi
活躍暫存器和後繼者的清單可以是空的。該語言還允許多個活躍暫存器和後繼者清單 - 它們由解析器組合成一個清單。
其他屬性¶
屬性 IsAddressTaken
、IsLandingPad
、IsInlineAsmBrIndirectTarget
和 Alignment
可以在區塊定義之後的括號中指定
bb.0.entry (address-taken):
<instructions>
bb.2.else (align 4):
<instructions>
bb.3(landing-pad, align 4):
<instructions>
bb.4 (inlineasm-br-indirect-target):
<instructions>
Alignment
以位元組為單位指定,並且必須是 2 的冪。
機器指令¶
機器指令由名稱、機器運算元、指令旗標 和機器記憶體運算元組成。
指令的名稱通常在運算元之前指定。以下範例顯示了具有單一機器運算元的 X86 RETQ
指令的實例
RETQ $eax
但是,如果機器指令具有一個或多個明確定義的暫存器運算元,則必須在它們之後指定指令的名稱。以下範例顯示了具有三個已定義暫存器運算元的 AArch64 LDPXpost
指令的實例
$sp, $fp, $lr = LDPXpost $sp, 2
指令名稱使用目標的 *InstrInfo.td
檔案中的確切定義進行序列化,並且它們區分大小寫。這意味著像 TSTri
和 tSTRi
這樣的相似指令名稱代表不同的機器指令。
指令旗標¶
旗標 frame-setup
或 frame-destroy
可以在指令名稱之前指定
$fp = frame-setup ADDXri $sp, 0, 0
$x21, $x20 = frame-destroy LDPXi $sp
捆綁指令¶
捆綁指令的語法如下
BUNDLE implicit-def $r0, implicit-def $r1, implicit $r2 {
$r0 = SOME_OP $r2
$r1 = ANOTHER_OP internal $r0
}
第一個指令通常是捆綁標頭。 {
和 }
之間的指令與第一個指令捆綁在一起。
暫存器¶
暫存器是機器指令序列化語言中的關鍵原語之一。它們主要用於 暫存器機器運算元 中,但它們也可以用於許多其他地方,例如 基本塊的活躍輸入列表。
物理暫存器由其名稱和「$」前綴符號標識。它們使用以下語法
$<name>
以下範例顯示了三個 X86 物理暫存器
$eax
$r15
$eflags
虛擬暫存器由其 ID 號和「%」符號標識。它們使用以下語法
%<id>
範例
%0
空暫存器使用底線(「_
」)表示。它們也可以使用「$noreg
」命名暫存器表示,儘管前一種語法是首選。
機器運算元¶
機器運算元共有 18 種不同類型,所有這些類型都可以序列化。
立即運算元¶
立即機器運算元是無類型的 64 位元有符號整數。以下範例顯示了具有立即機器運算元 -42
的 X86 MOV32ri
指令的實例
$eax = MOV32ri -42
當機器指令具有以下操作碼之一時,立即運算元也用於表示子暫存器索引
EXTRACT_SUBREG
INSERT_SUBREG
REG_SEQUENCE
SUBREG_TO_REG
如果是這種情況,則機器運算元將根據目標列印。
例如
在 AArch64RegisterInfo.td 中
def sub_32 : SubRegIndex<32>;
如果第三個運算元是值為 15
(目標相關值)的立即數,則根據指令的操作碼和運算元的索引,該運算元將列印為 %subreg.sub_32
%1:gpr64 = SUBREG_TO_REG 0, %0, %subreg.sub_32
對於 > 64 位元的整數,我們使用特殊的機器運算元 MO_CImmediate
,它使用 APInt
(LLVM 的任意精度整數)將立即數存儲在 ConstantInt
中。
暫存器運算元¶
暫存器 原語用於表示暫存器機器運算元。暫存器運算元還可以具有可選的 暫存器標誌、子暫存器索引 以及對綁定暫存器運算元的引用。暫存器運算元的完整語法如下所示
[<flags>] <register> [ :<subregister-idx-name> ] [ (tied-def <tied-op>) ]
此範例顯示了具有 5 個具有不同暫存器標誌的暫存器運算元的 X86 XOR32rr
指令的實例
dead $eax = XOR32rr undef $eax, undef $eax, implicit-def dead $eflags, implicit-def $al
暫存器標誌¶
下表顯示了所有可能的暫存器標誌以及相應的內部 llvm::RegState
表示形式
標誌 |
內部值 |
含義 |
---|---|---|
|
|
未發射暫存器(例如,進位或暫存結果)。 |
|
|
|
|
|
暫存器定義。 |
|
|
未使用的定義。 |
|
|
暫存器的最後一次使用。 |
|
|
暫存器的值無關緊要。 |
|
|
暫存器讀取在同一指令或組合內定義的值。 |
|
|
暫存器定義發生在使用之前。 |
|
|
暫存器「使用」是為了除錯目的。 |
|
|
可以重新命名的暫存器。 |
子暫存器索引¶
暫存器機器運算元可以使用子暫存器索引來引用暫存器的一部分。以下範例顯示了 COPY
虛擬指令的實例,該指令使用 X86 sub_8bit
子暫存器索引將 32 位元虛擬暫存器 0 的低 8 位元複製到 8 位元虛擬暫存器 1
%1 = COPY %0:sub_8bit
子暫存器索引的名稱是目標特定的,通常在目標的 *RegisterInfo.td
檔案中定義。
常數池索引¶
常數池索引 (CPI) 運算元使用其在函數的 MachineConstantPool
中的索引和偏移量來列印。
例如,索引為 1 且偏移量為 8 的 CPI
%1:gr64 = MOV64ri %const.1 + 8
對於索引為 0 且偏移量為 -12 的 CPI
%1:gr64 = MOV64ri %const.0 - 12
常數池條目綁定到 LLVM IR Constant
或目標特定的 MachineConstantPoolValue
。序列化所有函數的常數時,使用以下格式
constants:
- id: <index>
value: <value>
alignment: <alignment>
isTargetSpecific: <target-specific>
- 其中
<index>
是 32 位元無符號整數;<value>
是 LLVM IR 常數;<alignment>
是以位元組為單位的 32 位元無符號整數,且必須為 2 的冪;<target-specific>
為 true 或 false。
範例
constants:
- id: 0
value: 'double 3.250000e+00'
alignment: 8
- id: 1
value: 'g-(LPC0+8)'
alignment: 4
isTargetSpecific: true
全域值運算元¶
全域值機器運算元引用 嵌入式 LLVM IR 模組 中的全域值。以下範例顯示了 X86 MOV64rm
指令的實例,該指令具有一個名為 G
的全域值運算元
$rax = MOV64rm $rip, 1, _, @G, _
命名的全域值使用帶有「@」前綴的識別碼表示。如果識別碼不符合正規表達式 [-a-zA-Z$._][-a-zA-Z$._0-9]*,則必須將此識別碼用引號括起來。
未命名的全域值使用帶有「@」前綴的無符號數值表示,如下列範例所示:@0
、@989
。
目標相依索引運算元¶
目標索引運算元是目標特定索引和偏移量。目標特定索引使用目標特定名稱和正負偏移量來列印。
例如,amdgpu-constdata-start
在 AMDGPU 後端中與索引 0
相關聯。因此,如果我們有一個目標索引運算元,其索引為 0 且偏移量為 8
$sgpr2 = S_ADD_U32 _, target-index(amdgpu-constdata-start) + 8, implicit-def _, implicit-def _
跳轉表索引運算元¶
索引為 0 的跳轉表索引運算元列印如下
tBR_JTr killed $r0, %jump-table.0
機器跳轉表條目包含一個 MachineBasicBlocks
列表。序列化所有函數的跳轉表條目時,使用以下格式
jumpTable:
kind: <kind>
entries:
- id: <index>
blocks: [ <bbreference>, <bbreference>, ... ]
其中 <kind>
描述跳轉表的表示和發出方式(純地址、重定位、PIC 等),每個 <index>
是一個 32 位元無符號整數,而 blocks
包含一個 機器基本區塊參考 列表。
範例
jumpTable:
kind: inline
entries:
- id: 0
blocks: [ '%bb.3', '%bb.9', '%bb.4.d3' ]
- id: 1
blocks: [ '%bb.7', '%bb.7', '%bb.4.d3', '%bb.5' ]
外部符號運算元¶
外部符號運算元使用帶有 &
前綴的識別碼表示。識別碼用「“」括起來,如果其中包含任何特殊的不可列印字元,則會進行轉義。
範例
CALL64pcrel32 &__stack_chk_fail, csr_64, implicit $rsp, implicit-def $rsp
MCSymbol 運算元¶
MCSymbol 運算元持有一個指向 MCSymbol
的指標。有關此運算元在 MIR 中的限制,請參閱 限制。
語法如下
EH_LABEL <mcsymbol Ltmp1>
偵錯指令參考運算元¶
偵錯指令參考運算元是一對索引,分別指向指令和該指令中的運算元;請參閱 指令參考位置。
以下範例使用對指令 1、運算元 0 的參考
DBG_INSTR_REF !123, !DIExpression(DW_OP_LLVM_arg, 0), dbg-instr-ref(1, 0), debug-location !456
CFIIndex 運算元¶
CFI 索引運算元持有一個指向每個函數側邊表 MachineFunction::getFrameInstructions()
的索引,該表參考 MachineFunction
中的所有框架指令。一個 CFI_INSTRUCTION
可能看起來包含多個運算元,但它實際上只包含 CFI 索引這一個運算元。其他運算元由 MCCFIInstruction
物件追蹤。
語法如下
CFI_INSTRUCTION offset $w30, -16
稍後可能會在 MC 層發出為
.cfi_offset w30, -16
IntrinsicID 運算元¶
Intrinsic ID 運算元包含一個泛型 Intrinsic ID 或一個目標特定的 ID。
returnaddress
intrinsic 的語法為
$x0 = COPY intrinsic(@llvm.returnaddress)
謂詞運算元¶
謂詞運算元包含一個來自 CmpInst::Predicate
的 IR 謂詞,例如 ICMP_EQ
等。
對於 int eq 謂詞 ICMP_EQ
,語法為
%2:gpr(s32) = G_ICMP intpred(eq), %0, %1
除錯資訊結構¶
MIR 檔案中的大多數除錯資訊都可以在嵌入式模組的元資料中找到。在機器函式中,各種結構會參考該元資料,以描述原始碼位置和變數位置。
原始碼位置¶
每個 MIR 指令都可以在所有運算元和符號之後、但在記憶體運算元之前,選擇性地附加對 DILocation
元資料節點的參考
$rbp = MOV64rr $rdi, debug-location !12
原始碼位置附加與 LLVM-IR 中的 !dbg
元資料附加同義。缺少原始碼位置附加將由機器指令中的空 DebugLoc
物件表示。
固定變數位置¶
有幾種指定變數位置的方法。最簡單的方法是描述永久位於堆疊上的變數。在機器函式的堆疊或 fixedStack 屬性中,會提供變數、範圍和任何限定位置修飾詞
- { id: 0, name: offset.addr, offset: -24, size: 8, alignment: 8, stack-id: default,
4 debug-info-variable: '!1', debug-info-expression: '!DIExpression()',
debug-info-location: '!2' }
其中
debug-info-variable
識別 DILocalVariable 元資料節點,debug-info-expression
將限定詞新增至變數位置,debug-info-location
識別 DILocation 元資料節點。
這些元資料屬性對應於 #dbg_declare
IR 除錯記錄的運算元,請參閱原始碼層級除錯文件。
變動變數位置¶
並非始終位於堆疊上或位置會變更的變數,可以使用 DBG_VALUE
中繼機器指令來指定。它與 #dbg_value
IR 記錄同義,並寫入
DBG_VALUE $rax, $noreg, !123, !DIExpression(), debug-location !456
運算元分別對應於
識別機器位置,例如暫存器、立即數或框架索引,
如果要將額外的間接層級新增至第一個運算元,則為 $noreg 或立即值零,
識別
DILocalVariable
元資料節點,指定限定變數位置的運算式,可以是內嵌運算式,也可以是元資料節點參考,
雖然來源位置識別了變數作用域的 DILocation
。 但第二個運算元(IsIndirect
)已被棄用且將被刪除。 所有其他變數位置的限定符都應通過表達式中繼資料進行。
指令參考位置¶
此實驗性功能旨在將變數「值」的規範與變數採用該值的程式點分開。 變數值的更改以與 DBG_VALUE
中繼指令相同的方式發生,但使用 DBG_INSTR_REF
。 變數值由指令編號和運算元編號對來識別。 請參考以下範例
$rbp = MOV64ri 0, debug-instr-number 1, debug-location !12
DBG_INSTR_REF !123, !DIExpression(DW_OP_LLVM_arg, 0), dbg-instr-ref(1, 0), debug-location !456
指令編號直接附加到機器指令,並帶有一個可選的 debug-instr-number
附加項,位於可選的 debug-location
附加項之前。 上述程式碼中 $rbp
中定義的值將由 <1, 0>
對來識別。
上述 DBG_INSTR_REF
的第三個運算元記錄了指令和運算元編號 <1, 0>
,用於識別由 MOV64ri
定義的值。 DBG_INSTR_REF
的前兩個運算元與 DBG_VALUE_LIST
相同,並且 DBG_INSTR_REF
的位置記錄了變數採用指定值的位置。
有關如何使用這些構造的更多資訊,請參閱 偵錯資訊的指令參考。 相關文件 使用 LLVM 進行原始碼級別偵錯 和 如何更新偵錯資訊:LLVM Pass 作者指南 也可能有所幫助。
註解¶
機器運算元可以有 C/C++ 風格的註解,這些註解是括在
/*
和*/
之間的標註,用於提高例如立即運算元的可讀性。在以下範例中,ARM 指令 EOR 和 BCC 以及立即運算元14
和0
已使用其條件碼 (CC) 定義進行註解,即always
和eq
條件碼由於這些註解是註解,因此 MI 解析器會忽略它們。可以透過覆寫 InstrInfo 的掛鉤
createMIROperandComment()
來新增或自訂註解。