撰寫 LLVM Pass(傳統 PM 版本)¶
簡介 - 什麼是 Pass?¶
警告
本文件處理的是舊版 Pass 管理器。LLVM 使用新的 Pass 管理器來處理優化流程(程式碼生成流程仍然使用舊版 Pass 管理器),它有自己的 Pass 定義方式。如需更多詳細資訊,請參閱 撰寫 LLVM Pass 和 使用新的 Pass 管理器。
LLVM Pass 架構是 LLVM 系統的重要組成部分,因為 LLVM Pass 是編譯器中大多數有趣部分所在的位置。Pass 執行構成編譯器的轉換和優化,它們建立這些轉換所使用的分析結果,最重要的是,它們是一種用於編譯器程式碼的結構化技術。
所有 LLVM Pass 都是 Pass 類別的子類別,它們通過覆寫從 Pass
繼承的虛擬方法來實現功能。根據 Pass 的工作方式,您應該繼承自 ModulePass 、 CallGraphSCCPass、 FunctionPass 、 LoopPass 或 RegionPass 類別,這讓系統更了解 Pass 的功能及其如何與其他 Pass 結合。LLVM Pass 架構的主要功能之一是,它會根據 Pass 滿足的限制條件(由它們繼承的類別表示)來安排 Pass 以有效的方式運行。
Pass 類別和需求¶
設計新 Pass 時,首先要做的事情之一就是決定應該為 Pass 選擇哪個類別作為子類別。這裡我們討論可用的類別,從最通用到最具體。
為 Pass
選擇超類別時,您應該選擇盡可能**最具體**的類別,同時仍然能夠滿足列出的需求。這為 LLVM Pass 基礎結構提供了優化 Pass 運行方式所需的信息,以便生成的編譯器不會過慢。
ImmutablePass
類別¶
最簡單、最無聊的 Pass 類型是「ImmutablePass」類別。此 Pass 類型用於不需要運行、不更改狀態且永遠不需要更新的 Pass。這不是一種常見的轉換或分析類型,但可以提供有關當前編譯器配置的信息。
儘管這個 Pass 類別很少使用,但它對於提供有關當前編譯目標機器以及其他可能影響各種轉換的靜態信息非常重要。
ImmutablePass
永遠不會使其他轉換失效,永遠不會失效,也永遠不會被「運行」。
ModulePass
類別¶
ModulePass 類別是所有可使用的超類別中最通用的。繼承自 ModulePass
表示您的 Pass 將整個程式作為一個單元使用,以不可預測的順序引用函式主體,或添加和移除函式。由於不知道 ModulePass
子類別的行為,因此無法對其執行進行最佳化。
模組 Pass 可以使用函式層級的 Pass(例如支配樹),使用 getAnalysis
介面 getAnalysis<DominatorTree>(llvm::Function *)
來提供要取得分析結果的函式,如果函式 Pass 不需要任何模組或不可變的 Pass。請注意,這只能對已執行分析的函式執行,例如,在支配樹的情況下,您應該只要求定義函式的 DominatorTree
,而不是宣告。
要編寫正確的 ModulePass
子類別,請繼承自 ModulePass
並使用以下簽章覆寫 runOnModule
方法
runOnModule
方法¶
virtual bool runOnModule(Module &M) = 0;
runOnModule
方法執行 Pass 的主要工作。如果模組被轉換修改,則應返回 true
,否則返回 false
。
CallGraphSCCPass
類別¶
CallGraphSCCPass 由需要在呼叫圖上由下而上(被呼叫者在呼叫者之前)遍歷程式的 Pass 使用。繼承自 CallGraphSCCPass
提供了一些用於構建和遍歷 CallGraph
的機制,但也允許系統最佳化 CallGraphSCCPass
的執行。如果您的 Pass 符合以下概述的要求,並且不符合 FunctionPass 的要求,則應該繼承自 CallGraphSCCPass
。
TODO
:簡要說明 SCC、Tarjan 演算法和由下而上是什麼意思。
明確地說,CallGraphSCCPass 子類別是
… 不允許 檢查或修改當前 SCC 以及 SCC 的直接呼叫者和直接被呼叫者以外的任何
Function
。… 需要 保留當前的
CallGraph
物件,更新它以反映對程式所做的任何更改。… 不允許 從當前模組添加或移除 SCC,儘管它們可能會更改 SCC 的內容。
… 允許 從當前模組添加或移除全域變數。
… 允許 在 runOnSCC 的多次呼叫之間維持狀態(包括全域資料)。
在某些情況下,實作 CallGraphSCCPass
會稍微棘手,因為它必須處理包含多個節點的 SCC。 如果以下描述的所有虛擬方法修改了程式,則應返回 true
,否則返回 false
。
doInitialization(CallGraph &)
方法¶
virtual bool doInitialization(CallGraph &CG);
doInitialization
方法允許執行 CallGraphSCCPass
不允許執行的多數操作。它們可以新增和移除函式、取得函式的指標等。 doInitialization
方法旨在執行不依賴於所處理 SCC 的簡單初始化類型的操作。 doInitialization
方法呼叫不會排程為與任何其他 Pass 執行重疊(因此它應該非常快)。
runOnSCC
方法¶
virtual bool runOnSCC(CallGraphSCC &SCC) = 0;
runOnSCC
方法執行 Pass 的主要工作,如果模組被轉換修改,則應返回 true
,否則返回 false
。
doFinalization(CallGraph &)
方法¶
virtual bool doFinalization(CallGraph &CG);
doFinalization
方法是一種不常使用的方法,當 Pass 架構完成為正在編譯的程式中的每個 SCC 呼叫 runOnSCC 時,就會呼叫此方法。
FunctionPass
類別¶
與 ModulePass
子類別相比,FunctionPass 子類別確實具有可預測的局部行為,系統可以預期這種行為。 所有 FunctionPass
都會在程式中的每個函式上執行,而與程式中的所有其他函式無關。 FunctionPass
不需要以特定順序執行,並且 FunctionPass
不會修改外部函式。
明確地說,不允許 FunctionPass
子類別執行以下操作:
檢查或修改除目前正在處理的
Function
以外的任何Function
。從目前的
Module
新增或移除Function
。從目前的
Module
新增或移除全域變數。在 runOnFunction 的多次呼叫之間維持狀態(包括全域資料)。
實作 FunctionPass
通常很簡單。 FunctionPass
可以覆寫三個虛擬方法來完成它們的工作。所有這些方法如果修改了程式,應該返回 true
,如果沒有,則返回 false
。
doInitialization(Module &)
方法¶
virtual bool doInitialization(Module &M);
doInitialization
方法可以執行大多數 FunctionPass
不允許執行的操作。它們可以新增和移除函式、取得函式的指標等等。 doInitialization
方法設計用於執行不依賴於正在處理的函式的簡單初始化類型操作。 doInitialization
方法的呼叫不會與任何其他 Pass 的執行重疊(因此它應該非常快)。
一個關於如何使用此方法的好例子是 LowerAllocations Pass。這個 Pass 將 malloc
和 free
指令轉換為平台相關的 malloc()
和 free()
函式呼叫。它使用 doInitialization
方法來取得它需要的 malloc
和 free
函式的參考,如果需要,還會將原型添加到模組中。
runOnFunction
方法¶
virtual bool runOnFunction(Function &F) = 0;
您的子類別必須實作 runOnFunction
方法,以執行 Pass 的轉換或分析工作。和往常一樣,如果修改了函式,則應返回 true
值。
doFinalization(Module &)
方法¶
virtual bool doFinalization(Module &M);
doFinalization
方法是一個不常使用的方法,當 Pass 架構完成為正在編譯的程式中的每個函式呼叫 runOnFunction 後,就會呼叫此方法。
LoopPass
類別¶
所有 LoopPass
都會在函式中的每個 迴圈 上執行,而與函式中的所有其他迴圈無關。 LoopPass
會按照迴圈巢狀順序處理迴圈,以便最後處理最外層的迴圈。
LoopPass
子類別可以使用 LPPassManager
介面更新迴圈巢狀。實作迴圈 Pass 通常很簡單。 LoopPass
可以覆寫三個虛擬方法來完成它們的工作。所有這些方法如果修改了程式,應該返回 true
,如果沒有,則返回 false
。
一個 LoopPass
子類別,如果它打算作為主迴圈 Pass 流程的一部分運行,則需要保留其流程中其他迴圈 Pass 所需的所有相同*函式*分析。為了簡化這一點,LoopUtils.h
提供了一個 getLoopAnalysisUsage
函式。它可以在子類別的 getAnalysisUsage
覆寫中被呼叫,以獲得一致且正確的行為。類似地,INITIALIZE_PASS_DEPENDENCY(LoopPass)
將初始化這組函式分析。
doInitialization(Loop *, LPPassManager &)
方法¶
virtual bool doInitialization(Loop *, LPPassManager &LPM);
doInitialization
方法設計用於執行不依賴於正在處理的函式的簡單初始化類型操作。doInitialization
方法呼叫不會與任何其他 Pass 執行重疊(因此它應該非常快)。LPPassManager
介面應該用於訪問 Function
或 Module
級別的分析資訊。
runOnLoop
方法¶
virtual bool runOnLoop(Loop *, LPPassManager &LPM) = 0;
您的子類別必須實作 runOnLoop
方法,以執行 Pass 的轉換或分析工作。像往常一樣,如果函式被修改,則應返回 true
值。 LPPassManager
介面應該用於更新迴圈巢狀。
doFinalization()
方法¶
virtual bool doFinalization();
doFinalization
方法是一個不常使用的方法,當 Pass 架構完成對正在編譯的程式中的每個迴圈呼叫 runOnLoop 時,就會呼叫此方法。
RegionPass
類別¶
RegionPass
類似於 LoopPass,但它會在函式中的每個單一入口單一出口區域執行。 RegionPass
以巢狀順序處理區域,因此最外層的區域最後處理。
RegionPass
子類別可以使用 RGPassManager
介面更新區域樹。您可以覆寫 RegionPass
的三個虛擬方法來實作您自己的區域 Pass。如果這些方法修改了程式,則應返回 true
,如果沒有修改,則返回 false
。
doInitialization(Region *, RGPassManager &)
方法¶
virtual bool doInitialization(Region *, RGPassManager &RGM);
doInitialization
方法設計用於執行不依賴於所處理函數的簡單初始化類型操作。 doInitialization
方法呼叫不會排程為與任何其他 Pass 執行重疊(因此它應該非常快)。 RPPassManager
介面應該用於存取 Function
或 Module
級別的分析資訊。
runOnRegion
方法¶
virtual bool runOnRegion(Region *, RGPassManager &RGM) = 0;
您的子類別必須實作 runOnRegion
方法,以執行 Pass 的轉換或分析工作。 與往常一樣,如果區域被修改,則應返回 true 值。 RGPassManager
介面應該用於更新區域樹。
doFinalization()
方法¶
virtual bool doFinalization();
doFinalization
方法是一種不常使用的方法,當 Pass 架構完成對要編譯的程式中的每個區域呼叫 runOnRegion 時呼叫。
MachineFunctionPass
類別¶
MachineFunctionPass
是 LLVM 程式碼產生器的一部分,它在程式中每個 LLVM 函數的機器相關表示上執行。
程式碼產生器 Pass 由 TargetMachine::addPassesToEmitFile
和類似的例程特別註冊和初始化,因此它們通常不能從 opt 或 bugpoint 命令執行。
MachineFunctionPass
也是 FunctionPass
,因此適用於 FunctionPass
的所有限制也適用於它。 MachineFunctionPass
也有其他限制。 特別是,不允許 MachineFunctionPass
執行以下任何操作
修改或建立任何 LLVM IR
Instruction
、BasicBlock
、Argument
、Function
、GlobalVariable
、GlobalAlias
或Module
。修改
MachineFunction
以外的MachineFunction
。跨 runOnMachineFunction 的呼叫維護狀態(包括全域資料)。
runOnMachineFunction(MachineFunction &MF)
方法¶
virtual bool runOnMachineFunction(MachineFunction &MF) = 0;
runOnMachineFunction
可視為 MachineFunctionPass
的主要進入點;也就是說,您應該覆寫這個方法來執行 MachineFunctionPass
的工作。
runOnMachineFunction
方法會在 Module
中的每個 MachineFunction
上被呼叫,以便 MachineFunctionPass
可以對函式的機器依賴表示執行優化。如果您想取得正在處理的 MachineFunction
的 LLVM Function
,請使用 MachineFunction
的 getFunction()
存取器方法 - 但請記住,您不能從 MachineFunctionPass
修改 LLVM Function
或其內容。
Pass 註冊¶
Pass 使用 RegisterPass
範本註冊。範本參數是在命令列上用於指定應將 pass 添加到程式的 pass 名稱。第一個參數是 pass 的名稱,用於程式的 -help
輸出,以及 –debug-pass 選項產生的除錯輸出。
如果您希望您的 pass 易於傾印,則應該實作虛擬列印方法
print
方法¶
virtual void print(llvm::raw_ostream &O, const Module *M) const;
「分析」必須實作 print
方法,以便列印人類可讀的分析結果版本。這對於除錯分析本身以及其他人了解分析工作原理很有用。使用 opt -analyze
參數來呼叫此方法。
llvm::raw_ostream
參數指定要寫入結果的串流,而 Module
參數提供指向已分析程式的頂層模組的指標。但請注意,在某些情況下(例如從除錯器呼叫 Pass::dump()
),此指標可能是 NULL
,因此它只能用於增強除錯輸出,不應該依賴它。
指定 Pass 之間的交互作用¶
PassManager
的主要職責之一是確保 Pass 之間正確交互作用。因為 PassManager
試圖 優化 Pass 的執行,所以它必須知道 Pass 之間如何交互作用以及各個 Pass 之間存在哪些依賴關係。為了追蹤這一點,每個 Pass 都可以聲明在當前 Pass 之前需要執行的 Pass 集,以及被當前 Pass 廢止的 Pass 集。
通常,此功能用於要求在執行您的遍歷之前計算分析結果。執行任意轉換遍歷可能會使計算的分析結果失效,這正是失效集所指定的。如果遍歷沒有實現 getAnalysisUsage 方法,則預設為没有任何必要的遍歷,並且會使**所有**其他遍歷失效。
getAnalysisUsage
方法¶
virtual void getAnalysisUsage(AnalysisUsage &Info) const;
通過實現 getAnalysisUsage
方法,可以為您的轉換指定必要的和失效的集合。實現應該在 AnalysisUsage 對象中填寫有關哪些遍歷是必要的以及哪些遍歷不會失效的信息。為此,遍歷可以對 AnalysisUsage
對象調用以下任何方法
AnalysisUsage::addRequired<>
和 AnalysisUsage::addRequiredTransitive<>
方法¶
如果您的遍歷需要先執行先前的遍歷(例如分析),則可以使用其中一種方法來安排在您的遍歷之前運行它。LLVM 有許多不同類型的分析和遍歷可以被要求,範圍從 DominatorSet
到 BreakCriticalEdges
。例如,要求 BreakCriticalEdges
可以保證在運行您的遍歷時,CFG 中不會存在關鍵邊。
某些分析會鏈接到其他分析以完成其工作。例如,AliasAnalysis 實現需要 鏈接 到其他別名分析遍歷。在分析鏈接的情況下,應該使用 addRequiredTransitive
方法而不是 addRequired
方法。這會通知 PassManager
只要需要遍歷的遍歷處於活動狀態,就應該保持傳遞需要的遍歷處於活動狀態。
AnalysisUsage::addPreserved<>
方法¶
PassManager
的工作之一是優化分析的運行方式和時間。特別是,它會嘗試避免重新計算數據,除非有必要。因此,如果現有分析可用,則允許遍歷聲明它們保留(即,它們不會失效)現有分析。例如,簡單的常數折疊遍歷不會修改 CFG,因此它不可能影響支配樹分析的結果。默認情況下,假設所有遍歷都會使所有其他遍歷失效。
AnalysisUsage
類提供了幾種在某些情況下很有用的方法,這些方法與 addPreserved
相關。特別是,可以調用 setPreservesAll
方法來指示遍歷根本不修改 LLVM 程序(這對於分析是正確的),並且 setPreservesCFG
方法可以由更改程序中的指令但不修改 CFG 或終止符指令的轉換使用。
addPreserved
在進行 BreakCriticalEdges
之類的轉換時特別有用。這個 pass 知道如何更新一小部分迴圈和支配關係分析(如果存在),因此它可以保存它們,儘管它會修改 CFG。
getAnalysisUsage
的範例實作¶
// This example modifies the program, but does not modify the CFG
void LICM::getAnalysisUsage(AnalysisUsage &AU) const {
AU.setPreservesCFG();
AU.addRequired<LoopInfoWrapperPass>();
}
getAnalysis<>
和 getAnalysisIfAvailable<>
方法¶
您的類別會自動繼承 Pass::getAnalysis<>
方法,讓您可以存取您使用 getAnalysisUsage 方法宣告為必要的 pass。它採用單一模板參數來指定您想要的 pass 類別,並返回該 pass 的參考。例如
bool LICM::runOnFunction(Function &F) {
LoopInfo &LI = getAnalysis<LoopInfoWrapperPass>().getLoopInfo();
//...
}
此方法呼叫會返回所需 pass 的參考。如果您嘗試取得未在 getAnalysisUsage 實作中宣告為必要的分析,則可能會發生執行階段斷言失敗。此方法可以由您的 run*
方法實作或由 run*
方法呼叫的任何其他區域方法呼叫。
模組層級的 pass 可以使用此介面使用函數層級的分析資訊。例如
bool ModuleLevelPass::runOnModule(Module &M) {
//...
DominatorTree &DT = getAnalysis<DominatorTree>(Func);
//...
}
在上述範例中,pass 管理器會在返回所需 pass 的參考之前呼叫 DominatorTree
的 runOnFunction
。
如果您的 pass 能夠更新分析(如果存在)(例如,如上所述的 BreakCriticalEdges
),您可以使用 getAnalysisIfAvailable
方法,如果分析處於作用中狀態,則該方法會返回指向該分析的指標。例如
if (DominatorSet *DS = getAnalysisIfAvailable<DominatorSet>()) {
// A DominatorSet is active. This code will update it.
}
Pass 統計資料¶
Statistic 類別旨在提供一種簡單的方法來公開來自 pass 的各種成功指標。當在命令列上啟用 -stats
命令列選項時,這些統計資料會在執行結束時列印。如需詳細資訊,請參閱程式設計師手冊中的 統計資料章節。
PassManager 的功能¶
PassManager 類別 會取得一個 pass 清單,確保其 先決條件 設定正確,然後排程 pass 以有效率地執行。所有執行 pass 的 LLVM 工具都會使用 PassManager 來執行這些 pass。
PassManager 會執行兩項主要工作來嘗試減少一系列 pass 的執行時間
分享分析結果。
PassManager
會盡可能避免重新計算分析結果。這表示會持續追蹤已經可用的分析、已經失效的分析以及執行 pass 所需執行的分析。一項重要的工作是PassManager
會追蹤所有分析結果的確切生命週期,讓它能在分析結果不再需要時,釋放 用於保存分析結果的記憶體。將程式碼執行的 pass 流程化。
PassManager
會嘗試將一系列 pass 流程化,以取得更好的快取和記憶體使用行為。這表示,給定一系列連續的 FunctionPass,它會先在第一個函式上執行所有 FunctionPass,然後在第二個函式上執行所有 FunctionPasses,依此類推,直到整個程式碼都已通過所有 pass。這可以改善編譯器的快取行為,因為它一次只會觸碰單一函式的 LLVM 程式碼表示,而不是遍歷整個程式碼。它可以減少編譯器的記憶體消耗,因為例如一次只需要計算一個 DominatorSet。
PassManager
的效率直接受到其對所排程 pass 行為資訊的多寡影響。例如,對於未實作的 getAnalysisUsage 方法,「保留」集會刻意採取保守的做法。如果沒有在應該實作時實作,將會導致無法在 pass 執行期間保留任何分析結果。
PassManager
類別會公開一個 --debug-pass
命令列選項,這對於偵錯 pass 執行、查看運作方式以及診斷何時應該保留比目前更多分析結果很有用。(若要取得有關 --debug-pass
選項所有變化的資訊,請輸入「llc -help-hidden
」)。
例如,透過使用 –debug-pass=Structure 選項,我們可以查看預設的最佳化流程,例如(輸出已修剪)
$ llc -mtriple=arm64-- -O3 -debug-pass=Structure file.ll > /dev/null
(...)
ModulePass Manager
Pre-ISel Intrinsic Lowering
FunctionPass Manager
Expand large div/rem
Expand large fp convert
Expand Atomic instructions
SVE intrinsics optimizations
FunctionPass Manager
Dominator Tree Construction
FunctionPass Manager
Simplify the CFG
Dominator Tree Construction
Natural Loop Information
Canonicalize natural loops
(...)
releaseMemory
方法¶
virtual void releaseMemory();
PassManager
會自動判斷何時計算分析結果,以及要將其保留多久。由於 pass 物件本身的生命週期實際上就是整個編譯過程的持續時間,因此我們需要一些方法在分析結果不再有用時釋放它們。releaseMemory
虛擬方法就是用來執行此動作的方法。
如果您正在撰寫分析或任何其他保留大量狀態的 pass(供「需要」您的 pass 並使用 getAnalysis 方法的另一個 pass 使用),則應該實作 releaseMemory
來釋放用於維護此內部狀態的記憶體。這個方法會在類別的 run*
方法之後、您的 pass 中下一次呼叫 run*
之前呼叫。
動態載入 Pass 的註冊¶
在使用 LLVM 建構生產級別的工具時,*大小至關重要*,無論是為了發佈目的,還是為了在目標系統上運行時調節駐留代碼大小。因此,選擇性地使用某些 Pass,同時省略其他 Pass 並保持日後更改配置的靈活性變得非常重要。您希望能夠做到這一切,並且向用戶提供反饋。這就是 Pass 註冊發揮作用的地方。
Pass 註冊的基本機制是 MachinePassRegistry
類別和 MachinePassRegistryNode
的子類別。
MachinePassRegistry
的實例用於維護 MachinePassRegistryNode
物件的清單。這個實例維護清單並將新增和刪除傳達給命令行界面。
MachinePassRegistryNode
子類別的實例用於維護有關特定 Pass 的資訊。這些資訊包括命令行名稱、命令說明字串和用於創建 Pass 實例的函數地址。其中一個實例的全域靜態建構函數向相應的 MachinePassRegistry
*註冊*,靜態解構函數則 *取消註冊*。因此,靜態鏈接到工具中的 Pass 將在啟動時註冊。動態載入的 Pass 將在載入時註冊,在卸載時取消註冊。
使用現有註冊表¶
有一些預先定義的註冊表來追蹤指令排程 (RegisterScheduler
) 和暫存器分配 (RegisterRegAlloc
) 機器 Pass。在這裡,我們將描述如何 *註冊* 暫存器分配器機器 Pass。
實現您的暫存器分配器機器 Pass。在您的暫存器分配器 .cpp
檔案中添加以下 include
#include "llvm/CodeGen/RegAllocRegistry.h"
同樣在您的暫存器分配器 .cpp
檔案中,以以下形式定義一個創建器函數
FunctionPass *createMyRegisterAllocator() {
return new MyRegisterAllocator();
}
請注意,此函數的簽章應該與 RegisterRegAlloc::FunctionPassCtor
的類型相符。在同一個檔案中,添加「安裝」聲明,形式如下
static RegisterRegAlloc myRegAlloc("myregalloc",
"my register allocator help string",
createMyRegisterAllocator);
請注意,說明字串前的兩個空格在 -help
查詢時會產生整齊的結果。
$ llc -help
...
-regalloc - Register allocator to use (default=linearscan)
=linearscan - linear scan register allocator
=local - local register allocator
=simple - simple register allocator
=myregalloc - my register allocator help string
...
這樣就完成了。用戶現在可以自由使用 -regalloc=myregalloc
作為選項。註冊指令排程器與此類似,只是使用 RegisterScheduler
類別。請注意,RegisterScheduler::FunctionPassCtor
與 RegisterRegAlloc::FunctionPassCtor
有顯著差異。
要強制將您的暫存器分配器載入/鏈接到 llc/lli 工具中,請將您的創建器函數的全域聲明添加到 Passes.h
中,並在 llvm/Codegen/LinkAllCodegenComponents.h
中添加一個「偽」調用行。
創建新的註冊表¶
開始使用最簡單的方法是複製現有的註冊表;我們推薦 llvm/CodeGen/RegAllocRegistry.h
。關鍵是要修改類別名稱和 FunctionPassCtor
類型。
然後您需要宣告註冊表。範例:如果您的遍歷註冊表是 RegisterMyPasses
,則定義
MachinePassRegistry<RegisterMyPasses::FunctionPassCtor> RegisterMyPasses::Registry;
最後,宣告遍歷的命令列選項。範例
cl::opt<RegisterMyPasses::FunctionPassCtor, false,
RegisterPassParser<RegisterMyPasses> >
MyPassOpt("mypass",
cl::init(&createDefaultMyPass),
cl::desc("my pass option help"));
這裡的命令選項是「mypass
」,其中 createDefaultMyPass
是預設的建立器。
將 GDB 與動態載入的遍歷搭配使用¶
遺憾的是,將 GDB 與動態載入的遍歷搭配使用並不像它應該的那麼容易。首先,您無法在尚未載入的共用物件中設定斷點,其次,共用物件中的內嵌函式存在問題。以下是一些使用 GDB 除錯遍歷的建議。
為了方便討論,我假設您正在除錯由 opt 呼叫的轉換,儘管這裡描述的內容並不依賴於此。
在遍歷中設定斷點¶
您要做的第一件事是在 opt 程序上啟動 gdb
$ gdb opt
GNU gdb 5.0
Copyright 2000 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "sparc-sun-solaris2.6"...
(gdb)
請注意,opt 中包含許多除錯資訊,因此載入需要一些時間。請耐心等待。由於我們還無法在遍歷中設定斷點(共用物件要到執行時才會載入),因此我們必須執行程序,並讓它在呼叫遍歷之前停止,但在載入共用物件之後停止。最萬無一失的方法是在 PassManager::run
中設定斷點,然後使用您想要的參數執行程序
$ (gdb) break llvm::PassManager::run
Breakpoint 1 at 0x2413bc: file Pass.cpp, line 70.
(gdb) run test.bc -load $(LLVMTOP)/llvm/Debug+Asserts/lib/[libname].so -[passoption]
Starting program: opt test.bc -load $(LLVMTOP)/llvm/Debug+Asserts/lib/[libname].so -[passoption]
Breakpoint 1, PassManager::run (this=0xffbef174, M=@0x70b298) at Pass.cpp:70
70 bool PassManager::run(Module &M) { return PM->run(M); }
(gdb)
一旦 opt 在 PassManager::run
方法中停止,您就可以在遍歷中自由設定斷點,以便您可以追蹤執行或執行其他標準除錯操作。
其他問題¶
一旦您掌握了基本知識,GDB 就會出現一些問題,有些問題有解決方案,有些則沒有。
內嵌函式具有錯誤的堆疊資訊。一般來說,GDB 在取得堆疊追蹤和逐步執行內嵌函式方面做得相當不錯。但是,當動態載入遍歷時,它不知何故會完全失去這種能力。我知道的唯一解決方案是取消內嵌函式(將其從類別主體移至
.cpp
檔案)。重新啟動程序會中斷斷點。按照上述資訊操作後,您已成功在遍歷中設定了一些斷點。接下來,您重新啟動程序(例如,您再次輸入「
run
」),然後您開始收到有關無法設定斷點的錯誤。我發現「解決」此問題的唯一方法是刪除遍歷中已設定的斷點,執行程序,並在執行在PassManager::run
中停止後重新設定斷點。
希望這些技巧能幫助您處理常見的案例除錯情況。如果您想貢獻自己的技巧,請聯繫 Chris。