LLVM 位码文件格式

摘要

本文档描述了 LLVM 比特流文件格式以及 LLVM IR 如何编码到其中。

概述

通常所说的 LLVM 位码文件格式(有时也过时地称为字节码)实际上有两方面:一种比特流容器格式以及将LLVM IR 编码到该容器格式的方法。

比特流格式是一种结构化数据的抽象编码,在某些方面与 XML 非常相似。与 XML 一样,比特流文件包含标签和嵌套结构,并且您可以在不理解标签的情况下解析文件。与 XML 不同的是,比特流格式是一种二进制编码,并且与 XML 不同的是,它提供了一种机制,使文件能够自我描述“缩写”,这些缩写实际上是内容的大小优化。

LLVM IR 文件可以选择嵌入到包装器结构中,或者嵌入到原生目标文件中。这两种机制都使将额外数据与 LLVM IR 文件一起嵌入变得容易。

本文档首先描述 LLVM 比特流格式,然后描述包装器格式,最后描述 LLVM IR 文件使用的记录结构。

比特流格式

比特流格式实际上是一串比特流,具有非常简单的结构。此结构包含以下概念

  • 用于识别流内容的“幻数”。

  • 编码基本类型,例如变比特率整数。

  • ,用于定义嵌套内容。

  • 数据记录,用于描述文件中的实体。

  • 缩写,用于指定文件的压缩优化。

请注意,llvm-bcanalyzer 工具可用于转储和检查任意比特流,这对于理解编码非常有用。

幻数

比特流的前四个字节用作应用程序特定的幻数。通用位码工具可能会查看前四个字节以确定流是否为已知的流类型。但是,这些工具不应仅根据其幻数来确定比特流是否有效。新的应用程序特定比特流格式一直在开发中;工具不应仅仅因为它们具有迄今为止未见过的幻数而拒绝它们。

基本类型

比特流实际上由一串比特组成,这些比特按顺序读取,从每个字节的最低有效位开始。该流由一些基本值组成,这些基本值编码一串无符号整数值。这些整数以两种方式编码:作为定宽整数或作为变宽整数

定宽整数

定宽整数值将其低位直接输出到文件中。例如,一个 3 位整数值将 1 编码为 001。当字段的选项数量已知时,使用定宽整数。例如,布尔值通常使用 1 位宽整数进行编码。

变宽整数

变宽整数 (VBR) 值编码任意大小的值,针对值较小的情况进行了优化。给定一个 4 位 VBR 字段,任何 3 位值(0 到 7)都直接编码,高位设置为零。大于 N-1 位的值会将其位以一系列 N-1 位块的形式输出,其中除了最后一个块之外,所有块都设置高位。

例如,值 30 (0x1E) 在作为 vbr4 值输出时被编码为 62 (0b0011’1110)。从最低有效位开始的第一组四位表示值 6 (110),并带有一个延续部分(由高位为 1 表示)。下一组四位表示值 24 (011 << 3),没有延续。总和 (6+24) 产生值 30。

6 位字符

6 位字符将常用字符编码到固定的 6 位字段中。它们用以下 6 位值表示以下字符

'a' .. 'z' ---  0 .. 25
'A' .. 'Z' --- 26 .. 51
'0' .. '9' --- 52 .. 61
       '.' --- 62
       '_' --- 63

此编码仅适用于编码仅由上述字符组成的字符和字符串。它完全无法编码不在该集合中的字符。

字对齐

有时,将零位输出到比特流成为 32 位的倍数很有用。这确保了流中的位位置可以表示为 32 位字的倍数。

缩写 ID

比特流是数据记录的顺序序列。这两者都以一个缩写 ID 开头,该 ID 编码为一个定宽字段。宽度由当前块指定,如下所述。缩写 ID 的值指定内置 ID(具有特殊含义,如下所述)或流本身为当前块定义的缩写 ID 之一。

内置缩写 ID 集为

缩写 ID 4 及以上由流本身定义,并指定缩写记录编码

比特流中的块表示流的嵌套区域,并由内容特定的 ID 号标识(例如,LLVM IR 使用 ID 12 来表示函数体)。块 ID 0-7 保留用于标准块,其含义由位码定义;块 ID 8 及以上是应用程序特定的。嵌套块捕获其中编码的数据的层次结构,并且在解析文件时会将各种属性与块关联。块定义允许读取器以常数时间有效地跳过块,如果读取器需要块的摘要,或者如果它需要有效地跳过它不理解的数据。LLVM IR 读取器使用此机制跳过函数体,根据需要延迟读取它们。

在读取和编码流时,会为该块维护几个属性。特别是,每个块维护

  1. 当前缩写 ID 宽度。此值在流开始时为 2,并且每次输入块记录时都会设置。块条目指定块主体中缩写 ID 的宽度。

  2. 一组缩写。缩写可以在块内定义,在这种情况下,它们仅在该块中定义(子块或封闭块看不到该缩写)。缩写也可以在BLOCKINFO 块内定义,在这种情况下,它们在与BLOCKINFO 块描述的 ID 匹配的所有块中定义。

当输入子块时,会保存这些属性,并且新子块有自己的缩写集,以及自己的缩写 ID 宽度。当弹出子块时,会恢复保存的值。

ENTER_SUBBLOCK 编码

[ENTER_SUBBLOCK, blockidvbr8, newabbrevlenvbr4, <align32bits>, blocklen_32]

缩写 ID ENTER_SUBBLOCK 指定了一个新块记录的开始。 blockid 值被编码为一个 8 位 VBR 标识符,并指示正在进入的块的类型,可以是 标准块 或应用程序特定的块。 newabbrevlen 值是一个 4 位 VBR,它指定子块的缩写 ID 宽度。 blocklen 值是一个 32 位对齐的值,它指定子块的大小(以 32 位字为单位)。此值允许读取器一次跳过整个块。

END_BLOCK 编码

[END_BLOCK, <align32bits>]

缩写 ID END_BLOCK 指定当前块记录的结束。其结尾对齐到 32 位,以确保块的大小是 32 位的偶数倍。

数据记录

数据记录由记录代码和若干个(最多)64 位整数值组成。代码和值的解释是特定于应用程序的,并且在不同的块类型之间可能有所不同。记录可以使用未缩写记录或缩写进行编码。例如,在 LLVM IR 格式中,有一个记录编码了模块的目标三元组。代码是 MODULE_CODE_TRIPLE,记录的值是字符串中字符的 ASCII 码。

UNABBREV_RECORD 编码

[UNABBREV_RECORD, codevbr6, numopsvbr6, op0vbr6, op1vbr6, …]

UNABBREV_RECORD 提供了一个默认的回退编码,它既完全通用又极其低效。它可以通过将代码和操作数作为 VBR 发射来描述任意记录。

例如,将 LLVM IR 目标三元组作为未缩写记录发射需要发射 UNABBREV_RECORD 缩写 ID,用于 MODULE_CODE_TRIPLE 代码的 vbr6,用于字符串长度的 vbr6(等于操作数的数量),以及每个字符的 vbr6。因为没有字母的值小于 32,所以每个字母都需要至少作为两部分 VBR 发射,这意味着每个字母至少需要 12 位。这并不是一种有效的编码,但它完全通用。

缩写记录编码

[<abbrevid>, fields...]

缩写记录是一个缩写 ID,后面跟着一组根据 缩写定义 编码的字段。这允许记录比使用 UNABBREV_RECORD 类型编码的记录更密集地进行编码,并允许在流本身中指定缩写类型,从而允许文件完全自描述。缩写的实际编码定义如下。

记录代码(它是缩写记录的第一个字段)可以在缩写定义中编码(作为文字操作数)或在缩写记录中提供(作为固定或 VBR 操作数值)。

缩写

缩写是位流压缩的一种重要形式。其思想是为一类记录指定一次密集编码,然后使用该编码发射许多记录。将编码发射到文件中需要占用空间,但是当使用它的记录发射时,空间会得到恢复(希望加上一些)。

缩写可以根据客户端、根据文件动态确定。由于缩写存储在位流本身中,因此相同格式的不同流可以根据特定流的需求包含不同的缩写集。作为一个具体的例子,LLVM IR 文件通常为二元运算符发射一个缩写。如果特定 LLVM 模块不包含或包含很少的二元运算符,则不需要发射缩写。

DEFINE_ABBREV 编码

[DEFINE_ABBREV, numabbrevopsvbr5, abbrevop0, abbrevop1, …]

DEFINE_ABBREV 记录将缩写添加到此块范围内的当前定义的缩写列表中。此定义仅存在于此直接块内 - 在子块或包含块中不可见。缩写被隐式地分配 ID,从 4 开始(第一个应用程序定义的缩写 ID)。特定块类型的 BLOCKINFO 记录中定义的任何缩写都首先按顺序接收 ID,然后是块本身内定义的任何缩写。缩写数据记录引用此 ID 以指示它们正在调用哪个缩写。

缩写定义由 DEFINE_ABBREV 缩写 ID 后跟一个指定缩写操作数数量的 VBR,然后是缩写操作数本身组成。缩写操作数有三种形式。它们都以一位开始,该位指示缩写操作数是文字操作数(当位为 1 时)还是编码操作数(当位为 0 时)。

  1. 文字操作数 — [11, litvaluevbr8] — 文字操作数指定结果中的值始终是单个特定值。此特定值在指示它是文字操作数的位之后作为 vbr8 发射。

  2. 不带数据的编码信息 — [01, encoding3] — 不带额外数据的操作数编码仅作为其代码发射。

  3. 带数据的编码信息 — [01, encoding3, valuevbr5] — 带有额外数据的操作数编码作为其代码发射,后面跟着额外数据。

可能的操 作数编码为

  • 固定 (代码 1):字段应作为 固定宽度值 发射,其宽度由操作数的额外数据指定。

  • VBR (代码 2):字段应作为 可变宽度值 发射,其宽度由操作数的额外数据指定。

  • 数组 (代码 3):此字段是值的数组。数组操作数没有额外数据,但期望另一个操作数跟随它,指示数组的元素类型。在缩写记录中读取数组时,第一个整数是 vbr6,它指示数组长度,后面跟着数组的编码元素。数组只能作为缩写的最后一个操作数出现(除了给出数组类型的最后一个操作数)。

  • Char6 (代码 4):此字段应作为 char6 编码值 发射。此操作数类型不占用额外数据。Char6 编码通常用作数组元素类型。

  • Blob (代码 5):此字段作为 vbr6 发射,后面跟着填充到 32 位边界(用于对齐)和 8 位对象的数组。字节数组后面跟着尾部填充,以确保其总长度是 4 字节的倍数。这使得读取器能够非常有效地解码数据,而无需复制它:它可以使用映射到文件中的数据的指针并直接对其进行操作。Blob 只能作为缩写的最后一个操作数出现。

例如,LLVM 模块中的目标三元组被编码为 [TRIPLE, 'a', 'b', 'c', 'd'] 形式的记录。考虑如果位流发射了以下缩写条目

[0, Fixed, 4]
[0, Array]
[0, Char6]

在使用此缩写发射记录时,上述条目将作为

[4abbrevwidth, 24, 4vbr6, 06, 16, 26, 36]

这些值是

  1. 第一个值 4 是此缩写的缩写 ID。

  2. 第二个值 2 是 LLVM IR 文件 MODULE_BLOCK 块中 TRIPLE 记录的记录代码。

  3. 第三个值 4 是数组的长度。

  4. 其余的值是 "abcd" 的 char6 编码值。

使用此缩写,三元组仅以 37 位发射(假设缩写 ID 宽度为 3)。如果没有缩写,则需要更多空间来发射目标三元组。此外,由于 TRIPLE 值在缩写中没有作为文字发射,因此缩写也可以用于任何其他字符串值。

标准块

除了基本的块结构和记录编码之外,位流还定义了特定的内置块类型。这些块类型指定了如何解码流或其他元数据。将来,可能会添加新的标准块。块 ID 0-7 保留用于标准块。

#0 - BLOCKINFO 块

BLOCKINFO 块允许描述其他块的元数据。当前指定的记录为

[SETBID (#1), blockid]
[DEFINE_ABBREV, ...]
[BLOCKNAME, ...name...]
[SETRECORDNAME, RecordID, ...name...]

SETBID 记录(代码 1)指示正在描述哪个块 ID。SETBID 记录可以在整个块中多次出现以更改正在描述的块 ID。在任何其他记录之前都必须有一个 SETBID 记录。

标准 DEFINE_ABBREV 记录可以出现在 BLOCKINFO 块中,但与它们在普通块中的出现不同,缩写是为与我们正在描述的块 ID 匹配的块定义的,而不是 BLOCKINFO 块本身。在 BLOCKINFO 块中定义的缩写接收缩写 ID,如 DEFINE_ABBREV 中所述。

此块中可以可选地包含BLOCKNAME记录(代码 2)。记录的元素是块的字符串名称的字节。llvm-bcanalyzer 可以使用此功能以符号方式转储位代码文件。

此块中也可以可选地包含SETRECORDNAME记录(代码 3)。第一个操作数的值是记录 ID 号,记录的其余元素是记录的字符串名称的字节。llvm-bcanalyzer 可以使用此功能以符号方式转储位代码文件。

请注意,尽管BLOCKINFO块中的数据被描述为“元数据”,但它们包含的缩写对于解析对应块中的记录至关重要。跳过它们是不安全的。

位代码包装器格式

LLVM IR 的位代码文件可以可选地包装在一个简单的包装器结构中。此结构包含一个简单的头部,指示嵌入式 BC 文件的偏移量和大小。这允许在 BC 文件旁边存储其他信息。此文件头的结构如下所示:

[Magic32, Version32, Offset32, Size32, CPUType32]

每个字段都是以小端格式存储的 32 位字段(与位代码文件的其余字段相同)。Magic 数字始终为0x0B17C0DE,版本目前始终为0。Offset 字段是以字节为单位的文件中位代码流开始处的偏移量,Size 字段是以字节为单位的流的大小。CPUType 是一个目标特定的值,可用于编码目标的 CPU。

原生对象文件包装器格式

LLVM IR 的位代码文件也可以包装在原生对象文件(即 ELF、COFF、Mach-O)中。位代码必须存储在名为__LLVM,__bitcode(对于 MachO)或.llvmbc(对于其他对象格式)的对象文件的部分中。ELF 对象还支持一个用于FatLTO.llvm.lto部分,该部分包含适合 LTO 编译的位代码(即经过预链接 LTO 管道的位代码)。.llvmbc部分早于 LLVM 中的 FatLTO 支持,并且可能并不总是包含适合 LTO 的位代码(例如,来自-fembed-bitcode)。包装器格式对于在必须是包含其他部分中元数据的原生对象文件的中间对象中适应 LTO 编译管道很有用。

并非所有工具都支持此格式。例如,lld 和 gold 插件在链接对象文件时会忽略.llvmbc部分,但在传递正确的命令行选项时可以使用.llvm.lto部分。

LLVM IR 编码

LLVM IR 通过定义块和记录编码为比特流。它使用块来表示常量池、函数、符号表等。它使用记录来表示指令、全局变量描述符、类型描述符等。本文档未描述编写器使用的缩写集,因为这些缩写是在文件中完全自描述的,并且不允许读取器内置任何相关知识。

基础知识

LLVM IR 魔数

LLVM IR 文件的魔数为:

[‘B’8, ‘C’8, 0x04, 0xC4, 0xE4, 0xD4]

带符号的 VBR

可变宽度整数编码是一种有效地编码任意大小的无符号值的方法,但对于编码有符号值来说效率极低,因为有符号值会被视为最大无符号值。

因此,特定宽度的带符号 VBR 值的发出方式如下:

  • 正值作为指定宽度的 VBR 发出,但其值向左移一位。

  • 负值作为指定宽度的 VBR 发出,但其负值向左移一位,并且设置低位。

使用此编码,可以有效地发出小的正值和小负值。带符号的 VBR 编码用于CONSTANTS_BLOCK块中的CST_CODE_INTEGERCST_CODE_WIDE_INTEGER记录。它也用于MODULE_CODE_VERSION 1 中的 phi 指令操作数。

LLVM IR 块

LLVM IR 定义了以下块:

MODULE_BLOCK 内容

MODULE_BLOCK块(ID 8)是 LLVM 位代码文件的顶级块,位代码文件中的每个模块都必须包含一个。具有多模块位代码的位代码文件是有效的。除了包含有关模块的信息的记录(如下所述)外,MODULE_BLOCK块还可以包含以下子块:

MODULE_CODE_VERSION 记录

[VERSION, version#]

VERSION记录(代码 1)包含一个表示格式版本的值。目前支持版本 0、1 和 2。版本 0 和 1 之间的区别在于每个FUNCTION_BLOCK中指令操作数的编码。

在版本 0 中,每个由指令定义的值都会分配一个对该函数唯一的 ID。函数级值 ID 从NumModuleValues开始分配,因为它们与模块级值共享相同的命名空间。每个函数后值枚举器都会重置。当某个值是指令的操作数时,值 ID 用于表示操作数。对于大型函数或大型模块,这些操作数的值可能很大。

版本 1 中的编码尝试避免在常见情况下出现较大的操作数值。操作数不是直接使用值 ID,而是相对于当前指令进行编码。因此,如果操作数是由前一个指令定义的值,则操作数将编码为 1。

例如,而不是

#n = load #n-1
#n+1 = icmp eq #n, #const0
br #n+1, label #(bb1), label #(bb2)

版本 1 将指令编码为

#n = load #1
#n+1 = icmp eq #1, (#n+1)-#const0
br #1, label #(bb1), label #(bb2)

请注意,在示例中,常量操作数也使用相对编码,而基本块标签等操作数不使用相对编码。

前向引用将导致负值。这可能效率低下,因为操作数通常编码为无符号 VBR。但是,除了 phi 指令的情况外,前向引用很少见。对于 phi 指令,操作数被编码为带符号的 VBR以处理前向引用。

在版本 2 中,模块记录FUNCTIONGLOBALVARALIASIFUNCCOMDAT的含义发生变化,以便前两个操作数指定字符串表中字符串的偏移量和大小(请参阅STRTAB_BLOCK 内容),函数名称从值符号表中的FNENTRY记录中删除,并且顶级VALUE_SYMTAB_BLOCK可能仅包含FNENTRY记录。

MODULE_CODE_TRIPLE 记录

[TRIPLE, ...string...]

TRIPLE记录(代码 2)包含可变数量的值,表示target triple规范字符串的字节。

MODULE_CODE_DATALAYOUT 记录

[DATALAYOUT, ...string...]

DATALAYOUT记录(代码 3)包含可变数量的值,表示target datalayout规范字符串的字节。

MODULE_CODE_ASM 记录

[ASM, ...string...]

ASM记录(代码 4)包含可变数量的值,表示module asm字符串的字节,各个汇编块之间用换行符(ASCII 10)字符分隔。

MODULE_CODE_SECTIONNAME 记录

[SECTIONNAME, ...string...]

SECTIONNAME 记录(代码 5)包含可变数量的值,表示单个节名称字符串的字节。每个被引用的节名称(例如,模块内的全局变量或函数的 section 属性)都应该对应一个 SECTIONNAME 记录。这些记录可以通过 GLOBALVARFUNCTION 记录的 section 字段中的基于 1 的索引来引用。

MODULE_CODE_DEPLIB 记录

[DEPLIB, ...string...]

DEPLIB 记录(代码 6)包含可变数量的值,表示单个依赖库名称字符串的字节,即 deplibs 声明中提到的库之一。每个被引用的库名称都应该对应一个 DEPLIB 记录。

MODULE_CODE_GLOBALVAR 记录

[GLOBALVAR, strtab offset, strtab size, pointer type, isconst, initid, linkage, alignment, section, visibility, threadlocal, unnamed_addr, externally_initialized, dllstorageclass, comdat, attributes, preemptionspecifier]

GLOBALVAR 记录(代码 7)标记全局变量的声明或定义。操作数字段为:

  • strtab offset, strtab size:指定全局变量的名称。请参见STRTAB_BLOCK 内容

  • pointer type:用于指向此全局变量的指针类型的类型索引。

  • isconst:如果变量在模块内被视为常量,则为非零值;否则为零。

  • initid:如果非零,则为该变量的初始化程序的值索引加 1。

  • linkage:此变量的链接类型编码。

    • external:代码 0

    • weak:代码 1

    • appending:代码 2

    • internal:代码 3

    • linkonce:代码 4

    • dllimport:代码 5

    • dllexport:代码 6

    • extern_weak:代码 7

    • common:代码 8

    • private:代码 9

    • weak_odr:代码 10

    • linkonce_odr:代码 11

    • available_externally:代码 12

    • 已弃用:代码 13

    • 已弃用:代码 14

  • alignment*:变量请求的对齐方式的对数以 2 为底,加 1。

  • section:如果非零,则为 MODULE_CODE_SECTIONNAME 条目表中基于 1 的节索引。

  • visibility:如果存在,则为该变量可见性的编码。

    • default:代码 0

    • hidden:代码 1

    • protected:代码 2

  • threadlocal:如果存在,则为变量的线程局部存储模式的编码。

    • not thread local:代码 0

    • thread local; default TLS model:代码 1

    • localdynamic:代码 2

    • initialexec:代码 3

    • localexec:代码 4

  • unnamed_addr:如果存在,则为该变量的 unnamed_addr 属性的编码。

    • 不是 unnamed_addr:代码 0

    • unnamed_addr:代码 1

    • local_unnamed_addr:代码 2

  • dllstorageclass:如果存在,则为该变量的 DLL 存储类的编码。

    • default:代码 0

    • dllimport:代码 1

    • dllexport:代码 2

  • comdat:此函数的 COMDAT 编码。

  • attributes:如果非零,则为 AttributeLists 表中基于 1 的索引。

  • preemptionspecifier:如果存在,则为该变量的运行时抢占说明符的编码。

    • dso_preemptable:代码 0

    • dso_local:代码 1

MODULE_CODE_FUNCTION 记录

[FUNCTION, strtab offset, strtab size, type, callingconv, isproto, linkage, paramattr, alignment, section, visibility, gc, prologuedata, dllstorageclass, comdat, prefixdata, personalityfn, preemptionspecifier]

FUNCTION 记录(代码 8)标记函数的声明或定义。操作数字段为:

  • strtab offset, strtab size:指定函数的名称。请参见STRTAB_BLOCK 内容

  • type:描述此函数的函数类型的类型索引。

  • callingconv:调用约定编号:* ccc:代码 0 * fastcc:代码 8 * coldcc:代码 9 * anyregcc:代码 13 * preserve_mostcc:代码 14 * preserve_allcc:代码 15 * swiftcc :代码 16 * cxx_fast_tlscc:代码 17 * tailcc :代码 18 * cfguard_checkcc :代码 19 * swifttailcc :代码 20 * x86_stdcallcc:代码 64 * x86_fastcallcc:代码 65 * arm_apcscc:代码 66 * arm_aapcscc:代码 67 * arm_aapcs_vfpcc:代码 68

  • isproto*:如果此条目表示声明而不是定义,则为非零值。

  • linkage:此函数的链接类型编码。

  • paramattr:如果非零,则为 PARAMATTR_CODE_ENTRY 条目表中基于 1 的参数属性索引。

  • alignment:函数请求的对齐方式的对数以 2 为底,加 1。

  • section:如果非零,则为 MODULE_CODE_SECTIONNAME 条目表中基于 1 的节索引。

  • visibility:此函数的可见性编码。

  • gc:如果存在且非零,则为 MODULE_CODE_GCNAME 条目表中基于 1 的垃圾回收器索引。

  • unnamed_addr:如果存在,则为此函数的unnamed_addr 属性的编码。

  • prologuedata:如果非零,则为此函数的前导数据的值索引加 1。

  • dllstorageclass:此函数的dllstorageclass编码。

  • comdat:此函数的 COMDAT 编码。

  • prefixdata:如果非零,则为此函数的前缀数据的值索引加 1。

  • personalityfn:如果非零,则为此函数的个性函数的值索引加 1。

  • preemptionspecifier:如果存在,则为此函数的运行时抢占说明符编码。

MODULE_CODE_ALIAS 记录

[ALIAS, strtab offset, strtab size, alias type, aliasee val#, linkage, visibility, dllstorageclass, threadlocal, unnamed_addr, preemptionspecifier]

ALIAS 记录(代码 9)标记别名的定义。操作数字段为:

  • strtab offset, strtab size:指定别名的名称。请参见STRTAB_BLOCK 内容

  • alias type:别名的类型索引。

  • aliasee val#:被别名引用的值的索引。

  • linkage:此别名的链接类型编码。

  • visibility:如果存在,则为别名的可见性编码。

  • dllstorageclass:如果存在,则为别名的dllstorageclass编码。

  • threadlocal:如果存在,则为别名的线程局部属性编码。

  • unnamed_addr:如果存在,则为该别名的unnamed_addr属性的编码。

  • preemptionspecifier:如果存在,则为该别名的运行时抢占说明符编码。

MODULE_CODE_GCNAME 记录

[GCNAME, ...string...]

GCNAME 记录(代码 11)包含可变数量的值,表示单个垃圾回收器名称字符串的字节。模块内函数的 gc 属性中引用的每个垃圾回收器名称都应该对应一个 GCNAME 记录。这些记录可以通过 FUNCTION 记录的 gc 字段中基于 1 的索引来引用。

PARAMATTR_BLOCK 内容

PARAMATTR_BLOCK 块(id 9)包含一个表,其中包含描述函数参数属性的条目。这些条目由模块块 FUNCTION 记录的 paramattr 字段中基于 1 的索引引用,或在函数块 INST_INVOKEINST_CALL 记录的 attr 字段中引用。

PARAMATTR_BLOCK 中的条目被构造为确保每个条目都是唯一的(即,没有两个索引表示等效的属性列表)。

PARAMATTR_CODE_ENTRY 记录

[ENTRY, attrgrp0, attrgrp1, ...]

ENTRY 记录(代码 2)包含可变数量的值,这些值描述了一组唯一的函数参数属性。每个 attrgrp 值用作键,用于在 PARAMATTR_GROUP_BLOCK 块中描述的属性组表中查找条目。

PARAMATTR_CODE_ENTRY_OLD 记录

注意

这是属性的旧版编码,由 LLVM 3.2 及更早版本生成。根据 IR 向后兼容性 策略,保证当前 LLVM 版本可以理解它。

[ENTRY, paramidx0, attr0, paramidx1, attr1...]

ENTRY 记录(代码 1)包含偶数个值,这些值描述了一组唯一的函数参数属性。每个 paramidx 值指示表示哪组属性,其中 0 表示返回值属性,0xFFFFFFFF 表示函数属性,其他值表示基于 1 的函数参数。每个 attr 值是一个位图,其解释如下

  • 位 0: zeroext

  • 位 1: signext

  • 位 2: noreturn

  • 位 3: inreg

  • 位 4: sret

  • 位 5: nounwind

  • 位 6: noalias

  • 位 7: byval

  • 位 8: nest

  • 位 9: readnone

  • 位 10: readonly

  • 位 11: noinline

  • 位 12: alwaysinline

  • 位 13: optsize

  • 位 14: ssp

  • 位 15: sspreq

  • 位 16-31: align n

  • 位 32: nocapture

  • 位 33: noredzone

  • 位 34: noimplicitfloat

  • 位 35: naked

  • 位 36: inlinehint

  • 位 37-39: alignstack n,表示为请求的对齐方式以 2 为底的对数,加 1

PARAMATTR_GROUP_BLOCK 内容

PARAMATTR_GROUP_BLOCK 块(id 10)包含一个表,其中包含描述模块中存在的属性组的条目。这些条目可以在 PARAMATTR_CODE_ENTRY 条目中引用。

PARAMATTR_GRP_CODE_ENTRY 记录

[ENTRY, grpid, paramidx, attr0, attr1, ...]

ENTRY 记录(代码 3)包含 grpidparamidx 值,后面跟着可变数量的值,这些值描述了一组唯一的属性。 grpid 值是属性组的唯一键,可以在 PARAMATTR_CODE_ENTRY 条目中引用。 paramidx 值指示表示哪组属性,其中 0 表示返回值属性,0xFFFFFFFF 表示函数属性,其他值表示基于 1 的函数参数。

每个 attr 本身都表示为可变数量的值

kind, key [, ...], [value [, ...]]

每个属性要么是众所周知的 LLVM 属性(可能与其关联一个整数值),要么是任意字符串(可能与其关联一个任意字符串值)。 kind 值是一个整数代码,用于区分这些可能性

  • 代码 0:众所周知的属性

  • 代码 1:具有整数值的众所周知的属性

  • 代码 3:字符串属性

  • 代码 4:具有字符串值的字符串属性

对于众所周知的属性(代码 0 或 1), key 值是一个整数代码,用于标识该属性。对于具有整数参数的属性(代码 1), value 值指示该参数。

对于字符串属性(代码 3 或 4), key 值实际上是表示以 null 结尾的字符串的字节的可变数量的值。对于具有字符串参数的属性(代码 4), value 值类似地表示以 null 结尾的字符串的字节的可变数量的值。

整数代码映射到属性的方式如文件 LLVMBitCodes.h 中的 AttributeKindCodes 枚举中所述。

例如

enum AttributeKindCodes {
  // = 0 is unused
  ATTR_KIND_ALIGNMENT = 1,
  ATTR_KIND_ALWAYS_INLINE = 2,
  ...
  }

对应于

  • 代码 1: align(<n>)

  • 代码 2: alwaysinline

枚举和属性名称字符串之间的映射可以在文件 Attributes.td 中找到。

注意

allocsize 属性对其参数使用特殊的编码。它的两个参数(都是 32 位整数)被打包到一个 64 位整数值中(即 (EltSizeParam << 32) | NumEltsParam),其中 NumEltsParam 在未指定时取哨值 -1。

注意

vscale_range 属性对其参数使用特殊的编码。它的两个参数(都是 32 位整数)被打包到一个 64 位整数值中(即 (Min << 32) | Max),其中 Max 在未指定时取 Min 的值。

TYPE_BLOCK 内容

TYPE_BLOCK 块(id 17)包含构成类型运算符条目表的记录,用于表示 LLVM 模块中引用的类型。每个记录(NUMENTRY 除外)生成一个类型表条目,可以通过指令、常量、元数据、类型符号表条目或其他类型运算符记录中的基于 0 的索引引用。

TYPE_BLOCK 中的条目被构造为确保每个条目都是唯一的(即,没有两个索引表示结构上等效的类型)。

TYPE_CODE_NUMENTRY 记录

[NUMENTRY, numentries]

NUMENTRY 记录(代码 1)包含一个值,该值指示模块类型表中类型代码条目的总数。如果存在, NUMENTRY 应为块中的第一条记录。

TYPE_CODE_VOID 记录

[VOID]

VOID 记录(代码 2)向类型表中添加 void 类型。

TYPE_CODE_HALF 记录

[HALF]

HALF 记录(代码 10)向类型表中添加 half(16 位浮点数)类型。

TYPE_CODE_BFLOAT 记录

[BFLOAT]

BFLOAT 记录(代码 23)向类型表中添加 bfloat(16 位脑浮点数)类型。

TYPE_CODE_FLOAT 记录

[FLOAT]

FLOAT 记录(代码 3)向类型表中添加 float(32 位浮点数)类型。

TYPE_CODE_DOUBLE 记录

[DOUBLE]

DOUBLE 记录(代码 4)向类型表中添加 double(64 位浮点数)类型。

TYPE_CODE_LABEL 记录

[LABEL]

LABEL 记录(代码 5)向类型表中添加 label 类型。

TYPE_CODE_OPAQUE 记录

[OPAQUE]

OPAQUE 记录(代码 6)向类型表中添加 opaque 类型,其名称由之前遇到的 STRUCT_NAME 记录定义。请注意,不同的 opaque 类型不会被统一。

TYPE_CODE_INTEGER 记录

[INTEGER, width]

INTEGER 记录(代码 7)向类型表添加整数类型。单个 *width* 字段指示整数类型的宽度。

TYPE_CODE_POINTER 记录

[POINTER, pointee type, address space]

POINTER 记录(代码 8)向类型表添加指针类型。操作数字段为

  • pointee type:指向类型的类型索引

  • address space:如果提供,则为指向的对象所在的特定于目标的编号地址空间。否则,默认地址空间为零。

TYPE_CODE_FUNCTION_OLD 记录

注意

这是函数的旧版编码,由 LLVM 3.0 及更早版本生成。根据IR 向后兼容性策略,保证当前 LLVM 版本能够理解它。

[FUNCTION_OLD, vararg, ignored, retty, ...paramty... ]

FUNCTION_OLD 记录(代码 9)向类型表添加函数类型。操作数字段为

  • vararg:如果类型表示可变参数函数,则为非零值

  • ignored:此值字段仅用于向后兼容,将被忽略

  • retty:函数返回类型的类型索引

  • paramty:表示函数参数类型的零个或多个类型索引

TYPE_CODE_ARRAY 记录

[ARRAY, numelts, eltty]

ARRAY 记录(代码 11)向类型表添加数组类型。操作数字段为

  • numelts:此类型数组中的元素数

  • eltty:数组元素类型的类型索引

TYPE_CODE_VECTOR 记录

[VECTOR, numelts, eltty]

VECTOR 记录(代码 12)向类型表添加向量类型。操作数字段为

  • numelts:此类型向量中的元素数

  • eltty:向量元素类型的类型索引

TYPE_CODE_X86_FP80 记录

[X86_FP80]

X86_FP80 记录(代码 13)向类型表添加 x86_fp80(80 位浮点数)类型。

TYPE_CODE_FP128 记录

[FP128]

FP128 记录(代码 14)向类型表添加 fp128(128 位浮点数)类型。

TYPE_CODE_PPC_FP128 记录

[PPC_FP128]

PPC_FP128 记录(代码 15)向类型表添加 ppc_fp128(128 位浮点数)类型。

TYPE_CODE_METADATA 记录

[METADATA]

METADATA 记录(代码 16)向类型表添加 metadata 类型。

TYPE_CODE_X86_MMX 记录

[X86_MMX]

X86_MMX 记录(代码 17)已弃用,并作为 <1 x i64> 向量导入。

TYPE_CODE_STRUCT_ANON 记录

[STRUCT_ANON, ispacked, ...eltty...]

STRUCT_ANON 记录(代码 18)向类型表添加字面量结构体类型。操作数字段为

  • ispacked:如果类型表示打包结构体,则为非零值

  • eltty:表示结构体元素类型的零个或多个类型索引

TYPE_CODE_STRUCT_NAME 记录

[STRUCT_NAME, ...string...]

STRUCT_NAME 记录(代码 19)包含表示结构体名称字节的可变数量的值。下一个 OPAQUESTRUCT_NAMED 记录将使用此名称。

TYPE_CODE_STRUCT_NAMED 记录

[STRUCT_NAMED, ispacked, ...eltty...]

STRUCT_NAMED 记录(代码 20)向类型表添加已识别的结构体类型,其名称由之前遇到的 STRUCT_NAME 记录定义。操作数字段为

  • ispacked:如果类型表示打包结构体,则为非零值

  • eltty:表示结构体元素类型的零个或多个类型索引

TYPE_CODE_FUNCTION 记录

[FUNCTION, vararg, retty, ...paramty... ]

FUNCTION 记录(代码 21)向类型表添加函数类型。操作数字段为

  • vararg:如果类型表示可变参数函数,则为非零值

  • retty:函数返回类型的类型索引

  • paramty:表示函数参数类型的零个或多个类型索引

TYPE_CODE_X86_AMX 记录

[X86_AMX]

X86_AMX 记录(代码 24)向类型表添加 x86_amx 类型。

TYPE_CODE_TARGET_TYPE 记录

[TARGET_TYPE, num_tys, ...ty_params..., ...int_params... ]

TARGET_TYPE 记录(代码 26)向类型表添加目标扩展类型,其名称由之前遇到的 STRUCT_NAME 记录定义。操作数字段为

  • num_tys:参数中是类型(而不是整数)的数量

  • ty_params:表示类型参数的类型索引

  • int_params:对应于整数参数的数字。

CONSTANTS_BLOCK 内容

CONSTANTS_BLOCK 块(id 11)…

FUNCTION_BLOCK 内容

FUNCTION_BLOCK 块(id 12)…

除了下面描述的记录类型外,FUNCTION_BLOCK 块还可以包含以下子块

VALUE_SYMTAB_BLOCK 内容

VALUE_SYMTAB_BLOCK 块(id 14)…

METADATA_BLOCK 内容

METADATA_BLOCK 块(id 15)…

METADATA_ATTACHMENT 内容

METADATA_ATTACHMENT 块(id 16)…

STRTAB_BLOCK 内容

STRTAB 块(id 23)包含单个记录(STRTAB_BLOB,id 1),其中包含单个 blob 操作数,该操作数包含位代码文件的字符串表。

字符串表中的字符串没有以 null 结尾。记录的 *strtab offset* 和 *strtab size* 操作数指定字符串在字符串表中的字节偏移量和大小。

字符串表由位代码文件中所有前面的块使用,这些块后面没有其他插入的 STRTAB 块。通常,位代码文件只有一个字符串表,但如果它是通过多个位代码文件的二进制连接创建的,则它可能有多个。