【ARMv8 编程】A64 数据处理指令——算术指令
许多在应用程序级别编写代码的程序员不需要用汇编语言编写代码。然而,汇编代码在需要高度优化的代码的情况下很有用。在编写编译器时,或者需要使用 C 语言中无法直接提供的底层特性时,都会出现这种情况。部分引导代码、设备驱动程序或开发操作系统时可能需要它。最后,在调试 C 时能够阅读汇编代码是有用的,特别是理解汇编指令和 C 语句之间的映射。
A64 数据处理指令是处理器的基本算术和逻辑操作,作用于通用寄存器中的值,或寄存器和立即值。乘法和除法运算可以看作是这些运算的特殊情况。它们大多使用一个目标寄存器和两个源操作数。一般的格式可以认为是指令加上操作数,如下所示:
Instruction Rd, Rn, Operand2
第二个操作数可以是寄存器、修改过的寄存器或立即值。R 可以是 X
寄存器,也可以是 W
寄存器。
数据处理操作包括:
- 算术和逻辑运算。
- 移动和移位操作。
- 符号和零扩展的说明。
- 位和位域操作。
- 条件比较和数据处理。
下面测试程序运行环境使用的手机是华为 Mate 30(麒麟 990 系列)。
下面是算术运算的一些常见指令,加减乘除类的指令统称为算术指令。
指令类型 | 指令 |
---|---|
算术 | ADD、SUB、ADC、SBC、NEG、MADD、MNEG、MSUB、MUL、SMADDL、SMNEGL、SMSUBL、SMULH、SMULL、UMADDL、UMNEGL、UMSUBL、UMULH、UMULL、SDIV、UDIV |
有些指令还带有 S
后缀,表示指令设置标志。比如 ADDS、SUBS、ADCS。
1 ADD
ADD(扩展寄存器)指令
ADD(扩展寄存器)指令将一个寄存器值和一个符号或零扩展寄存器值相加,后跟一个可选的左移位量,并将结果写入目标寄存器。从 <Rm>
寄存器扩展的参数可以是字节、半字、字或双字。
32-bit (sf == 0)
ADD <Wd|WSP>, <Wn|WSP>, <Wm>{, <extend> {#<amount>}}
64-bit (sf == 1)
ADD <Xd|SP>, <Xn|SP>, <R><m>{, <extend> {#<amount>}}
<Wd|WSP>
是目标通用寄存器或堆栈指针的 32 位名称,编码在“Rd”字段中。
<Wn|WSP>
是第一个源通用寄存器或堆栈指针的 32 位名称,编码在“Rn”字段中。
<Wm>
是第二个通用源寄存器的 32 位名称,编码在“Rm”字段中。
<Xd|SP>
是目标通用寄存器或堆栈指针的 64 位名称,编码在“Rd”字段中。
<Xn|SP>
是第一个源通用寄存器或堆栈指针的 64 位名称,编码在“Rn”字段中。
<R>
是宽度说明符,编码为“option”如下:
option | |
---|---|
00x | W |
010 | W |
x11 | X |
10x | W |
110 | W |
<m>
是第二个通用源寄存器的编号 [0-30] 或名称 ZR(31),编码在“Rm”字段中。
<extend>
对于 32 位变量:是要应用于第二个源操作数的扩展名,编码为“option”如下:
option | <extend> |
---|---|
000 | UXTB |
001 | UXTH |
010 | LSL|UXTW |
011 | UXTX |
100 | SXTB |
101 | SXTH |
110 | SXTW |
111 | SXTX |
如果“Rd”或“Rn”为“11111”(WSP),“option”为“010”,则首选 LSL,但当“imm3”为“000”时,可以省略 LSL。在所有其他情况下,当“option”为“010”时,<extend>
是必需的,并且必须是 UXTW。
对于 64 位变量:是要应用于第二个源操作数的扩展名,编码为“option”如下:
option | <extend> |
---|---|
000 | UXTB |
001 | UXTH |
010 | UXTW |
011 | LSL|UXTX |
100 | SXTB |
101 | SXTH |
110 | SXTW |
111 | SXTX |
如果“Rd”或“Rn”为“11111”(SP),“option”为“011”,则首选 LSL,但当“imm3”为“000”时,可以省略 LSL。在所有其他情况下,当“option”为“011”时,<extend>
是必需的,并且必须是 UXTX。
<amount>
是在 0 到 4 范围内扩展后要应用的左移量,默认为 0,编码在“imm3”字段中。当 <extend>
不存在时,它必须不保留;当 <extend>
为 LSL 时,它是必需的;当存在 <extend>
但不存在 LSL 时它是可选的。
下面是使用 ADD(扩展寄存器)指令的例子。
long x = 1024;long z = 0;asm volatile("MOV X2, #64\n""ADD %x[z], %x[x], X2, LSL#4\n":[z] "+r"(z),[x] "+r"(x):: "cc", "memory");
z
的值最终为 2048,MOV
指令将立即数 64 移动到 X2
寄存器,接着 ADD
指令先将 X2
寄存器中的值逻辑左移 4 位(64*(2^4) = 1024),再和寄存器 %x[x]
(也就是 1024(x))相加,最终结果写入寄存器 %x[z]
,并从寄存器加载到内存中的变量 z
。
虽然上面的介绍中可以 <extend>
使用 UXTB
等扩展,实际编译(ADD %x[z], %x[x], X2, UXTB
)会报错:error: expected ‘sxtx’ ‘uxtx’ or ‘lsl’ with optional integer in range [0, 4]
ADD(立即数)指令
ADD(立即数)指令将寄存器值和可选择移位的立即数相加,并将结果写入目标寄存器。
32-bit (sf == 0)
ADD <Wd|WSP>, <Wn|WSP>, #<imm>{, <shift>}
64-bit (sf == 1)
ADD <Xd|SP>, <Xn|SP>, #<imm>{, <shift>}
<Wd|WSP>
目标通用寄存器或堆栈指针的 32 位名称,编码在“Rd”字段中。
<Wn|WSP>
源通用寄存器或堆栈指针的 32 位名称,编码在“Rn”字段中。
<Xd|SP>
目标通用寄存器或堆栈指针的 64 位名称,编码在“Rd”字段中。
<Xn|SP>
源通用寄存器或堆栈指针的 64 位名称,编码在“Rn”字段中。
<imm>
是一个无符号立即数,范围从 0 到 4095,编码在“imm12”字段中。
<shift>
是应用于立即数的可选左移,默认为 LSL#0
并以“sh”编码:
sh | <shift> |
---|---|
0 | LSL #0 |
1 | LSL #12 |
下面是使用 ADD(立即数)指令的例子。
long x = 1024;long z = 0;asm volatile("ADD %x[z], %x[x], #1, LSL#12\n":[z] "+r"(z),[x] "+r"(x):: "cc", "memory");
ADD %x[z], %x[x], #1, LSL#12
将立即数 1 逻辑左移 12 位(即为 2^12 = 4096),4096 + 1024 = 5120,所以 z
的值最后为 5120。
ADD(移位寄存器)指令
ADD(移位寄存器)指令将寄存器值和可选移位的寄存器值相加,并将结果写入目标寄存器。
32-bit (sf == 0)
ADD <Wd>, <Wn>, <Wm>{, <shift> #<amount>}
64-bit (sf == 1)
ADD <Xd>, <Xn>, <Xm>{, <shift> #<amount>}
<Wd>
是通用目的寄存器的 32 位名称,编码在“Rd”字段中。
<Wn>
是第一个通用源寄存器的 32 位名称,编码在“Rn”字段中。
<Wm>
是第二个通用源寄存器的 32 位名称,编码在“Rm”字段中。
<Xd>
是通用目的寄存器的 64 位名称,编码在“Rd”字段中。
<Xn>
是第一个通用源寄存器的 64 位名称,编码在“Rn”字段中。
<Xm>
是第二个通用源寄存器的 64 位名称,编码在“Rm”字段中。
<shift>
是应用于第二个源操作数的可选移位类型,默认为 LSL,并以“shift”编码:
shift | <shift> |
---|---|
00 | LSL |
01 | LSR |
10 | ASR |
11 | RESERVED |
<amount>
对于 32 位变量:是移位量,范围为 0 到 31,默认为 0,并编码在“imm6”字段中。对于64 位变量:是移位量,范围为 0 到 63,默认为 0,并编码在“imm6”字段中。
下面是使用 ADD(移位寄存器)指令的例子。
long x = 1024;long z = 0;asm volatile("MOV X2, #1\n""ADD %x[z], %x[x], X2, LSL#12\n":[z] "+r"(z),[x] "+r"(x):: "cc", "memory");
MOV X2, #1
将立即数 1 移动到 X2 寄存器内,ADD %x[z], %x[x], X2, LSL#12
首先 X2 寄存器的值逻辑左移 12 位(即为 2^12 = 4096),然后执行相加 4096 + 1024 = 5120,所以 z
的值最后为 5120。
2 SUB
SUB(扩展寄存器)指令
减法(扩展寄存器)从寄存器值中减去一个符号或零扩展寄存器值(后跟一个可选的左移位量),并将结果写入目标寄存器。从 <Rm>
寄存器扩展的参数可以是字节、半字、字或双字。
32-bit (sf == 0)
SUB <Wd|WSP>, <Wn|WSP>, <Wm>{, <extend> {#<amount>}}
64-bit (sf == 1)
SUB <Xd|SP>, <Xn|SP>, <R><m>{, <extend> {#<amount>}}
指令中的使用的符号说明和 ADD(扩展寄存器)指令一样。
下面是使用 SUB(扩展寄存器)指令的例子。
long x = 1025;long z = 0;asm volatile("MOV X2, #64\n""SUB %x[z], %x[x], X2, LSL#4\n":[z] "+r"(z),[x] "+r"(x):: "cc", "memory");
z
的值最终为 1,MOV
指令将立即数 64 移动到 X2
寄存器,接着 ADD
指令先将 X2
寄存器中的值逻辑左移 4 位(64*(2^4) = 1024),再和寄存器 %x[x]
(也就是 1025(x))做减法,最终结果写入寄存器 %x[z]
,并从寄存器加载到内存中的变量 z
。
SUB(立即数)指令
SUB(立即数)指令从寄存器值中减去一个可选移位的立即值,并将结果写入目标寄存器。
32-bit (sf == 0)
SUB <Wd|WSP>, <Wn|WSP>, #<imm>{, <shift>}
64-bit (sf == 1)
SUB <Xd|SP>, <Xn|SP>, #<imm>{, <shift>}
指令中的使用的符号说明和 ADD(立即数)指令一样。
下面是使用 SUB(立即数)指令的例子。
long x = 1024;long z = 0;asm volatile("SUB %x[z], %x[x], #1, LSL#12\n":[z] "+r"(z),[x] "+r"(x):: "cc", "memory");
SUB %x[z], %x[x], #1, LSL#12
将立即数 1 逻辑左移 12 位(即为 2^12 = 4096),1024 - 4096 = -3072,所以 z
的值最后为 -3072。
SUB(移位寄存器)指令
SUB(移位寄存器)指令从寄存器值中减去可选择移位的寄存器值,并将结果写入目标寄存器。
32-bit (sf == 0)
SUB <Wd>, <Wn>, <Wm>{, <shift> #<amount>}
64-bit (sf == 1)
SUB <Xd>, <Xn>, <Xm>{, <shift> #<amount>}
指令中的使用的符号说明和 ADD(移位寄存器)指令一样。
下面是使用 SUB(移位寄存器)指令的例子。
long x = 1024;long z = 0;asm volatile("MOV X2, #1\n""SUB %x[z], %x[x], X2, LSL#12\n":[z] "+r"(z),[x] "+r"(x):: "cc", "memory");
MOV X2, #1
将立即数 1 移动到 X2 寄存器内,SUB %x[z], %x[x], X2, LSL#12
首先 X2 寄存器的值逻辑左移 12 位(即为 2^12 = 4096),然后执行相减 1024 - 4096 = -3072,所以 z
的值最后为 -3072。
3 ADC
ADC(Add with Carry)指令将两个寄存器值和进位标志值相加,并将结果写入目标寄存器。
32-bit (sf == 0)
ADC <Wd>, <Wn>, <Wm>
64-bit (sf == 1)
ADC <Xd>, <Xn>, <Xm>
<Wd>
是通用目标寄存器的 32 位名称,在“Rd”字段中编码。
<Wn>
是第一个通用源寄存器的 32 位名称,在“Rn”字段中编码。
<Wm>
是第二个通用源寄存器的 32 位名称,在“Rm”字段中编码。
<Xd>
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。
<Xn>
是第一个通用源寄存器的 64 位名称,在“Rn”字段中编码。
<Xm>
是第二个通用源寄存器的 64 位名称,在“Rm”字段中编码。
下面是使用 ADC 指令的例子。
unsigned int x = 4294967295;// 2^32 - 1unsigned int y = 1;long z = 0;asm volatile("ADD W3, %w[x], %w[y]\n""ADC %x[z], X3, %x[z]\n":[z] "+r"(z),[y] "+r"(y),[x] "+r"(x):: "cc", "memory");
x 的值是 4294967295,也就是 2^32 - 1,它是 unsigned int 的最大值,ADD W3, %w[x], %w[y]
会将 4294967295 + 1 = 4294967296,也就是在 W
寄存器(32 位)相加会设置进位标志。最后执行 ADC %x[z], X3, %x[z]
,X3
中其实为 0,此时 %x[z]
中的值也为 0,但是 ADC
会同时加进位标志,所以最后 z 的值为 1。
程序中加入断点(图中蓝色标记即为断点处),可以看到此时 z 的值已经为 1。
断点中使用 lldb 命令(register read x3)马上查看 X3
寄存器的值,确实为 0。
4 SBC
SBC 指令从一个寄存器值中减去一个寄存器值和 NOT(进位标志)的值,并将结果写入目标寄存器。
32-bit (sf == 0)
SBC <Wd>, <Wn>, <Wm>
64-bit (sf == 1)
SBC <Xd>, <Xn>, <Xm>
<Wd>
是通用目标寄存器的 32 位名称,在“Rd”字段中编码。
<Wn>
是第一个通用源寄存器的 32 位名称,在“Rn”字段中编码。
<Wm>
是第二个通用源寄存器的 32 位名称,在“Rm”字段中编码。
<Xd>
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。
<Xn>
是第一个通用源寄存器的 64 位名称,在“Rn”字段中编码。
<Xm>
是第二个通用源寄存器的 64 位名称,在“Rm”字段中编码。
下面是使用 SBC 指令的例子。
int x = 1;int y = 3;long z = 2;asm volatile("SUBS W3, %w[x], %w[y]\n""SBC %x[z], %x[z], %x[x]\n":[z] "+r"(z),[y] "+r"(y),[x] "+r"(x):: "cc", "memory");
SUBS W3, %w[x], %w[y]
执行 1 减 3 的动作,产生借位,就会设置 PSTATE.{N, Z, C, V}
(lldb register read CPSR cpsr = 0x80001000(1000 0000 0000 0000 0001 0000 0000 0000))N 负数标志位为 1、C 进位标志为 0,SBC 指令执行 2 减 1 的动作,还要带上进位标志的反(NOT)也就是再减 1,所以 z 的值最终为 0。
5 NEG
NEG(移位寄存器)指令取反可选的移位寄存器值,并将结果写入目标寄存器。它是 SUB(移位寄存器)的别名。
32-bit (sf == 0)
NEG <Wd>, <Wm>{, <shift> #<amount>}
等价指令
SUB <Wd>, WZR, <Wm> {, <shift> #<amount>}
64-bit (sf == 1)
NEG <Xd>, <Xm>{, <shift> #<amount>}
等价指令
SUB <Xd>, XZR, <Xm> {, <shift> #<amount>}
<Wd>
通用目的寄存器的 32 位名称,编码在“Rd”字段中。
<Wm>
通用源寄存器的 32 位名称,编码在“Rm”字段中。
<Xd>
通用目的寄存器的 64 位名称,编码在“Rd”字段中。
<Xm>
通用源寄存器的 64 位名称,编码在“Rm”字段中。
<shift>
是可选的 shift 类型,将应用于第二个源操作数,默认为 LSL 并编码为“shift”:
shift | <shift> |
---|---|
00 | LSL |
01 | LSR |
10 | ASR |
11 | RESERVED |
<amount>
对于 32 位变量:是移位量,范围为 0 到 31,默认为 0,并编码在“imm6”字段中;对于 64 位变体:是移位量,范围为 0 到 63,默认为 0,并编码在“imm6”字段中。
下面是使用 NEG(移位寄存器)指令的例子。
int x = 1024;asm volatile("NEG %x[x], %x[x], LSL#2\n":[x] "+r"(x):: "cc", "memory");
NEG %x[x], %x[x], LSL#2
指令将 x 的值逻辑左移 2 位,也就是 1024 * (2^2) = 4096,接着取反等于 -4096,所以 x 的值最终为 -4096。
6 MADD
MADD (乘加)指令将两个寄存器值相乘,再加上第三个寄存器值,并将结果写入目标寄存器。
32-bit (sf == 0)
MADD <Wd>, <Wn>, <Wm>, <Wa>
64-bit (sf == 1)
MADD <Xd>, <Xn>, <Xm>, <Xa>
<Wd>
是通用目标寄存器的 32 位名称,在“Rd”字段中编码。
<Wn>
是保存被乘数的第一个通用源寄存器的 32 位名称,在“Rn”字段中编码。
<Wm>
是保存乘数的第二个通用源寄存器的 32 位名称,编码在“Rm”字段中。
<Wa>
是保存加数的第三个通用源寄存器的 32 位名称,在“Ra”字段中编码。
<Xd>
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。
<Xn>
是保存被乘数的第一个通用源寄存器的 64 位名称,在“Rn”字段中编码。
<Xm>
是保存乘数的第二个通用源寄存器的 64 位名称,在“Rm”字段中编码。
<Xa>
是保存加数的第三个通用源寄存器的 64 位名称,在“Ra”字段中编码。
下面是使用 MADD 指令的例子。
int x = 10;int y = 20;int z = 300;asm volatile("MADD %x[z], %x[x], %x[y], %x[z]\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");
MADD %x[z], %x[x], %x[y], %x[z]
相当于 x * y + z,也就是 10 * 20 + 300 = 500,所以 z 的值最终为 500。
7 MNEG
MNEG(乘法取反)指令将两个寄存器值相乘的结果取反,并将最终结果写入目标寄存器。
32-bit (sf == 0)
MNEG <Wd>, <Wn>, <Wm>
等价指令
MSUB <Wd>, <Wn>, <Wm>, WZR
64-bit (sf == 1)
MNEG <Xd>, <Xn>, <Xm>
等价指令
MSUB <Xd>, <Xn>, <Xm>, XZR
<Wd>
是通用目标寄存器的 32 位名称,在“Rd”字段中编码。
<Wn>
是保存被乘数的第一个通用源寄存器的 32 位名称,在“Rn”字段中编码。
<Wm>
是保存乘数的第二个通用源寄存器的 32 位名称,在“Rm”字段中编码。
<Xd>
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。
<Xn>
是保存被乘数的第一个通用源寄存器的 64 位名称,在“Rn”字段中编码。
<Xm>
是保存乘数的第二个通用源寄存器的 64 位名称,在“Rm”字段中编码。
下面是使用 MNEG 指令的例子。
int x = 10;int y = 20;int z = 0;asm volatile("MNEG %x[z], %x[x], %x[y]\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");
MNEG %x[z], %x[x], %x[y]
相当于 -(x * y),也就是 -(10 * 20) = -200,所以 z 的值最终为 -200。
8 MSUB
MSUB(乘减)指令将两个寄存器值相乘,从第三个寄存器值中减去乘积,并将结果写入目标寄存器。
32-bit (sf == 0)
MSUB <Wd>, <Wn>, <Wm>, <Wa>
64-bit (sf == 1)
MSUB <Xd>, <Xn>, <Xm>, <Xa>
<Wd>
是通用目标寄存器的 32 位名称,在“Rd”字段中编码。
<Wn>
是保存被乘数的第一个通用源寄存器的 32 位名称,在“Rn”字段中编码。
<Wm>
是保存乘数的第二个通用源寄存器的 32 位名称,编码在“Rm”字段中。
<Wa>
是保存被减数的第三个通用源寄存器的 32 位名称,在“Ra”字段中编码。
<Xd>
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。
<Xn>
是保存被乘数的第一个通用源寄存器的 64 位名称,在“Rn”字段中编码。
<Xm>
是保存乘数的第二个通用源寄存器的 64 位名称,在“Rm”字段中编码。
<Xa>
是保存被减数的第三个通用源寄存器的 64 位名称,在“Ra”字段中编码。
下面是使用 MNEG 指令的例子。
int x = 10;int y = 20;int z = 300;asm volatile("MSUB %x[z], %x[x], %x[y], %x[z]\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");
MSUB %x[z], %x[x], %x[y], %x[z]
相当于 z - (x * y),也就是 300 - (10 * 20) = 100,所以 z 的值最终为 100。
9 MUL
MUL(乘法)指令:Rd = Rn * Rm。
32-bit (sf == 0)
MUL <Wd>, <Wn>, <Wm>
等价指令
MADD <Wd>, <Wn>, <Wm>, WZR
64-bit (sf == 1)
MUL <Xd>, <Xn>, <Xm>
等价指令
MADD <Xd>, <Xn>, <Xm>, XZR
<Wd>
通用目的寄存器的 32 位名称,编码在“Rd”字段中。
<Wn>
保存被乘数的第一个通用源寄存器的 32 位名称,编码在“Rn”字段中。
<Wm>
是保存乘数的第二个通用源寄存器的 32 位名称,在“Rm”字段中编码。
<Xd>
通用目的寄存器的 64 位名称,编码在“Rd”字段中。
<Xn>
保存被乘数的第一个通用源寄存器的 64 位名称,编码在“Rn”字段中。
<Xm>
是保存乘数的第二个通用源寄存器的 64 位名称,在“Rm”字段中编码。
下面是使用 MUL 指令的例子。
int x = 10;int y = 20;int z = 0;asm volatile("MUL %x[z], %x[x], %x[y]\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");
MUL %x[z], %x[x], %x[y]
相当于 x * y,也就是 10 * 20 = 200,所以 z 的值最终为 200。
10 SMADDL
有符号长(Long)乘加:Xd = Xa + Wn * Wm。
SMADDL <Xd>, <Wn>, <Wm>, <Xa>
<Xd>
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。
<Wn>
是保存被乘数的第一个通用源寄存器的 32 位名称,在“Rn”字段中编码。
<Wm>
是保存乘数的第二个通用源寄存器的 32 位名称,在“Rm”字段中编码。
<Xa>
是保存加数的第三个通用源寄存器的 64 位名称,在“Ra”字段中编码。
下面是使用 SMADDL 指令的例子。
int x = 10;int y = 20;long z = 4294967296;//2^32asm volatile("SMADDL %x[z], %w[x], %w[y], %x[z]\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");
SMADDL %x[z], %w[x], %w[y], %x[z]
相当于 x * y + z,也就是 10 * 20 + 4294967296 = 4294967496,所以 z 的值最终为 4294967496。
11 SMNEGL
SMNEGL(Signed Multiply-Negate Long)指令将两个 32 位寄存器值相乘,对乘积取反,并将结果写入 64 位目标寄存器。
SMNEGL <Xd>, <Wn>, <Wm>
等价指令
SMSUBL <Xd>, <Wn>, <Wm>, XZR
<Xd>
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。
<Wn>
是保存被乘数的第一个通用源寄存器的 32 位名称,在“Rn”字段中编码。
<Wm>
是保存乘数的第二个通用源寄存器的 32 位名称,在“Rm”字段中编码。
下面是使用 SMNEGL 指令的例子。
int x = 10;int y = 20;long z = 0;asm volatile("SMNEGL %x[z], %w[x], %w[y]\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");
SMNEGL %x[z], %w[x], %w[y]
相当于 -(x * y),也就是 -(10 * 20) = -200,所以 z 的值最终为 -200。
12 SMSUBL
SMSUBL(Signed Multiply-Subtract Long)指令将两个 32 位寄存器值相乘,从 64 位寄存器值中减去乘积,并将结果写入 64 位目标寄存器。
SMSUBL <Xd>, <Wn>, <Wm>, <Xa>
<Xd>
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。
<Wn>
是保存被乘数的第一个通用源寄存器的 32 位名称,在“Rn”字段中编码。
<Wm>
是保存乘数的第二个通用源寄存器的 32 位名称,在“Rm”字段中编码。
<Xa>
是保存被减数的第三个通用源寄存器的 64 位名称,在“Ra”字段中编码。
下面是使用 SMSUBL 指令的例子。
int x = 10;int y = 20;long z = 68719476736;//2^36asm volatile("SMSUBL %x[z], %w[x], %w[y], %x[z]\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");
SMSUBL %x[z], %w[x], %w[y], %x[z]
相当于 z - (x * y),也就是 68719476736 - (10 * 20) = 68719476536,所以 z 的值最终为 68719476536。
13 SMULH
SMULH(Signed Multiply High)指令将两个 64 位寄存器值相乘,并将 128 位结果的位 [127:64] 写入 64 位目标寄存器。
SMULH <Xd>, <Xn>, <Xm>
<Xd>
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。
<Xn>
是保存被乘数的第一个通用源寄存器的 64 位名称,在“Rn”字段中编码。
<Xm>
是保存乘数的第二个通用源寄存器的 64 位名称,在“Rm”字段中编码。
下面是使用 SMULH 指令的例子。
long long int x = 4294967296;//2^32long long int y = 4294967296;long long int z = 0;asm volatile("SMULH %x[z], %x[x], %x[y]\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");
SMULH %x[z], %x[x], %y[y]
相当于 x * y 的积取高 64 位,也就是:
2 32 ∗ 2 32 = 2 64 2^{32} * 2^{32} = 2^{64} 232∗232=264
所以 z 的值最终为 1。
14 SMULL
SMULL(Signed Multiply Long)指令将两个 32 位寄存器值相乘,并将结果写入 64 位目标寄存器。
SMULL <Xd>, <Wn>, <Wm>
等价指令
SMADDL <Xd>, <Wn>, <Wm>, XZR
<Xd>
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。
<Wn>
是保存被乘数的第一个通用源寄存器的 32 位名称,在“Rn”字段中编码。
<Wm>
是保存乘数的第二个通用源寄存器的 32 位名称,编码在“Rm”字段中。
下面是使用 SMULL 指令的例子。
int x = 1000000;int y = 1000000;long long int z = 0;asm volatile("SMULL %x[z], %w[x], %w[y]\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");
SMULL %x[z], %w[x], %w[y]
相当于 x * y = 1000000 * 1000000 = 1000000000000(0xE8D4A51000 > 32 bits),即 z 为 1000000000000。
15 UMADDL
UMADDL(Unsigned Multiply-Add Long)指令将两个 32 位寄存器值相乘,将一个 64 位寄存器值相加,并将结果写入 64 位目标寄存器。
UMADDL <Xd>, <Wn>, <Wm>, <Xa>
<Xd>
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。
<Wn>
是保存被乘数的第一个通用源寄存器的 32 位名称,在“Rn”字段中编码。
<Wm>
是保存乘数的第二个通用源寄存器的 32 位名称,编码在“Rm”字段中。
<Xa>
是保存加数的第三个通用源寄存器的 64 位名称,在“Ra”字段中编码。
下面是使用 UMADDL 指令的例子。
int x = 1000000;int y = 1000000;long long int z = 1000000;asm volatile("UMADDL %x[z], %w[x], %w[y], %x[z]\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");
UMADDL %x[z], %w[x], %w[y], %x[z]
相当于 x * y + z = 1000000 * 1000000 + 1000000 = 1000001000000,即 z 为 1000001000000。
16 UMNEGL
UMNEGL(Unsigned Multiply-Negate Long)指令将两个 32 位寄存器值相乘,对乘积取反,并将结果写入 64 位目标寄存器。
UMNEGL <Xd>, <Wn>, <Wm>
等价指令
UMSUBL <Xd>, <Wn>, <Wm>, XZR
<Xd>
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。
<Wn>
是保存被乘数的第一个通用源寄存器的 32 位名称,在“Rn”字段中编码。
<Wm>
是保存乘数的第二个通用源寄存器的 32 位名称,编码在“Rm”字段中。
下面是使用 UMNEGL 指令的例子。
int x = 1000000;int y = 1000000;long long int z = 0;asm volatile("UMNEGL %x[z], %w[x], %w[y]\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");
UMADDL %x[z], %w[x], %w[y], %x[z]
相当于 -(x * y) = -(1000000 * 1000000) = -1000000000000,即 z 为 -1000000000000。
17 UMSUBL
UMSUBL(Unsigned Multiply-Subtract Long)指令将两个 32 位寄存器值相乘,从 64 位寄存器值中减去乘积,并将结果写入 64 位目标寄存器。
UMSUBL <Xd>, <Wn>, <Wm>, <Xa>
<Xd>
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。
<Wn>
是保存被乘数的第一个通用源寄存器的 32 位名称,在“Rn”字段中编码。
<Wm>
是保存乘数的第二个通用源寄存器的 32 位名称,编码在“Rm”字段中。
<Xa>
是保存被减数的第三个通用源寄存器的 64 位名称,在“Ra”字段中编码。
下面是使用 UMSUBL 指令的例子。
int x = 1000000;int y = 1000000;long long int z = 0;asm volatile("UMSUBL %x[z], %w[x], %w[y], %x[z]\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");
UMADDL %x[z], %w[x], %w[y], %x[z]
相当于 0 - (x * y) = 0 - (1000000 * 1000000) = -1000000000000,即 z 为 -1000000000000。
18 UMULH
UMULH(Unsigned Multiply High)指令将两个 64 位寄存器值相乘,并将 128 位结果的 [127:64] 位写入 64 位目标寄存器。
UMULH <Xd>, <Xn>, <Xm>
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。
是保存被乘数的第一个通用源寄存器的 64 位名称,在“Rn”字段中编码。
是保存乘数的第二个通用源寄存器的 64 位名称,在“Rm”字段中编码。
UMULH
和 SMULH
区别在于数据类型的差异,UMULH
使用的是无符号数。
下面是使用 UMULH 指令的例子。
long long int x = 4;long long int y = -1;long long int z = 0;asm volatile("UMULH %x[z], %x[x], %x[y]\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");
UMULH %x[z], %x[x], %y[y]
相当于 x * y 的积取高 64 位,
-1 强转为无符号数为:
2 64 − 1 2^{64} - 1 264−1
x * y 也就是:
4 ∗ ( 2 64 − 1 ) = 2 66 − 2 2 4 * (2^{64} - 1) = 2^{66} - 2^2 4∗(264−1)=266−22
所以 z 的值最终为 3。
19 UMULL
UMULL(Unsigned Multiply Long)指令将两个 32 位寄存器值相乘,并将结果写入 64 位目标寄存器。
<Xd>
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。
<Wn>
是保存被乘数的第一个通用源寄存器的 32 位名称,在“Rn”字段中编码。
<Wm>
是保存乘数的第二个通用源寄存器的 32 位名称,编码在“Rm”字段中。
同样,UMULL
和 SMULL
区别在于数据类型的差异,UMULL
使用的是无符号数。
下面是使用 UMULL 指令的例子。
int x = 1000000;int y = -1;long long int z = 0;asm volatile("UMULL %x[z], %w[x], %w[y]\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");
UMULL %x[z], %w[x], %w[y]
指令执行相当于 1000000 * 0xFFFFFFFF = 4294967295000000,即 z 的值为 4294967295000000。
20 UDIV
UDIV(无符号除法)指令将一个无符号整数寄存器值除以另一个无符号整数寄存器值,并将结果写入目标寄存器。 条件标志不受影响。
32-bit (sf == 0)
UDIV <Wd>, <Wn>, <Wm>
64-bit (sf == 1)
UDIV <Xd>, <Xn>, <Xm>
<Wd>
是通用目标寄存器的 32 位名称,在“Rd”字段中编码。
<Wn>
是第一个通用源寄存器的 32 位名称,在“Rn”字段中编码。
<Wm>
是第二个通用源寄存器的 32 位名称,在“Rm”字段中编码。
<Xd>
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。
<Xn>
是第一个通用源寄存器的 64 位名称,在“Rn”字段中编码。
<Xm>
是第二个通用源寄存器的 64 位名称,在“Rm”字段中编码。
下面是使用 SDIV 指令的例子。
int x = 0;int y = 100;long long int z = 100;asm volatile("UDIV %x[z], %x[x], %x[y]\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");
UDIV %x[z], %w[x], %w[y]
指令执行相当于 x / y = 0 / 100 = 0,即 z 的值为 0。最后来探讨一下对标志位的影响:
代码运行在断点 1 处,此时 CPSR((lldb) register read cpsr)等于 0x60001000(也就是 Z(零标志位) 和 C(进位标志位) 都为 1)。继续运行 ADDS
指令会修改标志位(结果非 0,无进位,不是负数也没有溢出。也就是 NZCV = 0b0000)。代码运行在断点 2 处时,CPSR 等于 0x00001000,说明 UDIV
结果为 0,并没有影响零标志位,也就是说 UDIV 没有影响这些标志。
21 SDIV
SDIV(有符号除法)指令将一个有符号整数寄存器值除以另一个有符号整数寄存器值,并将结果写入目标寄存器。 条件标志不受影响。
32-bit (sf == 0)
SDIV <Wd>, <Wn>, <Wm>
64-bit (sf == 1)
SDIV <Xd>, <Xn>, <Xm>
<Wd>
是通用目标寄存器的 32 位名称,在“Rd”字段中编码。
<Wn>
是第一个通用源寄存器的 32 位名称,在“Rn”字段中编码。
<Wm>
是第二个通用源寄存器的 32 位名称,在“Rm”字段中编码。
<Xd>
是通用目标寄存器的 64 位名称,在“Rd”字段中编码。
<Xn>
是第一个通用源寄存器的 64 位名称,在“Rn”字段中编码。
<Xm>
是第二个通用源寄存器的 64 位名称,在“Rm”字段中编码。
下面是使用 SDIV 指令的例子。
long long int x = -100;long long int y = 2;long long int z = 100;asm volatile("SDIV %x[z], %x[x], %x[y]\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");
SDIV %x[z], %x[x], %x[y]
指令执行相当于 x / y = -100 / 2 = -50,即 z 的值为 -50。
参考资料
1.《ARMv8-A-Programmer-Guide》
2.《Arm® A64 Instruction Set Architecture Armv8, for Armv8-A architecture profile》
【ARMv8 编程】A64 数据处理指令——算术指令相关推荐
- 【ARMv8 编程】A64 数据处理指令——位域字节操作指令
有些指令将字节.半字或字扩展到寄存器大小,可以是 X 或 W.这些指令存在于有符号(SXTB.SXTH.SXTW)和无符号(UXTB.UXTH)变体中,并且是适当的位域操作指令. 这些指令的有符号和无 ...
- 【ARMv8 编程】A64 数据处理指令——逻辑移位指令
逻辑指令包括与.或等指令,移位指令则分为逻辑移位和算术移位指令,下面则详细展开学习. 指令类型 指令 逻辑 AND.BIC.ORR.ORN.EOR.EON 移位 ASR.LSL.LSR.ROR 逻辑运 ...
- 【ARMv8 编程】A64 数据处理指令——移动比较指令
移动指令主要为 MOV 以及它的各种"变体",而比较指令主要用来进行比较并更新条件标志,用来实现条件判断等. 指令类型 指令 移动 MOV.MVN.MOVK.MOVZ.MOVN 比 ...
- 【ARMv8 编程】A64 内存访问指令——内存加载指令
与所有先前的 ARM 处理器一样,ARMv8 架构是一种加载/存储架构.这意味着没有数据处理指令直接对内存中的数据进行操作.数据必须首先被加载到寄存器中,修改,然后存储到内存中.该程序必须指定地址.要 ...
- 【ARMv8 SIMD和浮点指令编程】编程基础
ARM 高级 SIMD 架构.相关的实现和支持软件通常被称为 NEON 技术.AArch32(相当于 ARMv7 的 NEON 指令)和 AArch64 都有 NEON 指令集.两者都可以显著加速在大 ...
- 【ARMv8 编程】寄存器
ARMv8 用于描述整体架构,包括 32 位执行和 64 位执行.它使用 64 位位宽寄存器,同时保持向后兼容 v7. 现在来看看 ARMv8 都有哪些改进: 大的物理地址 这使处理器能够访问超过 4 ...
- 【ARMv8 编程】ARMv8 指令集介绍
ARMv8 架构中引入的最重要的变化之一是增加了 64 位指令集.该指令集补充了现有的 32 位指令集架构.这种增加提供了对 64 位宽整数寄存器和数据操作的访问,以及使用 64 位长度的内存指针的能 ...
- S5PV210体系结构与接口02:ARM编程模型 汇编指令
目录 1. ARM的基本设定 1.1 ARM数据类型 1.1.1 基本数据类型 1.1.2 浮点数据类型 1.1.3 存储器大小端 1.2 支持的指令集 2. Cortex-A8编程模型 2.1 处理 ...
- ARM指令寻址方式之: 数据处理指令的寻址方式
4.1 数据处理指令的寻址方式 4.1.1 数据处理指令的寻址方式概要 数据处理指令的基本语法格式如下. <opcode> {<cond>} {S} <Rd>, ...
最新文章
- sql查询第二大的记录(转)
- 阻挡一个人前进的东西是什么?无聊+浮躁!
- JavaScript 常用函数总结
- java仿聊天室项目总结_Java团队课程设计-socket聊天室(Day4总结篇)
- 数据仓库之电商数仓-- 1、用户行为数据采集
- IAR #pragma optimize 指令
- 浏览器中跨域创建cookie的问题
- scala 协变和逆变_Scala方差:协变,不变和逆变
- 用CentOS 6快速配置一台企业级Web代理服务器
- 在Visio里加上、下标方法
- __line__ php,hitcon 2018受虐笔记一:one-line-php-challenge 学习
- Quartus 13.0安装教程
- 建筑智能化资质升级需要的企业工程业绩
- 2019 Multi-University Training Contest 6 1005	Snowy Smile —— 线段树
- 王煜全分析:四大类手机游戏的未来机会
- 基于Labview的水位水温控制系统——虚拟仪器实验设计报告
- python最好用的助手_让Python爬虫变成你的好助手
- 让电脑键盘L键变成锁定计算机怎么办,教你电脑键盘按键错乱怎么恢复
- 如何确定自己不是高智商?蒙提霍尔三扇门问题
- 51单片机与ESP8266轻松上手Onenet(二)--onenet AT指令测试
热门文章
- Python Web开发 之 学生管理系统(2)[实现筛选,搜索,分页]
- PCB设计:生成结构检视文件DXF、EMP、EMN
- 使用git提交代码的流程
- 22种超全用户触点采集,易观方舟SDK又更新了
- css实现input搜索框展开动画
- 计算机 独立学院,[关于独立学院计算机教学方法的探讨] 计算机最好的独立学院...
- OpenGL编程低级错误 + 常见问题解答
- android+蓝牙体温计,如何制作一个带蓝牙功能的电子体温计?
- 用C语言写一个人员文件管理系统(一)
- 关于pycharm推送至gitee报Successfully created project 'XX' on Gitee, but initial push fail错解决办法