備註¶
LLVM 備註診斷簡介¶
LLVM 能夠從描述是否已執行或因特定原因而錯過優化的過程發出診斷,這應該讓使用者更深入地了解編譯器在編譯過程中做了什麼。
主要有三種類型的備註
已通過
描述編譯器成功執行的優化的備註。
- 範例:
foo inlined into bar with (cost=always): always inline attribute
已錯過
描述編譯器嘗試優化但無法執行的備註。
- 範例:
foo not inlined into bar because it should never be inlined (cost=never): noinline function attribute
分析
描述分析結果的備註,可以向使用者提供有關所產生程式碼的更多資訊。
- 範例:
16 stack bytes in function10 instructions in function
啟用優化備註¶
LLVM 中支援兩種模式來啟用優化備註:透過備註診斷或透過序列化備註。
備註診斷¶
優化備註可以作為診斷發出。如果需要,這些診斷將會傳播到前端,或由 llc 或 opt 等工具發出。
- -pass-remarks=<regex>¶
啟用名稱與給定 (POSIX) 正規表示式相符的過程的優化備註。
- -pass-remarks-missed=<regex>¶
啟用名稱與給定 (POSIX) 正規表示式相符的過程的已錯過優化備註。
- -pass-remarks-analysis=<regex>¶
啟用名稱與給定 (POSIX) 正規表示式相符的過程的優化分析備註。
序列化備註¶
雖然診斷在開發過程中很有用,但在編譯後(通常是在效能分析期間)參考優化備註通常更有用。
為此,LLVM 可以將每個編譯單元產生的註解序列化到一個檔案中,以便稍後使用。
預設情況下,序列化註解的格式為 YAML,並且可以在目標檔案中附帶一個 區段 以便於檢索。
基本 選項
內容 配置
其他支援註解的工具
llvm-lto
gold-plugin 和 lld
序列化模式¶
序列化備註有兩種可用的模式
分離式
在此模式中,備註和中繼資料是分別序列化的。用戶端負責先解析中繼資料,然後使用中繼資料正確解析備註。
獨立式
在此模式中,備註和中繼資料會序列化到同一個串流。中繼資料將始終位於備註之前。
編譯器不支援發出獨立備註。此模式更適用於連結器等後處理工具,這些工具可以合併整個專案的備註。
YAML 備註¶
序列化為 YAML 的典型備註如下所示
--- !<TYPE>
Pass: <pass>
Name: <name>
DebugLoc: { File: <file>, Line: <line>, Column: <column> }
Function: <function>
Hotness: <hotness>
Args:
- <key>: <value>
DebugLoc: { File: <arg-file>, Line: <arg-line>, Column: <arg-column> }
以下項目為必填
<TYPE>
:可以是Passed
、Missed
、Analysis
、AnalysisFPCommute
、AnalysisAliasing
、Failure
。<pass>
:發出此備註的遍次名稱。<name>
:來自<pass>
的備註名稱。<function>
:函式的修改名稱。
如果指定了 DebugLoc
項目,則需要以下欄位
<file>
<line>
<column>
如果指定了 arg
項目,則需要以下欄位
<key>
<value>
如果在 arg
項目中指定了 DebugLoc
項目,則需要以下欄位
<arg-file>
<arg-line>
<arg-column>
具有字串表的 YAML¶
YAML 序列化支援透過使用 yaml-strtab
格式來使用字串表。
此格式會將 YAML 輸出中的字串替換為表示字串表中索引的整數,這些字串表可以透過中繼資料單獨提供。
以下項目可以在遵守 YAML 規則的同時利用字串表
<pass>
<name>
<function>
<file>
<value>
<arg-file>
目前,opt-viewer 目錄 中的任何工具都不支援此格式。
YAML 中繼資料¶
與 YAML 格式一起使用的中繼資料是
一個魔術數字:"REMARKS\0"
版本號碼:一個小端序 uint64_t
字串表的總大小(不包括大小本身):小端序 uint64_t
以 null 終止的字串清單
選用
序列化備註診斷的絕對檔案路徑:一個以 null 終止的字串。
當中繼資料與備註分開序列化時,檔案路徑應該存在並指向備註序列化到的檔案。
如果中繼資料僅充當備註的標頭,則可以省略檔案路徑。
LLVM 位元流備註¶
此格式使用 LLVM 位元流 來序列化備註及其相關聯的元資料。
位元流備註流可以通过放置在開頭的魔術數字 "RMRK"
來識別。
序列化備註的格式由兩種不同的區塊類型組成
META_BLOCK¶
提供有關串流中其餘內容資訊的區塊。
預期只有一個區塊。具有多個元資料區塊是一個錯誤。
此區塊可以包含以下記錄
RECORD_META_CONTAINER_INFO
容器版本和類型。
版本:u32
類型:u2
RECORD_META_REMARK_VERSION
備註項目的版本。這可以獨立於容器版本進行變更。
版本:u32
RECORD_META_STRTAB
備註項目使用的字串表。字串表的格式是由
\0
分隔的字串序列。
RECORD_META_EXTERNAL_FILE
包含與此元資料相關聯的備註區塊的外部備註檔案路徑。這是一個絕對路徑。
REMARK_BLOCK¶
描述備註項目的區塊。
每個檔案允許 0 個或多個區塊。每個區塊都將依賴 META_BLOCK 才能正確解析。
此區塊可以包含以下記錄
RECORD_REMARK_HEADER
備註的標題。這包含有關備註的所有必要資訊。
類型
u3
備註名稱
VBR6(字串表索引)
遍歷名稱
VBR6(字串表索引)
函數名稱
VBR6(字串表索引)
RECORD_REMARK_DEBUG_LOC
對應備註的原始程式碼位置。此記錄是可選的。
檔案
VBR7(字串表索引)
行
u32
欄
u32
RECORD_REMARK_HOTNESS
備註的熱度。此記錄是可選的。
熱度 | VBR8(字串表索引)
RECORD_REMARK_ARG_WITH_DEBUGLOC
具有關聯偵錯位置的備註參數。
鍵
VBR7(字串表索引)
值
VBR7(字串表索引)
檔案
VBR7(字串表索引)
行
u32
欄
u32
RECORD_REMARK_ARG_WITHOUT_DEBUGLOC
具有關聯偵錯位置的備註參數。
鍵
VBR7(字串表索引)
值
VBR7(字串表索引)
備註容器¶
位元流備註設計用於兩種不同的模式
分離模式
分離模式是編譯期間通常使用的模式。它提供了一種在將某些元資料保留在記憶體中以在編譯產物(通常是目標檔)中發出的同時將備註項目序列化到串流的方法。
獨立模式
獨立模式通常在程式分發後儲存和使用。它包含允許在没有任何外部依賴項的情況下解析所有備註的所有資訊。
為了支援多種模式,該格式引入了位元流備註容器類型的概念。
SeparateRemarksMeta: 單獨發出的元資料
此容器類型僅需要一個僅包含以下內容的 META_BLOCK
通常,這會在目標檔中的區段中發出,允許客戶端直接從中間產物中檢索備註及其相關聯的元資料。
SeparateRemarksFile: 單獨發出的備註項目
此容器類型僅需要一個僅包含以下內容的 META_BLOCK
此容器類型需要 0 個或多個 REMARK_BLOCK。
通常,這個會與物件檔案一起發出到一個側邊檔案中,並且能夠在不增加編譯器記憶體消耗的情況下串流處理。這個會由 RECORD_META_EXTERNAL_FILE 在 SeparateRemarksMeta 容器中的項目所參考。
當解析器嘗試解析包含獨立備註中繼資料的容器時,它應該解析版本和類型,然後將字串表保留在記憶體中,同時打開外部檔案、驗證其元數據並解析備註項目。
來自獨立容器的容器版本應該相符,才能形成格式良好的檔案。
獨立: 一起發出的 中繼資料 和 備註 項目
此容器類型僅需要一個僅包含以下內容的 META_BLOCK
此容器類型需要 0 個或多個 REMARK_BLOCK。
llvm-bcanalyzer 在不同容器類型上的完整輸出
SeparateRemarksMeta
<BLOCKINFO_BLOCK/>
<Meta BlockID=8 NumWords=13 BlockCodeSize=3>
<Container info codeid=1 abbrevid=4 op0=5 op1=0/>
<String table codeid=3 abbrevid=5/> blob data = 'pass\\x00key\\x00value\\x00'
<External File codeid=4 abbrevid=6/> blob data = '/path/to/file/name'
</Meta>
SeparateRemarksFile
<BLOCKINFO_BLOCK/>
<Meta BlockID=8 NumWords=3 BlockCodeSize=3>
<Container info codeid=1 abbrevid=4 op0=0 op1=1/>
<Remark version codeid=2 abbrevid=5 op0=0/>
</Meta>
<Remark BlockID=9 NumWords=8 BlockCodeSize=4>
<Remark header codeid=5 abbrevid=4 op0=2 op1=0 op2=1 op3=2/>
<Remark debug location codeid=6 abbrevid=5 op0=3 op1=99 op2=55/>
<Remark hotness codeid=7 abbrevid=6 op0=999999999/>
<Argument with debug location codeid=8 abbrevid=7 op0=4 op1=5 op2=6 op3=11 op4=66/>
</Remark>
獨立式
<BLOCKINFO_BLOCK/>
<Meta BlockID=8 NumWords=15 BlockCodeSize=3>
<Container info codeid=1 abbrevid=4 op0=5 op1=2/>
<Remark version codeid=2 abbrevid=5 op0=30/>
<String table codeid=3 abbrevid=6/> blob data = 'pass\\x00remark\\x00function\\x00path\\x00key\\x00value\\x00argpath\\x00'
</Meta>
<Remark BlockID=9 NumWords=8 BlockCodeSize=4>
<Remark header codeid=5 abbrevid=4 op0=2 op1=1 op2=0 op3=2/>
<Remark debug location codeid=6 abbrevid=5 op0=3 op1=99 op2=55/>
<Remark hotness codeid=7 abbrevid=6 op0=999999999/>
<Argument with debug location codeid=8 abbrevid=7 op0=4 op1=5 op2=6 op3=11 op4=66/>
</Remark>
opt-viewer¶
opt-viewer
目錄包含一些工具,可以將序列化備註視覺化並進行總結。
這些工具僅支援 yaml
格式。
opt-viewer.py¶
輸出一個 HTML 頁面,提供編譯器與程式互動的視覺化回饋。
- 範例:
$ opt-viewer.py my_yaml_file.opt.yaml$ opt-viewer.py my_build_dir/
opt-stats.py¶
輸出輸入集中關於最佳化備註的統計資訊。
- 範例:
$ opt-stats.py my_yaml_file.opt.yaml Total number of remarks 3 Top 10 remarks by pass: inline 33% asm-printer 33% prologepilog 33% Top 10 remarks: asm-printer/InstructionCount 33% inline/NoDefinition 33% prologepilog/StackSize 33%
opt-diff.py¶
產生一個新的 YAML 檔案,其中包含兩個 YAML 檔案之間最佳化的所有變更。
通常,此工具應用於比較
新編譯器 + 已修復的原始碼 與 舊編譯器 + 已修復的原始碼
已修復的編譯器 + 新原始碼 與 已修復的編譯器 + 舊原始碼
此差異檔可以使用 opt-viewer.py 顯示。
- 範例:
$ opt-diff.py my_opt_yaml1.opt.yaml my_opt_yaml2.opt.yaml -o my_opt_diff.opt.yaml $ opt-viewer.py my_opt_diff.opt.yaml
在物件檔案中發出備註診斷資訊¶
將為以下格式發出一個包含備註診斷中繼資料的區段
yaml-strtab
bitstream
這可以透過使用 -remarks-section=<bool>
旗標來覆蓋。
該區段的名稱為
__LLVM,__remarks
(MachO)
C API¶
LLVM 提供了一個函式庫,可透過名為 libRemarks
的共用函式庫來解析備註。
透過 C API 的典型用法如下
LLVMRemarkParserRef Parser = LLVMRemarkParserCreateYAML(Buf, Size);
LLVMRemarkEntryRef Remark = NULL;
while ((Remark = LLVMRemarkParserGetNext(Parser))) {
// use Remark
LLVMRemarkEntryDispose(Remark); // Release memory.
}
bool HasError = LLVMRemarkParserHasError(Parser);
LLVMRemarkParserDispose(Parser);
備註串流器¶
RemarkStreamer
介面用於統一所有可以產生備註的組件的備註序列化功能。
所有備註序列化都應透過主備註串流器,即在 LLVMContext
中設定的 llvm::remarks::RemarkStreamer
。該介面會接收轉換為 llvm::remarks::Remark
的備註物件,並負責使用請求的元數據類型等將其序列化為請求的格式。
通常,專用的備註串流器會持有對在 LLVMContext
中設定的備註串流器的引用,並會對其自身類型的診斷資訊進行操作。
例如,LLVM IR pass 會發出 llvm::DiagnosticInfoOptimization*
,這些資訊會被轉換為 llvm::remarks::Remark
物件。然後,clang 可以設定自己的專用備註串流器,它會接收 clang::Diagnostic
物件。這允許前端的各個元件使用與 LLVM 備註相同的技術來發出備註。
這給了我們以下優點:
組合性:在編譯過程中,多個元件可以設定它們專用的備註串流器,這些串流器都透過同一個主串流器發出備註。
重複使用
lib/Remarks
中的備註基礎結構。在整個編譯過程中,為建立的備註發出器使用相同的檔案和格式。
代價是額外的一層抽象。