核心管線

GlobalISel 的核心管線是

../_images/pipeline-overview.png

圖表中顯示的四個階段包含

IRTranslator

LLVM-IR 轉換為 gMIR (通用 MIR)。這很大程度上是直接轉換,並且幾乎沒有目標客製化。它在某種程度上類似於 SelectionDAGBuilder,但建立的是一種稱為 gMIR 的 MIR 風格,而不是特殊化的表示法。gMIR 使用與 MIR 完全相同的資料結構,但約束更寬鬆。例如,虛擬暫存器可能會被約束為特定類型,而無需將其約束為特定的暫存器類別。

Legalizer

將不支援的操作替換為支援的操作。換句話說,它塑造 gMIR 以適應後端可以支援的內容。目標需要支援的操作集合非常小,但除此之外,目標可以隨意塑造 MIR。

Register Bank Selector

將虛擬暫存器綁定到暫存器組。此階段旨在通過將 MIR 的部分聚集在一起,來最小化跨暫存器組的複製。

Instruction Select

使用 gMIR 選擇目標指令。在此時,gMIR 已被充分約束,使其成為 MIR。

雖然我們傾向於將它們視為不同的階段來談論,但應該注意的是,這裡有很大的彈性,並且事物可以比下面描述的更早發生是可以的。例如,合法化器直接將內建函數合法化為目標指令並不罕見。具體要求是在每個階段之後都保留以下附加約束

IRTranslator

在此階段之後,表示法必須是 gMIR、MIR 或兩者的混合。 大部分通常會以 gMIR 開始,但後續階段將逐漸將 gMIR 過渡到 MIR。

Legalizer

在此階段之後,不得保留或引入任何非法操作。

Register Bank Selector

在此階段之後,所有虛擬暫存器都必須分配一個暫存器組。

Instruction Select

在此階段之後,不得保留或引入任何 gMIR。換句話說,我們必須完成從 gMIR 到 MIR 的轉換。

除了這些階段之外,還有一些可選階段執行最佳化。目前的可選階段是

Combiner

用更好的替代方案替換指令模式。通常,這意味著通過用更快的替代方案替換指令來提高運行時效能,但 Combiner 也可以專注於程式碼大小或其他指標。

可以插入諸如此類的額外階段,以支援更高的最佳化級別或目標特定需求。一個可能的管線是

../_images/pipeline-overview-with-combiners.png

當然,合併器也可以插入到其他地方。階段也可以完全替換,只要它們的任務完成即可,如這個(更客製化)的範例管線所示。

../_images/pipeline-overview-customized.png

MachineVerifier

階段方法讓我們可以使用 MachineVerifier 來強制執行超出管線某些點所需的不變性。例如,具有 legalized 屬性的函數可以使用 MachineVerifier 強制執行不發生非法指令。類似地,regBankSelected 函數可能沒有未分配暫存器組的虛擬暫存器。

注意

由於分層原因,MachineVerifier 無法成為 GlobalISel 中唯一的驗證器。目前,在我們找到解決此問題的方法時,一些階段也執行驗證。

主要問題是 GlobalISel 是一個獨立的函式庫,因此我們無法直接從 CodeGen 引用它。

測試

測試 GlobalISel 的能力比 SelectionDAG 大幅提升。SelectionDAG 有點像黑盒子,裡面有很多事情在進行。這使得難以編寫一個可靠地測試其行為特定方面的測試。為了比較,請參閱以下圖表

../_images/testing-pass-level.png

每個灰色方框都表示一個序列化當前狀態並測試管線中兩個點之間行為的機會。當前狀態可以使用 -stop-before-stop-after 序列化,並使用 -start-before-start-after-run-pass 載入。

我們還可以更進一步,因為 GlobalISel 的許多階段都很容易進行單元測試

../_images/testing-unit-level.png

可以創建一個虛構的目標,例如在 LegalizerHelperTest.cpp 中,並執行演算法的單一步驟並檢查結果。MIR 和 FileCheck 指令可以使用字串嵌入,因此您仍然可以使用 llvm-lit 中可用的便利性。

除錯

一種已被證明特別有價值的除錯技術是使用區塊提取器將基本區塊提取到新函數中。這可以用於追蹤正確性錯誤,也可用於追蹤效能衰退。它還可以與函數屬性結合使用,以禁用一個或多個提取函數的 GlobalISel。

../_images/block-extract.png

執行提取的命令是

./bin/llvm-extract -o - -S -b ‘foo:bb1;bb4’ <input> > extracted.ll

這個特定範例從名為 foo 的函數中提取兩個基本區塊。然後可以修改新的 LLVM-IR 以將 failedISel 屬性添加到包含 bb4 的提取函數,以使該函數使用 SelectionDAG。

這可以阻止一些最佳化,因為 GlobalISel 通常能夠一次處理單個函數。可以針對基本區塊的不同組合重複此技術,直到您確定了錯誤中涉及的關鍵區塊。

一旦確定了關鍵區塊,您可以通過拆分區塊(例如從

bb1:
  ... instructions group 1 ...
  ... instructions group 2 ...

bb1:
  ... instructions group 1 ...
  br %bb2

bb2:
  ... instructions group 2 ...

然後為新區塊重複該過程,從而進一步提高關鍵指令的解析度。

也可以在主函數使用 GlobalISel 編譯,而提取的基本區塊使用 SelectionDAG 編譯(或反之亦然)的模式下使用此技術,以利用另一個程式碼產生器的現有品質來追蹤錯誤。在追蹤效能衰退時,此技術還可以用於提高快速程式碼和慢速程式碼之間的相似性,並幫助您精確定位衰退的特定原因。