Machine IR (MIR) 格式參考手冊¶
警告
這是一個進行中的工作。
簡介¶
本文檔是 Machine IR (MIR) 序列化格式的參考手冊。MIR 是一種人類可讀的序列化格式,用於表示 LLVM 的機器特定中間表示法。
MIR 序列化格式旨在用於測試 LLVM 中的程式碼產生 Pass。
概觀¶
MIR 序列化格式使用 YAML 容器。YAML 是一種標準的資料序列化語言,完整的 YAML 語言規範可在yaml.org 查閱。
一個 MIR 檔案被拆分為一系列的 YAML 文件。第一個文件可以包含一個可選的嵌入式 LLVM IR 模組,其餘的文件則包含序列化的機器函式。
MIR 測試指南¶
您可以使用 MIR 格式以兩種不同的方式進行測試
您可以使用 llc 中的
-run-pass
選項編寫調用單個程式碼產生 Pass 的 MIR 測試。您可以將 llc 的
-stop-after
選項與現有的或新的 LLVM 組語測試一起使用,並檢查特定程式碼產生 Pass 的 MIR 輸出。
測試個別程式碼產生 Pass¶
llc 中的 -run-pass
選項允許您建立僅調用單個程式碼產生 Pass 的 MIR 測試。當使用此選項時,llc 將解析輸入 MIR 檔案,執行指定的程式碼產生 Pass,並輸出產生的 MIR 程式碼。
您可以使用 llc 中的 -stop-after
或 -stop-before
選項來產生測試的輸入 MIR 檔案。例如,如果您想為暫存器分配後偽指令擴展 Pass 編寫測試,您可以在 -stop-after
選項中指定機器複製傳播 Pass,因為它在我們要測試的 Pass 之前執行
llc -stop-after=machine-cp bug-trigger.ll -o test.mir
如果同一個 Pass 執行多次,則可以在名稱後用逗號包含執行索引。
llc -stop-after=dead-mi-elimination,1 bug-trigger.ll -o test.mir
產生輸入 MIR 檔案後,您必須新增一個執行行,使用 -run-pass
選項。為了在 X86-64 上測試暫存器分配後偽指令擴展 Pass,可以使用如下所示的執行行
# RUN: llc -o - %s -mtriple=x86_64-- -run-pass=postrapseudos | FileCheck %s
MIR 檔案是目標相依的,因此它們必須放置在目標特定的測試目錄中 (lib/CodeGen/TARGETNAME
)。它們也需要在執行行或嵌入式 LLVM IR 模組中指定目標三元組或目標架構。
簡化 MIR 檔案¶
從 -stop-after
/-stop-before
出來的 MIR 程式碼非常冗長;簡化後,測試更容易理解且更具未來性
使用 llc 的
-simplify-mir
選項。機器函式屬性通常具有預設值,或者測試使用預設值也能正常運作。典型的候選者包括:alignment:、exposesReturnsTwice、legalized、regBankSelected、selected。frameInfo 整個區段通常是不必要的,如果函式中沒有特殊的 frame 使用。tracksRegLiveness 另一方面,對於某些關心區塊 livein 列表的 Pass 來說通常是必要的。
(全域)liveins: 列表通常僅對早期的指令選擇 Pass 有意義,並且在測試後續的 Pass 時可以移除。另一方面,如果 tracksRegLiveness 為 true,則每個區塊的 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 組語語言字串,表示嵌入式 LLVM IR 模組。以下是一個包含 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)
Live In 暫存器¶
機器基本區塊的 live in 暫存器必須在任何指令之前指定
bb.0.entry:
liveins: $edi, $esi
live in 暫存器和後繼區塊的列表可以為空。該語言也允許多個 live in 暫存器和後繼區塊列表 - 它們由解析器組合成一個列表。
雜項屬性¶
屬性 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
}
第一個指令通常是捆綁標頭。{
和 }
之間的指令與第一個指令捆綁在一起。
暫存器¶
暫存器是機器指令序列化語言中的關鍵基本元素之一。它們主要用於暫存器機器運算元,但它們也可以用於許多其他地方,例如基本區塊的 live in 列表。
實體暫存器由其名稱和 '$' 前綴符號識別。它們使用以下語法
$<name>
下面的範例顯示了三個 X86 實體暫存器
$eax
$r15
$eflags
虛擬暫存器由其 ID 號碼和 '%' 符號識別。它們使用以下語法
%<id>
範例
%0
空暫存器使用底線 ('_
') 表示。它們也可以使用名為 '$noreg
' 的暫存器表示,儘管前一種語法是首選。
機器運算元¶
共有十八種不同的機器運算元,它們都可以被序列化。
立即運算元¶
立即機器運算元是無類型、64 位元有號整數。下面的範例顯示了一個 X86 MOV32ri
指令的實例,它具有一個立即機器運算元 -42
$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>) ]
此範例顯示了 X86 XOR32rr
指令的一個實例,它具有 5 個帶有不同暫存器旗標的暫存器運算元
dead $eax = XOR32rr undef $eax, undef $eax, implicit-def dead $eflags, implicit-def $al
暫存器旗標¶
下表顯示了所有可能的暫存器旗標以及對應的內部 llvm::RegState
表示
旗標 |
內部值 |
含義 |
---|---|---|
|
|
未發出的暫存器 (例如,進位或暫時結果)。 |
|
|
|
|
|
暫存器定義。 |
|
|
未使用的定義。 |
|
|
暫存器的最後一次使用。 |
|
|
暫存器的值無關緊要。 |
|
|
暫存器讀取在同一個指令或捆綁包內定義的值。 |
|
|
暫存器定義發生在使用之前。 |
|
|
暫存器 'use' 用於除錯目的。 |
|
|
可以重新命名的暫存器。 |
子暫存器索引¶
暫存器機器運算元可以使用子暫存器索引來參考暫存器的一部分。下面的範例顯示了 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 Constant;<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
中的所有 frame 指令。CFI_INSTRUCTION
看起來可能包含多個運算元,但它僅包含 CFI 索引。其他運算元由 MCCFIInstruction
物件追蹤。
語法為
CFI_INSTRUCTION offset $w30, -16
這可能會在 MC 層稍後發射為
.cfi_offset w30, -16
IntrinsicID 運算元¶
Intrinsic ID 運算元包含通用內建函數 ID 或目標特定 ID。
用於 returnaddress
內建函數的語法是
$x0 = COPY intrinsic(@llvm.returnaddress)
Predicate 運算元¶
Predicate 運算元包含來自 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
物件表示。
固定變數位置¶
有幾種指定變數位置的方式。最簡單的是描述一個永久位於堆疊上的變數。在機器函式的 stack 或 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
meta 機器指令指定。它與 #dbg_value
IR 記錄同義,並寫為
DBG_VALUE $rax, $noreg, !123, !DIExpression(), debug-location !456
運算元分別對應到
識別機器位置,例如暫存器、立即值或 frame 索引,
如果要在第一個運算元中新增額外的間接層級,則為 $noreg 或立即值零,
識別
DILocalVariable
元資料節點,指定限定變數位置的表達式,可以是內聯或作為元資料節點參考,
而原始碼位置識別變數作用域的 DILocation
。IsIndirect
第二個運算元已棄用,即將刪除。變數位置的所有其他限定符都應透過表達式元資料進行。
指令參考位置¶
此實驗性功能旨在將變數值的規範與變數取得該值的程式點分開。變數值的變更以與 DBG_VALUE
meta 指令相同的方式發生,但使用 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 的 hook
createMIROperandComment()
來新增或自訂註解。