Multilib

简介

本文档描述了 Clang 中 Multilib 的实现方式。

什么是 Multilib?为什么要关心它?如果您正在进行 交叉编译,那么您无法使用本机系统头文件和库。为了解决这个问题,您可以结合使用 --sysroot-isystem-L 选项,将 Clang 指向适合您目标的目录。但是,当存在很多可能的目录可供选择时,就不一定能明显地选择哪个目录。Multilib 允许工具链设计人员为工具链赋予自动选择合适目录的能力,该能力基于用户提供给 Clang 的选项。例如,如果用户指定 --target=arm-none-eabi -mcpu=cortex-m4,工具链可以选择包含适合 Armv7E-M 的头文件和库的目录,因为它知道这是 Arm Cortex-M4 的合适架构。Multilib 还可以根据其他选项在相同架构的库之间进行选择。例如,如果用户指定 -fno-exceptions,则工具链可以选择在没有异常支持的情况下构建的库,从而减少生成的二进制文件的大小。

设计

Clang 支持 GCC 的 -print-multi-lib-print-multi-directory 选项。这些选项在 GCC 开发人员选项 中有描述。

在 Clang 中配置 Multilib 有两种方法:硬编码或通过配置文件。

硬编码 Multilib

可用的库可以在 Clang 中进行硬编码。通常,这是使用 MultilibBuilder 接口在 clang/include/clang/Driver/MultilibBuilder.h 中完成的。在 lib/Driver/ToolChains/Gnu.cpp 中有许多此类示例。本文档的其余部分将不再关注这种类型的 Multilib。

实验性通过配置文件实现 Multilib

某些 Clang 工具链支持从 multilib.yaml 配置文件加载 Multilib 配置。

一个 multilib.yaml 配置文件指定了哪些 Multilib 变体可用、它们的相对位置、用于构建它们的编译选项以及选择它们的标准。

Multilib 处理

Clang 经历以下步骤以从配置文件使用 Multilib

  1. 规范化命令行选项。Clang 可以通过不同的选项接受相同的信息 - 例如,--target=arm-none-eabi -march=armv7-m--target=armv7m-none-eabi 是等效的。Clang 在将命令行选项传递给 Multilib 系统之前会对其进行规范化。要查看为给定的一组命令行选项发出的标志,请使用 -print-multi-flags-experimental 命令行选项以及您要使用的其他选项。

  2. 从 sysroot 加载 multilib.yaml

  3. 生成额外的标志。 multilib.yaml 包含一个 Mappings 部分,该部分指定了如何根据从命令行选项派生的标志生成额外的标志。使用正则表达式匹配标志。这些正则表达式应使用 POSIX 扩展正则表达式语法。

  4. 将标志与 Multilib 变体进行匹配。如果生成的标志是为 Multilib 变体指定的标志的超集,则该变体被视为匹配。如果多个变体匹配,则工具链可以选择仅使用最后一个匹配的 Multilib 变体,或者可以使用所有匹配的变体,从而对它们进行 分层

  5. 生成 -isystem-L 选项。以相反的顺序遍历匹配的 Multilib 变体,并根据 Multilib 变体的目录生成 -isystem-L 选项。

Multilib 分层

当 Clang 选择 Multilib 变体时,它可能会发现有多个变体匹配。

由 ToolChain 子类决定在这种情况下该怎么做。允许两种选择

  1. 仅使用最后一个匹配的 Multilib 变体。此选项主要用于与之前的 Multilib 设计保持兼容。

  2. 使用所有匹配的变体,从而对它们进行分层。

此决定是针对每个 ToolChain 子类进行硬编码的。对于没有向后兼容性要求的 ToolChain 子类,建议选择后一种选项。

如果选择了后一种选项,则将为每个匹配的 Multilib 变体生成 -isystem-L 选项,以相反的顺序。

这意味着编译器或链接器将在具有给定文件的最后一个匹配的 Multilib 变体中找到文件。此行为允许具有部分文件集的 Multilib 变体。这意味着工具链可以与包含所有系统头文件和包含文件的基线 Multilib 变体一起分发,以及仅包含与基线变体不同的文件的更专门的 Multilib 变体。

例如,Multilib 变体可以与 -fno-exceptions 一起编译。此选项不影响头文件的内容,也不影响 C 库。因此,如果 ToolChain 子类支持 Multilib 分层,并且存在合适的基线 Multilib 变体,则 -fno-exceptions Multilib 变体只需要包含 C++ 库。

层叠 Multilib 作者有责任确保每一层中的头文件和库都完整到足以掩盖任何不兼容性。

稳定性

通过配置文件实现 Multilib 应被视为实验性功能,直到 LLVM 18,届时 -print-multi-flags-experimental 应重命名为 -print-multi-flags。工具链可以通过在其分发中包含 multilib.yaml 文件来选择使用此功能,一旦相关 ToolChain 子类中添加了对其的支持。稳定性达到后,由 -print-multi-flags 发出的标志不应被删除或更改,尽管可以添加新标志。

限制

尽管名称如此,但 Multilib 用于定位 includelib 目录。因此,在编译和链接时,将一致的选项传递给 Clang 驱动程序非常重要。否则,可能会使用不一致的 includelib 目录,结果将是未定义的。

实验性 multilib.yaml

以下示例是可能的 Multilib 的一个小示例,并记录了可用的选项。

有关更全面的示例,请参阅 llvm-project 源代码中的 clang/test/Driver/baremetal-multilib.yaml

# multilib.yaml

# This format is experimental and is likely to change!

# Syntax is YAML 1.2

# This required field defines the version of the multilib.yaml format.
# Clang will emit an error if this number is greater than its current multilib
# version or if its major version differs, but will accept lesser minor
# versions.
MultilibVersion: 1.0

# The rest of this file is in two parts:
# 1. A list of multilib variants.
# 2. A list of regular expressions that may match flags generated from
#    command line options, and further flags that shall be added if the
#    regular expression matches.
# It is acceptable for the file to contain properties not documented here,
# and these will be ignored by Clang.

# List of multilib variants. Required.
# The ordering of items in the variants list is important if more than one
# variant can match the same set of flags. See the docs on multilib layering
# for more info.
Variants:

# Example of a multilib variant targeting Arm v6-M.
# Dir is the relative location of the directory containing the headers
# and/or libraries.
# Exactly how Dir is used is left up to the ToolChain subclass to define, but
# typically it will be joined to the sysroot.
- Dir: thumb/v6-m
  # List of one or more normalized command line options, as generated by Clang
  # from the command line options or from Mappings below.
  # Here, if the flags are a superset of {target=thumbv6m-unknown-none-eabi}
  # then this multilib variant will be considered a match.
  Flags: [--target=thumbv6m-unknown-none-eabi]

# Similarly, a multilib variant targeting Arm v7-M with an FPU (floating
# point unit).
- Dir: thumb/v7-m
  # Here, the flags generated by Clang must be a superset of
  # {--target=thumbv7m-none-eabi, -mfpu=fpv4-sp-d16} for this multilib variant
  # to be a match.
  Flags: [--target=thumbv7m-none-eabi, -mfpu=fpv4-sp-d16]

# If there is no multilib available for a particular set of flags, and the
# other multilibs are not adequate fallbacks, then you can define a variant
# record with an Error key in place of the Dir key.
- Error: this multilib collection has no hard-float ABI support
  Flags: [--target=thumbv7m-none-eabi, -mfloat-abi=hard]


# The second section of the file is a list of regular expressions that are
# used to map from flags generated from command line options to custom flags.
# This is optional.
# Each regular expression must match a whole flag string.
# Flags in the "Flags" list will be added if any flag generated from command
# line options matches the regular expression.
Mappings:

# Set a "--target=thumbv7m-none-eabi" flag if the regular expression matches
# any of the flags generated from the command line options.
# Match is a POSIX extended regular expression string.
- Match: --target=thumbv([7-9]|[1-9][0-9]+).*
  # Flags is a list of one or more strings.
  Flags: [--target=thumbv7m-none-eabi]

设计原则

稳定接口

multilib.yaml-print-multi-flags-experimental 是 Clang 的新接口。为了使它们随着时间的推移并在跨 LLVM 版本中可用,它们的接口应保持稳定。新的 Multilib 系统将在 LLVM 17 中被视为实验性,但在 LLVM 18 中它将稳定。特别重要的是 Clang 从命令行选项生成的 Multilib 选择标志。一旦发布版本的 Clang 生成一个标志,它就可以在独立于 LLVM 发布周期的 multilib.yaml 文件中使用,因此停止生成该标志将是一个重大更改,应避免。

但是,-march 的规范化是一个例外。-march 用于 Arm 架构包含启用和禁用的扩展列表,并且此列表可能会增长。因此,-march 标志是不稳定的。

不完整的接口

新的 Multilib 系统仅根据有限的命令行选项集执行 Multilib 选择,并限制了哪些标志可用于 Multilib 选择。这是为了避免承诺过大的接口。以后的 LLVM 版本可以根据需要添加对更多命令行选项的 Multilib 选择支持。

可扩展性

配置格式很可能需要在将来进行演变,以适应新的需求。使用像 YAML 这样的支持键值对的格式在这里有所帮助,因为在现有键旁边添加新键非常容易。

向后兼容性

新版本的 Clang 应该能够使用为早期 Clang 版本编写的配置。为了避免以可能微妙错误的方式运行,Clang 应该能够检测配置是否过新并发出错误。

向前兼容性

作为 Multilib 配置的作者,应该可以设计配置,使其很可能与将来的 Clang 版本一起使用。例如,如果将来的 Clang 版本可能会添加对架构的更新版本的支持,并且已知该架构设计用于向后兼容性,那么应该可以在 Multilib 配置中表达对该架构版本的兼容性。

不是 GNU spec 文件

GNU spec 文件标准很大且复杂,并且几乎没有意愿将这种复杂性导入 LLVM。它也高度面向处理命令行参数字符串,而这很难正确执行,因此 Clang 驱动程序中针对此任务进行了大量的逻辑处理。虽然与 GNU 的兼容性会带来好处,但这种情况下的成本被认为过高。

避免在配置中重新发明功能检测

Clang 驱动程序中大量逻辑用于根据给定的命令行选项推断哪些架构功能可用。在每个多库配置中重复此逻辑既不可取也不实用。相反,配置应该能够从 Clang 已经完成的检测功能的繁重工作中获益。

低维护

在整体方案中,多库是一个相对较小的功能,因此支持它应该相应地花费很少的时间。在可能的情况下,这应该通过根据 LLVM 代码库中的现有功能来实现它来实现。

最小的附加 API 表面

API 表面越大,保持其稳定性就越困难。在可能的情况下,应该通过将其定义为与现有 API 的关系来保持最小的附加 API 表面。一个例子是尽可能保持标志名称和命令行选项之间的简单关系。由于命令行选项是稳定 API 的一部分,因此它们不太可能更改,因此标志名称具有相同的稳定性。

低编译时间开销

如果必须在每次调用 Clang 驱动程序时执行选择多库目录的过程,那么它必须对整体编译时间的影响可以忽略不计。