LLVM bugpoint 工具:設計與使用¶
描述¶
bugpoint
縮小 LLVM 工具和 Pass 中問題的來源。它可以用於偵錯三種型態的故障:最佳化器當機、最佳化器錯誤編譯,或不良的原生碼產生(包括靜態和 JIT 編譯器中的問題)。其目標是將大型測試案例縮減為小型、有用的案例。例如,如果 opt
在最佳化檔案時當機,它將識別導致當機的最佳化(或最佳化組合),並將檔案縮減為觸發當機的小範例。
對於詳細的案例情境,例如偵錯 opt
,或其中一個 LLVM 程式碼產生器,請參閱 如何提交 LLVM 錯誤報告。
設計理念¶
bugpoint
的設計宗旨是成為一個有用的工具,而完全不需要任何 LLVM 基礎架構的 Hook。它可以與任何和所有 LLVM Pass 和程式碼產生器一起運作,並且不需要「知道」它們如何運作。因此,它可能看起來會做愚蠢的事情或錯過顯而易見的簡化。bugpoint
的設計也傾向於在編譯器偵錯過程中,以程式設計師的時間換取電腦時間;因此,它可能需要很長一段(無人值守的)時間來縮減測試案例,但我們認為這仍然是值得的。請注意,除非偵錯錯誤編譯,而程式的每次測試(需要執行它)都花費很長時間,否則 bugpoint
通常非常快速。
自動偵錯器選擇¶
bugpoint
讀取命令列上指定的每個 .bc
或 .ll
檔案,並將它們連結到一個單一模組中,稱為測試程式。如果在命令列上指定了任何 LLVM Pass,它會在測試程式上執行這些 Pass。如果任何 Pass 當機,或者它們產生格式錯誤的輸出(導致驗證器中止),bugpoint
會啟動當機偵錯器。
否則,如果未指定 -output
選項,bugpoint
會使用「安全」後端(假設它產生良好的程式碼)執行測試程式,以產生參考輸出。一旦 bugpoint
具有測試程式的參考輸出,它會嘗試使用選定的程式碼產生器執行它。如果選定的程式碼產生器當機,bugpoint
會在程式碼產生器上啟動 當機偵錯器。否則,如果產生的輸出與參考輸出不同,它會假設差異是由程式碼產生器故障引起的,並啟動程式碼產生器偵錯器。
最後,如果選定的程式碼產生器的輸出與參考輸出相符,bugpoint
會在所有 LLVM Pass 都已應用於測試程式後執行該測試程式。如果其輸出與參考輸出不同,它會假設差異是由其中一個 LLVM Pass 中的故障引起的,並進入錯誤編譯偵錯器。否則,bugpoint
無法偵錯任何問題。
當機偵錯器¶
如果最佳化器或程式碼產生器當機,bugpoint
將盡力縮減 Pass 列表(對於最佳化器當機)和測試程式的大小。首先,bugpoint
會找出哪個最佳化器 Pass 組合觸發了錯誤。當偵錯由 opt
暴露的問題時,這很有用,例如,因為它執行超過 38 個 Pass。
接下來,bugpoint
嘗試從測試程式中移除函數,以縮減其大小。在偵錯程序內最佳化時,它通常能夠將測試程式縮減為單一函數。一旦函數的數量減少,它會嘗試刪除控制流程圖中的各種邊緣,以盡可能縮減函數的大小。最後,bugpoint
會刪除任何個別的 LLVM 指令,即使缺少這些指令也不會消除故障。最後,bugpoint
應該會告訴您哪些 Pass 當機、提供您一個位元碼檔案,並提供您關於如何使用 opt
或 llc
重現故障的指示。
程式碼產生器偵錯器¶
程式碼產生器偵錯器嘗試縮小被選定程式碼產生器錯誤編譯的程式碼量。為此,它會將測試程式分割成兩部分:一部分使用「安全」後端(編譯成共享物件)編譯,另一部分使用 JIT 或靜態 LLC 編譯器執行。它使用多種技術來減少通過 LLVM 程式碼產生器推送的程式碼量,以縮小問題的潛在範圍。完成後,它會發出兩個位元碼檔案(分別稱為「test」[要使用程式碼產生器編譯] 和「safe」[要使用「安全」後端編譯]),以及重現問題的指示。程式碼產生器偵錯器假設「安全」後端產生良好的程式碼。
錯誤編譯偵錯器¶
錯誤編譯偵錯器的工作方式與程式碼產生器偵錯器類似。它的工作原理是將測試程式分割成兩部分,在一部分上執行指定的最佳化,將這兩部分連結回一起,然後執行結果。它嘗試縮小 Pass 列表到導致錯誤編譯的一個(或幾個)Pass,然後縮減正在被錯誤編譯的測試程式部分。錯誤編譯偵錯器假設選定的程式碼產生器運作正常。
使用 bugpoint 的建議¶
bugpoint
可能是一個非常有用的工具,但它有時以非顯而易見的方式運作。以下是一些提示和技巧
在程式碼產生器和錯誤編譯偵錯器中,
bugpoint
僅適用於具有確定性輸出的程式。因此,如果程式輸出argv[0]
、日期、時間或任何其他「隨機」資料,當輸出時,bugpoint
可能會將這些資料的差異誤解為錯誤編譯的結果。應暫時修改程式以停用可能因執行而異的輸出。在當機偵錯器中,
bugpoint
不區分縮減期間的不同當機。因此,如果發生新的當機或錯誤編譯,bugpoint
將繼續處理新的當機。如果您想堅持特定的當機,您應該編寫檢查腳本來驗證錯誤訊息,請參閱-compile-command
在 bugpoint - 自動測試案例縮減工具。在程式碼產生器和錯誤編譯偵錯器中,如果您手動修改程式或其輸入以減少執行時間,但仍然展現問題,偵錯速度會更快。
當開發新的最佳化時,
bugpoint
非常有用:它有助於快速追蹤回歸問題。但是,為了避免每次變更最佳化時都必須重新連結bugpoint
,請讓bugpoint
使用-load
選項動態載入您的最佳化。bugpoint
可以產生大量輸出並長時間執行。將程式的輸出捕獲到檔案通常很有用。例如,在 C shell 中,您可以執行$ bugpoint ... |& tee bugpoint.log
以在檔案
bugpoint.log
以及您的終端機上取得bugpoint
輸出的副本。bugpoint
無法偵錯 LLVM 連結器的問題。如果bugpoint
在您看到其「All input ok」訊息之前當機,您可以嘗試對同一組輸入檔案執行llvm-link -v
。如果它也當機,您可能遇到連結器錯誤。bugpoint
對於主動尋找 LLVM 中的錯誤很有用。使用-find-bugs
選項調用bugpoint
將導致指定的最佳化列表被隨機化並應用於程式。此過程將重複進行,直到找到錯誤或使用者終止bugpoint
為止。bugpoint
可以產生包含長名稱的 IR。在 IR 上執行opt -passes=metarenamer
以使用易於閱讀的、元語法名稱重新命名所有內容。或者,執行opt -passes=strip,instnamer
以使用非常短的(通常是純數字)名稱重新命名所有內容。
當 bugpoint 不足時該怎麼辦¶
有時,bugpoint
是不夠的。特別是,InstCombine 和 TargetLowering 都具有訪問者結構化的程式碼,其中包含許多潛在的轉換。如果使用 bugpoint 的過程讓您留下仍然太多的程式碼需要弄清楚,並且問題似乎出在 instcombine 中,則以下步驟可能會有所幫助。這些相同的技術也適用於 TargetLowering。
開啟 -debug-only=instcombine
並查看 instcombine 內正在觸發哪些轉換,方法是選取出包含「IC
」的行。
此時,您需要做出決定。轉換的數量是否夠少,足以使用偵錯器逐步執行它們?如果是,那麼請嘗試這樣做。
如果轉換太多,那麼來源修改方法可能會有所幫助。在這種方法中,您可以修改 instcombine 的原始碼,以僅停用正在對您的測試輸入執行的那些轉換,並對轉換集合執行二元搜尋。一組可以修改的位置是 InstCombiner
的「visit*
」方法(例如 visitICmpInst
),方法是在方法的首行新增「return false
」。
如果這樣仍然無法移除足夠的內容,那麼請變更 InstCombiner::DoOneIteration
的呼叫者 InstCombiner::runOnFunction
以限制迭代次數。
您現在也可能會發現使用「-stats
」很有用,以查看 instcombine 的哪些部分正在觸發。這可以引導您將額外的報告程式碼放在哪裡。
此時,如果轉換量仍然太大,那麼插入程式碼以限制是否執行 visit 函數中的程式碼主體可能會有所幫助。新增一個靜態計數器,每次調用函數時都會遞增。然後新增程式碼,該程式碼僅在所需的範圍內傳回 false。例如
static int calledCount = 0;
calledCount++;
LLVM_DEBUG(if (calledCount < 212) return false);
LLVM_DEBUG(if (calledCount > 217) return false);
LLVM_DEBUG(if (calledCount == 213) return false);
LLVM_DEBUG(if (calledCount == 214) return false);
LLVM_DEBUG(if (calledCount == 215) return false);
LLVM_DEBUG(if (calledCount == 216) return false);
LLVM_DEBUG(dbgs() << "visitXOR calledCount: " << calledCount << "\n");
LLVM_DEBUG(dbgs() << "I: "; I->dump());
可以新增到 visitXOR
以將 visitXor
限制為僅應用於呼叫 212 和 217。這來自一個實際的測試案例,並提出了一個重要的觀點——簡單的二元搜尋可能不足以解決問題,因為交互作用的轉換可能需要隔離多個呼叫。在 TargetLowering 中,使用 return SDNode();
而不是 return false;
。
現在轉換的數量已減少到可管理的數量,請嘗試檢查輸出,看看是否可以找出正在執行哪些轉換。如果可以找出,那麼請執行通常的偵錯。如果哪個程式碼對應於正在執行的轉換不明顯,請在基於呼叫計數停用後設定一個中斷點,並逐步執行程式碼。或者,您可以使用「printf
」樣式的偵錯來報告路點。