AArch64 Linux 可伸缩矢量扩展支持¶
作者: Dave Martin <Dave.Martin@arm.com>
日期: 2017 年 8 月 4 日
本文档简要概述了 Linux 提供给用户空间的接口,以便支持使用 ARM 可伸缩矢量扩展 (SVE),包括与可伸缩矩阵扩展 (SME) 添加的流式 SVE 模式的交互。
这只是对最重要特性和问题的概述,并非旨在详尽无遗。
本文档不旨在描述 SVE 架构或程序员模型。为了帮助理解,附录 A 包含对 SVE 相关程序员模型特性的最简描述。
1. 概述¶
SVE 寄存器 Z0..Z31、P0..P15 和 FFR 以及当前矢量长度 VL,按线程跟踪。
在流式模式下,FFR 不可访问,除非系统中存在 HWCAP2_SME_FA64,当不支持时,使用这些接口访问流式模式 FFR 将读取和写入为零。
SVE 的存在通过辅助向量 AT_HWCAP 条目中的 HWCAP_SVE 报告给用户空间。此标志的存在意味着存在 SVE 指令和寄存器,以及本文档中描述的 Linux 特定系统接口。SVE 在 /proc/cpuinfo 中报告为“sve”。
还可以通过使用 MRS 指令读取 CPU ID 寄存器 ID_AA64PFR0_EL1,并检查 SVE 字段的值是否为非零来检测用户空间中 SVE 指令的执行支持。[3]
它不保证存在以下各节中描述的系统接口:需要验证这些接口是否存在的软件必须检查 HWCAP_SVE。
在支持 SVE2 扩展的硬件上,AT_HWCAP2 辅助向量条目中也会报告 HWCAP2_SVE2。 除此之外,可以通过以下方式报告 SVE2 的可选扩展:
HWCAP2_SVE2 HWCAP2_SVEAES HWCAP2_SVEPMULL HWCAP2_SVEBITPERM HWCAP2_SVESHA3 HWCAP2_SVESM4 HWCAP2_SVE2P1
随着 SVE 架构的发展,此列表可能会随着时间的推移而扩展。
这些扩展也通过 CPU ID 寄存器 ID_AA64ZFR0_EL1 报告,用户空间可以使用 MRS 指令读取该寄存器。 有关详细信息,请参阅 ARM64 ELF hwcaps 和 ARM64 CPU 特性寄存器。
在支持 SME 扩展的硬件上,AT_HWCAP2 辅助向量条目中也会报告 HWCAP2_SME。 其中,SME 添加了流式模式,该模式使用单独的 SME 矢量长度和相同的 Z/V 寄存器提供 SVE 功能集的子集。 有关更多详细信息,请参阅 AArch64 Linux 的可伸缩矩阵扩展支持。
调试器应将自己限制为通过 NT_ARM_SVE regset 与目标交互。 检测对此 regset 支持的推荐方法是首先连接到目标进程,然后尝试 ptrace(PTRACE_GETREGSET, pid, NT_ARM_SVE, &iov)。 请注意,当存在 SME 且正在使用流式 SVE 模式时,寄存器的 FPSIMD 子集将通过 NT_ARM_SVE 读取,并且 NT_ARM_SVE 写入将退出目标中的流式模式。
每当 SVE 可伸缩寄存器值 (Zn, Pn, FFR) 在用户空间和内核之间以内存形式交换时,寄存器值都以字节序不变的布局在内存中编码,位 [(8 * i + 7) : (8 * i)] 在内存表示的起始位置偏移 i 个字节处编码。 这会影响例如信号帧 (struct sve_context) 和 ptrace 接口 (struct user_sve_header) 以及关联的数据。
请注意,在大端系统上,这会导致与 FPSIMD V 寄存器不同的字节顺序,FPSIMD V 寄存器存储为单个主机字节序 128 位值,寄存器的位 [(127 - 8 * i) : (120 - 8 * i)] 在偏移 i 个字节处编码。(struct fpsimd_context,struct user_fpsimd_state)。
2. 矢量长度术语¶
SVE 矢量 (Z) 寄存器的大小称为“矢量长度”。
为了避免对用于表示矢量长度的单位产生混淆,内核采用以下约定:
矢量长度 (VL) = Z 寄存器的大小(以字节为单位)
矢量四字 (VQ) = Z 寄存器的大小(以 128 位为单位)
(因此,VL = 16 * VQ。)
VQ 约定用于基础粒度很重要的地方,例如在数据结构定义中。 在大多数其他情况下,使用 VL 约定。 这与 SVE 指令集架构中“VL”伪寄存器的含义一致。
3. 系统调用行为¶
在系统调用上,V0..V31 被保留(就像没有 SVE 一样)。 因此,Z0..Z31 的位 [127:0] 被保留。 Z0..Z31 的所有其他位,以及所有 P0..P15 和 FFR 在从系统调用返回时变为零。
SVE 寄存器不用作将参数传递到任何系统调用或从任何系统调用接收结果。
线程的所有其他 SVE 状态,包括当前配置的矢量长度、PR_SVE_VL_INHERIT 标志的状态以及延迟矢量长度(如果有),都将在所有系统调用中保留,但第 6 节中描述的 execve() 的特定异常除外。
特别是,从 fork() 或 clone() 返回时,父进程和新的子进程或线程共享相同的 SVE 配置,与调用之前父进程的配置匹配。
4. 信号处理¶
一个新的信号帧记录 sve_context 在信号传递时编码 SVE 寄存器。[1]
此记录是 fpsimd_context 的补充。 FPSR 和 FPCR 寄存器仅存在于 fpsimd_context 中。 为方便起见,V0..V31 的内容在 sve_context 和 fpsimd_context 之间重复。
该记录包含一个标志字段,其中包括一个标志 SVE_SIG_FLAG_SM,如果设置,则表示该线程处于流式模式,并且矢量长度和寄存器数据(如果存在)描述流式 SVE 数据和矢量长度。
SVE 的信号帧记录始终包含基本元数据,特别是线程的矢量长度(在 sve_context.vl 中)。
SVE 寄存器可能包含在记录中,也可能不包含在记录中,具体取决于寄存器对于线程是否有效。 寄存器仅当以下情况时才存在:sve_context.head.size >= SVE_SIG_CONTEXT_SIZE(sve_vq_from_vl(sve_context.vl))。
如果寄存器存在,则记录的其余部分具有 vl 相关的尺寸和布局。 宏 SVE_SIG_* 定义为 [1],以方便访问成员。
每个可伸缩寄存器(Zn、Pn、FFR)都以字节序不变的布局存储,位 [(8 * i + 7) : (8 * i)] 存储在寄存器的内存表示形式的起始位置偏移 i 个字节处。
如果 SVE 上下文太大而无法容纳在 sigcontext.__reserved[] 中,则会在堆栈上分配额外的空间,并在 __reserved[] 中写入引用此空间的 extra_context 记录。 然后将 sve_context 写入额外的空间中。 有关此机制的更多详细信息,请参阅 [1]。
5. 信号返回¶
从信号处理程序返回时
如果信号帧中没有 sve_context 记录,或者如果记录存在但不包含上一节中描述的任何寄存器数据,则 SVE 寄存器/位将变为无效,并采用未指定的值。
如果 sve_context 存在于信号帧中并包含完整的寄存器数据,则 SVE 寄存器将变为有效,并填充指定的数据。 但是,出于向后兼容的原因,始终从 fpsimd_context.vregs[] 的相应成员恢复 Z0..Z31 的位 [127:0],而不是从 sve_context 恢复。 其余位从 sve_context 恢复。
无论 sve_context 是否存在,信号帧中都必须包含 fpsimd_context。
矢量长度无法通过信号返回进行更改。 如果信号帧中的 sve_context.vl 与当前矢量长度不匹配,则信号返回尝试将被视为非法,从而导致强制 SIGSEGV。
允许通过设置或清除 SVE_SIG_FLAG_SM 标志来进入或退出流式模式,但应用程序应注意确保在执行此操作时,sve_context.vl 和任何寄存器数据都适合新模式下的矢量长度。
6. prctl 扩展¶
添加了一些新的 prctl() 调用,以允许程序管理 SVE 矢量长度
prctl(PR_SVE_SET_VL, unsigned long arg)
设置调用线程的矢量长度和相关标志,其中 arg == vl | flags。 调用进程的其他线程不受影响。
vl 是所需的矢量长度,其中 sve_vl_valid(vl) 必须为真。
flags
PR_SVE_VL_INHERIT
在 execve() 中继承当前矢量长度。 否则,矢量长度将在 execve() 时重置为系统默认值。 (请参阅第 9 节。)
PR_SVE_SET_VL_ONEXEC
将请求的矢量长度更改推迟到此线程执行的下一个 execve()。
其效果等同于在此线程的下一个 execve()(如果有)之后立即隐式执行以下调用
prctl(PR_SVE_SET_VL, arg & ~PR_SVE_SET_VL_ONEXEC)
这允许启动具有不同矢量长度的新程序,同时避免调用方中的运行时副作用。
如果没有 PR_SVE_SET_VL_ONEXEC,请求的更改将立即生效。
- 返回值:成功时为非负值,错误时为负值
- EINVAL:不支持 SVE、请求的矢量长度无效或
标志无效。
成功后
调用线程的矢量长度或将在线程的下一个 execve() 时应用的延迟矢量长度(取决于 arg 中是否存在 PR_SVE_SET_VL_ONEXEC),设置为系统支持的最大值,该值小于或等于 vl。 如果 vl == SVE_VL_MAX,则设置的值将是系统支持的最大值。
调用线程中任何先前未完成的延迟矢量长度更改都将被取消。
返回的值描述了结果配置,编码方式与 PR_SVE_GET_VL 相同。 如果 arg 中不存在 PR_SVE_SET_VL_ONEXEC,则在此值中报告的矢量长度是此线程的新当前矢量长度; 否则,报告的矢量长度是将在调用线程的下一个 execve() 时应用的延迟矢量长度。
更改矢量长度会导致除 Z0 位 [127:0] .. Z31 位 [127:0] 之外的所有 P0..P15、FFR 和 Z0..Z31 的所有位变为未指定。 调用 vl 等于线程当前矢量长度的 PR_SVE_SET_VL,或调用带有 PR_SVE_SET_VL_ONEXEC 标志的 PR_SVE_SET_VL,并不构成此目的的矢量长度的更改。
prctl(PR_SVE_GET_VL)
获取调用线程的矢量长度。
以下标志可以 OR 运算到结果中
PR_SVE_VL_INHERIT
矢量长度将在 execve() 中继承。
无法确定是否存在未完成的延迟矢量长度更改(这通常仅发生在 fork() 或 vfork() 与典型使用中相应的 execve() 之间)。
要从结果中提取矢量长度,请使用 PR_SVE_VL_LEN_MASK 进行按位与运算。
- 返回值:成功时为非负值,错误时为负值
EINVAL:不支持 SVE。
7. ptrace 扩展¶
定义了新的 regsets NT_ARM_SVE 和 NT_ARM_SSVE,以用于 PTRACE_GETREGSET 和 PTRACE_SETREGSET。 NT_ARM_SSVE 描述了流式模式 SVE 寄存器,NT_ARM_SVE 描述了非流式模式 SVE 寄存器。
在此描述中,当目标处于适当的流式或非流式模式并且正在使用超出与 FPSIMD Vn 寄存器共享的子集的数据时,寄存器集被称为“有效”。
有关定义,请参阅 [2]。
regset 数据以 struct user_sve_header 开头,其中包含
size
完整 regset 的大小(以字节为单位)。 这取决于 vl,并且可能取决于未来的其他因素。
如果对 PTRACE_GETREGSET 的调用请求的数据少于 size 的值,则调用方可以分配更大的缓冲区并重试,以便读取完整的 regset。
max_size
regset 对于目标线程可以增长到的最大大小(以字节为单位)。 即使目标线程更改其矢量长度等,regset 也不会增长得更大。
vl
目标线程的当前矢量长度(以字节为单位)。
max_vl
目标线程的最大可能矢量长度。
flags
最多一个
SVE_PT_REGS_FPSIMD
SVE 寄存器无效 (GETREGSET) 或将变为无效 (SETREGSET)。
有效负载的类型为 struct user_fpsimd_state,其含义与 NT_PRFPREG 相同,从 user_sve_header 的 SVE_PT_FPSIMD_OFFSET 偏移量开始。
将来可能会附加额外的数据:有效负载的大小应使用 SVE_PT_FPSIMD_SIZE(vq, flags) 获取。
vq 应使用 sve_vq_from_vl(vl) 获取。
或
SVE_PT_REGS_SVE
SVE 寄存器有效 (GETREGSET) 或将变为有效 (SETREGSET)。
有效负载包含 SVE 寄存器数据,从 user_sve_header 的 SVE_PT_SVE_OFFSET 偏移量开始,大小为 SVE_PT_SVE_SIZE(vq, flags);
... 与零个或多个以下标志进行 OR 运算,这些标志具有与相应 PR_SET_VL_* 标志相同的含义和行为
SVE_PT_VL_INHERIT
SVE_PT_VL_ONEXEC(仅 SETREGSET)。
如果未提供 FPSIMD 和 SVE 标志,则没有寄存器有效负载可用,这仅在实现 SME 时才有可能。
更改矢量长度和/或标志的效果与 PR_SVE_SET_VL 的文档记录的效果相同。
如果调用方需要知道 SETREGSET 实际设置的 VL 是什么,则必须进行进一步的 GETREGSET 调用,除非事先知道支持请求的 VL。
在 SVE_PT_REGS_SVE 情况下,有效负载的大小和布局取决于标头字段。 提供了 SVE_PT_SVE_*() 宏,以方便访问成员。
在任何一种情况下,对于 SETREGSET,都可以省略有效负载,在这种情况下,仅更改矢量长度和标志(以及这些更改的任何后果)。
在支持 SME 的系统中,当处于流式模式时,NT_REG_SVE 的 GETREGSET 将仅返回没有寄存器数据的 user_sve_header,类似地,当不处于流式模式时,NT_REG_SSVE 的 GETREGSET 将不返回任何寄存器数据。
NT_ARM_SSVE 的 GETREGSET 永远不会返回 SVE_PT_REGS_FPSIMD。
对于 SETREGSET,如果存在 SVE_PT_REGS_SVE 有效负载且不支持请求的 VL,则效果将与省略有效负载的效果相同,但会报告 EIO 错误。 不会尝试将有效负载数据转换为实际设置的矢量长度的正确布局。 线程的 FPSIMD 状态被保留,但 SVE 寄存器的其余位变为未指定。 由调用方转换实际 VL 的有效负载布局并重试。
在实现 SME 的情况下,无论硬件在两种模式之间共享数据的实现定义的行为如何,都不可能在处于流式模式时 GETREGSET 正常 SVE 的寄存器状态,也不可能在处于正常模式时 GETREGSET 流式模式寄存器状态。
任何 NT_ARM_SVE 的 SETREGSET 都将退出流式模式(如果目标处于流式模式),并且任何 NT_ARM_SSVE 的 SETREGSET 都将进入流式模式(如果目标不处于流式模式)。
如果提供任何寄存器数据以及 SVE_PT_VL_ONEXEC,则将使用当前矢量长度而不是配置为在 exec 上使用的矢量长度来解释寄存器数据。
写入部分、不完整的有效负载的效果是未指定的。
8. ELF 核心转储扩展¶
NT_ARM_SVE 和 NT_ARM_SSVE 注释将添加到转储进程的每个线程的每个核心转储中。 这些内容将等效于如果在生成核心转储时为每个线程执行相应类型的 PTRACE_GETREGSET 读取的数据。
9. 系统运行时配置¶
为了减轻信号帧扩展对 ABI 的影响,提供了一种策略机制,供管理员、发行版维护人员和开发人员设置用户空间进程的默认矢量长度
/proc/sys/abi/sve_default_vector_length
将整数的文本表示形式写入此文件会将系统默认矢量长度设置为指定的值,该值使用与通过 PR_SVE_SET_VL 设置矢量长度相同的规则舍入为支持的值。
可以通过重新打开文件并读取其内容来确定结果。
在引导时,默认矢量长度最初设置为 64 或最大支持的矢量长度,以较小者为准。 这确定了 init 进程 (PID 1) 的初始矢量长度。
读取此文件将返回当前系统默认矢量长度。
在每次 execve() 调用时,新进程的新矢量长度都设置为系统默认矢量长度,除非
为调用线程设置了 PR_SVE_VL_INHERIT(或等效的 SVE_PT_VL_INHERIT),或者
存在通过 PR_SVE_SET_VL_ONEXEC 标志(或 SVE_PT_VL_ONEXEC)建立的挂起的延迟矢量长度更改。
修改系统默认矢量长度不会影响未进行 execve() 调用的任何现有进程或线程的矢量长度。
10. Perf 扩展¶
arm64 特定 DWARF 标准 [5] 在索引 46 处添加了 VG(矢量粒度)寄存器。 当可变长度 SVE 寄存器被推送到堆栈上时,此寄存器用于 DWARF 展开。
其值等效于当前 SVE 矢量长度 (VL)(以位为单位)除以 64。
如果设置了 PERF_SAMPLE_REGS_USER 并且 sample_regs_user 掩码的位 46 已设置,则该值将包含在 Perf 样本的 regs[46] 字段中。
该值是获取样本时当前的值,并且会随着时间的推移而变化。
如果在使用这些设置调用 perf_event_open 时系统不支持 SVE,则事件将无法打开。
附录 A. SVE 程序员模型(信息性)¶
本节提供 SVE 对 ARMv8-A 程序员模型的添加的最简描述,这些添加与本文档相关。
注意:本节仅供参考,并非旨在完整或替代任何架构规范。
A.1. 寄存器¶
在 A64 状态下,SVE 添加了以下内容
32 个 8VL 位矢量寄存器 Z0..Z31 对于每个 Zn,Zn 位 [127:0] 是 ARMv8-A 矢量寄存器 Vn 的别名。
使用 Vn 寄存器名称写入寄存器会将相应 Zn 的所有位清零,但位 [127:0] 除外。
16 个 VL 位谓词寄存器 P0..P15
1 个 VL 位专用谓词寄存器 FFR(“首个故障寄存器”)
一个 VL “伪寄存器”,用于确定每个矢量寄存器的大小
SVE 指令集架构不提供直接写入 VL 的方法。 相反,它只能由 EL1 及以上版本通过写入适当的系统寄存器来修改。
VL 的值可以由 EL1 及以上版本在运行时配置:16 <= VL <= VLmax,其中 VL 必须是 16 的倍数。
最大矢量长度由硬件确定:16 <= VLmax <= 256。
(SVE 架构指定 256,但允许将来的架构修订版提高此限制。)
FPSR 和 FPCR 从 ARMv8-A 保留,并且以类似于与 ARMv8 浮点运算交互的方式与 SVE 浮点运算交互
8VL-1 128 0 bit index +---- //// -----------------+ Z0 | : V0 | : : Z7 | : V7 | Z8 | : * V8 | : : : Z15 | : *V15 | Z16 | : V16 | : : Z31 | : V31 | +---- //// -----------------+ 31 0 VL-1 0 +-------+ +---- //// --+ FPSR | | P0 | | +-------+ : | | *FPCR | | P15 | | +-------+ +---- //// --+ FFR | | +-----+ +---- //// --+ VL | | +-----+
- (*)被调用者保存
这仅适用于 Z-/V 寄存器的位 [63:0]。 FPCR 包含被调用者保存和调用者保存位。 有关详细信息,请参阅 [4]。
A.2. 过程调用标准¶
ARMv8-A 基本过程调用标准在附加的 SVE 寄存器状态方面进行了如下扩展
未与 FP/SIMD 共享的所有 SVE 寄存器位都是调用者保存的。
Z8 位 [63:0] .. Z15 位 [63:0] 是被调用者保存的。
这是因为这些位以这种方式映射到 V8..V15,V8..V15 在基本过程调用标准中是调用者保存的。
附录 B. ARMv8-A FP/SIMD 程序员模型¶
注意:本节仅供参考,并非旨在完整或替代任何架构规范。
有关更多信息,请参阅 [4]。
ARMv8-A 定义了以下浮点/ SIMD 寄存器状态
32 个 128 位向量寄存器 V0..V31
2 个 32 位状态/控制寄存器 FPSR、FPCR
127 0 bit index
+---------------+
V0 | |
: : :
V7 | |
* V8 | |
: : : :
*V15 | |
V16 | |
: : :
V31 | |
+---------------+
31 0
+-------+
FPSR | |
+-------+
*FPCR | |
+-------+
- (*)被调用者保存
这仅适用于 V 寄存器的 [63:0] 位。FPCR 包含被调用者保存位和调用者保存位的混合。
参考¶
- [1] arch/arm64/include/uapi/asm/sigcontext.h
AArch64 Linux 信号 ABI 定义
- [2] arch/arm64/include/uapi/asm/ptrace.h
AArch64 Linux ptrace ABI 定义
[3] ARM64 CPU 功能寄存器
- [4] ARM IHI0055C
http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055c/IHI0055C_beta_aapcs64.pdf http://infocenter.arm.com/help/topic/com.arm.doc.subset.swdev.abi/index.html 用于 ARM 64 位架构(AArch64)的过程调用标准
[5] https://github.com/ARM-software/abi-aa/blob/main/aadwarf64/aadwarf64.rst