通用機器碼 IR

通用 MIR (gMIR) 是一種中繼表示法,其與 MachineIR (MIR) 共享相同的資料結構,但具有更寬鬆的約束。隨著編譯管線的進行,這些約束會逐漸收緊,直到 gMIR 變成 MIR。

本文檔的其餘部分將假設您熟悉 MachineIR (MIR) 中的概念,並將重點介紹 MIR 和 gMIR 之間的差異。

通用機器指令

注意

本節擴展了 MIR 語言參考中的 機器指令

MIR 主要處理目標指令,並且只有一小組目標獨立的運算碼,例如 COPYPHIREG_SEQUENCE,而 gMIR 定義了豐富的 通用運算碼 集合,這些運算碼是目標獨立的,並且描述了目標通常支援的操作。一個例子是 G_ADD,它是整數加法的通用運算碼。有關每個通用運算碼的更多資訊,請參見 通用運算碼

MachineIRBuilder 類別包裝了 MachineInstrBuilder,並提供了一種建立這些通用指令的便捷方法。

通用虛擬暫存器

注意

本節擴展了 MIR 語言參考中的 暫存器

通用虛擬暫存器類似於虛擬暫存器,但它們未被分配暫存器類別約束。相反,通用虛擬暫存器具有較寬鬆的約束,首先是 低階類型,然後進一步約束到 暫存器組。最終,它們將被約束到一個暫存器類別,屆時它們將變成普通的虛擬暫存器。

通用虛擬暫存器可以與 MachineRegisterInfo 提供的所有虛擬暫存器 API 一起使用。特別是,def-use 鏈 API 可以使用,而無需將它們與非通用虛擬暫存器區分開來。

為了簡化起見,大多數通用指令僅接受虛擬暫存器(包括通用和非通用)。有一些例外情況,但總體而言

  • 它們不使用立即值,而是使用由實現立即值的指令定義的通用虛擬暫存器(請參閱 常數的轉換)。通常,這是 G_CONSTANT 或 G_FCONSTANT。此規則的一個例外是 G_SEXT_INREG,其中必須具有立即值。

  • 它們不使用實體暫存器,而是使用通用虛擬暫存器,該暫存器由來自實體暫存器的 COPY 定義,或由定義實體暫存器的 COPY 使用。

歷史記錄

我們從一種替代表示法開始,其中 MRI 追蹤每個通用虛擬暫存器的大小,並且指令具有類型列表。這有兩個缺陷:類型和大小是冗餘的,並且沒有通用的方法來獲取給定運算元的類型(因為指令類型和運算元之間沒有 1:1 映射)。我們考慮將類型放入 MCInstrDesc 的某種變體中:請參閱 PR26576:[GlobalISel] 通用 MachineInstrs 需要類型,但這會增加相關物件的記憶體 footprint

暫存器組

暫存器組是目標定義的一組暫存器類別。這個定義相當寬鬆,所以讓我們談談它們可以實現什麼。

假設我們有一個處理器,它有兩個暫存器檔案,A 和 B。它們在各方面都是相等的,並且以相同的成本支援相同的指令。它們只是在物理上分開儲存,並且每個指令只能存取來自 A 或 B 的暫存器,但絕不能混合兩者。如果我們想對分散在兩個暫存器檔案中的資料執行操作,我們必須首先將所有資料複製到單個暫存器檔案中。

給定像這樣的處理器,我們將受益於將相關資料聚集到一個暫存器檔案中,以便我們最大限度地減少來回複製資料的成本,以滿足所有指令(可能衝突的)需求。暫存器組是一種約束暫存器分配器為虛擬暫存器使用特定暫存器檔案的方法。

實際上,暫存器檔案 A 和 B 很少相等。它們通常可以儲存相同的資料,但通常對您可以對每個暫存器檔案執行的操作有一些限制。一個相當常見的模式是其中一個可供整數運算存取,另一個可供浮點運算存取。為了適應這一點,讓我們將 A 和 B 重命名為 GPR(通用暫存器)和 FPR(浮點暫存器)。

我們現在有一些額外的約束來限制我們。像 G_FMUL 這樣的操作必須在 FPR 中發生,而 G_ADD 必須在 GPR 中發生。但是,即使這規定了很多分配,我們仍然有一些自由。G_LOAD 可以在 GPR 和 FPR 中發生,我們想要哪個取決於誰將消耗載入的資料。同樣,G_FNEG 可以在 GPR 和 FPR 中發生。如果我們將其分配給 FPR,那麼我們將使用浮點取反。但是,如果我們將其分配給 GPR,那麼我們可以等效地將符號位與 1 進行 G_XOR 運算以反轉它。

總之,暫存器組是一種根據在給定上下文中應用每個選擇時的差異分析,來消除看似等效的選擇之間歧義的方法。

為了給出一些具體例子

AArch64

AArch64 有三個主要組。GPR 用於整數運算,FPR 用於浮點和 NEON 向量指令集。第三個是 CCR,它描述了用於預測的條件碼暫存器。

MIPS

MIPS 有五個主要組,其中許多程式實際上只使用一個或兩個。GPR 是用於整數運算的通用組。FGR 或 CP1 用於浮點運算以及 MSA 向量指令和一些其他特定於應用程式的擴展。CP0 用於系統暫存器,很少有程式會使用它。CP2 和 CP3 用於晶片中可能存在的任何特定於應用程式的協處理器。可以說,還有第六個用於 LO 和 HI 暫存器,但這些僅用於少數運算的結果,並且從 GPR 區分建模的價值值得懷疑。

X86

X86 可以被視為有 3 個主要組:通用、x87 和向量(可以進一步拆分為每個域的組,用於單精度與雙精度指令)。看起來也可能還有更多潛在的組,例如用於 AVX512 遮罩暫存器的組。

暫存器組由目標提供的 API RegisterBankInfo 描述。

低階類型

此外,每個通用虛擬暫存器都有一個類型,由 LLT 類別的實例表示。

EVT/MVT/Type 類似,它不區分無符號和有符號整數類型。此外,它也不區分整數和浮點類型:它主要傳達絕對必要的資訊,例如大小和向量通道數

  • sN 用於純量

  • pN 用於指標

  • <N x sM> 用於向量

LLT 旨在取代 SelectionDAG 中 EVT 的用法。

以下是一些 LLT 範例及其 EVTType 等效項

LLT

EVT

IR 類型

s1

i1

i1

s8

i8

i8

s32

i32

i32

s32

f32

float

s17

i17

i17

s16

N/A

{i8, i8} [1]

s32

N/A

[4 x i8] [1]

p0

iPTR

i8*, i32*, %opaque*

p2

iPTR

i8 addrspace(2)*

<4 x s32>

v4f32

<4 x float>

s64

v1f64

<1 x double>

<3 x s32>

v3i32

<3 x i32>

理由:指令已經編碼了類型的特定解釋(例如,addfadd,或 sdivudiv)。在類型系統中也編碼該資訊需要引入 bitcast,而對於選擇器來說沒有實際優勢。

指標類型通過位址空間來區分。這與 IR 匹配,而不是 SelectionDAG,後者位址空間是運算的一個屬性。這種表示法更好地支援根據其位址空間具有不同大小的指標。

注意

注意

這仍然是真的嗎?我以為我們已經移除了 1 元素向量的概念。假設上,它可以與純量不同,但我認為我們未能找到真實的例子。

目前,LLT 要求向量中至少有 2 個元素,但某些目標具有「1 元素向量」的概念。將它們表示為其底層純量類型是一種很好的簡化。

腳註

通用運算碼參考

可用的通用運算碼在 通用運算碼 中描述。