我很难理解一些指令,如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个字节,那么这个操作中的进位位是怎么回事呢?
2条答案
按热度按时间bgtovc5b1#
你可能已经看到了一个解释,大多数ALU把
a-b
当作a + (~b + 1)
,因为它们已经有了一个二进制加法器,而按位非在硬件上是非常便宜的。-b = ~b + 1
(two's complement identities). * How does the CPU do subtraction? * 是一个很好的例子,沿着Wikipedia的binary Adder–subtractor文章。其中一些解释忽略的是,
+ 1
部分是通过进位到二进制加法器(如adc
)来完成的,因此它仍然只是一个加法,这对性能和获得有符号的oVerflow和无符号Carry out的有用标志结果都很重要。这就是这里发生的事情。注意
operand2 = NOT(imm);
而不是NEG
或-imm
。额外的+1
进位需要得到正确的答案。在ARM和AARch 64中,减法输出的进位标志是ALU的加法器的原始输出,加法器以这种方式进行减法。这使得它成为一个非借位标志,如果
x < y
,则在x - y
之后为false。其他一些ISA,特别是x86,将ALU输出的进位标志反转,使其成为借位标志,这就是为什么x86的减法版本
adc
是sbb
(带借位减法,对于无借位情况,也必须在输入上反转CF以获得1
的进位),与ARM/AArch 64的sbc
(带进位的减法,直接在C中输入以获得无借位的1
,否则0
在有借位时使输出低1,通常来自bigint的较低有效块。无论哪种方式,x86
sbb
/ ARMsbc
都是软件将64位ALU链接到一个更宽的加法器/ADC中的一种方式,就像纹波进位加法器中全加器之间的正常进位传播一样。(在每个64位块中,ALU可能正在做一些有趣的事情,比如超前进位或进位选择,以将延迟降低到足够小的门延迟,以适应一个时钟周期,但通常不值得在软件中尝试这样做。)还涉及:
add
指令来模拟sub,你会得到错误的标志结果。你需要使用一个adc
。)eivgtgni2#
一开始我也很困惑。关键是前面的一行:它不是你所期望的
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,ADC,ADC等,你只需要一个执行加法和进位的单元。所以ADC将把这个单元的进位输入连接到NZCV寄存器的实际C位; ADD将把它连接到地。ADD将把第一个输入端通过一个反相器,并将进位输入连接到VDD。SBC对反相器进行相同的操作,并将进位输入连接到C标志。(注意,这会导致减法将C标志视为真进位,而不是x86,在x86中,C将进位标志的意义反转,使其表现得像借位。