备注¶
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,并且它可以附带目标文件中的 section,以便轻松检索它。
基本 选项
内容 配置
其他支持备注的工具
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 名称。
如果指定了 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
中重用备注基础设施。对整个编译过程中创建的备注发射器使用相同的文件和格式。
以额外的抽象层为代价。