【ARMv8 编程】寄存器

ARMv8 用于描述整体架构,包括 32 位执行和 64 位执行。它使用 64 位位宽寄存器,同时保持向后兼容 v7。

在这里插入图片描述
现在来看看 ARMv8 都有哪些改进:

大的物理地址

这使处理器能够访问超过 4GB 的物理内存。

64 位虚拟寻址

这使得虚拟内存可以超过 4GB 的限制。这对现在来说实现桌面和服务器软件使用内存映射文件I/O或稀疏寻址是很重要的。

自动事件信号

这使得实现高效、高性能的自旋锁成为可能。

更大的注册文件

31 个 64 位通用寄存器可以提高性能并降低 stack 使用。

高效的 64 位即时生成

对文字池(literal pool 的本质就是 ARM 汇编语言代码节中的一块用来存放常量数据而非可执行代码的内存块)的需求更少。

较大的 PC 相对寻址范围

+/-4GB 寻址范围,用于在共享库中有效地对数据进行寻址和位置独立的可执行代码。

额外的 16KB 和 64KB 翻译粒度

这减少了翻译后备缓冲区(Translation Lookaside Buffer,TLB)的失误率和页面检索深度。

新的异常模式

这降低了操作系统和 hypervisor 软件的复杂性。

高效的缓存管理

用户空间缓存操作可以提高动态代码生成效率。使用 Data cache Zero 指令快速清除数据缓存。

硬件加速密码学

提供 3× ~ 10× 更好的软件加密性能。这对于小粒度的解密和加密太小无法 offload 到硬件高效加速器上很有用,例如 https。

Load-Acquire、Store-Release 指令

专为 C++ 11、C11 和 Java 内存模型设计。他们提高了通过消除显式内存屏障指令实现线程安全的代码。

NEON 双精度浮点高级 SIMD

这使得 SIMD 矢量化可以应用于更广泛的算法。例如,科学计算、高性能计算(HPC)和超级计算机。

一、异常级别

在 ARMv8 中,运行发生在四个异常级别之一上。在 AArch64 中,异常级别以类似于 ARMv7 中定义的特权级别的方式确定特权级别。异常级别决定了特权级别,因此在 ELn 上的运行对应于特权 PLn。

异常级别提供了跨应用的软件执行特权的逻辑分离 ARMv8 架构的所有操作状态。它类似于,并支持,计算机科学中常见的分级保护域。

下面是在每异常级别上运行的软件的典型例子:

EL0 普通用户应用程序。

EL1 操作系统内核通常被描述为具有特权。

EL2 Hypervisor。

EL3 底层固件,包括安全监视器。

在这里插入图片描述

二、通用寄存器

AArch64 执行状态提供了 31 × 64 位通用寄存器,在任何时候和所有异常级别都可以访问。每个寄存器是 64 位宽的,它们通常被称为寄存器 X0~X30。

在这里插入图片描述
每个 AArch64 64 位通用寄存器(X0~X30)也有 32 位(W0~W30)形式。

在这里插入图片描述
32 位 W 寄存器对应的 64 位 X 寄存器的下半部分。即 W0 映射到 X0 的下半部分,W1 映射到 X1 的下半部分。

从 W 寄存器读取时,忽略对应的 X 寄存器中较高的 32 位,并保持不变。写入 W 寄存器将 X 寄存器的高 32 位设置为零。也就是说,将 0xFFFFFFFF 写入 W0 会将 X0 设置为 0x00000000FFFFFFFF。

三、特殊寄存器

在这里插入图片描述
没有叫 X31 或 W31 的寄存器。许多指令被编码成 31 这样的数字表示零寄存器ZR(WZR/XZR)。还有一组受限制的指令其中一个或多个参数被编码成 31 表示栈指针(SP)。

名称 位宽 描述
WZR 32 bits 零寄存器
XZR 64 bits 零寄存器
WSP 32 bits 当前栈指针
SP 64 bits 当前栈指针
PC 64 bits 程序计数器

当访问零寄存器时,所有的写操作都被忽略,所有的读操作都返回 0。注意,64 位形式的 SP 寄存器不使用 X 前缀。

在 ARMv8 架构中,当在 AArch64 中执行时,每个异常级别的异常返回状态保存在以下专用寄存器中:

  1. Exception Link Register 异常链接寄存器(ELR)。
  2. Saved Processor State Register 保存处理器状态寄存器(SPSR)。

零寄存器 ZR

当用作源寄存器时,零寄存器读取为零,当用作目标寄存器时丢弃结果。你可以在大多数,但不是所有的指令中使用零寄存器。

栈指针 SP

在 ARMv8 体系结构中,要使用的堆栈指针的选择在某种程度上与异常级别分离。默认情况下,接受异常会选择目标异常级别 SP_ELn 的堆栈指针。例如,接受 EL1 的异常将选择 SP_EL1。每个异常级别都有自己的堆栈指针 SP_EL0、SP_EL1、SP_EL2 和 SP_EL3。

当 AArch64 的异常级别不是 EL0 时,处理器可以使用以下任何一种:

  1. 与异常级别(SP_ELn)相关的专用 64 位栈指针。
  2. 与 EL0 相关的堆栈指针(SP_EL0)。

EL0 只能访问 SP_EL0。

异常级别 操作
EL0 EL0t
EL1 EL1t, EL1h
EL2 EL2t, EL2h
EL3 EL3t, EL3h

后缀 t 表示选择 SP_EL0 堆栈指针。h 后缀表示选择了 SP_ELn 堆栈指针。

SP 不能被大多数指令引用。然而,某些形式的算术指令,例如 ADD 指令,可以读写当前栈指针来调整函数中的栈指针。

ADD SP, SP, #0x10 // 将 SP 调整为比当前值大 0x10

程序计数器 PC

原始 ARMv7 指令集的一个特性是使用 R15,即程序计数器(PC)作为通用寄存器。PC 能够实现一些巧妙的编程技巧,但它也为编译器和复杂管道的设计带来了复杂性。在 ARMv8 中取消对 PC 的直接访问使得返回预测更容易,并简化了 ABI 规范。PC 永远不能以命名寄存器的形式访问。它的使用隐含在某些指令中,比如相对于 PC 的加载和地址生成。不能将 PC 指定为数据处理指令或加载指令的目标。

异常链接寄存器 ELR

异常链接寄存器保存着异常返回地址。

保存处理器状态寄存器 SPSR

当接受异常时,处理器状态被存储在相关的保存程序状态寄存器(SPSR)中,与 ARMv7 中的 CPSR 类似。SPSR 在接受异常之前保存 PSTATE 的值,用于在执行异常返回时恢复 PSTATE 的值。

在这里插入图片描述

标志 描述
N 负数标志位。结果为负数则 N=1,否则 N=0
Z 零标志位。如果结果为零,则 Z=1,否则 Z=0
C 进位标志位。如果结果有进位,则 C=1,否则 C=0
V 溢出标志位。如果结果有溢出,则 V=1,否则 V=0
SS 软件单步调试。该位为1,表示在异常处理时使能了软件单步功能。
IL 非法执行状态位。在异常处理之前立即显示 PSTATE.IL 的值。
D 进程状态调试掩码。指示来自观察点、断点和软件单步调试事件的调试异常是否被屏蔽,这些事件的目标是发生的异常所在异常级别。
A SError(系统错误)掩码。
I IRQ 掩码位。
F FIQ 掩码位。
M[4] 异常发生所处的执行状态。取值为 0 表示 AArch64
M[3:0] 异常发生时所处的模式或异常级别。

处理器状态 PSTATE

AArch64 没有直接等效于 ARMv7 当前程序状态寄存器(CPSR)。在 AArch64 中,传统 CPSR 的组件是作为字段提供的,可以独立访问。这些状态统称为处理器状态(PSTATE)。

AArch64 的处理器状态或 PSTATE 字段有以下定义:

名称 描述
N 负数标志位。
Z 零标志位。
C 进位标志位。
V 溢出标志位。
D 进程状态调试掩码。
A SError(系统错误)掩码。
I IRQ 掩码位。
F FIQ 掩码位。
SS 软件单步调试。
IL 非法执行状态位。
EL (2) 异常级别。
nRW 执行状态:0 = 64-bit,1 = 32-bit
SP 栈指针选择:0 = SP_EL0,1 = SP_ELn

在 AArch64 中,通过执行 ERET 指令从异常中返回,这将导致 SPSR_ELn 被复制到 PSTATE。这将恢复 ALU 标志、执行状态、异常级别和处理器分支。从这里开始,从 ELR_ELn 中的地址继续执行。

PSTATE.{N, Z, C, V} 字段可以在 EL0 访问。所有其他的 PSTATE 字段可以在 EL1 或更高的值访问,并且在 EL0 是未定义的。

四、系统寄存器

在 AArch64 中,系统配置通过系统寄存器控制,并使用 MSR 和 MRS 指令访问。这与 ARMv7-A 不同,在 ARMv7-A 中,这些寄存器通常是通过协处理器 15(CP15)操作访问的。寄存器的名称告诉你可以访问它的最低异常级别。例如:

  1. TTBR0_EL1 可通过 EL1、EL2、EL3 访问。
  2. TTBR0_EL2 可从 EL2 和 EL3 访问。

具有 _ELn 后缀的寄存器在某些或所有级别中具有单独的存储副本,尽管通常不是 EL0。很少有系统寄存器可以从 EL0 访问,尽管缓存类型寄存器(CTR_EL0)是一个可以访问的例子。

访问系统寄存器的代码采用以下形式:

MRS x0, TTBR0_EL1 //将 TTBR0_EL1 移动到 x0
MSR TTBR0_EL1, x0 //将 x0 移到 TTBR0_EL1 中

以前版本的 ARM 架构使用协处理器进行系统配置。但是 AArch64 不支持。下表显示了每个寄存器的独立副本的异常级别。例如,ACTLR(辅助控制寄存器)以 ACTLR_EL1、ACTLR_EL2 和 ACTLR_EL3 的形式存在。

名称 寄存器 描述 允许的 n 值
ACTLR_ELn 辅助控制寄存器 控制特定于处理器的特性。 1, 2, 3
CCSIDR_ELn 当前缓存大小 ID 寄存器 提供有关当前选定缓存的体系结构信息。 1
CLIDR_ELn 缓存级 ID 寄存器 缓存的类型,或在每个级别实现的缓存。 1, 2, 3
CNTFRQ_ELn 计数型计时器的频率寄存器 上报系统计时器的频率。 0
CNTPCT_ELn 计数型计时器的物理计数寄存器 保存 64 位的当前计数值。 0
CNTKCTL_ELn 计数型计时器的内核控制寄存器 控制从虚拟计数器生成事件流。还控制从 EL0 到物理计数器、虚拟计数器、EL1 物理计时器和虚拟计时器的访问。 1
CNTP_CVAL_ELn 计数型计时器的物理计时器比较值寄存器 保存 EL1 物理计时器的比较值。 0
CPACR_ELn 协处理器访问控制寄存器 控制对 Trace、浮点和 NEON 功能的访问。 1
CSSELR_ELn 缓存大小选择寄存器 通过指定所需的缓存级别和缓存类型(指令缓存或数据缓存),选择当前的缓存大小 ID 寄存器 CCSIDR_EL1。 1
CNTP_CTL_ELn 计数型计时器的物理控制寄存器 控制寄存器的 EL1 物理计时器。 0
CTR_ELn 缓存类型寄存器 关于集成缓存架构的信息。 0
DCZID_ELn 数据缓存零 ID 寄存器 由 DCZVA(Data Cache 0 by Virtual Address)系统指令写入的字节值为 0 的块大小。 0
ELR_ELn 异常链接寄存器 保存导致异常指令的地址。 1, 2, 3
ESR_ELn 异常诊断(Syndrome)寄存器 包括有关异常原因的信息。 1, 2, 3
FAR_ELn 故障地址寄存器 保存虚拟故障地址。 1, 2, 3
FPCR 浮点控制寄存器 控制浮点扩展行为。这个寄存器中的字段映射到 AArch32 FPSCR 中的等效字段。 -
FPSR 浮点状态寄存器 提供浮点系统状态信息。这个寄存器中的字段映射到 AArch32 FPSCR 中的等效字段。 -
HCR_ELn Hypervisor 配置寄存器 控制虚拟化设置和捕获 EL2 的异常。 2
MAIR_ELn 内存属性间接寄存器 提供与 ELn 的阶段 1 转换的长描述格式转换表项中可能值对应的内存属性编码。 1, 2, 3
MIDR_ELn 主 ID 寄存器 运行代码的处理器类型(部件号和版本号)。 1
MPIDR_ELn 多处理器亲和性寄存器 多核或集群系统中的处理器和集群 ID。 1
SCR_ELn 安全配置寄存器 控制 EL3 的安全状态和异常捕获。 3
SCTLR_ELn 系统控制寄存器 控制架构特性,例如 MMU、缓存和对齐检查。 0, 1, 2, 3
SPSR_ELn 保存程序状态寄存器 当此模式或异常级别发生异常时,保持已保存的处理器状态。 abt, fiq, irq, und, 1,2, 3
TCR_ELn 转换控制寄存器 确定哪个转换表基址寄存器定义了从 ELn 访问内存的阶段 1 转换所需的转换表遍历的基地址。还控制转换表格式并保存可缓存性和可共享性信息。 1, 2, 3
TPIDR_ELn 用户读/写线程 ID 寄存器 提供在 ELn 处执行的软件可以存储线程标识信息的位置,以用于 OS 管理。 0, 1, 2, 3
TPIDRRO_ELn 用户仅允许读线程 ID 寄存器 提供在 EL1 或更高版本执行的软件可以存储线程标识信息的位置。出于 OS 管理目的,此信息对在 EL0 处执行的软件可见。 0
TTBR0_ELn 转换表基址寄存器 0 保存转换表 0 的基地址,以及有关它占用的内存的信息。这是 ELn 内存访问阶段 1 转换的转换表之一。 1, 2, 3
TTBR1_ELn 转换表基址寄存器 1 保存转换表 1 的基地址,以及有关它占用的内存的信息。这是 EL0 和 EL1 内存访问的阶段 1 转换的转换表之一。 1
VBAR_ELn 基于向量的地址寄存器 保存被带到 ELn 的任何异常的异常基地址。 1, 2, 3
VTCR_ELn 虚拟化转换控制寄存器 控制从非安全 EL0 和 EL1 进行内存访问的第 2 阶段转换所需的转换表遍历。还保存访问的可缓存性和可共享性信息。 2
VTTBR_ELn 虚拟化转换表基址寄存器 保存从非安全 EL0 和 EL1 进行内存访问的阶段 2 转换的转换表的基地址。 2

五、NEON 和浮点寄存器

除了通用寄存器,ARMv8 还有 32 个 128 位浮点寄存器,标记为 V0-V31。 32 个寄存器用于保存标量浮点指令的浮点操作数以及 NEON 操作的标量和向量操作数。

AArch64 中的浮点寄存器组织

在对标量数据进行操作的 NEON 和浮点指令中,浮点和 NEON 寄存器的行为类似于主要的通用整数寄存器。因此,仅访问低位,读取时忽略未使用的高位,写入时设置为零。标量浮点和 NEON 名称的限定名称表示有效位数如下,其中 n 是寄存器编号 0-31。

不同大小的浮点数的操作数名称表

精度 大小(bits) 名称
Half(半) 16 Hn
Single(单) 32 Sn
Double(双) 64 Dn

在这里插入图片描述
支持 16 位浮点,但仅作为要转换的格式。数据处理操作不支持它。

前缀 F 和浮点数大小由浮点 ADD 指令指定:

FADD Sd, Sn, Sm // 单精度
FADD Dd, Dn, Dm // 双精度

半精度浮点指令用于不同大小之间的转换:

FCVT Sd, Hn // 半精度到单精度 
FCVT Dd, Hn // 半精度到双精度 
FCVT Hd, Sn // 单精度到半精度 
FCVT Hd, Dn // 双精度到半精度

标量寄存器大小

在 AArch64 中,整数标量的映射如下图:

在这里插入图片描述
图中 S0 为 D0 的下半部分,D0 为 Q0 的下半部分。S1 是 D1 的下半部分,D1 是 Q1 的下半部分,以此类推。这消除了编译器在自动向量化高级代码时存在的许多问题。

  1. 每个 Q 寄存器的底部 64 位也可以被视为 D0-D31, 32 位浮点和 NEON 使用的 64 位寄存器。

  2. 每个 Q 寄存器的底部 32 位也可以被视为 S0-S31, 32 位浮点和 NEON 使用的32 位宽寄存器。

  3. 每个 S 寄存器的底部 16 位也可以被视为 H0-H31, 32 个 16 位宽寄存器用于浮点和 NEON 使用。

  4. 每个 H 寄存器的底部 8 位也可以被视为 B0-B31, 32 个 8 位宽寄存器用于 NEON。

在每种情况下,仅使用每个寄存器组的低位。其余的寄存器空间读取时忽略,写入时填充零。

这种映射的结果是,如果在 AArch64 中执行的程序正在解释来自 AArch32 的 D 或 S 寄存器。那么,在使用 D 或 S 寄存器之前,程序必须将它们从 V 寄存器中解包。

对于标量 ADD 指令:

ADD Vd, Vn, Vm

例如,如果大小为 32 位,则指令为:

ADD Sd, Sn, Sm

不同尺寸标量操作数的名称见下表

字大小 大小(bits) 名称
Byte 8 Bn
Halfword 16 Hn
Word 32 Sn
Doubleword 64 Dn
Quadword 128 Qn

向量寄存器大小

向量可以是 64 位宽,包含一个或多个元素,也可以是 128 位宽,包含两个或多个元素,如图所示:

在这里插入图片描述
对于向量 ADD 指令:

ADD Vd.T, Vn.T, Vm.T

这里对于 32 位向量,有 4 个通道(lane),指令变为:

ADD Vd.4S, Vn.4S, Vm.4S

不同大小的向量的操作数名称见下表

名称 形态
Vn.8B 8 个通道,每个通道包含一个 8 位元素
Vn.16B 16 个通道,每个通道包含一个 8 位元素
Vn.4H 4 个通道,每个通道包含一个 16 位元素
Vn.8H 8 个通道,每个通道包含一个 16 位元素
Vn.2S 2 个通道,每个通道包含一个 32 位元素
Vn.4S 4 个通道,每个通道包含一个 32 位元素
Vn.1D 1 个通道,每个通道包含一个 64 位元素
Vn.2D 2 个通道,每个通道包含一个 64 位元素

当这些寄存器以特定的指令形式使用时,必须进一步限定名称以指示数据形态。更具体地说,这意味着数据元素的大小以及其中包含的元素或通道的数量。

NEON 指令的简单使用

下面这段代码的主要作用是将图像 RGBA 数组转化为 BGRA 数组。rgbaImgDataPtr 指向 RGBA 数组。其中的 MOV 指令是做 B 和 R 通道交换,借助了 v4 寄存器。实际上 ST4 存储指令还是逐个寄存器通道取值,依次从 v0、v1、v2 和 v3 取值,但由于 v2 和 v0 中的值已经交换,所以下图中存储的时候画了虚线先从 v2 开始。

for (int i = 0; i < len; i++) {
    
    
   // RGBA -> BGRA
    asm volatile(
    "LD4 {v0.16B-v3.16B},[%0]\t\n"
    "MOV v4.16B,v0.16B\t\n"
    "MOV v0.16B,v2.16B\t\n"
    "MOV v2.16B,v4.16B\t\n"
    "ST4 {v0.16B-v3.16B},[%0]\t\n"
    :"+r"(rgbaImgDataPtr)//%0
    :
    : "memory", "v0", "v1", "v2", "v3", "v4"
    );
    rgbaImgDataPtr += 64;// 16 * 4,一次16个元素,每个元素占用 4 个 char
}

在这里插入图片描述
参考资料

《ARMv8-A-Programmer-Guide》

猜你喜欢

转载自blog.csdn.net/tyyj90/article/details/126577440
今日推荐