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 按线程进行跟踪。
在流式模式下,除非系统中存在 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 特性集的子集。有关更多详细信息,请参阅 AArch64 Linux 的可扩展矩阵扩展支持。
调试器应将自身限制为通过 NT_ARM_SVE regset 与目标交互。检测此 regset 支持的推荐方法是先连接到目标进程,然后尝试 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) 以及相关数据。
请注意,在 Big-endian 系统上,这会导致与 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 标志的状态以及延迟的向量长度(如果有))都将在所有系统调用中保留,但 execve() 部分 6 中描述的特定例外情况除外。
特别地,在从 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) 必须为 true。
标志
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() 时应用的延迟向量长度。
更改向量长度会导致 P0..P15、FFR 以及 Z0..Z31 的所有位(除了 Z0 位 [127:0] .. Z31 位 [127:0])变为未指定。调用 PR_SVE_SET_VL 时 vl 等于线程的当前向量长度,或者调用 PR_SVE_SET_VL 时带有 PR_SVE_SET_VL_ONEXEC 标志,不构成对此目的的向量长度更改。
prctl(PR_SVE_GET_VL)
获取调用线程的向量长度。
以下标志可以 OR 运算到结果中
PR_SVE_VL_INHERIT
向量长度将在 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]。
寄存器集数据以结构体 user_sve_header 开头,其中包含:
size
完整寄存器集的大小,以字节为单位。这取决于 vl,并且将来可能取决于其他因素。
如果对 PTRACE_GETREGSET 的调用请求的数据少于 size 的值,则调用者可以分配更大的缓冲区并重试,以便读取完整的寄存器集。
max_size
目标线程的寄存器集可以增长到的最大大小,以字节为单位。即使目标线程更改其向量长度等,寄存器集也不会增长到大于此值。
vl
目标线程的当前向量长度,以字节为单位。
max_vl
目标线程的最大可能向量长度。
标志
以下之一(至多):
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) 获取。
应使用 sve_vq_from_vl(vl) 获取 vq。
或
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 的寄存器状态,也不能在处于普通模式时获取流模式寄存器状态,而无论硬件在两种模式之间共享数据的实现定义行为如何。
任何对 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. 过程调用标准¶
对于额外的 SVE 寄存器状态,ARMv8-A 基本过程调用标准扩展如下:
所有未与 FP/SIMD 共享的 SVE 寄存器位都是调用者保存的。
Z8 位 [63:0] .. Z15 位 [63:0] 是被调用者保存的。
这是因为这些位被映射到 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