機器 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:exposesReturnsTwicelegalizedregBankSelectedselected。如果函數中沒有特殊的框架使用,則通常不需要整個 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 格式。此外,測試某些依賴於目標特定 MachineFunctionInfoMachineConstantPoolValue 子類別狀態的行為的測試目前也無法使用 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

活躍暫存器和後繼者的清單可以是空的。該語言還允許多個活躍暫存器和後繼者清單 - 它們由解析器組合成一個清單。

其他屬性

屬性 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
}

第一個指令通常是捆綁標頭。 {} 之間的指令與第一個指令捆綁在一起。

暫存器

暫存器是機器指令序列化語言中的關鍵原語之一。它們主要用於 暫存器機器運算元 中,但它們也可以用於許多其他地方,例如 基本塊的活躍輸入列表

物理暫存器由其名稱和「$」前綴符號標識。它們使用以下語法

$<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 表示形式

標誌

內部值

含義

隱式

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

暫存器「使用」是為了除錯目的。

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 常數

  • <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

註解

機器運算元可以有 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 的掛鉤 createMIROperandComment() 來新增或自訂註解。

除錯資訊結構

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

運算元分別對應於

  1. 識別機器位置,例如暫存器、立即數或框架索引,

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

  3. 識別 DILocalVariable 元資料節點,

  4. 指定限定變數位置的運算式,可以是內嵌運算式,也可以是元資料節點參考,

雖然來源位置識別了變數作用域的 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 作者指南 也可能有所幫助。