许多在应用程序层面进行编程的程序员无需编写汇编语言代码。然而,在需要高度优化代码的情况下,汇编代码会很有用。编写编译器时就是这种情况,或者当需要使用 C 语言中无法直接使用的底层特性时也是如此。在编写部分引导代码、设备驱动程序,或者开发操作系统时,可能会有这样的需求。最后,在调试 C 语言程序时,能够读懂汇编代码会很有帮助,尤其是理解汇编指令和 C 语句之间的映射关系时。
6.1 指令助记符(Instruction mnemonics)
A64 汇编语言对指令助记符进行了重载,并根据操作数寄存器名来区分同一条指令的不同形式。例如,下面的所有 ADD 指令都有不同的编码,但你只需记住一个助记符,汇编器会根据操作数自动选择正确的编码。
ADD W0, W1, W2 // add 32-bit registers
ADD X0, X1, X2 // add 64-bit registers
ADD X0, X1, W2, SXTW // add sign extended 32-bit register to 64-bit extended
// register
ADD X0, X1, #42 // add immediate to 64-bit register
ADD V0.8H, V1.8H, V2.8H // NEON 16-bit add, in each of 8 lanes
指令功能概述
这是一组不同形式的 ADD
指令示例,ADD
指令的基本功能是执行加法操作,不同形式的 ADD
指令根据操作数的类型、大小和指令后缀等因素,在不同位数的寄存器或向量寄存器上执行加法。以下是对每条指令的详细解释:
指令逐行解释
-
ADD W0, W1, W2 // add 32-bit registers
-
该指令将 32 位寄存器
W1
和W2
中的值相加,然后把结果存储到 32 位寄存器W0
中。在 ARM64 架构里,以W
开头的寄存器是 32 位通用寄存器。
-
-
ADD X0, X1, X2 // add 64-bit registers
-
此指令把 64 位寄存器
X1
和X2
中的值相加,接着将结果存于 64 位寄存器X0
中。在 ARM64 架构下,以X
开头的寄存器是 64 位通用寄存器。
-
-
ADD X0, X1, W2, SXTW // add sign extended 32-bit register to 64-bit extended register
-
该指令先对 32 位寄存器
W2
中的值进行符号扩展(将 32 位值扩展为 64 位,符号位填充到高 32 位),然后把扩展后的值与 64 位寄存器X1
中的值相加,最后将结果存于 64 位寄存器X0
中。SXTW
是符号扩展 32 位到 64 位的后缀。
-
-
ADD X0, X1, #42 // add immediate to 64-bit register
-
这条指令把立即数
42
与 64 位寄存器X1
中的值相加,再将结果存于 64 位寄存器X0
中。立即数是直接在指令中给出的常数。
-
-
ADD V0.8H, V1.8H, V2.8H // NEON 16-bit add, in each of 8 lanes
-
这是一条 NEON 指令,NEON 是 ARM 架构中用于加速多媒体和信号处理的 SIMD(单指令多数据)指令集。该指令在 8 个通道(lane)上并行执行 16 位加法操作。具体来说,它将 128 位向量寄存器
V1
中 8 个 16 位元素和 128 位向量寄存器V2
中对应的 8 个 16 位元素分别相加,然后把结果存于 128 位向量寄存器V0
中。.8H
表示向量寄存器被视为包含 8 个 16 位半字(half-word)元素。
-
6.2 数据处理指令(Data processing instructions)
这些是处理器的基本算术和逻辑运算,它们对通用寄存器中的值,或者一个寄存器与一个立即数进行操作。第 6 - 4 页的乘法和除法指令可被视为这些指令的特殊情况。
数据处理指令大多使用一个目标寄存器和两个源操作数。其通用格式可认为是指令后面跟着操作数,如下所示: 指令 目标寄存器(Rd),源寄存器(Rn),操作数 2(Operand2)
第二个操作数可以是一个寄存器、一个经过修改的寄存器,或者一个立即数。使用“R”表示它可以是 X 寄存器或 W 寄存器。
数据处理操作包括: • 算术和逻辑运算。 • 移动和移位操作。 • 符号扩展和零扩展指令。 • 位和位段操作。 • 条件比较和数据处理。
6.2.1 算术和逻辑运算(Arithmetic and logical operations)
表 6 - 1 展示了部分可用的算术和逻辑运算。
Table 6-1 Arithmetic and logical operations
类型 | 指令 |
---|---|
算术运算 | ADD, SUB, ADC, SBC, NEG |
逻辑运算 | AND, BIC, ORR, ORN, EOR, EON |
比较运算 | CMP, CMN, TST |
移动操作 | MOV, MVN |
一些指令还带有 “S” 后缀,这表明该指令会设置标志位。在表 6 - 1 中的指令里,带有 “S” 后缀并会设置标志位的指令包括 ADDS、SUBS、ADCS、SBCS、ANDS 和 BICS。还有其他一些设置标志位的指令,特别是 CMP、CMN 和 TST,但这些指令并不使用 “S” 后缀。
ADC 和 SBC 操作执行加法和减法运算,并且还会将进位条件标志位作为输入。
ADC{S}: Rd = Rn + Rm + C
SBC{S}: Rd = Rn - Rm - 1 + C
Example 6-1 Arithmetic instructions
ADD W0, W1, W2, LSL #3 // W0 = W1 + (W2 << 3)
SUBS X0, X4, X3, ASR #2 // X0 = X4 - (X3 >> 2), set flags
MOV X0, X1 // Copy X1 to X0
CMP W3, W4 // Set flags based on W3 - W4
ADD W0, W5, #27 // W0 = W5 + 27
逻辑运算本质上与对寄存器的各个位进行操作的相应布尔运算符相同。
BIC(按位清零)指令会将目标寄存器之后的第一个寄存器的值,与第二个操作数取反后的值进行按位与运算。例如,要清除寄存器 X0 的第 11 位,可使用以下操作:
MOV X1, #0x800
BIC X0, X0, X1
ORN 和 EON 分别将第二个操作数按位取反后,再进行按位或(OR)和按位异或(EOR)运算。
比较指令仅修改标志位,没有其他作用。这些指令的立即数范围为 12 位,并且这个值可以选择向左移动 12 位。
6.2.2 乘法和除法指令(Multiply and divide instructions)
所提供的乘法指令与 ARMv7 - A 中的指令大致相似,但具备在单条指令中执行 64 位乘法的能力。
Table 6-2 Multiplication operations in assembly language
操作码(Opcode) | 描述(Description) |
---|---|
乘法指令(Multiply instructions) | |
MADD | 乘加 |
MNEG | 乘取反 |
MSUB | 乘减 |
MUL | 乘法 |
SMADDL | 有符号长乘加 |
SMNEGL | 有符号长乘取反 |
SMSUBL | 有符号长乘减 |
SMULH | 有符号乘法,返回高半部分结果 |
SMULL | 有符号长乘法 |
UMADDL | 无符号长乘加 |
UMNEGL | 无符号长乘取反 |
UMSUBL | 无符号长乘减 |
UMULH | 无符号乘法,返回高半部分结果 |
UMULL | 无符号长乘法 |
除法指令(Divide instructions) | |
SDIV | 有符号除法 |
UDIV | 无符号除法 |
存在一些乘法指令,它们可以对 32 位或 64 位的值进行操作,并返回与操作数大小相同的结果。例如,使用 MUL
指令可以将两个 64 位寄存器中的值相乘,从而得到一个 64 位的结果。
在很多处理器架构中,这种特性是很有用的。对于 32 位操作数的乘法,能满足一般的数据处理需求,比如在处理整数数组、计数等场景中。而对于 64 位操作数的乘法,当需要处理更大范围的数据时就显得尤为重要,像在进行高精度计算、处理大型内存地址或者进行复杂的科学计算时,64 位的乘法操作能够保证计算结果的准确性和完整性。
使用 MUL
指令进行 64 位乘法时,它直接对两个 64 位寄存器中的值执行乘法运算,并将结果存放在一个同样 64 位的寄存器或存储位置中。不过需要注意的是,由于是 64 位乘法,当两个较大的 64 位值相乘时,结果可能会超出 64 位所能表示的范围,此时就会发生溢出情况。在实际编程和使用中,可能需要对这种溢出情况进行特殊处理,以避免程序出现错误的计算结果。
MUL X0, X1, X2 // X0 = X1 * X2
此外,还可以使用 MADD
或 MSUB
指令,将第三个源寄存器中的累加器值进行加或减操作。
MNEG
指令可用于对结果取反,例如:
MNEG X0, X1, X2 // X0 = -(X1 * X2)
此外,还有一系列能产生长结果的乘法指令,即对两个 32 位数字进行乘法运算并生成一个 64 位的结果。这些长乘法运算有有符号和无符号两种变体(UMULL
、SMULL
)。还有一些选项可以累加来自另一个寄存器的值(UMADDL
、SMADDL
)或者进行取反操作(UMNEGL
、SMNEGL
)。
包含可选累加操作的 32 位和 64 位乘法,会产生与操作数大小相同的结果:
• 32 ± (32 × 32) gives a 32-bit result.
• 64 ± (64 × 64) gives a 64-bit result.
• ± (32 × 32) gives a 32-bit result.
• ± (64 × 64) gives a 64-bit result.
带累加功能的有符号和无符号扩展乘法会产生一个 64 位的结果:
• 64 ± (32 × 32) gives a 64-bit result.
• ± (32 × 32) gives a 64-bit result.
一次 64 位乘 64 位得到 128 位结果的乘法运算需要两条指令构成的指令序列,以生成一对 64 位的结果寄存器。
• ± (64 × 64) gives the lower 64 bits of the result [63:0].
• (64 × 64) gives the higher 64 bits of the result [127:64].
注意:
列表中没有 32 位乘 64 位的选项。你不能直接将一个 32 位的 W 寄存器与一个 64 位的 X 寄存器相乘。
ARMv8 - A 架构支持 32 位和 64 位有符号和无符号值的除法运算。例如:
-
UDIV W0, W1, W2
//W0 = W1 / W2
(无符号 32 位除法) -
SDIV X0, X1, X2
//X0 = X1 / X2
(有符号 64 位除法)
溢出和除零错误不会引发异常:
-
任何整数除以零的运算结果都返回零。
-
溢出仅可能在有符号除法指令
SDIV
中出现:-
INT_MIN / -1
的结果返回INT_MIN
,其中INT_MIN
是该运算所使用寄存器能够编码的最小负数。和大多数 C/C++ 方言一样,结果总是向零取整。
-
6.2.3 移位操作(Shift operations)
以下指令专门用于移位操作:
-
逻辑左移(LSL):LSL 指令用于将操作数乘以 2 的指定次幂。例如,将一个二进制数左移一位相当于将该数乘以 2。
-
逻辑右移(LSR):LSR 指令用于将操作数除以 2 的指定次幂。逻辑右移时,空位用 0 填充,常用于无符号数的除法运算。
-
算术右移(ASR):ASR 指令同样用于将操作数除以 2 的指定次幂,但它会保留符号位。也就是说,在右移过程中,最高位(符号位)会被复制到新的空位上,这对于有符号数的除法运算非常有用。
-
循环右移(ROR):ROR 指令执行按位循环右移操作。当最低有效位(LSB)移出时,它会被移到最高有效位(MSB)的位置,形成一个循环的移位效果。
这些移位操作在计算机体系结构中是基本的操作,常用于位操作、数值计算和数据处理等场景。不同的移位操作适用于不同的数据类型和计算需求。例如,逻辑移位通常用于无符号数,而算术移位则用于有符号数。循环移位则在一些特定的算法和编码中非常有用。
在 ARM 架构中,这些移位操作可以与其他指令结合使用,以实现更复杂的计算和数据处理任务。例如,可以使用移位操作来优化乘法和除法运算,或者进行位掩码操作以提取或修改特定的位。
这些移位操作是 ARM 架构中强大的工具,它们为程序员提供了高效处理数据的能力,同时也增加了代码的灵活性和可维护性。
Table 6-3 Shift and move operations
指令 | 描述 |
---|---|
ASR | 算术右移 |
LSL | 逻辑左移 |
LSR | 逻辑右移 |
ROR | 循环右移 |
MOV | 数据传送 |
MVN | 按位取反传送 |
为移位操作指定的寄存器可以是 32 位或 64 位的。移位的位数可以通过两种方式指定:一种是使用立即数,其取值范围最大可达寄存器大小减 1;另一种是通过寄存器指定,此时仅取该寄存器低 5 位(对于 32 位寄存器,进行模 32 运算)或低 6 位(对于 64 位寄存器,进行模 64 运算)的值作为移位位数。
6.2.4 位域和字节操作指令(Bitfield and byte manipulation instructions)
存在一类指令,可将字节、半字或字扩展至寄存器大小,寄存器类型可以是 X 寄存器或 W 寄存器。这类指令有有符号(SXTB、SXTH、SXTW)和无符号(UXTB、UXTH)两种变体,它们是相应位域操作指令的别名。
这些指令的有符号和无符号变体都能将字节、半字或字(不过只有 SXTW 是对字进行操作)扩展到寄存器大小。源操作数始终是一个 W 寄存器。目标寄存器可以是 X 寄存器或 W 寄存器,但 SXTW 的目标寄存器必须是 X 寄存器。
例如:
SXTB X0, W1 // 对寄存器 W1 的最低有效字节进行符号扩展
// 通过重复该字节最左边的位,将其从 8 位扩展到 64 位。
位域指令与 ARMv7 中已有的指令类似,包括位域插入(BFI)以及有符号和无符号位域提取((S/U)BFX)。此外,还有一些额外的位域指令,如 BFXIL(低位域提取并插入)、UBFIZ(零填充的无符号位域插入)和 SBFIZ(零填充的有符号位域插入)。
BFI
是位域插入(Bit Field Insert)指令,下面详细解释 BFI W0, W0, #9, #6
这条指令:
指令格式与功能概述
BFI
指令用于将一个寄存器中的位域插入到另一个寄存器的指定位置。其一般格式为:
BFI <Rd>, <Rn>, #<lsb>, #<width>
-
<Rd>
:目标寄存器,操作完成后结果会存储在这个寄存器中。 -
<Rn>
:源寄存器,从中提取要插入的位域。 -
#<lsb>
:目标寄存器中要插入位域的起始位(最低有效位)。 -
#<width>
:要插入的位域的宽度(位数)。
对 BFI W0, W0, #9, #6
的具体解释
-
目标寄存器
<Rd>
:W0
。这意味着操作完成后,结果会存储在W0
寄存器中。 -
源寄存器
<Rn>
:W0
。说明要插入的位域是从W0
寄存器本身提取的。 -
起始位
#<lsb>
:#9
。表示要将提取的位域插入到W0
寄存器的第 9 位(从 0 开始计数)。 -
位域宽度
#<width>
:#6
。表示要提取并插入的位域宽度为 6 位。
操作过程
-
提取位域:从源寄存器
W0
中提取从第 0 位开始的连续 6 位作为要插入的位域。 -
插入位域:将提取的 6 位位域插入到目标寄存器
W0
的第 9 位开始的位置。插入时,目标寄存器W0
中对应位置的原有 6 位会被覆盖,而其他位保持不变。
示例代码的伪代码表示
以下是用伪代码来表示 BFI W0, W0, #9, #6
的操作过程:
// 提取源寄存器 W0 中从第 0 位开始的 6 位
bitfield = W0 & 0x3F; // 0x3F 的二进制表示是 0011 1111,用于提取低 6 位
// 清除目标寄存器 W0 中从第 9 位开始的 6 位
mask = ~(0x3F << 9);
W0 = W0 & mask;
// 将提取的位域插入到目标寄存器 W0 的第 9 位开始的位置
W0 = W0 | (bitfield << 9);
综上所述,BFI W0, W0, #9, #6
指令的作用是将 W0
寄存器的低 6 位提取出来,插入到 W0
寄存器的第 9 位开始的位置。
注意:
还有 BFM、UBFM 和 SBFM 指令。这些是位域移动(Bit Field Move)指令,是 ARMv8 新增的指令。不过,这些指令无需显式使用,因为针对所有情况都提供了别名。这些别名就是前面已经介绍过的位域操作指令,即 [有符号/无符号] 扩展 [字节/半字/字/位域]([SU]XT[BHWX])、算术右移/逻辑左移/逻辑右移立即数(ASR/LSL/LSR immediate)、位域插入(BFI)、低位域提取并插入(BFXIL)、零填充的有符号位域插入(SBFIZ)、有符号位域提取(SBFX)、零填充的无符号位域插入(UBFIZ)以及无符号位域提取(UBFX)。
如果你熟悉 ARMv7 架构,你可能会认出其他的位操作指令:
-
CLZ:对寄存器中前导零的位数进行计数。
同样,还有一些类似的字节操作指令:
-
RBIT:反转寄存器中所有位的顺序。
-
REV:反转寄存器中字节的顺序。
-
REV16:反转寄存器中每个半字里字节的顺序。
-
REV32:反转寄存器中每个字的字节顺序。
除了 REV32 仅适用于 64 位寄存器外,这些操作可以在字(32 位)或双字(64 位)大小的寄存器上执行。
6.2.5 条件指令(Conditional instructions)
A64指令集并非支持每条指令都进行条件执行。指令的谓词执行所带来的益处并不足以证明其对操作码空间的大量占用是合理的。
第4 - 6页的处理器状态部分描述了四个状态标志,即零标志(Z)、负标志(N)、进位标志(C)和溢出标志(V)。表6 - 4给出了这些标志位在标志设置操作时的取值情况。
Table 6-4 Condition flag
标志名称 | 英文描述 | 中文描述 |
---|---|---|
N | 负标志 | 设置为与结果的第31位相同的值。对于32位有符号整数,若第31位被置位,则表示该值为负数。 |
Z | 零标志 | 若结果为零,则置为1;否则置为0。 |
C | 进位标志 | 设置为结果的进位输出值,或者设置为移位操作中最后移出的位的值。 |
V | 溢出标志 | 若发生有符号溢出或下溢,则置为1;否则置为0。 |
若无符号运算的结果超出了结果寄存器的表示范围,则进位标志(C标志)会被置位。 溢出标志(V标志)的工作方式与C标志相同,但它是针对有符号运算的。
注意:
条件标志(NZCV)和条件码与 A32 和 T32 中的相同。不过,A64 新增了 NV(二进制 0b1111),但其行为与它的补码 AL(二进制 0b1110)相同。这与 A32 不同,在 A32 中,二进制 0b1111 没有被赋予任何含义。
Table 6-5 Condition codes
代码 | 编码 | 含义(由 CMP 设置时) | 含义(由 FCMP 设置时) | 条件标志 |
---|---|---|---|---|
EQ | 0b0000 | 等于 | 等于 | Z = 1 |
NE | 0b0001 | 不等于 | 无序,或不等于 | Z = 0 |
CS | 0b0010 | 进位标志置位(等同于 HS) | 大于、等于或无序(等同于 HS) | C = 1 |
HS | 0b0010 | 大于或等于(无符号)(等同于 CS) | 大于、等于或无序(等同于 CS) | C = 1 |
CC | 0b0011 | 进位标志清零(等同于 LO) | 小于(等同于 LO) | C = 0 |
LO | 0b0011 | 无符号小于(等同于 CC) | 小于(等同于 CC) | C = 0 |
MI | 0b0100 | 负,负数 | 小于 | N = 1 |
PL | 0b0101 | 正数或零 | 大于、等于或无序 | N = 0 |
VS | 0b0110 | 有符号溢出 | 无序(至少有一个参数为 NaN) | V = 1 |
VC | 0b0111 | 无有符号溢出 | 非无序(没有参数为 NaN) | V = 0 |
HI | 0b1000 | 大于(无符号) | 大于或无序 | (C = 1) && (Z = 0) |
LS | 0b1001 | 小于或等于(无符号) | 小于或等于 | (C = 0) |
GE | 0b1010 | 大于或等于(有符号) | 大于或等于 | N == V |
LT | 0b1011 | 小于(有符号) | 小于或无序 | N != V |
GT | 0b1100 | 大于(有符号) | 大于 | (Z == 0) && (N == V) |
LE | 0b1101 | 小于或等于(有符号) | 小于、等于或无序 | (Z == 1) |
AL | 0b1110 | 总是执行。默认 | 总是执行 | 任意 |
NV | 0b1111 | 总是执行 | 总是执行 | 任意 |
有一小部分条件数据处理指令。这些指令会无条件执行,但会将条件标志作为指令的额外输入。提供这组指令是为了替代 ARM 代码中条件执行的常见用法。
读取条件标志的指令类型如下:
带进位的加/减法指令
这是传统的 ARM 指令,例如用于多精度算术运算和校验和计算。
可选增量、取负或取反的条件选择指令
根据条件在一个源寄存器和另一个经过增量、取负、取反或未修改的源寄存器之间进行选择。 这些是 A32 和 T32 中单个条件指令最常见的用法。典型的应用包括条件计数或计算有符号数的绝对值。
条件操作指令
A64 指令集仅支持程序流控制分支指令的条件执行。这与 A32 和 T32 不同,在 A32 和 T32 中,大多数指令都可以通过条件码进行谓词判断。这些指令可总结如下:
条件选择(移动)指令
-
CSEL:根据条件在两个寄存器之间进行选择。无条件指令之后再跟一个条件选择指令,可以替代简短的条件序列。
-
CSINC:根据条件在两个寄存器之间进行选择。返回第一个源寄存器,或者返回第二个源寄存器加 1 的结果。
-
CSINV:根据条件在两个寄存器之间进行选择。返回第一个源寄存器,或者返回第二个源寄存器按位取反的结果。
-
CSNEG:根据条件在两个寄存器之间进行选择。返回第一个源寄存器,或者返回第二个源寄存器取负的结果。
条件设置指令
根据条件在 0 和 1(CSET)或 0 和 -1(CSETM)之间进行选择。例如,用于将条件标志作为布尔值或掩码设置到通用寄存器中。
条件比较指令
(CMP 和 CMN)如果原始条件为真,则将条件标志设置为比较结果。如果条件不为真,则将条件标志设置为指定的条件标志状态。条件比较指令对于表达嵌套或复合比较非常有用。
注意:
使用 FCSEL 和 FCCMP 指令,条件选择和条件比较操作同样适用于浮点寄存器。
例如:
CSINC X0, X1, X0, NE // Set the return register X0 to X1 if Zero flag clear,
// else increment X0
针对示例指令给出了一些别名形式,在这些形式中,要么使用零寄存器,要么将同一个寄存器既用作指令的目标寄存器,又用作源寄存器。
例如:
CINC X0, X0, LS // If less than or same (LS) then X0 = X0 + 1
CSET W0, EQ // If the previous comparison was equal (Z=1) then W0 = 1,
// else W0 = 0
CSETM X0, NE // If not equal then X0 = -1, else X0 = 0
这类指令提供了一种强大的方法,可避免使用分支指令或条件执行指令。编译器或汇编程序员可以采用一种技术,即针对 “if - then - else” 语句的两个分支都执行相应操作,然后在最后选择正确的结果。
例如,考虑下面这段简单的 C 代码:
if (i == 0) r = r + 2; else r = r - 1;
这可能会生成类似如下的代码:
CMP w0, #0 // if (i == 0)
SUB w2, w1, #1 // r = r - 1
ADD w1, w1, #2 // r = r + 2
CSEL w1, w1, w2, EQ // select between the two results
6.3 Memory access instructions
和之前所有的 ARM 处理器一样,ARMv8 架构是一种加载/存储(Load/Store)架构。这意味着没有数据处理指令可以直接对内存中的数据进行操作。数据必须先被加载到寄存器中,进行修改,然后再存储回内存。程序必须指定一个地址、要传输的数据大小,以及源寄存器或目标寄存器。还有一些额外的加载和存储指令,它们提供了更多的选项,例如非临时加载/存储、独占加载/存储以及获取/释放操作。
内存指令可以以非对齐的方式访问普通内存(详见第 13 章“内存排序”)。但独占访问、加载获取或存储释放变体不支持这种非对齐访问方式。如果不希望进行非对齐访问,可以将其配置为产生错误。
6.3.1 加载指令格式
加载指令的一般形式如下: LDR Rt, <addr>
对于加载到整数寄存器的操作,你可以选择加载的数据大小。例如,若要加载小于指定寄存器值大小的数据,可在 LDR 指令后附加以下后缀之一:
-
LDRB(8 位,零扩展)。
-
LDRSB(8 位,符号扩展)。
-
LDRH(16 位,零扩展)。
-
LDRSH(16 位,符号扩展)。
-
LDRSW(32 位,符号扩展)。
还有非缩放偏移形式,如 LDUR<type>(详见第 6 - 14 页的“指定加载或存储指令的地址”)。程序员通常无需显式使用 LDUR 形式,因为大多数汇编器可以根据所使用的偏移量选择合适的版本。
你无需指定向 X 寄存器进行零扩展加载,因为对 W 寄存器进行写入操作实际上会将数据零扩展至整个寄存器宽度。
6.3.2 存储指令格式 (Store instruction format)
同样,存储指令的一般形式如下:
STR Rn, <addr>
也存在非缩放偏移形式,例如 STUR<type>(参见第 6 - 14 页的“指定加载或存储指令的地址”)。通常情况下,程序员不需要显式使用 STUR 形式,因为大多数汇编器可以根据所使用的偏移量来选择合适的版本。
要存储的数据大小可能比寄存器的大小小。你可以通过在 STR 后面添加 B 或 H 后缀来指定这一点。在这种情况下,总是存储寄存器的最低有效部分。
6.3.3Floating-point and NEON scalar loads and stores
加载和存储指令也可以访问浮点/NEON 寄存器。在这里,数据大小仅由被加载或存储的寄存器决定,这些寄存器可以是 B、H、S、D 或 Q 寄存器中的任意一种。此信息总结在表 6 - 6 和表 6 - 7 中。 对于加载指令:
Table 6-6 Memory bits written by Load instructions
加载指令 | Xt | Wt | Qt | Dt | St | Ht | Bt |
---|---|---|---|---|---|---|---|
LDR | 64 | 32 | 128 | 64 | 32 | 16 | 9 |
LDP | 128 | 64 | 256 | 128 | 64 | - | - |
LDRB | - | 8 | - | - | - | - | - |
LDRH | - | 16 | - | - | - | - | - |
LDRSB | 8 | 8 | - | - | - | - | - |
LDRSH | 16 | 16 | - | - | - | - | - |
LDRSW | 32 | - | - | - | - | - | - |
LDPSW | - | - | - | - | - | - | - |
对于存储指令:
Table 6-7 Memory bits read by Store instructions
存储指令 | Xt | Wt | Qt | Dt | St | Ht | Bt |
---|---|---|---|---|---|---|---|
STR | 64 | 32 | 126 | 64 | 32 | 16 | 8 |
STP | 128 | 64 | 256 | 128 | 64 | - | - |
STRB | - | 8 | - | - | - | - | - |
STRH | - | 16 | - | - | - | - | - |
对于加载数据到浮点/单指令多数据(FP/SIMD)寄存器的操作,不存在符号扩展选项。此类加载操作的地址仍然使用通用寄存器来指定。
例如:
LDR D0, [X0, X1]
将 X0
与 X1
的值相加所得地址处的双字数据加载到寄存器 D0
中。
注意:
浮点和标量 NEON(高级单指令多数据扩展)的加载和存储操作所使用的寻址模式与整数的加载和存储操作相同。