ARMv8-A 汇编 整数算数逻辑指令(64bit)

参考自官方 《ARM架构参考手册ARMv8》https://developer.arm.com/documentation/ddi0487/aj

加法指令

ADC

指令介绍

带进位位的加:将两个寄存器值和进位标志值相加,并将结果写入目标寄存器。

ADC <Xd>, <Xn>, <Xm> 
<Xd>

是 64 位的通用目的目标寄存器的名称,编码在“Rd”字段中。

<Xn>

是第一个 64 位的通用目的源寄存器的名称,编码在“Rn”字段中。

<Xm>

是第二个 64 位的通用目的源寄存器的名称,编码在“Rm”字段中。

解析

简单写个汇编指令验证下

mov x8, #0x8f
mov x9, #0x7000000000000000
mov x10, #0x6000000000000000
mov x11, #0xf000000000000000
mov x12, #0xd000000000000000
adc x13, x12, x11
adcs x13, x12, x11
adc x13, x12, x8
adc x13, x12, x11

我们利用 adcs 指令来设置进位位。当指令第一次执行 adc 指令时,进位位为 0,所有 x13 寄存器中的值只是简单的等于 x11+x12。

通过 adcs 指令设计进位标记后,我们来执行一个没有进位的加法。adc x13, x12, x8

从结果中我们可以看出,前面的 adcs 设置了进位和负数标记,adc 在结果中加上了进位标记中的1,并且没有将进位标记清除。从最后一条加法指令中可以验证 adc 指令执行后不会清除进位标记。

adc x13, x12, x11

ADCS

指令介绍

与进位相加,设置标志,将两个寄存器值和进位标志值相加,并将结果写入目标寄存器。 并根据它的结果更新条件标志。

ADCS <Wd>, <Wn>, <Wm>
<Xd>

是 64 位的通用目的目标寄存器的名称,编码在“Rd”字段中。

<Xn>

是第一个 64 位的通用目的源寄存器的名称,编码在“Rn”字段中。

<Xm>

是第二个 64 位的通用目的源寄存器的名称,编码在“Rm”字段中。

解析

再简单写个汇编指令验证下。

    mov x8, #0x8f
    mov x9, #0x7000000000000000
    mov x10, #0x6000000000000000
    mov x11, #0xf000000000000000
    mov x12, #0xd000000000000000
    adcs x13, x9, x10
    adcs x13, x12, x11
    adcs x13, x9, x8

X9 + X10 对于有符号数计算发生了溢出,所以 adcs 设置了负数和溢出两个标记。

换两 x11 和 x12 中的个负数相加,结果发生了进位所以设置了进位标记和负数标记。

在执行一条正数没有溢出的指令。

结果将上一次结果中的进位位加入了本次计算,并按本次计算结构更新了标记位。

ADD(扩展寄存器)

指令介绍

加(扩展寄存器)将寄存器值与符号或零扩展寄存器值相加,后跟可选的左移量,并将结果写入目标寄存器。 从 <Rm> 寄存器扩展的参数可以是字节、半字、字或双字。

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

是 64 位的通用目的目标寄存器或栈指针的名称,编码在“Rd”字段中。

<Xn|SP>

是第一个 64 位的通用目的源寄存器或栈指针的名称,编码在“Rn”字段中。

<R>

是一个宽度说明符,编码在“option”字段中。 它可以具有以下值:

  • W option = 00x
  • W option = 010
  • X option = X11
  • W option = 10x
  • W option = 110
<m>

是第二个通用目的源寄存器的编号 [0-30] 或者是名称ZR(31)的通用目的寄存器,编码在“RM”字段。

<extend>

是应用于第二个源操作数的扩展,在“选项”字段中编码。它可以具有以下值:

  • UXTB option = 000
  • UXTH option = 001
  • UXTW option = 010
  • LSL|UXTX option = 011
  • SXTB option = 100
  • SXTH option = 101
  • SXTW option = 110
  • SXTX option = 111

如果“Rd”或“Rn”为“11111”(SP)且“选项”为“011”,则首选 LSL,但当“imm3”为“000”时可省略。在所有其他情况下,<extend> 是必需的,并且当“选项”为“011”时必须是 UXTX。

<amount>

是在 0 到 4 范围内的扩展后应用的左移量,默认为 0,在“imm3”字段中编码。 <extend> 不存在时必须不存在,<extend> 是 LSL 时是必需的,当 <extend> 存在但不是 LSL 时是可选的。

示例

这里有个奇怪的地方就是指令中的 option 字段在指令描述中由 <R> 和 <extend> 同时编码,而且编码相同的字段。那么指令会以二者中的哪个编码为准?且指令中用 <R> 来编码时,行为模式又会如何?我们做如下实验。

首先我们写出不同的指令来对比测试。

    mov x8, #0x8f
    mov x9, #0x3fff
    mov x10, #0x7fffffffffffffff
    mov x11, #0xf00000000000ffff
    mov x12, #0xd000000000000000
    add x13, sp, W8
    add sp, sp, W9
    add sp, x10, W11
    add sp, x10, W11
    add x13, x11, X10
    add x13, x11, W10, SXTB
    add x13, x11, X10, SXTB
    add x13, x11, W8, SXTB
    add x13, sp, x8, LSL #3
    add sp, x11, w8, LSL #3

在使用 <extend> 时我们还使用了错误的 <R> 来测试。经过汇编转换后 gdb 中给出的指令格式如下:

可以看出当使用 <R>,并省略 <extend> 时,不论寄存器中的值的宽度,正负,都是按 32bit 0 扩展指令执行的。我们使用了 <extend> 后,我们故意写错的 <R> 也被汇编器修正了,并且汇编器以 <extend> 为准。有一个特例就是 <extend> 字段为 lsl 时,故意写错 <R> 后,<extend> 中的 lsl 被改为了 uxtw。还是以 <R> 为准的。这个行为模式应该和汇编程序有关吧。

有了前面的说明,剩下的指令就相对简单一些,只不过时将第二个数据源换成立即数或移位寄存器而已。

ADDS (立即数)

指令介绍

加 (立即数) 将一个寄存器值和一个可选移位的立即值相加,并将结果写入目标寄存器。

ADD <Xd|SP>, <Xn|SP>, #<imm>{, <shift>}
<Xd|SP>

是 64 位的通用目的目标寄存器或栈指针的名称,编码在“Rd”字段中。

<Xn|SP>

是 64 位的通用目的源寄存器或栈指针的名称,编码在“Rn”字段中。

<imm>

是一个无符号的立即数,在 0 到 4095 的范围内,在“imm12”字段中编码。

<shift>

是应用于立即数的可选左移,默认为 LSL #0 并在“shift”字段中编码。 它可以具有以下值:

LSL #0  //when shift = 00
LSL #12 //when shift = 01

编码 shift = 1x 被保留。

ADD(移位寄存器)

指令介绍

加(移位寄存器)将寄存器值和可选移位的寄存器值相加,并将结果写入目标寄存器。

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

是 64 位的通用目的目标寄存器的名称,编码在“Rd”字段中。

<Xn>

是第一个 64 位的通用目的源寄存器的名称,编码在“Rn”字段中。

<Xm>

是第二个 64 位的通用目的目标寄存器的名称,编码在“Rm”字段中。

<shift>

是应用于第二个源操作数的可选移位类型,默认为 LSL 并在“移位”字段中编码。 它可以具有以下值:

LSL     when shift = 00
LSR     when shift = 01
ASR     when shift = 10

编码 shift = 11 被保留。

<amount>

移位量的范围为 0 到 63,默认为 0 并在“imm6”字段中编码。

ADDS(扩展寄存器)

加(扩展寄存器),设置标志,将寄存器值和符号或零扩展寄存器值相加,后跟可选的左移量,并将结果写入目标寄存器。 从 <Rm> 寄存器扩展的参数可以是字节、半字、字或双字。 它根据结果更新条件标志。

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

是 64 位的通用目的目标寄存器的名称,编码在“Rd”字段中。

<Xn|SP>

是第一个 64 位的通用目的源寄存器或栈指针的名称,编码在“Rn”字段中。<R>是一个宽度说明符,编码在“option”字段中。 它可以具有以下值:

  • W option = 00x
  • W option = 010
  • X option = X11
  • W option = 10x
  • W option = 110
<m>

是第二个通用目的源寄存器的编号 [0-30] 或者是名称ZR(31)的通用目的寄存器,编码在“RM”字段。

<extend>

是应用于第二个源操作数的扩展,在“选项”字段中编码。它可以具有以下值:

  • UXTB option = 000
  • UXTH option = 001
  • UXTW option = 010
  • LSL|UXTX option = 011
  • SXTB option = 100
  • SXTH option = 101
  • SXTW option = 110
  • SXTX option = 111

如果“Rd”或“Rn”为“11111”(SP)且“选项”为“011”,则首选 LSL,但当“imm3”为“000”时可省略。在所有其他情况下,<extend> 是必需的,并且当“选项”为“011”时必须是 UXTX。

<amount>

是在 0 到 4 范围内的扩展后应用的左移量,默认为 0,在“imm3”字段中编码。 <extend> 不存在时必须不存在,<extend> 是 LSL 时是必需的,当 <extend> 存在但不是 LSL 时是可选的。

ADDS(立即数)

加(立即数),设置标志,将寄存器值和可选移位的立即值相加,并将结果写入目标寄存器。 它根据结果更新条件标志。

ADDS <Xd>, <Xn|SP>, #<imm>{, <shift>}
<Xd>

是 64 位的通用目的目标寄存器的名称,编码在“Rd”字段中。

<Xn|SP>

是 64 位的通用目的源寄存器或栈指针的名称,编码在“Rn”字段中。

<imm>

是一个无符号的立即数,在 0 到 4095 的范围内,在“imm12”字段中编码。

<shift>

是应用于立即数的可选左移,默认为 LSL #0 并在“shift”字段中编码。 它可以具有以下值:

LSL #0  //when shift = 00
LSL #12 //when shift = 01

编码 shift = 1x 被保留。

ADDS (移位寄存器)

加(移位寄存器),设置标志,将寄存器值和可选移位寄存器值相加,并将结果写入目标寄存器。 它根据结果更新条件标志。

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

是 64 位的通用目的目标寄存器的名称,编码在“Rd”字段中。

<Xn>

是第一个 64 位的通用目的源寄存器的名称,编码在“Rn”字段中。

<Xm>

是第二个 64 位的通用目的目标寄存器的名称,编码在“Rm”字段中。

<shift>

是应用于第二个源操作数的可选移位类型,默认为 LSL 并在“移位”字段中编码。 它可以具有以下值:

LSL     when shift = 00
LSR     when shift = 01
ASR     when shift = 10

编码 shift = 11 被保留。

<amount>

移位量的范围为 0 到 63,默认为 0 并在“imm6”字段中编码。