LLVM Bitcode 文件格式

摘要

本文档描述了 LLVM 位流文件格式以及将 LLVM IR 编码到其中的方法。

概述

通常被称为 LLVM bitcode 文件格式(有时也过时地称为字节码)实际上是两件事:一个 位流容器格式 和一个 LLVM IR 的编码 到容器格式中。

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

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

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

位流格式

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

  • 一个“魔数”,用于标识流的内容。

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

  • ,用于定义嵌套内容。

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

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

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

魔数

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

基本类型

位流实际上由比特流组成,这些比特按顺序读取,从每个字节的最低有效位开始。该流由许多原始值组成,这些原始值编码一系列无符号整数值。这些整数以两种方式编码:要么作为 固定宽度整数,要么作为 可变宽度整数

固定宽度整数

固定宽度整数值将其低位直接发送到文件。例如,3 位整数值将 1 编码为 001。当字段具有已知数量的选项时,使用固定宽度整数。例如,布尔值通常使用 1 位宽的整数编码。

可变宽度整数

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

例如,当作为 vbr4 值发出时,值 30 (0x1E) 被编码为 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 保留用于 标准块,其含义由 Bitcode 定义;块 ID 8 及更大是特定于应用程序的。嵌套块捕获其中编码的数据的层次结构,并且在解析文件时,各种属性与块关联。块定义允许读取器在恒定时间内有效地跳过块,如果读取器想要块的摘要,或者如果它想要有效地跳过它不理解的数据。LLVM IR 读取器使用此机制来跳过函数体,并在需要时延迟读取它们。

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

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

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

当输入子块时,这些属性将被保存,新的子块有自己的一组缩写和自己的缩写 id 宽度。当弹出子块时,将恢复保存的值。

ENTER_SUBBLOCK 编码

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

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

END_BLOCK 编码

[END_BLOCK, <align32bits>]

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

缩写定义由 DEFINE_ABBREV 缩写 id 后跟一个 VBR 组成,该 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 块中定义的缩写按照 DEFINE_ABBREV 中描述的方式接收缩写 ID。

BLOCKNAME 记录(代码 2)可以选择性地出现在此块中。记录的元素是块的字符串名称的字节。llvm-bcanalyzer 可以使用它以符号方式转储 bitcode 文件。

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

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

Bitcode 封装格式

LLVM IR 的 Bitcode 文件可以选择封装在简单的封装结构中。此结构包含一个简单的标头,指示嵌入式 BC 文件的偏移量和大小。这允许将附加信息与 BC 文件一起存储。此文件标头的结构为

[Magic32, Version32, Offset32, Size32, CPUType32]

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

原生对象文件封装格式

LLVM IR 的 Bitcode 文件也可以封装在原生对象文件(即 ELF、COFF、Mach-O)中。bitcode 必须存储在对象文件的节中,MachO 的节名为 __LLVM,__bitcode,其他对象格式的节名为 .llvmbc。ELF 对象还额外支持用于 FatLTO.llvm.lto 节,其中包含适用于 LTO 编译的 bitcode(即,已经过预链接 LTO 管道的 bitcode)。.llvmbc 节早于 LLVM 中的 FatLTO 支持,并且可能并不总是包含适用于 LTO 的 bitcode(即来自 -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 发出,但取反后的值左移一位,并将低位设置为 1。

使用此编码,小的正值和小负值都可以有效地发出。带符号 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 offsetstrtab 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

    • deprecated : 代码 13

    • deprecated : 代码 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 属性编码

    • not 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 offsetstrtab 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:如果非零,则为此函数的 personality 函数的值索引加 1。

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

MODULE_CODE_ALIAS 记录

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

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

  • strtab offsetstrtab 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* 字段中,或函数块 INST_INVOKEINST_CALL 记录的 *attr* 字段中,通过从 1 开始的索引来引用。

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

PARAMATTR_CODE_ENTRY 记录

[ENTRY, attrgrp0, attrgrp1, ...]

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

PARAMATTR_CODE_ENTRY_OLD 记录

注意

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

[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 值实际上是可变数量的值,这些值表示以空字符结尾的字符串的字节。对于带有字符串参数的属性(代码 4), value 值类似地是可变数量的值,这些值表示以空字符结尾的字符串的字节。

整数代码映射到属性,如文件 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,则采用 sentinel 值 -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 及更早版本生成。当前 LLVM 版本保证可以理解它,如 IR 向后兼容性 策略中所指定。

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

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

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

  • 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:如果类型表示 varargs 函数,则为非零值

  • 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 操作数,该操作数包含 bitcode 文件的字符串表。

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

字符串表由 bitcode 文件中所有先前的块使用,这些块之后没有另一个介入的 STRTAB 块。通常,一个 bitcode 文件将只有一个字符串表,但如果它是通过多个 bitcode 文件的二进制连接创建的,则它可能具有多个字符串表。