x86-64与arm64对寄存器访问操作的一些特征

本文主要介绍Intel x86-64架构与ARM64架构对通用目的寄存器以及SIMD寄存器的访问操作的情况。比如,哪些寄存器或某些指令对它们低位修改时,高位仍保持不变;而哪些寄存器或某些操作对它们低位修改时,高位会被清零。


Intel x86_64处理器架构

通用目的寄存器

在Intel软件开发者指南第1卷的3.4小节(BASIC PROGRAM EXECUTION REGISTERS)中介绍了程序开发者能访问的通用目的寄存器、段寄存器以及EFLAGS状态寄存器。其中,3.4.1.1小节(General-Purpose Registers in 64-Bit Mode)则重点介绍了在64位执行模式下,程序员能访问到的所有通用目的寄存器有哪些。官方文档列出了以下图表来阐明在32位模式下以及64位模式下分别能访问哪些通用目的寄存器。

x86-64_gpregs
上图中,中间栏的Without REX表明32位执行模式;而右侧栏的With REX表明是在64位执行模式下。这里各位需要注意的是,某些在32位模式下能访问的8位寄存器(Byte Registers)在64位模式下是不可被访问的! 官方文档如下说明:

在64位模式下,对字节寄存器的访问是受限的。一条指令不能访问32位模式下可访问的高字节寄存器(比如:AH、BH、CH和DH),但是与此同时,64位模式下新增了一些字节寄存器(比如,DIL、SIL、BPL以及R8L到R15L)。在64位模式下,新架构会迫使这种限制应用于把对AH、BH、CH和DH的访问强制改变为对BPL、SPL、DIL和SIL的访问。

所以,当我们写汇编时,有些汇编器解析到我们代码中包含AH、BH等寄存器时,会默认生成32位的x86架构的目标文件(比如NASM和YASM);而有些汇编器则会按照上述限制的应用而自动把AH修改为对BPL的引用(比如GAS汇编器)。但对于我们程序员而言还是得务必小心,避免在64位执行模式下引用AH、BH、CH和DH。如果碰到像 LAHF 这种指令,我们至少要以 AX 寄存器去处理所得到的状态标志数据。

下面我们将介绍对于通用目的寄存器,对其低位修改时,高位会做何变化。以下为官方描述:

当处于64位模式下,操作数大小确定了目标通用目的寄存器的有效比特个数:

  • 64位操作数在目标通用目的寄存器中生成64位结果。
  • 32位操作数在目标通用目的寄存器中先生成一个32位结果,然后以高位填0扩展到一个64位结果。
  • 8位和16位操作数生成一个8位或16位结果。目标通用目的寄存器的高56位或高48位保持不变。如果某些对8位或16位操作的结果有意对64位地址进行计算,那么将会显式地对该寄存器带符号扩展到完整的64位。

SSE寄存器

对SSE寄存器的访问操作涵盖了从SSE到SSE4.2所有指令。对一个SSE寄存器做向量类型操作,那么没啥好多说的,一个SSE寄存器的所有元素会参与相同的操作。而对一个SSE做标量操作时,那么仅仅对该SSE寄存器的最低位元素(即第0个元素)进行操作修改,而高位则保持不变。看官方文档所示的图:

sse-regs
上图中,最上面一行表示第一个源操作数,也是目的操作数;中间一行是第二个源操作数;最下面一行就是第一行目的操作数的最终计算结果。


AVX、AVX-512寄存器

AVX指令集扩展引入了YMM寄存器(共256位),而AVX-512指令集扩展则引入了ZMM寄存器(共512位),由于无论是YMM寄存器还是ZMM寄存器都是基于原来的XMM寄存器做高位更宽的向量扩展,因此比如对YMM0寄存器做低128位操作也会影响XMM0寄存器的值;同样对ZMM0寄存器做低256位操作也会影响YMM0寄存器的值。

对于一个普通向量操作(比如 VADDPS 指令),如果是AVX指令,并且操作数为XMM寄存器,那么目的操作数寄存器的128位到其最高位全被清零;如果操作数为YMM寄存器,那么目的操作数寄存器的256位到其最高位全被清零。

而对于一个普通标量操作(比如 VADDSS 指令),则比较复杂了,下面以 VADDSS xmm0, xmm1, xmm2 这条指令为例进行说明计算结果:xmm0的0到31位作为计算结果进行存放;xmm0的32到127位的数据为xmm1的32到127位的数值;而xmm0所对应的zmm0的其余高位(即从128位起到最高位)被清零。

关于AVX-512更多详细说明,请见此博文:《Intel AVX-512简介


ARM64处理器架构

在ARMv8架构参考指南中的C1.2.5小节(Register names)列出了关于通用目的寄存器以及SIMD寄存器的命名、访问范围以及相关特征。


通用目的寄存器

在ARMv8架构中的64位执行模式下(以下简称ARM64),我们可访问31个通用目的寄存器(X0到X30),并且有一个独立的栈寄存器SP,还有一个特殊的零寄存器(ZR)。这里各位需要注意的是,由于SP寄存器与ZR寄存器在指令编码中用的是同一个寄存器编码(31),因此这就意味着有些指令中,31表示的是栈寄存器;而有些指令中,31则表示零寄存器。具体用的是哪个寄存器,我们需要对照文档的指令汇编格式来看。我们稍后将会介绍。

下面是官方文档列出的通用目的寄存器的表:

arm64-gpregs
上图也很清楚地列出了这些寄存器的编码格式。其中,W寄存器为32位寄存器;X寄存器则为64位。对于上表,官方文档做了如下说明:

  • Xn与Wn都引用为同一个通用目的寄存器Rn。
  • 没有名字叫W31或X31的寄存器。(这里大家写汇编的时候千万注意)
  • 名字SP表示64位操作数的栈指针,在相应的寄存器域中编码值为31,它会被解析为读或写当前的栈指针。当一些指令不把此操作数编码为栈指针时,使用SP名字将是一个错误。
  • 名字WSP表示一个32位环境下的当前栈指针。
  • 名字XZR表示64位操作数的零寄存器,它在相应寄存器域中的编码值为31,它会被解析为在读的时候返回零而在写的时候则被忽略。当一些指令不把此操作数解析为零寄存器时,使用XZR将是一个错误。
  • 名字WZR表示一个32位环境下的零寄存器。

由此看来,XZR寄存器与SP寄存器两者是互斥的,即如果一条指令会把寄存器的编码值31视作为SP,那么它就绝不会把该寄存器当作XZR。这里各位还要注意以下官方描述:

当数据大小为32位时,通用目的寄存器的低32位被使用,而对于高32位,在读时会被忽略,而在写时则被清零。

下面给大家介绍一下如何判定一条指令中可使用SP还是XZR。其实很简单,大家看指令描述中,如果在某个寄存器操作数中有显式的 |SP,那么编码值31的寄存器就被视作栈指针寄存器;否则一律被视作为零寄存器。

比如我们看 ADD (extended register) 这条指令,其64位版本的描述如下:

ADD <Xd|SP>, <Xn|SP>, <R><m>{, <extend> {#<amount>}}

这里大家就能看到,这条指令的目的操作数与第一个源操作数均有 |SP,说明目的寄存器与第一个源寄存器会将编码值31的寄存器视作为SP寄存器;而第二个操作数<R><m>由于没有 |SP 的指示,因此对于第二个源寄存器而言如果是编码值为31,那么它就会被视为XZR零寄存器,而不是SP栈指针寄存器。

我们再看一条指令:ADD (shifted register)

ADD <Xd>, <Xn>, <Xm>{, <shift> #<amount>}

我们看到,对此指令的描述无论哪个操作数都没有出现上面的 |SP,说明对于这条指令,无论哪个操作数寄存器用了编码值31,那么全都被视为零寄存器XZR。


SIMD寄存器

ARM64也有32个SIMD寄存器,其命名如下图所示:

armv8-simd
这里官方文档也明确提到:

如果SIMD指令和浮点指令对标量数据进行操作,那么它仅访问一个SIMD寄存器或浮点寄存器的低位。而没被使用的高位则在读时被忽略,在写时被清零。


ARMv7中的SIMD及浮点寄存器的分布

我们最后来看一下ARMv7的SIMD寄存器情况。由于ARMv7为了能兼容更多低性能的设备,因此允许某些CPU仅支持浮点操作而不支持SIMD操作,比如nVidia所出品的第一代Tegra处理器。因此它是将浮点寄存器与SIMD寄存器做了分段拼接处理,如官方下图所示:

armv7-simd
这也就意味着我们对32位单精度浮点寄存器S0进行修改,它不会影响S1寄存器的值,这也就意味着64位双精度寄存器D0的高32位不受影响,从而也就意味着128位的Q0寄存器的32位到127位均不受影响。这操作就跟x86的SSE比较类似了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值