LLVM 的分析與轉換 Pass

簡介

警告

本文件並未經常更新,且程式清單很可能不完整。可以使用 opt -print-passes 列出 opt 工具已知的程式。

本文件旨在高度概述 LLVM 提供的最佳化功能。最佳化是以 Pass 的形式實作,Pass 會遍歷程式的某個部分以收集資訊或轉換程式。下表將 LLVM 提供的 Pass 分為三大類。分析 Pass 會計算其他 Pass 可以使用的資訊,或用於除錯或程式視覺化目的。轉換 Pass 可以使用(或使無效)分析 Pass。轉換 Pass 都會以某種方式改變程式。工具程式提供了一些實用功能,但不屬於其他分類。例如,將函數提取到位元碼或將模組寫入位元碼的 Pass 既不是分析 Pass 也不是轉換 Pass。以上目錄簡要概述了每個 Pass,並提供了指向文件中稍後更完整 Pass 說明的連結。

分析 Pass

本節說明 LLVM 分析 Pass。

aa-eval:窮舉式別名分析精確度評估器

這是一個簡單的 N^2 別名分析準確度評估器。基本上,對於程式中的每個函數,它只是查詢別名分析實作如何回答函數中每對指標之間的別名查詢。

此工具的靈感和改編自 Naveen Neelakantam、Francesco Spadini 和 Wojciech Stryjewski 的程式碼。

basic-aa:基本別名分析(無狀態別名分析實作)

一種基本別名分析過程,它實作了身份(兩個不同的全局變數不能別名,等等),但不執行狀態分析。

basiccg:基本呼叫圖建構

尚未撰寫。

da:相依性分析

相依性分析框架,用於檢測記憶體存取中的相依性。

domfrontier:支配邊界建構

此過程是一個簡單的支配樹建構演算法,用於尋找前向支配邊界。

domtree:支配樹建構

此過程是一個簡單的支配樹建構演算法,用於尋找前向支配節點。

dot-callgraph:將呼叫圖列印到「dot」檔案

此過程僅在 opt 中可用,會將呼叫圖列印到 .dot 圖表中。然後可以使用「dot」工具處理此圖表,將其轉換為 PostScript 或其他合適的格式。

dot-cfg:將函數的控制流程圖列印到「dot」檔案

此過程僅在 opt 中可用,會將控制流程圖列印到 .dot 圖表中。然後可以使用 dot 工具處理此圖表,將其轉換為 PostScript 或其他合適的格式。此外,可以使用 -cfg-func-name=<substring> 選項來篩選要列印的函數。將會列印所有包含指定子字串的函數。

dot-cfg-only:將函數的控制流程圖列印到「dot」檔案(不含函數主體)

此過程僅在 opt 中可用,會將控制流程圖列印到 .dot 圖表中,省略函數主體。然後可以使用 dot 工具處理此圖表,將其轉換為 PostScript 或其他合適的格式。此外,可以使用 -cfg-func-name=<substring> 選項來篩選要列印的函數。將會列印所有包含指定子字串的函數。

dot-dom:將函式的支配樹輸出到「dot」檔案

這個僅在 opt 中可用的 pass 會將支配樹輸出到 .dot 圖形檔。然後可以使用 dot 工具處理此圖形,將其轉換為 PostScript 或其他合適的格式。

dot-dom-only:將函式的支配樹輸出到「dot」檔案(不包含函式主體)

這個僅在 opt 中可用的 pass 會將支配樹輸出到 .dot 圖形檔,但不包含函式主體。然後可以使用 dot 工具處理此圖形,將其轉換為 PostScript 或其他合適的格式。

dot-post-dom:將函式的後支配樹輸出到「dot」檔案

這個僅在 opt 中可用的 pass 會將後支配樹輸出到 .dot 圖形檔。然後可以使用 dot 工具處理此圖形,將其轉換為 PostScript 或其他合適的格式。

dot-post-dom-only:將函式的後支配樹輸出到「dot」檔案(不包含函式主體)

這個僅在 opt 中可用的 pass 會將後支配樹輸出到 .dot 圖形檔,但不包含函式主體。然後可以使用 dot 工具處理此圖形,將其轉換為 PostScript 或其他合適的格式。

globals-aa:針對全域變數進行簡單的 mod/ref 分析

這個簡單的 pass 為地址未被使用的全域變數提供別名和 mod/ref 資訊,並追蹤函式是否讀取或寫入記憶體(是否為「純粹的」)。對於這種簡單(但非常常見)的情況,我們可以提供相當準確和有用的資訊。

instcount:計算各種 Instruction 的數量

這個 pass 會收集所有指令的數量並報告。

iv-users:歸納變數使用者

記錄從歸納變數計算出的表達式的「有趣」使用者。

lazy-value-info:延遲值資訊分析

用於延遲計算值約束資訊的介面。

lint:對 LLVM IR 進行靜態程式碼檢查

此遍靜態檢查在 LLVM IR 中產生未定義或可能非預期行為的常見且易於識別的結構。

它不保證正確性,原因有兩個。首先,它並不全面。有些可以靜態完成的檢查尚未實現。其中一些由 TODO 註釋指出,但這些註釋也不全面。其次,許多條件無法靜態檢查。此遍不執行動態檢測,因此無法檢查所有可能的問題。

另一個限制是它假設所有程式碼都會被執行。在永遠不會到達的基本區塊中,透過空指標進行的存放是無害的,但此遍無論如何都會發出警告。

優化遍可能會使此遍檢查的條件變得更明顯或更不明顯。如果優化遍似乎引入了警告,則可能是優化遍僅僅暴露了程式碼中現有的條件。

此程式碼可以在 instcombine 之前執行。在許多情況下,instcombine 會檢查相同類型的問題,並將具有未定義行為的指令轉換為不可到達(或等效的)。因此,此遍會盡力查看位元轉換等。

迴圈: 自然迴圈資訊

此分析用於識別自然迴圈並確定 CFG 各個節點的迴圈深度。請注意,識別出的迴圈實際上可能是共用同一個標頭節點的多個自然迴圈…而不仅仅是單個自然迴圈。

memdep: 記憶體依賴分析

一種分析,用於確定給定記憶體操作所依賴的先前記憶體操作。它建立在別名分析資訊的基礎上,並嘗試為常見的別名資訊查詢提供延遲、快取的介面。

postdomtree: 後支配樹建構

此遍是一種簡單的後支配建構演算法,用於尋找後支配節點。

function(print):將函式印出到標準錯誤輸出

PrintFunctionPass 類別設計用於與其他 FunctionPasses 串聯,並在處理函式時將其印出。

module(print):將模組印出到標準錯誤輸出

這個 pass 只是在執行時將整個模組印出。

regions:偵測單一入口單一出口區域

RegionInfo pass 會偵測函式中的單一入口單一出口區域,其中區域定義為任何僅在兩個點連接到剩餘圖形的子圖。此外,還會建立階層式區域樹。

scalar-evolution:純量演化分析

ScalarEvolution 分析可用於分析和分類迴圈中的純量運算式。它專精於識別一般的歸納變數,並使用抽象且不透明的 SCEV 類別來表示它們。透過此分析,可以獲得迴圈的迭代次數和其他重要屬性。

此分析主要用於歸納變數替換和強度折減。

scev-aa:基於 ScalarEvolution 的別名分析

根據 ScalarEvolution 查詢實作的簡單別名分析。

這與傳統的迴圈相依性分析不同,它測試的是迴圈單次迭代內的相依性,而不是不同迭代之間的相依性。

ScalarEvolutionBasicAliasAnalysis 的臨時分析集合更完整地理解指標算術。

stack-safety:堆疊安全分析

StackSafety 分析可用於判斷堆疊配置的變數是否可以被視為不受記憶體存取錯誤的影響。

此分析的主要目的是供消毒器使用,以避免對安全變數進行不必要的檢測。

轉換 Pass

本節描述 LLVM 轉換 Pass。

adce:積極的無用程式碼消除

ADCE 會積極嘗試移除無用程式碼。此過程類似於 DCE,但它假設值在被證明有用之前都是無用的。這與 SCCP 類似,只是應用於值的活躍性。

always-inline:針對 always_inline 函式的內聯器

一個自訂內聯器,僅處理標記為「始終內聯」的函式。

argpromotion:將「按引用傳遞」的參數提升為純量

此過程會將「按引用傳遞」的參數提升為「按值傳遞」的參數。實際上,這表示尋找具有指標參數的內部函式。如果它可以透過使用別名分析證明參數「僅」被載入,則它可以將值傳遞到函式中,而不是值的地址。這可能會導致程式碼的遞迴簡化,並導致消除配置(尤其是在 C++ 模板程式碼中,例如 STL)。

此過程還會處理傳遞到函式的聚合參數,如果僅載入聚合的元素,則將其純量化。請注意,它拒絕純量化需要傳遞三個以上運算元到函式的聚合,因為為大型陣列或結構傳遞數千個運算元是沒有效率的!

請注意,此轉換也可以針對僅儲存的參數執行(返回值而不是地址),但目前尚未實現。當且僅當 LLVM 開始支援從函式返回多個值時,才最適合處理這種情況。

block-placement:設定檔引導的基本區塊放置

此過程是一個非常簡單的設定檔引導基本區塊放置演算法。其想法是將經常執行的區塊放在函式開頭,並希望增加條件分支的墜落次數。如果沒有特定函式的設定檔資訊,則此過程基本上會以深度優先順序對區塊進行排序。

break-crit-edges:斷開 CFG 中的關鍵邊緣

透過插入虛擬基本區塊來斷開 CFG 中的所有關鍵邊緣。無法處理關鍵邊緣的過程可能「需要」這樣做。這種轉換顯然會使 CFG 無效,但可以更新前向支配者(集合、直接支配者、樹和邊界)資訊。

codegenprepare:針對程式碼生成進行優化

此過程會修改輸入函式中的程式碼,以便更好地準備基於 SelectionDAG 的程式碼生成。這可以解決其一次處理一個基本區塊的方法中的限制。它最終應該被移除。

constmerge:合併重複的全局常數

將重複的全局常數合併成一個共享的常數。這很有用,因為某些過程(例如 TraceValues)會在程式中插入許多字串常數,而不管是否存在現有的字串。

dce:無用程式碼移除

無用程式碼消除類似於無用指令消除,但它會重新檢查被移除指令所使用的指令,以查看它們是否也變成了無用指令。

deadargelim:無用參數消除

此過程會從內部函數中刪除無用參數。無用參數消除會移除直接無用的參數,以及僅作為其他函數的無用參數傳遞到函數調用中的參數。此過程也以類似的方式刪除無用參數。

此過程通常可用作在積極的程序間過程之後運行的清理過程,這些過程會添加可能無用的參數。

dse:無用存放消除

一個僅考慮基本塊局部冗餘存放的簡單無用存放消除。

function-attrs:推斷函數屬性

一個簡單的程序間過程,它會遍歷調用圖,尋找不訪問或只讀取非局部內存的函數,並將其標記為readnone/readonly。此外,如果對函數的調用沒有創建任何超出調用生命週期的指針值的副本,它會將函數參數(指針類型)標記為“nocapture”。這或多或少意味著指針僅被解引用,而不會從函數返回或存儲在全局變量中。此過程實現為調用圖的自下而上遍歷。

globaldce:無用全局變量消除

此轉換旨在從程式中消除無法訪問的內部全局變量。它使用一種積極的算法,找出已知存在的全局變量。在找到所有需要的全局變量後,它會刪除剩下的任何內容。這允許它刪除程式中無法訪問的遞歸塊。

globalopt:全局變量優化器

此過程會轉換從未獲取地址的簡單全局變量。如果顯然為真,它會將讀/寫全局變量標記為常量,刪除僅存儲到的變量,等等。

gvn:全局值編號

此過程執行全局值編號以消除完全和部分冗餘的指令。它還執行冗餘加載消除。

indvars:歸一化歸納變量

此轉換分析歸納變量(以及從中得出的計算),並將其轉換為適合後續分析和轉換的更簡單形式。

此轉換對每個具有可識別歸納變量的循環進行以下更改

  • 所有循環都將轉換為具有_單個_規範歸納變量,該變量從零開始,步長為一。

  • 保證規範歸納變量是循環標題塊中的第一個 PHI 節點。

  • 任何指標算術遞迴都會被提升為使用陣列索引。

如果迴圈的迭代次數是可計算的,這個過程也會進行以下更改

  • 迴圈的退出條件被規範化為將歸納值與退出值進行比較。這會將迴圈轉換成如下形式

    for (i = 7; i*i < 1000; ++i)
    
    into
    
    for (i = 0; i != 25; ++i)
    
  • 迴圈外任何從歸納變數派生的表達式的使用都會被更改為在迴圈外計算派生值,從而消除了對歸納變數退出值的依賴。如果迴圈的唯一目的是計算某些派生表達式的退出值,則此轉換將使迴圈失效。

在執行完所有所需的迴圈轉換之後,應該接著進行強度折減。此外,在有利可圖的目標上,可以將迴圈轉換為倒計時到零(“do 迴圈”優化)。

inline:函數整合/內聯

將函數自下而上地內聯到被呼叫者中。

instcombine:組合冗餘指令

組合指令以形成更少、更簡單的指令。此過程不會修改 CFG。此過程是進行代數簡化的地方。

此過程組合如下内容

%Y = add i32 %X, 1
%Z = add i32 %Y, 1

進入

%Z = add i32 %X, 2

這是一個簡單的工作清單驅動演算法。

此過程確保對程式執行以下規範化

  1. 如果二元運算符具有常數運算元,則將其移至右側。

  2. 具有常數運算元的位元運算符始終按以下順序分組:首先執行移位,然後是 or,然後是 and,最後是 xor

  3. 如果可能,比較指令會從 <> 轉換為 =

  4. 布林值上的所有 cmp 指令都替換為邏輯運算。

  5. add X, X 表示為 mul X, 2shl X, 1

  6. 具有常數 2 的冪參數的乘法會轉換為移位。

  7. … 等等。

此過程還可以簡化對特定眾所周知函數呼叫(例如運行時函數庫函數)的呼叫。例如,main() 函數中出現的呼叫 exit(3) 可以簡單地轉換為 return 3。是否簡化函數庫呼叫由 -function-attrs 過程和 LLVM 對不同目標上的函數庫呼叫的了解來控制。

aggressive-instcombine:組合表達式模式

組合表達式模式以形成具有更少、更簡單指令的表達式。

例如,在適用時,此過程會將 TruncInst 後置支配的表達式的寬度縮減為較小的寬度。

它與 instcombine pass 不同之處在於,它可以修改 CFG 並包含比 O(1) 更高複雜度的模式優化,因此它應該比 instcombine pass 運行次數更少。

internalize:內部化全域符號

這個 pass 會迴圈遍歷輸入模組中的所有函數,尋找主函數。如果找到主函數,則所有其他函數和所有具有初始值設定項的全域變數都會被標記為內部。

ipsccp:程序間稀疏條件常數傳播

稀疏條件常數傳播 的程序間變體。

jump-threading:跳轉線程化

跳轉線程化嘗試找到貫穿基本塊的不同控制流程線程。這個 pass 會查看具有多個前驅和多個後繼的塊。如果可以證明塊的一個或多個前驅始終導致跳轉到其中一個後繼,我們將通過複製此塊的內容將邊從前驅轉發到後繼。

這種情況可能發生的示例代碼如下

if () { ...
  X = 4;
}
if (X < 3) {

在這種情況下,第一個 if 結束處的無條件分支可以重新導向到第二個 if 的 false 側。

lcssa:迴圈封閉 SSA 形式 Pass

這個 pass 通過在迴圈結束處為所有跨迴圈邊界存活的值放置 phi 節點來轉換迴圈。例如,它將左側代碼轉換為右側代碼

for (...)                for (...)
    if (c)                   if (c)
        X1 = ...                 X1 = ...
    else                     else
        X2 = ...                 X2 = ...
    X3 = phi(X1, X2)         X3 = phi(X1, X2)
... = X3 + 4              X4 = phi(X3)
                            ... = X4 + 4

這仍然是有效的 LLVM;額外的 phi 節點純粹是冗餘的,並且將被 InstCombine 輕鬆消除。這種轉換的主要好處是它使許多其他迴圈優化(例如 LoopUnswitching)變得更簡單。您可以在 迴圈術語部分的 LCSSA 形式 中閱讀更多信息。

licm:迴圈不變代碼移動

這個 pass 執行迴圈不變代碼移動,嘗試從迴圈體中刪除盡可能多的代碼。它通過將代碼提升到預處理塊中,或者在安全的情況下將代碼下沉到退出塊中來實現這一點。這個 pass 還將迴圈中必須別名的內存位置提升到寄存器中,從而提升和下沉“不變”的加載和存儲。

將操作提升到迴圈之外是一種規範化轉換。它可以啟用和簡化中間端中的後續優化。通過重新實現提升後的指令來降低寄存器壓力是後端的責任,後端擁有關於寄存器壓力的更準確信息,並且還可以處理 LICM 之外的其他增加活動範圍的優化。

這個 pass 將別名分析用於兩個目的

  1. 將迴圈不變加載和調用移出迴圈。如果我們可以確定迴圈內的加載或調用從不與存儲到的任何內容別名,我們就可以像任何其他指令一樣提升或下沉它。

  2. 記憶體的純量提升。如果迴圈內部有儲存指令,我們會嘗試將儲存移動到迴圈之後而不是迴圈內部進行。只有在滿足以下幾個條件時才能這樣做:

    1. 儲存的指標是迴圈不變量。

    2. 迴圈中沒有任何儲存或載入操作*可能*與指標別名。迴圈中沒有任何呼叫會修改/引用指標。

    如果這些條件為真,我們可以提升迴圈中指標的載入和儲存操作,使其使用一個臨時分配的變數。然後,我們使用mem2reg 功能來為變數構造適當的 SSA 形式。

loop-deletion:刪除死迴圈

此文件實作了死迴圈刪除過程。此過程負責消除具有不可計算的非無限行程計數、沒有副作用或易變指令,並且對函數返回值的計算沒有貢獻的迴圈。

loop-extract:將迴圈提取到新函數中

一個圍繞 ExtractLoop() 純量轉換的過程包裝器,用於將每個頂層迴圈提取到其自己的新函數中。如果迴圈是給定函數中*唯一*的迴圈,則不會觸及它。這是通過 bugpoint 進行除錯時最有用的一個過程。

loop-reduce:迴圈強度降低

此過程對迴圈內部數組引用執行強度降低,這些迴圈的一個或多個組件是迴圈歸納變數。這是通過創建一個新值來保存第一次迭代的數組訪問的初始值,然後在迴圈中創建一個新的 GEP 指令以將值增加適當的量來實現的。

loop-rotate:旋轉迴圈

一個簡單的迴圈旋轉轉換。可以在 旋轉迴圈的迴圈術語 中找到它的摘要。

loop-simplify:規範化自然迴圈

此過程執行多項轉換以將自然迴圈轉換為更簡單的形式,這使得後續的分析和轉換更加簡單有效。可以在 迴圈術語,迴圈簡化形式 中找到它的摘要。

迴圈前置標頭插入確保從迴圈外部到迴圈標頭只有一條非關鍵進入邊緣。這簡化了许多分析和轉換,例如 LICM

迴圈出口區塊插入確保迴圈的所有出口區塊(迴圈外部、在迴圈內部具有前驅的區塊)僅具有來自迴圈內部的 (並因此由迴圈標頭支配)。這簡化了內建於 LICM 中的儲存沉降等轉換。

此過程還確保迴圈只有一條回邊。

請注意,simplifycfg 會清除被分割出來但不必要的區塊,因此使用這個 pass 不應該會降低生成的程式碼的效能。

這個 pass 顯然會修改 CFG,但會更新迴圈資訊和支配樹資訊。

loop-unroll:展開迴圈

這個 pass 實現了一個簡單的迴圈展開器。當迴圈已經被 indvars pass 正規化後,它會運作得最好,允許它輕鬆地確定迴圈的迭代次數。

loop-unroll-and-jam:展開和合併迴圈

這個 pass 實現了一個簡單的展開和合併經典迴圈優化 pass。它將迴圈從

for i.. i+= 1              for i.. i+= 4
  for j..                    for j..
    code(i, j)                 code(i, j)
                               code(i+1, j)
                               code(i+2, j)
                               code(i+3, j)
                           remainder loop

這可以看作是展開外迴圈並將內迴圈「合併」(融合)成一個。當變數或載入可以在新的內迴圈中共享時,這可以顯著提高效能。它使用 相依性分析 來證明轉換是安全的。

lower-global-dtors:降低全域解構函數

這個 pass 通過創建包裝函數來降低全域模組解構函數(llvm.global_dtors),這些包裝函數在 llvm.global_ctors 中註冊為全域構造函數,並包含對 __cxa_atexit 的呼叫來註冊它們的解構函數。

lower-atomic:將原子內建函數降低為非原子形式

這個 pass 會將原子內建函數降低為非原子形式,以便在已知的非搶佔環境中使用。

該 pass 不會驗證環境是否為非搶佔式的(通常這需要了解程式的整個呼叫圖,包括可能無法以位元碼形式提供的任何程式庫);它只是降低每個原子內建函數。

lower-invoke:將 invoke 降低為呼叫,適用於無展開程式碼生成器

此轉換設計用於尚不支持堆疊展開的程式碼生成器。這個 pass 會將 invoke 指令轉換為 call 指令,以便任何異常處理 landingpad 區塊都變成死碼(之後可以通過運行 -simplifycfg pass 來刪除)。

lower-switch:將 SwitchInst 降低為分支

使用一系列分支重寫 switch 指令,這使得目標可以在方便之前不必實現 switch 指令。

mem2reg:將記憶體提升為寄存器

這個文件將記憶體參照提升為暫存器參照。它提升了只有載入和儲存作為用途的 alloca 指令。一個 alloca 會透過使用支配邊界來放置 phi 節點,然後以深度優先順序遍歷函數來適當地重寫載入和儲存,從而進行轉換。這只是建構「修剪過的」SSA 形式的標準 SSA 建構演算法。

memcpyopt:MemCpy 優化

這個過程會執行與消除 memcpy 呼叫或將儲存集轉換為 memsets 相關的各種轉換。

mergefunc:合併函數

這個過程會尋找可以合併的等效函數並將其合併。

在函數集中引入全序關係:我們定義了比較,可以回答每兩個函數中哪一個更大。它允許將函數排列成二元樹。

對於每個新函數,我們都會在樹中檢查等效函數。

如果存在等效函數,我們會合併這些函數。如果兩個函數都是可覆蓋的,我們會將功能移動到一個新的內部函數中,並為其留下兩個可覆蓋的 thunk。

如果沒有等效函數,則將此函數添加到樹中。

查詢例程的複雜度為 O(log(n)),而整個合併過程的複雜度為 O(n*log(n))。

閱讀這篇文章以了解更多詳細信息。

mergereturn:統一函數退出節點

確保函數中最多只有一條 ret 指令。此外,它還會跟踪哪個節點是 CFG 的新退出節點。

partial-inliner:部分內聯器

這個過程會執行部分內聯,通常是透過內聯圍繞函數主體的 if 語句來實現。

reassociate:重新關聯表達式

這個過程會以旨在促進更好的常量傳播、GCSE、LICM、PRE 等的順序重新關聯交換表達式。

例如:4 + (x + 5) ⇒ x + (4 + 5)

在這個演算法的實現中,常量被賦予等級 = 0,函數參數被賦予等級 = 1,其他值被賦予與當前函數的反向後序遍歷相對應的等級(從 2 開始),這有效地使深層迴圈中的值比不在迴圈中的值具有更高的等級。

rel-lookup-table-converter:相對查找表轉換器

這個過程會將查找表轉換為對 PIC 友好的相對查找表。

reg2mem:將所有值降級為堆棧位置

這個文件將所有寄存器降級為內存引用。它旨在作為 mem2reg 的逆向操作。通過轉換為 load 指令,唯一跨基本塊存活的值是 alloca 指令和 phi 節點之前的 load 指令。這樣做的目的是使 CFG 破解變得更加容易。為了使後續的破解更容易,入口塊被分成兩個,這樣所有引入的 alloca 指令(並且沒有其他指令)都在入口塊中。

sroa:聚合體的純量替換

眾所周知的聚合體純量替換轉換。如果可能,此轉換會將聚合類型(結構或數組)的 alloca 指令分解為每個成員的單獨 alloca 指令。然後,如果可能,它會將單獨的 alloca 指令轉換為簡潔的純量 SSA 形式。

sccp:稀疏條件常量傳播

稀疏條件常量傳播和合併,可以概括為:

  • 除非證明為非,否則假設值為常量

  • 除非證明為非,否則假設基本塊已失效

  • 證明值為常量,並將其替換為常量

  • 證明條件分支為無條件分支

請注意,此遍歷有一個使定義失效的習慣。在運行此遍歷之後的某個時間運行 DCE 遍歷是個好主意。

simplifycfg:簡化 CFG

執行無用代碼消除和基本塊合併。具體來說:

  • 刪除沒有前任的基本塊。

  • 如果只有一個前任且前任只有一個後繼,則將基本塊合併到其前任中。

  • 消除具有單個前任的基本塊的 PHI 節點。

  • 消除僅包含無條件分支的基本塊。

sink:代碼沉沒

此遍歷在可能的情況下將指令移至後繼塊中,這樣它們就不會在不需要其結果的路徑上執行。

simple-loop-unswitch:取消循環開關

此遍歷將包含循環不變條件分支的循環轉換為具有多個循環。例如,它將左側代碼轉換為右側代碼

for (...)                  if (lic)
    A                          for (...)
    if (lic)                       A; B; C
        B                  else
    C                          for (...)
                                   A; C

這可能會成倍增加代碼的大小(每次取消循環開關時都會加倍),因此我們僅在結果代碼小於閾值時才取消循環開關。

此遍歷期望在它之前運行 LICM 以將不變條件提升到循環之外,以使取消循環開關的機會顯而易見。

strip:從模塊中刪除所有符號

執行程式碼剝離。此轉換可以刪除

  • 虛擬暫存器名稱

  • 內部全域變數和函式的符號

  • 除錯資訊

請注意,此轉換會使程式碼的可讀性降低很多,因此它應該只在使用 strip 工具的情況下使用,例如減少程式碼大小或使反向工程程式碼變得更加困難。

strip-dead-debug-info:移除未使用符號的除錯資訊

執行程式碼剝離。類似於 strip,但只會移除未使用符號的除錯資訊。

strip-dead-prototypes:移除未使用的函式原型

此過程會循環處理輸入模組中的所有函式,尋找未使用的宣告並將其移除。未使用的宣告是指沒有可用實作的函式宣告(即,未使用的程式庫函式宣告)。

strip-debug-declare:移除所有 llvm.dbg.declare 內建函式和 #dbg_declare 記錄。 ——————————————————————-

執行程式碼剝離。類似於 strip,但只會移除 llvm.dbg.declare 內建函式。

strip-nondebug:從模組中移除所有符號,但 dbg 符號除外

執行程式碼剝離。類似於 strip,但會保留 dbg 資訊。

tailcallelim:尾端呼叫消除

此檔案會將當前函式的呼叫(自我遞迴)後跟 return 指令的程式碼轉換為分支到函式入口的程式碼,從而建立一個迴圈。此過程還實現了以下對基本演算法的擴展

  1. 呼叫和 return 之間的簡單指令不會阻止轉換發生,儘管目前分析無法支援移動任何真正有用的指令(只有無效的指令)。

  2. 此過程會將因關聯式運算式而無法進行尾端遞迴的函式轉換為使用累加器變數,從而將典型的 naive 階乘或費氏數列實作編譯成高效的程式碼。

  3. 如果函式返回 void,如果 return 返回呼叫返回的結果,或者如果函式在所有出口返回一個執行時常數,則會執行 TRE。return 返回其他值(例如常數 0)並仍然可以進行 TRE,儘管可能性很小。如果函式中*所有其他* return 指令都返回完全相同的值,則可以進行 TRE。

  4. 如果可以證明被呼叫者不會存取其呼叫者堆疊框架,則它們會被標記為符合尾端呼叫消除的條件(由程式碼產生器標記)。

工具過程

此章節介紹 LLVM 工具過程。

deadarghaX0r:無效參數駭客攻擊(僅供 BUGPOINT 使用;請勿使用)

與無效參數消除相同,但會刪除外部函式的參數。這僅供 bugpoint 使用。

extract-blocks:從模組中提取基本區塊(供 bugpoint 使用)

bugpoint 使用這個遍歷將模組中的所有區塊提取到它們自己的函式中。

instnamer:為匿名指令賦予名稱

這是一個小的工具遍歷,用於為指令賦予名稱,這在比較最佳化效果時非常有用,因為刪除未命名的指令會改變所有其他指令的編號,使差異非常嘈雜。

verify:模組驗證器

驗證 LLVM IR 代碼。這在對正在進行測試的最佳化之後運行非常有用。請注意,llvm-as 在發出位元碼之前會驗證其輸入,並且格式錯誤的位元碼可能會導致 LLVM 崩潰。因此,鼓勵所有語言前端在執行最佳化轉換之前驗證其輸出。

  1. 二元運算符的兩個參數都屬於同一類型。

  2. 驗證記憶體存取指令的索引是否與其他運算元匹配。

  3. 驗證算術和其他操作僅在第一類類型上執行。驗證移位和邏輯運算僅在整數上發生,例如。

  4. switch 語句中的所有常數都屬於正確的類型。

  5. 代碼採用有效的 SSA 形式。

  6. 將標籤放入任何其他類型(例如結構)或返回標籤是非法的。

  7. 只有 phi 節點可以自我引用:%x = add i32 %x%x 無效。

  8. PHI 節點必須為每個前驅節點都有一個條目,沒有額外的條目。

  9. PHI 節點必須是基本區塊中的第一個元素,全部組合在一起。

  10. PHI 節點必須至少有一個條目。

  11. 所有基本區塊都應僅以終止符指令結束,而不應包含它們。

  12. 函式的入口節點不能有前驅節點。

  13. 所有指令都必須嵌入到基本區塊中。

  14. 函式不能採用 void 類型的參數。

  15. 驗證函式的參數列表是否与其聲明的類型一致。

  16. 為 void 值指定名稱是非法的。

  17. 擁有沒有初始值的內部全域值是非法的。

  18. 擁有返回的值與函式返回值類型不一致的 ret 指令是非法的。

  19. 函式呼叫參數類型與函式原型匹配。

  20. 所有其他通過散佈在代碼中的斷言進行測試的內容。

請注意,這不提供完整的安全性驗證(如 Java),而只是嘗試確保代碼格式良好。

view-cfg:檢視函式的 CFG

使用 GraphViz 工具顯示控制流程圖。此外,-cfg-func-name=<substring> 選項可用於篩選要顯示的函式。將顯示包含指定子字串的所有函式。

view-cfg-only:檢視函式的 CFG(不含函式主體)

使用 GraphViz 工具顯示控制流程圖,但省略函式主體。此外,-cfg-func-name=<substring> 選項可用於篩選要顯示的函式。將顯示包含指定子字串的所有函式。

view-dom: 檢視函式的支配樹

使用 GraphViz 工具顯示支配樹。

view-dom-only: 檢視函式的支配樹(不含函式主體)

使用 GraphViz 工具顯示支配樹,但不包含函式主體。

view-post-dom: 檢視函式的後支配樹

使用 GraphViz 工具顯示後支配樹。

view-post-dom-only: 檢視函式的後支配樹(不含函式主體)

使用 GraphViz 工具顯示後支配樹,但不包含函式主體。

transform-warning: 回報遺漏的強制轉換

發出關於尚未套用強制轉換的警告(例如,來自 #pragma omp simd)。