备注

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

  • 一个空终止字符串列表

可选

  • 序列化备注诊断的绝对文件路径:一个空终止字符串。

当元数据与备注分开序列化时,应存在文件路径并指向序列化备注的文件。

如果元数据仅充当备注的标题,则可以省略文件路径。

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容器中引用。

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

分离容器中的容器版本应该匹配,以确保文件格式正确。

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 文件之间优化更改的所有内容。

通常,此工具应用于在以下方面进行差异比较:

  • 新编译器 + 修复的源代码与旧编译器 + 修复的源代码

  • 修复的编译器 + 新源代码与修复的编译器 + 旧源代码

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

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

通常,专门的备注流将保存对在LLVMContext中设置的一个的引用,并将在其自己的诊断类型上操作。

例如,LLVM IR 传递将发出llvm::DiagnosticInfoOptimization*,这些传递将转换为llvm::remarks::Remark对象。然后,clang 可以设置自己的专门备注流,该流接收clang::Diagnostic对象。这可以让前端的各个组件使用与 LLVM 备注相同的技术发出备注。

这为我们提供了以下优势

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

  • lib/Remarks中重用备注基础结构。

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

以额外的抽象层为代价。