核心管線

GlobalISel 的核心管線是

../_images/pipeline-overview.png

圖中顯示的四個過程包含:

IRTranslator

LLVM-IR 轉換為 gMIR(通用 MIR)。這基本上是直接轉換,幾乎沒有目標客製化。它有點類似於 SelectionDAGBuilder,但建構的是一種稱為 gMIR 的 MIR,而不是專門的表示法。gMIR 使用與 MIR 完全相同的資料結構,但約束條件更寬鬆。例如,虛擬暫存器可以被約束到特定類型,而無需將其約束到特定的暫存器類別。

Legalizer

將不支援的操作替換為支援的操作。換句話說,它會根據後端支援的內容調整 gMIR 的形狀。目標需要支援的操作集非常小,但除此之外,目標可以根據需要調整 MIR 的形狀。

暫存器庫選擇器

將虛擬暫存器綁定到暫存器庫。此過程旨在透過將 MIR 的部分聚集在一起來最大程度地減少跨暫存器庫的複製。

指令選擇

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

雖然我們傾向於將它們視為不同的過程,但應注意的是,這裡有很大的靈活性,而且事情可以在下述時間之前發生。例如,legalizer 直接將內建函數合法化為目標指令的情況並不少見。具體的要求是,在每個過程之後都保留以下額外約束:

IRTranslator

在此過程之後,表示法必須是 gMIR、MIR 或兩者的混合。大多數情況下,一開始會是 gMIR,但後續過程會逐漸將 gMIR 轉換為 MIR。

Legalizer

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

暫存器庫選擇器

在此過程之後,必須為所有虛擬暫存器分配暫存器庫。

指令選擇

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

除了這些過程之外,還有一些可選的過程可以執行最佳化。目前的選用過程有:

Combiner

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

可以插入其他類似的過程來支援更高級別的最佳化或特定目標的需求。可能的管線是:

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

當然,combiner 也可以插入到其他位置。此外,只要任務完成,也可以完全替換過程,如下所示(更客製化的)範例管線:

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

MachineVerifier

傳遞方法讓我們可以使用 MachineVerifier 來強制執行在管線的某些點之後所需的約束條件。例如,具有 legalized 屬性的函數可以使用 MachineVerifier 來強制執行不出現非法指令。同樣地,regBankSelected 函數可能沒有分配暫存器庫的虛擬暫存器。

注意

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

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

測試

與 SelectionDAG 相比,測試 GlobalISel 的能力得到了顯著提高。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 中提供的便利性。

除錯

一種證明特別有價值的除錯技術是使用 BlockExtractor 將基本塊提取到新函數中。這可以用於追蹤正確性錯誤,也可以用於追蹤效能回歸。它還可以與函數屬性結合使用,為一個或多個提取的函數禁用 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 編譯(反之亦然)的模式下使用此技術,以利用現有程式碼產生器的品質來追蹤錯誤。在追蹤效能回歸並幫助您找出回歸的特定原因時,此技術還可以用於提高快速程式碼和慢速程式碼之間的相似性。