许多在应用程序级别编写代码的程序员不需要用汇编语言编写代码。然而,汇编代码在需要高度优化的代码的情况下很有用。在编写编译器时,或者需要使用 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”字段中编码。

UMULHSMULH 区别在于数据类型的差异,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”字段中。

同样,UMULLSMULL 区别在于数据类型的差异,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 数据处理指令——算术指令相关推荐

  1. 【ARMv8 编程】A64 数据处理指令——位域字节操作指令

    有些指令将字节.半字或字扩展到寄存器大小,可以是 X 或 W.这些指令存在于有符号(SXTB.SXTH.SXTW)和无符号(UXTB.UXTH)变体中,并且是适当的位域操作指令. 这些指令的有符号和无 ...

  2. 【ARMv8 编程】A64 数据处理指令——逻辑移位指令

    逻辑指令包括与.或等指令,移位指令则分为逻辑移位和算术移位指令,下面则详细展开学习. 指令类型 指令 逻辑 AND.BIC.ORR.ORN.EOR.EON 移位 ASR.LSL.LSR.ROR 逻辑运 ...

  3. 【ARMv8 编程】A64 数据处理指令——移动比较指令

    移动指令主要为 MOV 以及它的各种"变体",而比较指令主要用来进行比较并更新条件标志,用来实现条件判断等. 指令类型 指令 移动 MOV.MVN.MOVK.MOVZ.MOVN 比 ...

  4. 【ARMv8 编程】A64 内存访问指令——内存加载指令

    与所有先前的 ARM 处理器一样,ARMv8 架构是一种加载/存储架构.这意味着没有数据处理指令直接对内存中的数据进行操作.数据必须首先被加载到寄存器中,修改,然后存储到内存中.该程序必须指定地址.要 ...

  5. 【ARMv8 SIMD和浮点指令编程】编程基础

    ARM 高级 SIMD 架构.相关的实现和支持软件通常被称为 NEON 技术.AArch32(相当于 ARMv7 的 NEON 指令)和 AArch64 都有 NEON 指令集.两者都可以显著加速在大 ...

  6. 【ARMv8 编程】寄存器

    ARMv8 用于描述整体架构,包括 32 位执行和 64 位执行.它使用 64 位位宽寄存器,同时保持向后兼容 v7. 现在来看看 ARMv8 都有哪些改进: 大的物理地址 这使处理器能够访问超过 4 ...

  7. 【ARMv8 编程】ARMv8 指令集介绍

    ARMv8 架构中引入的最重要的变化之一是增加了 64 位指令集.该指令集补充了现有的 32 位指令集架构.这种增加提供了对 64 位宽整数寄存器和数据操作的访问,以及使用 64 位长度的内存指针的能 ...

  8. S5PV210体系结构与接口02:ARM编程模型 汇编指令

    目录 1. ARM的基本设定 1.1 ARM数据类型 1.1.1 基本数据类型 1.1.2 浮点数据类型 1.1.3 存储器大小端 1.2 支持的指令集 2. Cortex-A8编程模型 2.1 处理 ...

  9. ARM指令寻址方式之: 数据处理指令的寻址方式

    4.1  数据处理指令的寻址方式 4.1.1  数据处理指令的寻址方式概要 数据处理指令的基本语法格式如下. <opcode> {<cond>} {S} <Rd>, ...

最新文章

  1. sql查询第二大的记录(转)
  2. 阻挡一个人前进的东西是什么?无聊+浮躁!
  3. JavaScript 常用函数总结
  4. java仿聊天室项目总结_Java团队课程设计-socket聊天室(Day4总结篇)
  5. 数据仓库之电商数仓-- 1、用户行为数据采集
  6. IAR #pragma optimize 指令
  7. 浏览器中跨域创建cookie的问题
  8. scala 协变和逆变_Scala方差:协变,不变和逆变
  9. 用CentOS 6快速配置一台企业级Web代理服务器
  10. 在Visio里加上、下标方法
  11. __line__ php,hitcon 2018受虐笔记一:one-line-php-challenge 学习
  12. Quartus 13.0安装教程
  13. 建筑智能化资质升级需要的企业工程业绩
  14. 2019 Multi-University Training Contest 6 1005 Snowy Smile —— 线段树
  15. 王煜全分析:四大类手机游戏的未来机会
  16. 基于Labview的水位水温控制系统——虚拟仪器实验设计报告
  17. python最好用的助手_让Python爬虫变成你的好助手
  18. 让电脑键盘L键变成锁定计算机怎么办,教你电脑键盘按键错乱怎么恢复
  19. 如何确定自己不是高智商?蒙提霍尔三扇门问题
  20. 51单片机与ESP8266轻松上手Onenet(二)--onenet AT指令测试

热门文章

  1. Python Web开发 之 学生管理系统(2)[实现筛选,搜索,分页]
  2. PCB设计:生成结构检视文件DXF、EMP、EMN
  3. 使用git提交代码的流程
  4. 22种超全用户触点采集,易观方舟SDK又更新了
  5. css实现input搜索框展开动画
  6. 计算机 独立学院,[关于独立学院计算机教学方法的探讨] 计算机最好的独立学院...
  7. OpenGL编程低级错误 + 常见问题解答
  8. android+蓝牙体温计,如何制作一个带蓝牙功能的电子体温计?
  9. 用C语言写一个人员文件管理系统(一)
  10. 关于pycharm推送至gitee报Successfully created project 'XX' on Gitee, but initial push fail错解决办法