备注

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,并且它可以附带目标文件中的 section,以便轻松检索它。

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>: 可以是 Passed, Missed, Analysis, AnalysisFPCommute, AnalysisAliasing, Failure

  • <pass>: 发出此备注的 pass 的名称。

  • <name>: 来自 <pass> 的备注的名称。

  • <function>: 函数的 mangled 名称。

如果指定了 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 (字符串表索引)

Pass 名称

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

通常,这在目标文件的一个 section 中发出,允许客户端直接从中间产品中检索备注及其关联的元数据。

SeparateRemarksFile: 单独发出的 备注 条目

此容器类型仅期望包含 META_BLOCK

此容器类型期望 0 个或多个 REMARK_BLOCK

通常,这在与目标文件并排的 side-file 中发出,并且旨在能够流式传输,而不会增加编译器的内存消耗。这由 RECORD_META_EXTERNAL_FILE 条目在 SeparateRemarksMeta 容器中引用。

当解析器尝试解析包含单独备注的元数据的容器时,它应解析版本和类型,然后将字符串表保留在内存中,同时打开外部文件,验证其元数据并解析备注条目。

来自单独容器的容器版本应匹配,以便具有格式良好的文件。

Standalone: 一起发出的 元数据 备注 条目

此容器类型仅期望包含 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

在目标文件中发出备注诊断

将为以下格式发出包含有关备注诊断元数据的 section

  • yaml-strtab

  • bitstream

这可以通过使用标志 -remarks-section=<bool> 来覆盖。

该 section 被命名为

  • __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 接口用于统一所有可以生成备注的组件的备注序列化功能。

所有备注序列化都应通过主备注流 llvm::remarks::RemarkStreamer 进行,该流在 LLVMContext 中设置。 该接口接受转换为 llvm::remarks::Remark 的备注对象,并负责使用请求的元数据类型等将其序列化为请求的格式。

通常,专用备注流将保存对 LLVMContext 中设置的备注流的引用,并将对其自身的诊断类型进行操作。

例如,LLVM IR pass 将发出 llvm::DiagnosticInfoOptimization*,这些信息将转换为 llvm::remarks::Remark 对象。 然后,clang 可以设置其自己的专用备注流,该流接受 clang::Diagnostic 对象。 这可以允许前端的各种组件使用与 LLVM 备注相同的技术来发出备注。

这给我们带来了以下优势

  • 组合:在编译管道期间,多个组件可以设置其专用备注流,这些流都通过相同的主流发出备注。

  • lib/Remarks 中重用备注基础设施。

  • 对整个编译过程中创建的备注发射器使用相同的文件和格式。

以额外的抽象层为代价。