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:exposesReturnsTwicelegalizedregBankSelectedselectedframeInfo 整個區段通常是不必要的,如果函式中沒有特殊的 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 格式。同樣地,測試某些行為的測試,這些行為依賴於目標特定 MachineFunctionInfoMachineConstantPoolValue 子類別的狀態,目前也無法使用 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 暫存器和後繼區塊列表 - 它們由解析器組合成一個列表。

雜項屬性

屬性 IsAddressTakenIsLandingPadIsInlineAsmBrIndirectTargetAlignment 可以在區塊定義後的括號中指定

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 檔案中的精確定義進行序列化,並且它們區分大小寫。這表示類似的指令名稱,例如 TSTritSTRi 代表不同的機器指令。

指令旗標

旗標 frame-setupframe-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 表示

旗標

內部值

含義

implicit

RegState::Implicit

未發出的暫存器 (例如,進位或暫時結果)。

implicit-def

RegState::ImplicitDefine

implicitdef

def

RegState::Define

暫存器定義。

dead

RegState::Dead

未使用的定義。

killed

RegState::Kill

暫存器的最後一次使用。

undef

RegState::Undef

暫存器的值無關緊要。

internal

RegState::InternalRead

暫存器讀取在同一個指令或捆綁包內定義的值。

early-clobber

RegState::EarlyClobber

暫存器定義發生在使用之前。

debug-use

RegState::Debug

暫存器 'use' 用於除錯目的。

renamable

RegState::Renamable

可以重新命名的暫存器。

子暫存器索引

暫存器機器運算元可以使用子暫存器索引來參考暫存器的一部分。下面的範例顯示了 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

註解

機器運算元可以具有 C/C++ 樣式的註解,這些註解是夾在 /**/ 之間的註釋,以提高例如立即運算元的可讀性。在下面的範例中,ARM 指令 EOR 和 BCC 以及立即運算元 140 已使用其條件碼 (CC) 定義進行註釋,即 alwayseq 條件碼

dead renamable $r2, $cpsr = tEOR killed renamable $r2, renamable $r1, 14 /* CC::always */, $noreg
t2Bcc %bb.4, 0 /* CC:eq */, killed $cpsr

由於這些註釋是註解,因此 MI 解析器會忽略它們。可以透過覆寫 InstrInfo 的 hook createMIROperandComment() 來新增或自訂註解。

除錯資訊結構

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

運算元分別對應到

  1. 識別機器位置,例如暫存器、立即值或 frame 索引,

  2. 如果要在第一個運算元中新增額外的間接層級,則為 $noreg 或立即值零,

  3. 識別 DILocalVariable 元資料節點,

  4. 指定限定變數位置的表達式,可以是內聯或作為元資料節點參考,

而原始碼位置識別變數作用域的 DILocationIsIndirect 第二個運算元已棄用,即將刪除。變數位置的所有其他限定符都應透過表達式元資料進行。

指令參考位置

此實驗性功能旨在將變數的規範與變數取得該值的程式點分開。變數值的變更以與 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 作者指南 也可能很有用。