AOSC Wiki / 开发者 / 打包 / .

AOSC OS glibc HWCAPS 子架构构建指南

为特定的运行库启用 HWCAPS 子架构打包

GNU glibc HWCAPS 子架构打包指南

HWCAPS (Hardware Capabilities),在本文中指 GNU glibc 动态链接器 ld.so 根据处理器的指令集扩展支持情况(或微架构等级),自动加载为对应指令集优化过的共享库的行为。如,支持 x86-64-v3 (AVX2) 的处理器可以调用针对 x86-64-v3 优化过的库,而不是使用为基线编译的库,从而提升性能。HWCAPS 由 glibc 2.33 版引入,但目前鲜有发行版针对 HWCAPS 打包。

Autobuild4 目前已经初步实现 HWCAPS 打包支持。本指南将指引您利用 Autobuild4 的 HWCAPS 打包支持为各类基础库增添针对 HWCAPS 支持的微架构优化过的版本。

HWCAPS 支持的处理器架构

目前 glibc HWCAPS 支持三种处理器架构:

在这三个架构中,安同 OS 支持 x86-64 及 POWER 架构,因此只有在安同 OS 中只有两个架构能够利用 HWCAPS 机制。您可以在 GNU glibc 的源码目录中寻找 dl-hwcaps-subdirs.c 来判断哪些架构支持 HWCAPS 子目录。本文主要以 x86-64 举例。

在 glibc 中,支持 HWCAPS 的处理器架构被分为数个子架构,以区分不同微架构等级(或指令集扩展)的处理器。处理器架构的基线指令集不包括其中。ld.so 会选择处理器支持的子架构中最高的子架构。如,第十一代 Intel Core i7-11700K 处理器支持 SSE4.2、AVX2 及 AVX-512 指令集,因此 ld.so 会尽量加载为 AVX-512 指令集优化的共享库;而第十二代 Intel Core 17-12700 不支持 AVX-512,因此 ld.so 会尝试加载为 AVX2 优化的共享库。

以下是各个处理器架构中支持的 HWCAPS 子架构:

x86-64

x86-64 除基线本身之外有三种微架构等级,因此在 glibc 中有三个子架构。这三个子架构如下:

子架构名称描述编译器参数
x86-64-v2对应 x86-64-v2 微架构等级,主要支持 SSE4.2 指令集扩展-march=x86-64-v2 -mtune=sandybridge
x86-64-v3对应 x86-64-v3 微架构等级,主要支持 AVX2 指令集扩展-march=x86-64-v3 -mtune=haswell
x86-64-v4对应 x86-64-v4 微架构等级,主要支持 AVX512 指令集扩展-march=x86-64-v4 -mtune=generic

IBM POWER

IBM POWER 以 POWER 8 为基线,支持两个子架构:

子架构名称描述编译器参数
power9第九代 IBM POWER 架构-march=power9
power10第十代 IBM POWER 架构-march=power10

IBM Z (s390x)

安同 OS 没有 s390x 移植,但出于文档性质在此列出 s390x 的 HWCAPS 子架构:

子架构名称描述
z13IBM z13 处理器
z14IBM z14 处理器
z15IBM z15 处理器
z16IBM Telum 处理器(为 IBM z16 大型机所用)

HWCAPS 打包对象

由于 HWCAPS 是动态加载共享库的机制,所以为应用程序包启用 HWCAPS 并无意义。HWCAPS 只能够为运行时共享库启用。

同时,不是所有共享库都需要打包 HWCAPS。集成 HWCAPS 库会耗费较大空间,因此能够启用的范围有限。我们规定如下类型的软件包可以启用 HWCAPS:

HWCAPS 子目录

HWCAPS 子目录是 glibc 的动态连接器寻找针对 HWCAPS 子架构优化过的共享库的路径。每个子目录中均存放针对对应微架构优化过的共享库 (.so)。HWCAPS 子目录遵循如下格式:

$LIBDIR/glibc-hwcaps/$SUBTARGET

其中,LIBDIR 是系统共享库的路径,在安同 OS 中为 /usr/lib。例如,在 x86-64 中就有三个 HWCAPS子目录:

$ ls /usr/lib/glibc-hwcaps
x86-64-v2  x86-64-v3  x86-64-v4

不过 ld.so 同时会根据 ld.so.conf 中的路径及 LD_LIBRARY_PATH 环境变量中指定的路径扩展默认的寻找路径。例如,如果 ld.so.conf 中存在如下目录:

/usr/local/lib
/opt/32/lib
/opt/lib

那么 ld.so 同时会搜索以下路径:

/usr/local/lib/glibc-hwcaps/$SUBTARGET
/opt/32/lib/glibc-hwcaps/$SUBTARGET
/opt/lib/glibc-hwcaps/$SUBTARGET

[!Caution] 除非有特殊需要,否则打包时软件的共享库不应该出现在 /usr/lib 以外的地方。

Autobuild4 的 HWCAPS 打包流程

Autobuild4 实现为 HWCAPS 打包的方式是在软件包主体构建完毕后,按照预配置的构建参数,分别为支持的子架构构建一次。如,amd64 有三个 HWCAPS 子架构,因此软件包会被构建四次:第一次使用 amd64 基线的参数构建,然后使用 x86-64-v2 的参数、x86-64-v3 的参数和 x86-64-v4 的参数为支持的 HWCAPS 子架构构建。

Autobuild4 处理 HWCAPS 的流程如下:

  1. 为源码打补丁 (patch)
  2. 运行 prepare 步骤
  3. 运行 build 步骤(构建模板或自定义脚本)
  4. 运行 beyond 步骤
  5. 如果启用了 HWCAPS,则开始为 HWCAPS 打包
    • 打包步骤根据软件包主体的打包方式决定
  6. 打包后期处理步骤及 QA
  7. 出包,打包结束

所有打包流程均在 proc/51-build-hwcaps.sh 文件中。

HWCAPS 打包支持范围

如果软件包主体是用构建模板自动处理的,则在为 HWCAPS 子架构打包时也会复用构建模板自动处理。自动处理 HWCAPS 的行为支持的构建模板如下:

如果软件包并未使用构建模板(以自定义构建脚本 build 构建),或者软件包并未使用上述构建模板,您就必须为其编写 HWCAPS 构建脚本。

所有 HWCAPS 相关的文件(脚本)都必须存放于 autobuild/hwcaps 文件夹中。除非另行说明(或指定了绝对路径),否则本文中所有相对路径都是以 autobuild 文件夹为起点。

启用 HWCAPS 打包行为

HWCAPS 功能默认禁用,可选启用。您可以在 defines 文件中指定 AB_HWCAPS 变量控制 HWCAPS 打包行为:

# 设置为 1 即可启用 HWCAPS 打包
AB_HWCAPS=1

启用后,Autobuild4 会在构建完软件包主体后运行 HWCAPS 构建步骤。

自动处理 HWCAPS 打包

如果软件包使用 Autotools、CMake 或 Meson 构建模板,在启用 HWCAPS 构建后 Autobuild4 理应能够自动处理大多数包。不过自动处理过程可能无法满足您的需求,此时您可以编写在构建前后运行的脚本,灵活处理每一次构建。

您也许需要在每次构建前设置额外的编译参数,或者在每次构建完毕后额外链接共享库,或创建额外的符号链接。这些操作都可以通过编写构建前后运行的 hwcaps/preparehwcaps/beyond 来完成:

hwcaps/prepare:

abinfo "Adding extra optimizations..."
case "$CUR_SUBTGT" in
    x86-64-v2)
        export CFLAGS="$CFLAGS -mssse3"
        ;;
    x86-64-v3)
        # Add some optimizations
        ;;
    *)
        # do nothing
        ;;
esac

hwcaps/beyond:

abinfo "Installing additional symbolic links ..."
ln -s libfoo.so.1.2.3 $PKGDIR/usr/lib/libfoo.so
ln -s libbar.so.1.2.3 $PKGDIR/usr/lib/libbar.so.1

[!Important] 每为一个子架构构建时,hwcaps/preparehwcaps/beyond 都会运行一次。您可以通过下文中提到的变量判断当前构建所面向的子架构。

在为 HWCAPS 打包期间,Autotools 构建模板会为 configure 脚本传递宿主 (--host) 参数,来避免在不支持的机器上运行等级更高(如在 x86-64-v2 的机器上编译针对 x86-64-v3 的共享库)的 HWCAPS 子架构的二进制的情况。此行为一般用于测试某段程序能否编译(或运行),作为配置项目时的参考。

有些时候只指定 --host 可能不足以避免上述情况。如果在微架构等级较低的机器上构建不兼容的库时 configure 脚本仍旧执行失败,您可以启用如下开关,让 Autotools 认为需要交叉编译,因此跳过尝试运行不兼容的二进制程序的情况:

# 设置为 1 即可启用交叉编译模式
AB_HWCAPS_CROSS=1

启用上述开关后,Autobuild4 会在执行 configure 脚本时添加一对不一样的宿主 (--host) 及构建宿主 (--host) 参数,因此 configure 脚本会认为目前处于交叉编译环境,但实则不然:

./configure --build=x86_64-pc-linux-gnu --host=x86_64-aosc-linux-gnu

脚本中可供使用的变量

Autobuild4 在运行 HWCAPS 脚本时会提供如下变量,方便您引用各种路径:

其中,CUR_SUBTGT 仅在自动处理 HWCAPS 打包期间运行 preparebeyondinstall 脚本时才能够使用。

自动处理 HWCAPS 打包的流程

如果目标软件包使用了构建模板,在您启用 HWCAPS 打包后,Autobuild4 会自动在软件包本体构建完毕后运行 HWCAPS 打包步骤。目前自动处理 HWCAPS 的行为已经在下列构建模板中测试:

同时,您也可以使用自定义脚本 hwcaps/preparehwcaps/beyond 脚本,在构建前后运行自定义命令。在自动处理 HWCAPS 构建的情况下,Autobuild4 会为每一个定义的子架构运行如下步骤:

自动处理 HWCAPS 时,所有自定义脚本(preparebeyondinstall)都会执行多次,即有多少个子架构,就需要构建多少次,也就会运行多少次脚本。您可以在脚本内利用 stamp 机制避免重复执行部分逻辑。

自定义安装逻辑(TBD)

[!Caution] 如需手动处理 HWCAPS 构建,强烈建议您在 hwcaps/install 脚本中完成安装步骤,而不是在 hwcaps/build 脚本中一气呵成。

在自动处理 HWCAPS 构建的情况下,Autobuild4 会将每个子架构对应的 $PKGDIR/usr/lib (不包含子文件夹)中所有共享库及符号链接安装至软件包主体。您可以自行编写安装共享库的逻辑。共享库安装的逻辑需编写在 hwcaps/install 文件中。

install 脚本运行在 beyond 脚本之后、QA 过程之前,且需要将这些库安装到 $HWCAPSDIR/$CUR_SUBTGT 中。您可以通过逻辑自行判断要安装的共享库。

[!Note] 您也可以像 build 脚本那样单独为特定架构编写 install 脚本:

  • hwcaps/install-amd64
  • hwcaps/install-ppc64el

手动处理 HWCAPS 构建

对于通过自定义脚本编译软件包主体的包,您需要为 HWCAPS 子架构单独编写构建脚本。您可以单独为支持的架构编写脚本 (hwcaps/build-$ARCH),也可以选择将它们集成进一个文件中 (hwcaps/build)。您在编写自定义构建脚本时,需要注意以下几个问题:

Autobuild4 会分别导出所有子架构的 C、C++ 及 Rust 编译器参数和 C 预处理器及链接器参数,供您在构建期间参考引用:

# 以 amd64 为例。amd64 有三个子架构,将各种存放参数的变量
# 与三个子架构名称组合起来,总共有 15 个变量。
# 由于变量名中不允许使用短横,因此以下划线代替之:
# CFLAGS_HWCAPS_x86_64_v2	CFLAGS_HWCAPS_x86_64_v3	CFLAGS_HWCAPS_x86_64_v4
# CXXFLAGS_HWCAPS_x86_64_v2	CXXFLAGS_HWCAPS_x86_64_v3	CXXFLAGS_HWCAPS_x86_64_v4
# RUSTFLAGS_HWCAPS_x86_64_v2	RUSTFLAGS_HWCAPS_x86_64_v3	RUSTFLAGS_HWCAPS_x86_64_v4
# CPPFLAGS_HWCAPS_x86_64_v2	CPPFLAGS_HWCAPS_x86_64_v3	CPPFLAGS_HWCAPS_x86_64_v4
# LDFLAGS_HWCAPS_x87_64_v2	LDFLAGS_HWCAPS_x86_64_v3	LDFLAGS_HWCAPS_x86_64_v4

# 每次更换子架构时,可以使用如下逻辑快速替换对应变量(假设 $cap 存放当前子架构的名称):
for comp in C CXX RUST CPP LD ; do
    # _vname = "CFLAGS_HWCAPS_x86_64_v2"
    _vname="${comp}FLAGS_HWCAPS_${cap//[.-+]/_}"
    # $_val expands to the value of "${CFLAGS_HWCAPS_x86_64_v2}"
    # Quotes must be present!
    _val="${!_vname}"
    # export CFLAGS="-march=x86-64-v2 ..."
    export "${comp}FLAGS=$_val"
done

build-amd64 为例,自动构建脚本的总体逻辑如下:

abinfo "Building foo for HWCAPS targets ..."
# BLDDIR and PKGDIR are saved before this script runs.
for cap in "${HWCAPS[@]}" ; do
    abinfo "Preparing to build for $cap ..."
    export BLDDIR="$SRCDIR/"build-"$cap"
    export PKGDIR="$SRCDIR"/dist-"$cap"
    abinfo "$cap: Setting corresponding compiler and linker flags ..."
    for comp in C CXX RUST CPP LD ; do
        _varname="${comp}FLAGS_HWCAPS_${cap//[.-+]/_}"
        _val=${!_varname}
        export "${comp}FLAGS"="${_val}"
    done
    # more preparation setps
    abinfo "$cap: Building ..."
    mkdir "$BLDDIR"
    pushd "$BLDDIR"
    $SRCDIR/configure "${AUTOTOOLS_DEF[@]}" ...
    make ...
    abinfo "$cap: Installing into a separate PKGDIR ..."
    make install DESTDIR=$PKGDIR
done

附录 A 为 Autobuild4 增加新的 HWCAPS 架构

一旦 glibc 中有新的处理器架构引入了 HWCAPS 子目录功能,您就需要及时为 Autobuild4 实现对应架构的 HWCAPS 构建支持。

以 AArch64 为例。假设 AArch64 引入了 HWCAPS 子目录支持,且 AArch64 的子架构定义如下(以 ARMv8 为基线):

1. 添加 HWCAPS 定义

首先我们需要为 AArch64 标记 HWCAPS 支持情况。编辑 arch/arm64.sh,加入 HWCAPS 子架构定义:

# Yay, AArch64 now supports HWCAPS subdirectories!
HAS_HWCAPS=1
HWCAPS=('armv8.2a-sve' 'armv9a')

2. 检查 *FLAGS_COMMON_ARCH

如果 CFLAGS_COMMON_ARCH 中存在与微架构无关的参数,需要将其分离,加入 CFLAGS_COMMON_ARCH_BASE 中。这里以 amd64 举例:

CFLAGS_COMMON_ARCH_BASE=('-fomit-frame-pointer')
CFLAGS_COMMON_ARCH=('-march=x86-64' '-mtune=sandybridge' '-msse2')

3. 分别指定编译器参数

您需要分别为两个子架构分别指定 C 编译器及 Rust 编译器的编译参数(C++ 编译器参数会复用 C 编译器的参数):

CFLAGS_HWCAPS_armv8_2a_sve=('-march=armv8.2-a' '-mtune=cortex-a76' '-msve')
CFLAGS_HWCAPS_armv9a=('-march=armv9-a' '-mtune=cortex-a710' '-msve' '-msve2')
RUSTFLAGS_HWCAPS_armv8_2a_sve=('-Ctarget-cpu=generic')
RUSTFLAGS_HWCAPS_armv9a=('-Ctarget-cpu=generic')

[!Important] AArch64 等没有规定微架构等级的处理器架构中,Rust 编译器目前需要通过 -C target-feature 参数指定对应的指令集扩展,但 -C target-feature 选项不安全,因此 Rust 程序无法在 AArch64 上参与 HWCAPS 优化。 您仍需为每个子架构指定 RUSTFLAGS,因此上面的例子中为两个子架构制定了同样的编译器参数。

至此,AArch64 的 HWCAPS 架构支持就添加好了。接下来,您就可以将修改提交至 Autrobuild4 仓库的新分支上。同时您需要尝试构建一些包,以确保 HWCAPS 打包在 AArch64 上是工作的。

4. 构建测试

前往您的 AArch64 Ciel 工作区,进入 ABBS 树开设新主题(如 autobuild4-hwcaps-arm64)来测试新的 Autobuild4。在新建的分支上,将 Autobuild4 的版本更新至任意高版本(如 999),并且将源码指向 Autobuild4 Git 仓库的新分支:

VER=999
SRCS="git::commit=hwcaps-arm64::https://github.com/AOSC-Dev/autobuild4"
CHKSUMS="SKIP"

[!NOTE] 请及时 Rebase 您的 HWCAPS 分支,保证您的分支比 master 分支新。

接下来就可以构建 Autobuild4 和任意已经启用 HWCAPS 构建的库:

# ciel build -i INSTANCE autobuild4 glibc

如果构建成功,即可开始大范围测试,待稳定后即可为 Autobuild4 发版。

附录 B 测试指南

一旦为新的软件包启用了 HWCAPS 构建,您就需要大范围测试 HWCAPS 兼容情况。以 x86-64 为例(POWER 很难找到全面的硬件),您应该能够访问到能够运行桌面版的如下硬件环境(每个子架构各取一种即可):

[!Note] 支持 AVX-512 的产品并不常见,如无条件可以省略 x86-64-v4 测试。

测试维度

ld.so 动态链接器的 HWCAPS 识别情况

执行 /lib/ld-linux-x86-64.so.2 --help,动态链接器就会输出 HWCAPS 识别情况:

$ /lib/ld-linux-x86-64.so.2 --help
...
Subdirectories of glibc-hwcaps directories, in priority order:
  x86-64-v4
  x86-64-v3 (supported, searched)
  x86-64-v2 (supported, searched)

查看输出尾部的 “Subdirectories of glibc-hwcaps directories” 一节,检查对应子架构是否被标记为 “supported, searched”。

ld.so 加载的库中是否包含 HWCAPS 子目录中的库

ldd 命令查看程序需要加载的库。其中启用了 HWCAPS 构建的库应该由对应的 HWCAPS 子目录加载:

$ ldd /usr/lib/gcc/x86_64-aosc-linux-gnu/14.1.0/cc1
        linux-vdso.so.1 (0x00007f4947fc9000)
        libisl.so.23 => /usr/lib/glibc-hwcaps/x86-64-v3/libisl.so.23.3.0 (0x00007f4947c00000)
        libmpc.so.3 => /usr/lib/glibc-hwcaps/x86-64-v3/libmpc.so.3.3.1 (0x00007f4947f51000)
        libmpfr.so.6 => /usr/lib/glibc-hwcaps/x86-64-v3/libmpfr.so.6.2.1 (0x00007f4947800000)
        libgmp.so.10 => /usr/lib/glibc-hwcaps/x86-64-v3/libgmp.so.10.5.0 (0x00007f4947ea1000)
        libz.so.1 => /usr/lib/libz.so.1 (0x00007f4947e82000)
        libzstD.so.1 => /usr/lib/libzstd.so.1 (0x00007f4947b17000)
        libm.so.6 => /usr/lib/glibc-hwcaps/x86-64-v3/libm.so.6 (0x00007f4947753000)
        libc.so.6 => /usr/lib/glibc-hwcaps/x86-64-v3/libc.so.6 (0x00007f4947598000)
        /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f4947fcb000)

依赖测试目标库的程序能否正常运行

运行任意依赖测试目标的程序,检查是否出现程序因非法指令而异常退出的情况。如果程序执行期间出现非法指令错误,请通过 ldd 检查加载的运行库是否符合该处理器指令集支持情况(如下例,处理器仅支持到 x86-64-v3,因此不应该加载针对 x86-64-v4 的库):

$ python3
Illegal instruction (core dumped)
$ ldd /usr/bin/python3
	linux-vdso.so.1 (0x00007f11741c0000)
	libpython3.10.so.1.0 => /usr/lib/libpython3.10.so.1.0 (0x00007f1173c00000)
	libc.so.6 => /usr/lib/glibc-hwcaps/x86-64-v3/libc.so.6 (0x00007f1173fac000)
	libm.so.6 => /usr/lib/glibc-hwcaps/x86-64-v3/libm.so.6 (0x00007f1173b53000)
	/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f11741c2000)

有关定位错误的详细信息,请参考 “故障排除” 一节。

有些程序可能起初运行正常,但执行到某个功能时可能会调用到包含不支持的指令的库。您在测试时需要尽可能全面地执行程序。

附录 C 故障排除

一旦在测试时遇到 SIGILL 错误,您就需要执行故障排除步骤。以下是一些通用步骤:

确定当前测试用环境的支持情况

您可以执行 /lib/ld-linux-x86-64.so.2 --help 并查看当前环境的 HWCAPS 支持情况。

[!NOTE] ppc64el 架构下的动态链接器名称是 /lib64/ld64.so.1

移除 HWCAPS 子目录

您可以移除 HWCAPS 子目录,然后更新链接器缓存,测试程序是否运行正常:

$ mv /usr/lib/glibc-hwcaps /usr/lib/glibc-hwcaps.bak
$ sudo ldconfig
$ ldd /usr/bin/python3
	linux-vdso.so.1 (0x00007fe30feaa000)
	libpython3.13.so.1.0 => /usr/local/lib/libpython3.13.so.1.0 (0x00007fe30f800000)
	libm.so.6 => /usr/lib/libm.so.6 (0x00007fe30fddd000)
	libc.so.6 => /usr/lib/libc.so.6 (0x00007fe30f400000)
	/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fe30feac000)
$ python3
Python 3.13.0 (main, Oct 22 2024, 11:20:27) [GCC 13.2.0 20230727 (AOSC OS, Core)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

如果程序运行正常

如果程序运行正常,则说明程序加载的库中包含错误优化的库。您现在可以恢复 HWCAPS 子目录,逐个移除加载的 HWCAPS 子目录中的库、刷新动态链接器缓存并测试程序,直到找出问题库。

找到出问题的库后,请向开发者报告出问题的库。

如果程序运行不正常

如果移除了 HWCAPS 子目录后程序依旧无法运行,请检查程序所依赖运行库的构建步骤。构建 HWCAPS 时不应该将库直接安装在 /usr/lib 下,并且 HWCAPS 构建步骤期间不应该安装可执行程序。

附录 D 可能出现的负面情况

HWCAPS 打包需要大范围测试。一旦出现负面情况,您应该立即为对应的软件包禁用 HWCAPS 打包,并将问题报告至安同 OS 开发者。主要的负面情况如下:

ABI Break

由于 HWCAPS 会导致程序加载的库所面向的微架构参差不齐,因此极有可能会出现 ABI Break。一旦出现 ABI Break,您需要找到出现问题的库,并将其从 HWCAPS 子目录中删除,然后再次尝试运行。面对 ABI Break 有如下解决方案:

  1. 为出现问题的软件包禁用 HWCAPS 构建。
  2. 有些情况下可能是程序(或依赖该库的共享库)过时,需要重构。尝试重构一次,然后再次尝试运行。