问题描述
我被要求在 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