備註

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 function
10 instructions in function

啟用最佳化備註

LLVM 支援兩種模式來啟用最佳化備註:透過備註診斷,或透過序列化備註。

備註診斷

最佳化備註可以作為診斷訊息發出。如果需要,這些診斷訊息將傳播到前端,或由 llcopt 等工具發出。

-pass-remarks=<regex>

啟用名稱與給定的 (POSIX) 正規表示式相符的 Pass 的最佳化備註。

-pass-remarks-missed=<regex>

啟用名稱與給定的 (POSIX) 正規表示式相符的 Pass 的錯過最佳化備註。

-pass-remarks-analysis=<regex>

啟用名稱與給定的 (POSIX) 正規表示式相符的 Pass 的最佳化分析備註。

序列化備註

雖然診斷訊息在開發期間很有用,但在編譯後參考最佳化備註通常更有用,尤其是在效能分析期間。

為此,LLVM 可以將每個編譯單元產生的備註序列化到一個檔案,以便稍後使用。

預設情況下,序列化備註的格式為 YAML,並且可以附帶物件檔案中的 區段,以便輕鬆檢索。

llcopt 支援以下選項

基本 選項

-pass-remarks-output=<filename>

啟用將備註序列化到 <filename> 中指定檔案。

預設情況下,輸出會序列化為 YAML

-pass-remarks-format=<format>

指定序列化備註的輸出格式。

支援的格式

內容 配置

-pass-remarks-filter=<regex>

只有名稱與給定的 (POSIX) 正規表示式相符的 Pass 會序列化到最終輸出。

-pass-remarks-with-hotness

使用 PGO 時,在最佳化備註中包含 Profile 計數。

-pass-remarks-hotness-threshold

發出最佳化備註所需的最小 Profile 計數。

其他支援備註的工具

llvm-lto

-lto-pass-remarks-output=<filename>
-lto-pass-remarks-filter=<regex>
-lto-pass-remarks-format=<format>
-lto-pass-remarks-with-hotness
-lto-pass-remarks-hotness-threshold

gold-pluginlld

-opt-remarks-filename=<filename>
-opt-remarks-filter=<regex>
-opt-remarks-format=<format>
-opt-remarks-with-hotness

序列化模式

有兩種模式可用於序列化備註

分離

在此模式下,備註和元數據會分開序列化。客戶端負責先解析元數據,然後使用元數據正確解析備註。

獨立

在此模式下,備註和元數據會序列化到同一個串流中。元數據將始終在備註之前。

編譯器不支援發出獨立備註。此模式更適合後處理工具,如連結器,它可以合併整個專案的備註。

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>: 可以是 PassedMissedAnalysisAnalysisFPCommuteAnalysisAliasingFailure

  • <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 中重複使用備註基礎架構。

  • 對在整個編譯過程中建立的備註發射器使用相同的文件和格式。

以額外的抽象層為代價。