64 位数字的 Mips 减法

问题描述

我被要求在 Mips 中实现 64 位数字的减法,其中数字以 2 的补码给出,并且低/高 32 位存储在不同的寄存器中。 我的想法:我们先减去下部,然后减去上部。如果第一个数字的下半部分编号小于第二个数字的下半部分编号,则会出现“有问题”的情况,即考虑一个我们只有 4 位寄存器的小例子

 0110 0011 
-0010 1000 

所以首先是下部

 0000 0011    0000 0011
-0000 1000 = +1111 1000 = 1111 1011

然后是上半部分

 0110 0000    0110 0000
-0010 0000 = +1110 0000 = 0100 0000

然后将两部分相加

 0100 0000
+1111 1011 = 0011 1011

我的 Mips 实现看起来像这样(为了简单起见,寄存器 1-8):

// A-B
lw  $1,0($8) // upper part of A
lw  $2,4($8) // lower part of A
lw  $3,8($8) // upper part of B
lw  $4,12($8) // lower part of B

subu $5,$2,$4
stlu $6,$4 // if lower part of A < lower part of B we need to add 1111 0000 i.e. 
                // subtract 1 from upper part result
subu $7,$1,$3
subu $7,$7,$6

// Now the upper part of the result is stored in $7$ and the lower part in $5

我的想法正确吗?

解决方法

比较 GCC 和 clang 在 32 位 MIPS 上减去 uint64_t 的做法:https://godbolt.org/z/eGY3aWoKq

是的,一个 sltu 用于从低半部分生成借位输出,加上 3x subu 是正确的指令组合。做低半部分减法,然后减去高半部分和借位。

手动将其转换为加法会更慢,因此最简单的考虑是从高半部分减去借位,否则分别进行高半和低半减法。


在带有进位/借位标志的 ISA(如 x86 或 ARM https://godbolt.org/z/sfG5PzsPW)上,您会这样做

     subs    r0,r0,r2      # sub low half and set flags
     sbc     r1,r1,r3      # sub-with-carry does high half including borrow

ARM 和 x86 将它们的进位标志设置为彼此相反的减法(!借或借),但两者都设计为按进位传播顺序从低到高链接,因此只有 2 条指令。

# clang -O3 -m32 -mregparm=3   first uint64_t passed in EDX:EAX,second in mem
     sub     eax,dword ptr [esp + 4]
     sbb     edx,dword ptr [esp + 8]    # x86 sub-with-borrow

由于 MIPS 没有 FLAGS,您必须手动处理进位/借位传播,但这就是您要做的全部。

当您需要处理借入借出时,对于更宽的整数会变得更加不方便,因为模拟 sbb 包括进位 输出 需要在之后检查回绕在减去整数操作数后减去借位 。就像模拟adc