使用 -opt-bisect-limit 除錯最佳化錯誤

簡介

-opt-bisect-limit 選項提供一種方法,可以停用所有高於指定限制的最佳化 pass,而無需修改 Pass 管理器的填充方式。此選項的目的是協助追蹤問題,這些問題發生在最佳化期間不正確的轉換導致不正確的執行時期行為。

此功能以選擇加入的方式實作。可以安全地跳過,同時仍允許正確程式碼產生的 Pass,會呼叫函數以檢查 opt-bisect 限制,然後再執行最佳化。必須執行或不修改 IR 的 Pass 不會執行此檢查,因此永遠不會被跳過。一般來說,這表示分析 pass、在 CodeGenOptLevel::None 執行的 pass,以及暫存器分配所需的 pass。

-opt-bisect-limit 選項可以與任何工具一起使用,包括使用核心 LLVM 函式庫進行最佳化和程式碼產生的前端,例如 clang。下面將討論調用此選項的確切語法。

此功能並非旨在取代其他除錯工具,例如 bugpoint。相反地,當重現問題需要複雜的建置基礎架構,以至於使用 bugpoint 不切實際,或者當重現失敗需要一系列難以使用 opt 和 llc 等工具複製的轉換時,它提供了一種替代方案。

入門

-opt-bisect-limit 命令列選項可以直接傳遞給諸如 opt、llc 和 lli 之類的工具。語法如下

<tool name> [other options] -opt-bisect-limit=<limit>

如果使用值 -1,工具將執行所有最佳化,但會為每個可以跳過的最佳化向 stderr 列印一則訊息,指出與該最佳化相關聯的索引值。若要跳過最佳化,請將要執行的最後一個最佳化的值作為 opt-bisect-limit 傳遞。將會跳過所有索引值較高的最佳化。

為了將 -opt-bisect-limit 選項與提供 LLVM 核心函式庫包裝函式的驅動程式一起使用,可能需要額外的字首選項,如驅動程式所定義。例如,若要將此選項與 clang 一起使用,則必須使用 “-mllvm” 字首。典型的 clang 調用看起來會像這樣

clang -O2 -mllvm -opt-bisect-limit=256 my_file.c

-opt-bisect-limit 選項也可以透過使用字首來應用於連結時間最佳化,以指示這是連結器的外掛程式選項。以下語法將為 LTO 轉換設定二分限制

# When using lld, or ld64 (macOS)
clang -flto -Wl,-mllvm,-opt-bisect-limit=256 my_file.o my_other_file.o
# When using Gold
clang -flto -Wl,-plugin-opt,-opt-bisect-limit=256 my_file.o my_other_file.o

LTO pass 由連結器調用的函式庫實例執行。因此,在主要驅動程式編譯階段中執行的任何 pass 都不會受到透過 ‘-Wl,-plugin-opt’ 傳遞的選項影響,而 LTO pass 也不會受到透過 ‘-mllvm’ 傳遞給驅動程式調用的 LLVM 調用的選項影響。

傳遞 -opt-bisect-print-ir-path=path/foo.ll 將在 -opt-bisect-limit 開始跳過 pass 時,將 IR 傾印到 path/foo.ll

二分索引值

與單一索引值相關聯的最佳化粒度是可變的。根據最佳化 pass 的檢測方式,該值可能與最佳化 pass 在調用它的 IR 單元上執行的所有轉換(例如,在 FunctionPass 的單次 runOnFunction 呼叫期間)一樣多,或與單次轉換一樣少。索引值也可能是巢狀的,因此如果 pass 的調用未被跳過,則該調用中的個別轉換可能仍會被跳過。

保證分配的值的順序從一次執行到下一次執行,直到並包括指定為限制的值,都保持穩定和一致。在限制值之上,跳過最佳化可能會導致編號發生變化,但由於所有高於限制的最佳化都被跳過,因此這不是問題。

當 opt-bisect 索引值指的是 pass 的 run 函數的整個調用時,pass 將在每次調用時查詢是否應跳過它,並且每次調用都將被分配一個唯一的值。例如,如果 FunctionPass 用於包含三個函數的模組,則當 pass 執行時,將為每個函數的 pass 分配不同的索引值。pass 可能在兩個函數上執行,但在第三個函數上跳過。

如果 pass 在內部對較小的 IR 單元執行操作,則必須對 pass 進行特殊檢測,以在此更精細的粒度層級啟用二分(詳情請參閱下文)。

範例用法

$ opt -O2 -o test-opt.bc -opt-bisect-limit=16 test.ll

BISECT: running pass (1) Simplify the CFG on function (g)
BISECT: running pass (2) SROA on function (g)
BISECT: running pass (3) Early CSE on function (g)
BISECT: running pass (4) Infer set function attributes on module (test.ll)
BISECT: running pass (5) Interprocedural Sparse Conditional Constant Propagation on module (test.ll)
BISECT: running pass (6) Global Variable Optimizer on module (test.ll)
BISECT: running pass (7) Promote Memory to Register on function (g)
BISECT: running pass (8) Dead Argument Elimination on module (test.ll)
BISECT: running pass (9) Combine redundant instructions on function (g)
BISECT: running pass (10) Simplify the CFG on function (g)
BISECT: running pass (11) Remove unused exception handling info on SCC (<<null function>>)
BISECT: running pass (12) Function Integration/Inlining on SCC (<<null function>>)
BISECT: running pass (13) Deduce function attributes on SCC (<<null function>>)
BISECT: running pass (14) Remove unused exception handling info on SCC (f)
BISECT: running pass (15) Function Integration/Inlining on SCC (f)
BISECT: running pass (16) Deduce function attributes on SCC (f)
BISECT: NOT running pass (17) Remove unused exception handling info on SCC (g)
BISECT: NOT running pass (18) Function Integration/Inlining on SCC (g)
BISECT: NOT running pass (19) Deduce function attributes on SCC (g)
BISECT: NOT running pass (20) SROA on function (g)
BISECT: NOT running pass (21) Early CSE on function (g)
BISECT: NOT running pass (22) Speculatively execute instructions if target has divergent branches on function (g)
... etc. ...

Pass 跳過實作

-opt-bisect-limit 的實作取決於個別 pass 選擇加入 opt-bisect 流程。管理此流程的 OptBisect 物件完全是被動的,並且不知道任何 pass 是如何實作的。當 pass 執行時,如果 pass 可以被跳過,它應該呼叫 OptBisect 物件以查看是否應該被跳過。

OptBisect 物件旨在透過 LLVMContext 存取,並且每個 Pass 基底類別都包含一個輔助函數,該函數抽象化了細節,以便使此檢查在所有 pass 中保持一致。這些輔助函數是

bool ModulePass::skipModule(Module &M);
bool FunctionPass::skipFunction(const Function &F);
bool LoopPass::skipLoop(const Loop *L);

MachineFunctionPass 應使用 FunctionPass::skipFunction(),如下所示

bool MyMachineFunctionPass::runOnMachineFunction(Function &MF) {
  if (skipFunction(*MF.getFunction())
    return false;
  // Otherwise, run the pass normally.
}

除了與 OptBisect 類別檢查以查看是否應跳過 pass 之外,skipFunction()、skipLoop() 和 skipBasicBlock() 輔助函數還會尋找 “optnone” 函數屬性的存在。呼叫 pass 將無法確定它是因為存在 “optnone” 屬性而被跳過,還是因為已達到 opt-bisect-limit。這是理想的,因為在任何一種情況下,行為都應該相同。

大多數可以跳過的 LLVM pass 都已經以如上所述的方式進行了檢測。如果您正在新增一個新的 pass,或者認為您發現了一個未包含在 opt-bisect 流程中但應該包含的 pass,您可以按照上述方式新增它。

增加更細的粒度

一旦確定了執行不正確轉換的 pass,執行進一步的分析以確定哪個特定轉換導致問題可能會很有用。除錯計數器可用於此目的。