Aarch64 SUB 等指令定义为加法和进位操作

问题描述 投票:0回答:2

我无法理解一些指令,例如

sub
指令,根据手册将其定义为
AddWithCarry
操作,其中进位设置为
1
的硬编码值:

bits(datasize) result;
bits(datasize) operand1 = if n == 31 then SP[]<datasize-1:0> else X[n, datasize];
bits(datasize) operand2;
operand2 = NOT(imm);
(result, -) = AddWithCarry(operand1, operand2, '1');
if d == 31 then
    SP[] = ZeroExtend(result, 64);
else
    X[d, datasize] = result;

AddWithCarry
操作定义如下:

(bits(N), bits(4)) AddWithCarry(bits(N) x, bits(N) y, bit carry_in)
    integer unsigned_sum = UInt(x) + UInt(y) + UInt(carry_in);
    integer signed_sum = SInt(x) + SInt(y) + UInt(carry_in);
    bits(N) result = unsigned_sum<N-1:0>; // same value as signed_sum<N-1:0>
    bit n = result<N-1>;
    bit z = if IsZero(result) then '1' else '0';
    bit c = if UInt(result) == unsigned_sum then '0' else '1';
    bit v = if SInt(result) == signed_sum then '0' else '1';
    return (result, n:z:c:v);

是否始终将

1
作为进位传递并根据
AddWithCarry
的定义进行查找,使减法运算也从所有操作中减去
1

我知道当我们写这样的东西时:

sub sp, sp, #0x20

我们实际上只从

sp
中减去 32 个字节,那么这个操作中的进位位是怎么回事?

assembly cpu-architecture arm64 subtraction carryflag
2个回答
2
投票

您可能已经看到这样的解释:大多数 ALU 将

a-b
当作
a + (~b + 1)
,因为它们已经有一个二进制加法器,并且按位 NOT 在硬件上非常便宜。
-b = ~b + 1
补码恒等式)。 CPU 如何进行减法? 就是一个很好的例子,还有 Wikipedia 的 binary Adder–subtractor 文章。

其中一些解释忽略的是

+ 1
部分是通过二进制加法器的进位完成的(如
adc
),因此它仍然只是一个加法,这对于性能和获得有符号的有用标志结果都很重要o溢出和未签名的结转。

这就是这里发生的事情。请注意

operand2 = NOT(imm);
,而不是
NEG
-imm
。需要额外的
+1
进位才能得到正确的答案。

在 ARM 和 AARch64 中,减法输出的进位标志是 ALU 加减法器的原始输出,它以这种方式进行减法。这使其成为非借用标志,如果

x - y
,则在
x < y
之后为 false。


其他一些 ISA,尤其是 x86,会反转 ALU 输出的进位标志,因此它是借位标志。这就是为什么 x86 的

adc
减法版本是
sbb
(带借位减法,它还必须在输入上反转 CF,以获得无借位情况下的
1
进位),而 ARM/AArch64 则相反
sbc
(带进位减法,直接输入 C 以获得无借位的
1
,否则
0
使输出在有借位时降低 1,通常来自不太重要的块)一个 bigint。)

无论哪种方式,x86

sbb
/ ARM
sbc
都是软件将 64 位 ALU 链接到更宽的加法器/减法器中的一种方法,就像行进位加法器中全加法器之间的正常进位传播一样。 (在每个 64 位块内,ALU 可能会做一些有趣的事情,例如进位前瞻或进位选择,以将延迟保持在足够少的门延迟以适应一个时钟周期,但通常不值得尝试在软件中这样做.)


还有相关:


1
投票

一开始这也让我很困惑。关键是前面的那行:它不是您所期望的

operand2 = -imm
,而是
operand2 = NOT(imm)
,即按位非(补码)。在二进制补码算术中,您可以轻松检查
NOT(imm) = -imm - 1
。因此,进位设置为 1 可以有效地计算
x + (-imm - 1) + 1
,这确实是
x - imm

这是伪代码语言设置方式的产物:它们的整数类型是能够表示任何数字的纯数学整数,并且算术运算符仅在此类类型上定义。但这里的操作数是

bits(n)
类型,只是一个位串,它们仅定义逻辑运算符。所以写
operand2 = -imm
的格式不正确。他们必须说类似
operand2 = (-SInt(imm))<n-1:0>
之类的话,这会更令人困惑。

它也可能反映了简单 ALU 实现加法指令的一种方式。您不需要单独的 ADD、SUB、ADC 等单元,而只需要一个可以进行加法进位的单元。因此ADC会将该单元的进位输入连接到NZCV寄存器的实际C位; ADD 会将其接地。 SUB 将使第一个输入通过反相器,并将进位输入连接到 VDD。 SBC 对反相器执行相同的操作,并将进位输入连接到 C 标志。 (请注意,这会导致减法将 C 标志视为真正的进位,而不是 x86,其中 SUB 反转进位标志的含义,使其表现得像借位。)

© www.soinside.com 2019 - 2024. All rights reserved.