仪器配置文件格式¶
概述¶
Clang 通过仪器支持两种类型的分析 [1]:基于前端和基于 IR 的,两者都可以支持各种用例 [2]。本文档描述了两种二进制序列化格式(原始和索引)来存储已检测配置文件,并特别强调了 IRPGO 用例,因为当特定头部字段和有效负载部分在不同用例中具有不同的解释方式时,文档将基于 IRPGO。
注意
前端生成的配置文件与覆盖率映射一起用于 基于源代码的代码覆盖率。 覆盖率映射格式 与配置文件格式不同。
原始配置文件格式¶
原始配置文件是通过运行已检测二进制文件生成的。来自可执行文件或共享库 [3] 的原始配置文件数据由头部和多个部分组成,每个部分都是内存转储。原始配置文件数据需要足够紧凑且生成速度快。
原始配置文件格式没有向后或向前版本兼容性保证。也就是说,编译器和工具 需要 特定的原始配置文件版本来解析配置文件。
要将配置文件反馈给编译器以进行优化构建(例如,通过 -fprofile-use
进行 IR 检测),必须将原始配置文件转换为索引格式。
通用存储布局¶
下面说明了原始配置文件数据格式的存储布局。基本上,当原始配置文件读入内存缓冲区时,部分的实际字节偏移是从布局中部分的顺序以及其前面所有部分的大小信息推断出来的。
+----+-----------------------+
| | Magic |
| +-----------------------+
| | Version |
| +-----------------------+
H | Size Info for |
E | Section 1 |
A +-----------------------+
D | Size Info for |
E | Section 2 |
R +-----------------------+
| | ... |
| +-----------------------+
| | Size Info for |
| | Section N |
+----+-----------------------+
P | Section 1 |
A +-----------------------+
Y | Section 2 |
L +-----------------------+
O | ... |
A +-----------------------+
D | Section N |
+----+-----------------------+
注意
部分可能会填充以满足特定的对齐要求。为简单起见,上面数据布局图和本文档的其余部分省略了仅用于填充目的的头部字段和数据部分。
头部¶
魔数
魔数编码配置文件格式(原始、索引或文本)。对于原始格式,魔数还编码生成配置文件的平台的字节序(大端或小端)和 C 指针大小(4 或 8 字节)。
工厂方法读取魔数以正确构造读取器,并在格式无法识别时返回错误。具体来说,工厂方法和原始配置文件读取器实现确保可以在具有相反字节序和/或其他 C 指针大小的平台上读取原始配置文件。
版本
低 32 位指定实际版本,最高 32 位指定配置文件的变体类型。基于 IR 的检测 PGO 和上下文相关的基于 IR 的检测 PGO 是两种变体类型。
BinaryIdsSize
二进制 ID 部分的字节大小。
NumData
配置文件元数据的数量。可以使用此字段计算 配置文件元数据 部分的字节大小。
NumCounter
配置文件计数器部分中条目的数量。可以使用此字段计算 计数器 部分的字节大小。
NumBitmapBytes
配置文件 位图 部分的字节数。
NamesSize
名称部分的字节数。
CountersDelta
此字段记录已检测二进制文件中 配置文件元数据 和计数器部分之间的内存地址差,即
start(__llvm_prf_cnts) - start(__llvm_prf_data)
。它与 CounterPtr 字段一起使用,以计算相对于
start(__llvm_prf_cnts)
的计数器偏移量。查看 计数器偏移量的计算 以获取可视化说明。BitmapDelta
此字段记录已检测二进制文件中 配置文件元数据 和位图部分之间的内存地址差,即
start(__llvm_prf_bits) - start(__llvm_prf_data)
。它与 BitmapPtr 一起使用,以查找配置文件数据记录的位图,其方式类似于 计数器偏移量的计算 中解释的计数器引用方式。
与 CountersDelta 字段类似,此字段在配置文件的非 PGO 变体中可能未使用。
NamesDelta
记录名称部分的内存地址。除原始配置文件读取器错误检查外,不使用。
NumVTables
记录二进制文件中已检测 vtable 条目的数量。用于 类型分析。
VNamesSize
记录虚拟表名称部分的字节大小。用于 类型分析。
ValueKindLast
记录值类型的数量。宏 VALUE_PROF_KIND 定义了值类型,并附带了类型的描述。
有效负载部分¶
二进制 ID¶
存储已检测二进制文件的二进制 ID,以将二进制文件与配置文件关联以进行源代码覆盖率。有关设计,请参阅 二进制 ID RFC。
配置文件元数据¶
此部分存储元数据,以将计数器和值配置文件映射回已检测代码区域(例如,IRPGO 的 LLVM IR)。
元数据的内存表示是 __llvm_profile_data。一些字段用于引用配置文件中其他部分的数据。这些字段的文档如下
NameRef
函数的 PGO 名称的 MD5。PGO 名称的格式为
[<filepath><delimiter>]<mangled-name>
,其中<filepath>
和<delimiter>
用于本地链接函数,以区分可能相同的函数。
FuncHash
函数的 IR 的校验和,考虑控制流图和已检测值站点。有关详细信息,请参阅 computeCFGHash。
CounterPtr
配置文件数据与相应计数器开始位置之间的内存地址差。计数器位置以这种方式(作为链接时常量)存储,以减少与直接快照符号地址相比的已检测二进制文件大小。有关更多信息,请参阅 提交 a1532ed。
注意
CounterPtr
可能代表非 IRPGO 用例的不同值。例如,对于 二进制配置文件关联,它表示计数器的绝对地址。如有疑问,请检查源代码。
BitmapPtr
配置文件数据与相应位图的起始地址之间的内存地址差。
注意
与 CounterPtr 类似,此字段可能代表非 IRPGO 用例的不同值。
FunctionPointer
记录已检测二进制文件运行时的函数地址。这用于在从原始配置文件转换为索引配置文件期间将间接调用的已检测调用方地址映射到
NameRef
。Values
在二维数组中表示值配置文件。第一维中的元素数量是跨所有类型的已检测值站点的数量。第一维中的每个元素都是链接列表的头部,第二维中的每个元素都是链接列表元素,携带
<profiled-value, count>
作为有效负载。这在编译器运行时写入值配置文件时使用。注意
前端和 IR PGO 检测都支持值分析,但并非在所有情况下都支持(例如, 轻量级检测)。
NumCounters
已检测函数的计数器数量。
NumValueSites
这是一个计数器数组,每个计数器表示函数中某一类型值的已检测站点的数量。
NumBitmapBytes
函数的位图字节数。
配置文件计数器¶
对于 PGO [4],特定 FuncHash 的已插桩函数内的计数器会连续存储,并且按照与插桩点选择一致的顺序排列。
如上所述,记录的计数器偏移量相对于配置文件元数据。那么函数计数器如何在原始配置文件数据中定位呢?
基本上,配置文件读取器会迭代配置文件元数据(来自 配置文件元数据 部分)并利用记录的相对距离,如下所示。
+ --> start(__llvm_prf_data) --> +---------------------+ ------------+
| | Data 1 | |
| +---------------------+ =====|| |
| | Data 2 | || |
| +---------------------+ || |
| | ... | || |
Counter| +---------------------+ || |
Delta | | Data N | || |
| +---------------------+ || | CounterPtr1
| || |
| CounterPtr2 || |
| || |
| || |
+ --> start(__llvm_prf_cnts) --> +---------------------+ || |
| ... | || |
+---------------------+ -----||----+
| Counter for | ||
| Data 1 | ||
+---------------------+ ||
| ... | ||
+---------------------+ =====||
| Counter for |
| Data 2 |
+---------------------+
| ... |
+---------------------+
| Counter for |
| Data N |
+---------------------+
在图中,
配置文件头记录
CounterDelta
,其值为start(__llvm_prf_cnts) - start(__llvm_prf_data)
。为了方便起见,我们将在下面将其称为CounterDeltaInitVal
。对于每个配置文件数据记录
ProfileDataN
,CounterPtr
记录为start(CounterN) - start(ProfileDataN)
,其中ProfileDataN
是__llvm_prf_data
中的第 N 个条目,而CounterN
代表相应的配置文件计数器。
每次读取器前进到下一个数据记录时,它都会 更新 CounterDelta
为减去一个 ProfileData
的大小。
对于对应于第一个数据记录的计数器,相对于计数器部分开头的字节偏移量计算为 CounterPtr1 - CounterDeltaInitVal
。当配置文件读取器前进到第二个数据记录时,请注意 CounterDelta
已更新为 CounterDeltaInitVal - sizeof(ProfileData)
。因此,相对于计数器部分开头的字节偏移量计算为 CounterPtr2 - (CounterDeltaInitVal - sizeof(ProfileData))
。
位图¶
此部分用于基于源代码的 修改条件/决策覆盖率 代码覆盖率。请查看 位图 RFC 以了解设计。
名称¶
此部分包含可能压缩的函数 PGO 名称的连接字符串。如果已压缩,则使用 zlib 库。
函数名称用作 PGO 数据哈希表中的键,当原始配置文件转换为索引配置文件时使用。它们对于 llvm-profdata
以人类可读的方式显示配置文件也至关重要。
虚表配置文件数据¶
此部分用于 类型分析。每个条目对应一个虚表,并由以下 C++ 结构体定义
struct VTableProfData {
// The start address of the vtable, collected at runtime.
uint64_t StartAddress;
// The byte size of the vtable. `StartAddress` and `ByteSize` specifies an address range to look up.
uint32_t ByteSize;
// The hash of vtable's (PGO) name
uint64_t MD5HashOfName;
};
在配置文件使用时,编译器会在排序的虚表地址范围内查找已分析的地址,并通过哈希名称将地址映射到特定的虚表。
虚表名称¶
此部分类似于上面的 函数名称 部分,但它包含已分析虚表的 PGO 名称。它是一个独立的部分,以便原始配置文件读取器可以通过访问相应配置文件数据部分直接找到每个名称集。
此部分存储在原始配置文件中,以便 llvm-profdata 可以以人类可读的方式显示配置文件。
值配置文件数据¶
此部分包含值分析的配置文件数据。
对应于配置文件元数据的值配置文件会作为一条记录连续序列化,并且值配置文件记录的存储顺序与相应的配置文件数据相同,以便原始配置文件读取器 前进 指向配置文件数据的指针和指向值配置文件记录的指针同时 [5] 以查找每个函数、每个 FuncHash 配置文件数据的值配置文件。
索引配置文件格式¶
索引配置文件由 llvm-profdata
生成。在索引配置文件中,函数数据被组织为磁盘上的哈希表,以便编译器可以在 IR 模块中查找函数的配置文件数据。
编译器和工具必须保持与索引配置文件的向后兼容性。也就是说,在较新版本的代码中构建的工具或编译器必须能够理解由较旧的工具或编译器生成的配置文件。
常规存储布局¶
ASCII 艺术描绘了索引配置文件的常规存储布局。具体来说,索引配置文件头描述了各个有效负载部分的字节偏移量。
+-----------------------+---+
| Magic | |
+-----------------------+ |
| Version | |
+-----------------------+ |
| HashType | H
+-----------------------+ E
| Byte Offset | A
+------ | of section A | D
| +-----------------------+ E
| | Byte Of fset | R
+-----------| of section B | |
| | +-----------------------+ |
| | | ... | |
| | +-----------------------+ |
| | | Byte Offset | |
+---------------| of section Z | |
| | | +-----------------------+---+
| | | | Profile Summary | |
| | | +-----------------------+ P
| | +------>| Section A | A
| | +-----------------------+ Y
| +---------->| Section B | L
| +-----------------------+ O
| | ... | A
| +-----------------------+ D
+-------------->| Section Z | |
+-----------------------+---+
注意
配置文件摘要部分位于有效负载的开头。它紧随标题之后,因此在读取标题后隐式地知道其位置。
标题¶
标题结构体 是事实来源,结构体字段应该解释标题中的内容。在高级别上,*Offset 字段记录部分字节偏移量,读取器使用这些偏移量来定位感兴趣的部分并跳过不感兴趣的部分。
注意
为了保持索引配置文件的向后兼容性,不应该从结构体定义中删除现有字段;字段顺序不应修改。新字段应附加到后面。
有效负载部分¶
(CS) 配置文件摘要¶
此部分紧随配置文件标题之后。它存储序列化的配置文件摘要。对于基于上下文敏感 IR 的插桩 PGO,此部分存储一个额外的配置文件摘要,对应于上下文敏感的配置文件。
函数数据¶
此部分将函数及其分析数据存储为磁盘上的哈希表。具有相同名称的函数的配置文件数据会组合在一起并共享一个哈希表条目(例如,这些函数可能来自不同的共享库)。它们对应的配置文件数据被组织成一系列键值对,其中键是 FuncHash,值是函数的已分析信息(由 InstrProfRecord 表示)。
MemProf 配置文件数据¶
此部分存储函数的内存分析数据。请参阅 MemProf 二进制序列化格式 RFC 以了解设计。
二进制 ID¶
此部分用于从原始配置文件中承载 二进制 ID 信息。
时间配置文件跟踪¶
此部分用于从原始配置文件中承载时间配置文件信息。请参阅 时间分析 以了解设计。
虚表名称¶
此部分用于将原始配置文件中的虚表名称存储在索引配置文件中。
与作为 函数数据 哈希表的键存储的函数名称不同,虚表名称需要存储在索引配置文件中的独立部分中。这样,llvm-profdata 就可以以人类可读的方式显示已分析的虚表信息。
配置文件数据用法¶
llvm-profdata
是用于显示和处理基于插桩的配置文件数据的命令行工具。有关支持的用法,请查看 llvm-profdata 文档。