Clang Offload Bundler

简介

对于异构单源编程语言,使用一个或多个 --offload-arch=<target-id> Clang 选项指定要为卸载代码区域生成的代码的目标 ID。

工具链可能对一个翻译单元执行多次编译,以生成主机和潜在的多个卸载设备的独立代码对象。 clang-offload-bundler 工具可用作工具链的一部分,将这些多个代码对象组合到一个捆绑的代码对象中。

工具链可能会使用捆绑的代码对象作为中间步骤,以便每个工具链步骤像传统非异构工具链一样消费和生成单个文件。 捆绑的代码对象包含主机和所有卸载设备的代码对象。

捆绑的代码对象也可以用来捆绑仅仅卸载的代码对象,并作为数据嵌入到主机代码对象中。 主机编译包括一个 init 函数,该函数将使用与卸载类型相对应的运行时(见 捆绑代码对象卸载类型)来加载主机程序执行时存在于设备上的适当卸载代码对象。

clang-offload-bundler 位于 clang/tools/clang-offload-bundler 中。

$ clang-offload-bundler -help
OVERVIEW: A tool to bundle several input files of the specified type <type>
referring to the same source file but different targets into a single
one. The resulting file can also be unbundled into different files by
this tool if -unbundle is provided.

USAGE: clang-offload-bundler [options]

OPTIONS:

Generic Options:

  --help                  - Display available options (--help-hidden for more)
  --help-list             - Display list of available options (--help-list-hidden for more)
  --version               - Display the version of this program

clang-offload-bundler options:

  --###                   - Print any external commands that are to be executed instead of actually executing them - for testing purposes.
  --allow-missing-bundles - Create empty files if bundles are missing when unbundling.
  --bundle-align=<uint>   - Alignment of bundle for binary files
  --check-input-archive   - Check if input heterogeneous archive is valid in terms of TargetID rules.
  --inputs=<string>       - [<input file>,...]
  --list                  - List bundle IDs in the bundled file.
  --outputs=<string>      - [<output file>,...]
  --targets=<string>      - [<offload kind>-<target triple>,...]
  --type=<string>         - Type of the files to be bundled/unbundled.
                            Current supported types are:
                              i   - cpp-output
                              ii  - c++-cpp-output
                              cui - cuda/hip-output
                              d   - dependency
                              ll  - llvm
                              bc  - llvm-bc
                              s   - assembler
                              o   - object
                              a   - archive of bundled files
                              gch - precompiled-header
                              ast - clang AST file
  --unbundle              - Unbundle bundled file into several output files.

使用

此工具可以使用以下方法进行捆绑

clang-offload-bundler -targets=triple1,triple2 -type=ii -inputs=a.triple1.ii,a.triple2.ii -outputs=a.ii

或者,它可以用来解绑

clang-offload-bundler -targets=triple1,triple2 -type=ii -outputs=a.triple1.ii,a.triple2.ii -inputs=a.ii -unbundle

支持的文件格式

支持多种文本和二进制文件格式进行捆绑/解绑。 见 支持的文件格式 以获取当前支持的输入格式列表。 使用 File Type 列根据输入文件的类型确定要传递给 --type 选项的值,这些值在捆绑/解绑时使用。

支持的文件格式

文件格式

文件类型

文本/二进制

CPP 输出

i

文本

C++ CPP 输出

ii

文本

CUDA/HIP 输出

cui

文本

依赖项

d

文本

LLVM

ll

文本

LLVM 位码

bc

二进制

汇编器

s

文本

对象

o

二进制

捆绑文件的存档

a

二进制

预编译头文件

gch

二进制

Clang AST 文件

ast

二进制

捆绑文本文件布局

文本文件格式通过带有魔术字符串和捆绑条目 ID 的注释连接起来。 表示代码对象捆绑文件的 BNF 语法是

<file>    ::== <bundle> | <bundle> <file>
<bundle>  ::== <comment> <start> <bundle_id> <eol> <bundle> <eol>
               <comment> end <bundle_id> <eol>
<start>   ::== OFFLOAD_BUNDLER_MAGIC_STR__START__
<end>     ::== OFFLOAD_BUNDLER_MAGIC_STR__END__
注释

构成捆绑的文件类型中用于开始单行注释的符号。 例如,对于 ll File Type,它是“;”,对于“s” File Type,它是“#”。

bundle_id

封闭捆绑的 捆绑条目 ID

eol

行尾字符。

捆绑

以支持的文本文件格式之一存储的代码对象。

OFFLOAD_BUNDLER_MAGIC_STR__

标记卸载数据存在的魔术字符串,即“__CLANG_OFFLOAD_BUNDLE__”。

捆绑二进制文件布局

捆绑代码对象的布局由下表定义

捆绑代码对象布局

字段

类型

大小(字节)

描述

魔术字符串

字符串

24

__CLANG_OFFLOAD_BUNDLE__

捆绑条目数

整数

8

捆绑条目的数量。

第一个捆绑条目代码对象偏移量

整数

8

从捆绑代码对象开头到第一个代码对象的字节偏移量。

第一个捆绑条目代码对象大小

整数

8

第一个代码对象的字节大小。

第一个捆绑条目 ID 长度

整数

8

第一个代码对象的捆绑条目 ID 的字符长度。

第一个捆绑条目 ID

字符串

第一个捆绑条目 ID 长度

第一个代码对象的捆绑条目 ID。 这是不以 NUL 结尾的。 见 捆绑条目 ID

...

第 n 个捆绑条目代码对象偏移量

整数

8

第 n 个捆绑条目代码对象大小

整数

8

第 n 个捆绑条目 ID 长度

整数

8

第 n 个捆绑条目 ID

字符串

第一个捆绑条目 ID 长度

第一个捆绑条目代码对象

字节

第一个捆绑条目代码对象大小

...

第 n 个捆绑条目代码对象

字节

第 n 个捆绑条目代码对象大小

捆绑条目 ID

捆绑代码对象中的每个条目(见 捆绑文本文件布局捆绑二进制文件布局)都有一个捆绑条目 ID,它指示条目的代码对象的类型以及管理它的运行时。

捆绑条目 ID 语法由以下 BNF 语法定义

<bundle-entry-id> ::== <offload-kind> "-" <target-triple> [ "-" <target-id> ]

哪里

卸载类型

负责管理捆绑条目代码对象的运行时。 见 捆绑代码对象卸载类型

捆绑代码对象卸载类型

卸载类型

描述

主机

主机代码对象。 clang-offload-bundler 始终将此条目作为第一个捆绑代码对象条目包含在内。 对于嵌入式捆绑代码对象,此条目不会被运行时使用,因此通常是一个空代码对象。

hip

HIP 语言的卸载代码对象。 当 clang-offload-bundler 用于将代码对象捆绑为工具链的中间步骤时,用于所有 HIP 语言卸载代码对象。 当 clang-offload-bundler 用于创建要由 HIP 运行时加载的fat binary时,也用于 AMD GPU 代码对象(在 ABI 版本 V4 之前)。 fat binary 可以直接从文件加载,也可以嵌入到主机代码对象中,作为名称为 .hip_fatbin 的数据部分。

hipv4

HIP 语言的卸载代码对象。 当 clang-offload-bundler 用于创建要由 HIP 运行时加载的fat binary时,用于至少具有 ABI 版本 V4 及更高版本的 AMD GPU 代码对象。 fat binary 可以直接从文件加载,也可以嵌入到主机代码对象中,作为名称为 .hip_fatbin 的数据部分。

openmp

OpenMP 语言扩展的卸载代码对象。

注意:hiphipv4 卸载类型的区别在历史上是基于的。 最初,这些名称可能表示代码对象 ABI 的不同版本。 然而,随着系统的发展,ABI 版本现在直接嵌入到代码对象本身中,使这些历史上的区别在解绑过程中变得无关紧要。 因此,hiphipv4 在当前实现中被视为兼容,从而方便地互换处理代码对象,而无需根据卸载类型进行区分。

目标三重

代码对象的目标三重。 见 目标三重

捆绑器接受带或不带可选环境字段的目标三重

<arch><sub>-<vendor>-<sys><arch><sub>-<vendor>-<sys>-<env>

但是,为了标准化消费位码捆绑的工具的输出,捆绑器编写的捆绑内部仅使用 4 字段目标三重

<arch><sub>-<vendor>-<sys>-<env>

目标 ID

代码对象的规范目标 ID。 仅当目标支持目标 ID 时才存在。 见 目标 ID

捆绑代码对象组成

  • 捆绑代码对象的每个条目都必须具有不同的捆绑条目 ID。

  • 对于同一处理器,可以有多个条目,前提是它们在目标功能设置上有所不同。

  • 如果有一个条目将目标功能指定为Any,那么对于同一处理器,所有条目都必须将该目标功能指定为Any

可能存在其他目标特定的限制。

捆绑条目 ID 的兼容性规则

可以使用其捆绑条目 ID 指定的代码对象在目标处理器上加载和执行,如果

  • 它们的卸载类型相同或兼容。

  • 它们的目标三重兼容。

  • 它们的目标 ID 兼容,如 目标 ID 的兼容性规则 中所定义。

目标 ID

目标 ID 用于指示处理器以及可选地由一组影响 ISA 生成的目标功能表示的配置。 如果目标支持目标 ID,或者如果仅目标三重足以指定 ISA 生成,则它就是目标特定的。

它与 -mcpu=<target-id>--offload-arch=<target-id> Clang 编译选项一起使用,以指定要生成的代码类型。

它也用作捆绑条目 ID 的一部分,以识别代码对象。 见 捆绑条目 ID

目标 ID 语法由以下 BNF 语法定义

<target-id> ::== <processor> ( ":" <target-feature> ( "+" | "-" ) )*

哪里

处理器

是目标特定的处理器或任何其他处理器名称。

目标功能

是处理器支持的目标功能名称。 每个目标功能在目标 ID 中最多出现一次,并且可以具有以下三种值之一

任何

通过从目标 ID 中省略目标特性来指定。使用指定目标特性的默认值的 target ID 编译的代码对象可以在配置了目标特性开启或关闭的处理器上加载并执行。

开启

+ 指定,表示目标特性已启用。使用指定目标特性开启的 target ID 编译的代码对象只能加载到配置了目标特性开启的处理器上。

关闭

- 指定,表示目标特性已禁用。使用指定目标特性关闭的 target ID 编译的代码对象只能加载到配置了目标特性关闭的处理器上。

目标 ID 的兼容性规则

如果满足以下条件,则认为针对某个目标 ID 编译的代码对象与某个目标兼容:

  • 它们拥有相同的处理器。

  • 它们的功能集如上定义的兼容。

目标 ID 有两种形式:

非规范形式

非规范形式用作用户命令的输入,以便为用户提供更多便利。它允许使用主处理器名称和备用处理器名称,并且目标特性可以按任何顺序指定。

规范形式

规范形式用于所有生成的输出,以便为使用信息的工具提供更多便利。它也用于工具之间内部传递信息。只使用主处理器名称,而不是备用处理器名称,并且目标特性按字母顺序指定。命令行工具将非规范形式转换为规范形式。

目标特定信息

以下内容提供了目标特定信息:

AMD GPU

AMD GPU 支持目标 ID 和目标特性。请参阅 AMDGPU 后端的用户指南,其中定义了支持的 处理器目标特性

大多数其他目标不支持目标 ID。

归档文件解包

异构设备归档 (HDA) 的解包是用来创建设备特定归档文件。HDA 采用与 GNU ar 工具兼容的格式,包含捆绑的设备二进制文件集合,其中每个捆绑文件将包含一个主机和一个或多个目标的设备二进制文件。输出的设备特定归档文件采用与 GNU ar 工具兼容的格式,包含针对特定目标的设备二进制文件集合。

Heterogeneous Device Archive, HDA = {F1.X, F2.X, ..., FN.Y}
where, Fi = Bundle{Host-DeviceBinary, T1-DeviceBinary, T2-DeviceBinary, ...,
                   Tm-DeviceBinary},
       Ti = {Target i, qualified using Bundle Entry ID},
       X/Y = \*.bc for AMDGPU and \*.cubin for NVPTX

Device Specific Archive, DSA(Tk) = {F1-Tk-DeviceBinary.X, F2-Tk-DeviceBinary.X, ...
                                    FN-Tk-DeviceBinary.Y}
where, Fi-Tj-DeviceBinary.X represents device binary of i-th bundled device
binary file for target Tj.

clang-offload-bundler 从异构设备归档文件中的捆绑设备二进制文件中提取针对给定目标的兼容设备二进制文件,并创建一个不包含捆绑的特定目标设备归档文件。

clang-offload-bundler 通过比较捆绑 ID 来确定设备二进制文件是否与目标兼容。如果满足以下条件,则认为两个捆绑 ID 兼容:

  • 它们的卸载类型相同。

  • 它们的 target triples 相同。

  • 它们的 target ID 相同。

创建异构设备归档文件

  1. 编译源文件以生成目标文件。

clang -O2 -fopenmp -fopenmp-targets=amdgcn-amd-amdhsa,amdgcn-amd-amdhsa,\
   nvptx64-nvidia-cuda, nvptx64-nvidia-cuda \
  -Xopenmp-target=amdgcn-amd-amdhsa -march=gfx906:sramecc-:xnack+ \
  -Xopenmp-target=amdgcn-amd-amdhsa -march=gfx906:sramecc+:xnack+ \
  -Xopenmp-target=nvptx64-nvidia-cuda -march=sm_70 \
  -Xopenmp-target=nvptx64-nvidia-cuda -march=sm_80 \
  -c func_1.c -o func_1.o

clang -O2 -fopenmp -fopenmp-targets=amdgcn-amd-amdhsa,amdgcn-amd-amdhsa,
  nvptx64-nvidia-cuda, nvptx64-nvidia-cuda \
  -Xopenmp-target=amdgcn-amd-amdhsa -march=gfx906:sramecc-:xnack+ \
  -Xopenmp-target=amdgcn-amd-amdhsa -march=gfx906:sramecc+:xnack+ \
  -Xopenmp-target=nvptx64-nvidia-cuda -march=sm_70 \
  -Xopenmp-target=nvptx64-nvidia-cuda -march=sm_80 \
  -c func_2.c -o func_2.o
  1. 通过组合所有目标文件来创建异构设备归档文件。

llvm-ar cr libFatArchive.a func_1.o func_2.o

提取设备特定归档文件

UnbundleArchive 以包含捆绑设备二进制文件的异构设备归档文件 (“.a”) 作为输入,以及一个卸载目标列表(不包括主机),并将设备二进制文件提取到每个卸载目标的新归档文件中。每个生成的归档文件都包含与该特定卸载目标兼容的所有设备二进制文件。HDA 中的设备二进制文件与目标之间的兼容性取决于它们的捆绑条目 ID 之间的兼容性,如 捆绑条目 ID 的兼容性规则 中所定义。

在兼容性测试期间,可能会出现以下情况:

  • 二进制文件与一个或多个目标兼容:将该二进制文件插入到每个兼容目标的设备特定归档文件中。

  • 二进制文件与任何目标都不兼容:跳过该二进制文件。

  • 一个或多个二进制文件与目标兼容:将所有二进制文件插入到目标的设备特定归档文件中。插入不需要排序。

  • 没有二进制文件与目标兼容:如果存在 allow-missing-bundles 选项,则为该目标创建一个空归档文件。否则,在不创建归档文件的情况下生成错误。

生成的归档文件不包含符号索引,设备二进制文件以 <<父捆绑名称>-<设备二进制文件的 target ID>> 的形式命名,其中 ‘:’ 将替换为 ‘_’。

用法

clang-offload-bundler --unbundle --inputs=libFatArchive.a -type=a \
 -targets=openmp-amdgcn-amdhsa-gfx906:sramecc+:xnack+, \
          openmp-amdgcn-amdhsa-gfx908:sramecc-:xnack+  \
 -outputs=devicelib-gfx906.a,deviceLib-gfx908.a

归档文件解包时的其他选项

-allow-missing-bundles

如果找不到兼容的设备二进制文件,则创建一个空归档文件。

-check-input-archive

检查输入的异构设备归档文件是否符合 捆绑代码对象的组成 中定义的组成规则,然后再创建设备特定归档文件。

-debug-only=CodeObjectCompatibility

详细打印 HDA 中设备二进制文件的捆绑条目 ID 与给定目标处理器的捆绑条目 ID 之间的匹配/不匹配比较(请参阅 捆绑条目 ID 的兼容性规则)。

压缩和解压缩

clang-offload-bundler 提供了压缩和解压缩整个捆绑文件的特性,利用了捆绑条目中的固有冗余。使用 -compress 命令行选项可以启用此压缩功能。

压缩的卸载捆绑文件以一个头部开头,后面是压缩的二进制数据。

  • 幻数(4 字节):

    这是一个唯一的标识符,用于区分压缩的卸载捆绑文件。该值为字符串 ‘CCOB’(Compressed Clang Offload Bundle)。

  • 版本号(16 位无符号整数):

    这表示压缩的卸载捆绑文件格式的版本。当前版本为 2

  • 压缩方法(16 位无符号整数):

    此字段指示使用的压缩方法。该值对应于 zlibzstd,以从 LLVM 压缩枚举转换的 16 位无符号整数表示。

  • 总文件大小(32 位无符号整数):

    这是文件总大小(以字节为单位),包括头部。在版本 2 及更高版本中可用。

  • 未压缩的二进制大小(32 位无符号整数):

    这是二进制数据在压缩之前的大小(以字节为单位)。

  • 哈希值(64 位无符号整数):

    这是一个 64 位截断的未压缩二进制数据的 MD5 哈希值。它用于验证和缓存目的。

  • 压缩数据:

    实际的压缩二进制数据位于头部之后。其大小可以从文件总大小减去头部大小推断出来。