備註

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

啟用優化備註

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

備註診斷

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

-pass-remarks=<regex>

啟用名稱與給定 (POSIX) 正規表示式相符的過程的優化備註。

-pass-remarks-missed=<regex>

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

-pass-remarks-analysis=<regex>

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

序列化備註

雖然診斷在開發過程中很有用,但在編譯後(通常是在效能分析期間)參考優化備註通常更有用。

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

預設情況下,序列化註解的格式為 YAML,並且可以在目標檔案中附帶一個 區段 以便於檢索。

llcopt 支援以下選項

基本 選項

-pass-remarks-output=<filename>

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

預設情況下,輸出序列化為 YAML 格式。

-pass-remarks-format=<format>

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

支援的格式

內容 配置

-pass-remarks-filter=<regex>

只有名稱與給定的 (POSIX) 正規表示式匹配的遍歷才會被序列化到最終輸出。

-pass-remarks-with-hotness

使用 PGO 時,在優化註解中包含程式碼熱度計數。

-pass-remarks-hotness-threshold

發出優化註解所需的最小程式碼熱度計數。

其他支援註解的工具

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>:發出此備註的遍次名稱。

  • <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_FILESeparateRemarksMeta 容器中的項目所參考。

當解析器嘗試解析包含獨立備註中繼資料的容器時,它應該解析版本和類型,然後將字串表保留在記憶體中,同時打開外部檔案、驗證其元數據並解析備註項目。

來自獨立容器的容器版本應該相符,才能形成格式良好的檔案。

獨立: 一起發出的 中繼資料 備註 項目

此容器類型僅需要一個僅包含以下內容的 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 中的備註基礎結構。

  • 在整個編譯過程中,為建立的備註發出器使用相同的檔案和格式。

代價是額外的一層抽象。