備註¶
LLVM 備註診斷簡介¶
LLVM 能夠從 Pass 發出診斷訊息,描述是否已執行最佳化或因特定原因而錯過最佳化,這應讓使用者更深入了解編譯器在編譯流程期間所執行的操作。
主要有三種備註類型
已通過
描述編譯器執行的成功最佳化的備註。
- 範例:
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 的最佳化備註。
- -pass-remarks-missed=<regex>¶
啟用名稱與給定的 (POSIX) 正規表示式相符的 Pass 的錯過最佳化備註。
- -pass-remarks-analysis=<regex>¶
啟用名稱與給定的 (POSIX) 正規表示式相符的 Pass 的最佳化分析備註。
序列化備註¶
雖然診斷訊息在開發期間很有用,但在編譯後參考最佳化備註通常更有用,尤其是在效能分析期間。
為此,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>
: 發出此備註的 Pass 的名稱。<name>
: 來自<pass>
的備註名稱。<function>
: 函式的 Mangled Name。
如果指定了 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 格式一起使用的元數據是
Magic Number: “REMARKS\0”
版本號碼:小端 uint64_t
字串表總大小 (不包含大小本身):小端 uint64_t
以 Null 終止的字串列表
可選
序列化備註診斷的絕對檔案路徑:以 Null 終止的字串。
當元數據與備註分開序列化時,檔案路徑應存在並指向備註序列化到的檔案。
如果元數據僅作為備註的標頭,則可以省略檔案路徑。
LLVM 位元流備註¶
此格式使用 LLVM 位元流 來序列化備註及其關聯的元數據。
位元流備註串流可以透過放置在最開始的 Magic Number "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 (字串表索引)
Pass 名稱
VBR6 (字串表索引)
函式名稱
VBR6 (字串表索引)
RECORD_REMARK_DEBUG_LOC
對應備註的原始碼位置。此記錄是可選的。
檔案
VBR7 (字串表索引)
行
u32
列
u32
RECORD_REMARK_HOTNESS
備註的 Hotness。此記錄是可選的。
Hotness | VBR8 (字串表索引)
RECORD_REMARK_ARG_WITH_DEBUGLOC
具有關聯 Debug 位置的備註參數。
鍵
VBR7 (字串表索引)
值
VBR7 (字串表索引)
檔案
VBR7 (字串表索引)
行
u32
列
u32
RECORD_REMARK_ARG_WITHOUT_DEBUGLOC
具有關聯 Debug 位置的備註參數。
鍵
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 檔案之間最佳化的所有變更。
通常,此工具應用於比較
新編譯器 + 已修正原始碼 vs 舊編譯器 + 已修正原始碼
已修正編譯器 + 新原始碼 vs 已修正編譯器 + 舊原始碼
可以使用 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::remarks::Remark
物件的 llvm::DiagnosticInfoOptimization*
。然後,Clang 可以設定自己的專用備註串流器,該串流器接受 clang::Diagnostic
物件。這可以允許前端的各種組件使用與 LLVM 備註相同的技術發出備註。
這為我們帶來以下優勢
組合:在編譯流程期間,多個組件可以設定其專用的備註串流器,這些串流器都透過相同的主串流器發出備註。
在
lib/Remarks
中重複使用備註基礎架構。對在整個編譯過程中建立的備註發射器使用相同的文件和格式。
以額外的抽象層為代價。