AArch64 Linux 的可伸缩向量扩展支持¶
作者:Dave Martin <Dave.Martin@arm.com>
日期:2017年8月4日
本文简要概述了 Linux 为支持使用 ARM 可伸缩向量扩展(SVE)而向用户空间提供的接口,包括与可伸缩矩阵扩展(SME)添加的流式 SVE 模式(Streaming SVE mode)的交互。
这仅是关于最重要特性和问题的概述,并不旨在穷举所有内容。
本文不旨在描述 SVE 架构或程序员模型。为了帮助理解,附录 A 中包含对 SVE 相关程序员模型特性的最简要描述。
1. 概述¶
SVE 寄存器 Z0..Z31、P0..P15 和 FFR 以及当前向量长度 VL 是按线程进行跟踪的。
在流式模式下,除非系统中存在 HWCAP2_SME_FA64,否则 FFR 是不可访问的;如果不被支持且使用这些接口来访问流式模式下的 FFR,则读取和写入的值均为零。
SVE 的存在通过 aux 向量 AT_HWCAP 条目中的 HWCAP_SVE 报告给用户空间。此标志的存在意味着存在 SVE 指令和寄存器,以及本文档中描述的 Linux 专用系统接口。SVE 在 /proc/cpuinfo 中显示为 “sve”。
通过使用 MRS 指令读取 CPU ID 寄存器 ID_AA64PFR0_EL1 并检查 SVE 字段的值是否为非零,也可以检测用户空间对执行 SVE 指令的支持情况。 [3]
这并不能保证下文所述系统接口的存在:需要验证这些接口是否存在软件必须检查 HWCAP_SVE。
在支持 SVE2 扩展的硬件上,AT_HWCAP2 aux 向量条目中也会报告 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 aux 向量条目中也会报告 HWCAP2_SME。除其他功能外,SME 还添加了流式模式,该模式使用单独的 SME 向量长度和相同的 Z/V 寄存器提供 SVE 特性集的子集。有关更多详细信息,请参阅 Scalable Matrix Extension support for AArch64 Linux。
调试器应仅限于通过 NT_ARM_SVE 寄存器集与目标进行交互。检测对该寄存器集支持的推荐方法是:首先连接到目标进程,然后尝试调用 ptrace(PTRACE_GETREGSET, pid, NT_ARM_SVE, &iov)。请注意,当存在 SME 且正在使用流式 SVE 模式时,将通过 NT_ARM_SVE 读取寄存器的 FPSIMD 子集,并且对 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)寄存器的大小被称为“向量长度”。
为了避免对用于表示向量长度的单位产生混淆,内核采用了以下约定:
Vector length (VL) = size of a Z-register in bytes
Vector quadwords (VQ) = size of a Z-register in units of 128 bits
(So, 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 寄存器变为活动状态并被填入指定的数据。然而,出于向后兼容的原因,Z0..Z31 的位 [127:0] 始终从 fpsimd_context.vregs[] 的相应成员恢复,而不是从 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 not supported, invalid vector length requested, or
invalid flags.
成功时:
调用线程的向量长度或该线程将在下次 execve() 时应用的延迟向量长度(取决于 arg 中是否存在 PR_SVE_SET_VL_ONEXEC),将被设置为系统支持的小于或等于 vl 的最大值。如果 vl == SVE_VL_MAX,则设置的值将是系统支持的最大值。
调用线程中任何先前未完成的延迟向量长度更改都将被取消。
返回值描述了最终的配置,其编码方式与 PR_SVE_GET_VL 相同。如果 arg 中不存在 PR_SVE_SET_VL_ONEXEC,则此值中报告的向量长度是该线程的新当前向量长度;否则,报告的向量长度是将由调用线程在下次 execve() 时应用的延迟向量长度。
更改向量长度会导致 P0..P15、FFR 以及 Z0..Z31 中除 Z0 的位 [127:0] .. Z31 的位 [127:0] 之外的所有位变为未指定状态。使用等于线程当前向量长度的 vl 调用 PR_SVE_SET_VL,或者使用 PR_SVE_SET_VL_ONEXEC 标志调用 PR_SVE_SET_VL,就此目的而言不构成对向量长度的更改。
prctl(PR_SVE_GET_VL)
获取调用线程的向量长度。
以下标志可能会按位或合并到结果中:
PR_SVE_VL_INHERIT
Vector length will be inherited across execve().
无法确定是否存在未完成的延迟向量长度更改(在典型用法中,这通常只存在于 fork() 或
vfork()与相应的 execve() 之间)。要从结果中提取向量长度,请将其与 PR_SVE_VL_LEN_MASK 进行按位与运算。
- 返回值:成功时为非负值,出错时为负值。
EINVAL:不支持 SVE。
7. ptrace 扩展¶
定义了新的寄存器集 NT_ARM_SVE 和 NT_ARM_SSVE,供 PTRACE_GETREGSET 和 PTRACE_SETREGSET 使用。NT_ARM_SSVE 描述流式模式的 SVE 寄存器,NT_ARM_SVE 描述非流式模式的 SVE 寄存器。
在本说明中,当目标处于适当的流式或非流式模式,并且正在使用超出与 FPSIMD Vn 寄存器共享的子集的数据时,寄存器集被称为处于“活动”状态。
有关定义,请参阅 [2]。
寄存器集数据以 struct user_sve_header 开头,包含:
size
完整寄存器集的大小(以字节为单位)。这取决于 vl,未来可能还取决于其他因素。
如果对 PTRACE_GETREGSET 的调用请求的数据少于 size 的值,则调用方可以分配更大的缓冲区并重试,以便读取完整的寄存器集。
max_size
目标线程的寄存器集可以增长到的最大大小(以字节为单位)。即使目标线程更改其向量长度等,寄存器集也不会增长得比这更大。
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);
... 与以下零个或多个标志按位或组合,这些标志具有与对应的 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 的地方,当处于流式模式时,无法对普通 SVE 的寄存器状态执行 GETREGSET;当处于普通模式时,也无法对流式模式的寄存器状态执行 GETREGSET,无论硬件在两种模式之间共享数据的实现定义行为如何。
如果目标处于流式模式,则对 NT_ARM_SVE 的任何 SETREGSET 都将退出流式模式;如果目标不在流式模式,则对 NT_ARM_SSVE 的任何 SETREGSET 都将进入流式模式。
在不支持 SVE 的系统上,允许通过 NT_ARM_SVE 使用 SETREGSET 写入 SVE_PT_REGS_FPSIMD 格式的数据,此时向量长度应指定为 0。这允许在具有 SME 但没有 SVE 的系统上禁用流式模式。
如果与 SVE_PT_VL_ONEXEC 一起提供了任何寄存器数据,则寄存器数据将根据当前的向量长度进行解释,而不是根据配置用于 exec 的向量长度。
写入部分、不完整的有效负载的效果是未指定的。
8. ELF 核心转储扩展¶
NT_ARM_SVE 和 NT_ARM_SSVE note 将被添加到被转储进程的每个线程的每个核心转储中。其内容将等效于在生成核心转储时为每个线程执行相应类型的 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(Vector Granule,向量颗粒)寄存器。当将可变长度的 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 中保留,其与 SVE 浮点操作的交互方式类似于它们与 ARMv8 浮点操作的交互方式:
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. 过程调用标准¶
关于附加的 SVE 寄存器状态,ARMv8-A 基础过程调用标准作了如下扩展:
所有未与 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