首先,您可以使用blt $9, $2, End 之类的伪指令将SLT 和BNEZ 写入一个源代码行。但我怀疑这就是你的意思;剩下的答案只涉及减少硬件 MIPS 指令的数量。
除非另有说明,否则只有在对每个可能的输入提供相同行为的情况下,优化才是安全/允许的。为了优化 asm -> asm,您需要知道行为的哪些部分是需要的,哪些只是实现细节,不需要保留。
在这种情况下,我认为我们应该假设创建 $8 = $3+1 不是可见/期望行为的一部分。我认为重点只是使用少于 3 条指令进行分支或失败,无论我们创建或不创建什么临时。 (例如subu $8, $2, $3 / bgez 可能是一个选项。)
只有在更改 $3 = INT_MAX 极端情况的行为时才能优化/简化这一点。 (或者,如果您对输入的可能范围有一些保证)。
在原始版本中,MIPS addi 捕获签名溢出。 (这就是为什么它通常从不使用的原因;addiu 像您期望的那样包装,并且在其他方面是相同的。)为$3 = 0x7FFFFFFF 引发此异常,而不是在任何其他情况下,几乎需要您使用 addi 和立即1。这是原始序列行为的一部分,问题中没有任何内容告诉我们我们可以放松这一点。
如果您使用 addiu 来实现带有 2 的补码环绕的 jump if ($2 < $3+1),那么 INT_MAX 仍然是一个特例。如果 x <= 0x7FFFFFFF 对所有 x 都为真,但 x < -0x8000000 对所有 x 都为假(32 位 2 的补码,这是 MIPS slt 实现的)。 x < y+1 等效于 x <= y,但前提是 y+1 不换行。
例如在 C 中,有符号整数溢出是 UB,但无符号环绕。并且将无符号转换为有符号的规则被明确定义为模减少,这意味着 2 的补码机器(如 MIPS)可以将无符号位模式视为有符号,即强制转换是免费的,只是一个类型双关语.
// C equivalent to asm using `addiu`. C for MIPS uses 32-bit unsigned and 2's complement int
void foo_wrapping(int x, int y)
{
unsigned tmp = y;
tmp++; // wraps without UB
int wrapped_yp1 = tmp;
// y+1 with 2's complement wraparound, without using -fwrapv
if (! (x < wrapped_yp1))
sink = 0;
}
GCC5.4 for MIPS 编译如下 (Godbolt)。寄存器编号与您的问题不同,但模式相同。 (在 SLT 的右侧添加结果,然后在该 SLT 结果上添加 BNEZ。)
(add 与 addu 恰好与 C 签名溢出匹配,即 UB,无符号只是包装,但请注意 C 编译器使用 addu/addiu 甚至用于签名加法,因为一件事 UB允许做的是换行。C 不要求它被检测到并崩溃,重点是它允许优化器假设它根本不会发生。)
# gcc5.4 -O3
foo_wrapping(int, int):
addiu $5,$5,1
slt $4,$4,$5
bne $4,$0,$L7
... a store that it conditionally jumps over, then jr $ra
clang9.0 也发出相同的代码。编译器没有找到优化并不能证明不可能,但可以很好地检查我上面的推理是否正确。
使用普通的y+1 并使用gcc 或clang -fwrapv 进行编译(使有符号溢出明确定义为2 的补码环绕)也会得到相同的结果。然后使用if (y == 0x7FFFFFFF) __builtin_unreachable(); 正确地向编译器承诺y!=INT_MAX,让编译器进行优化。编译器在使用 unsigned 的版本中遗漏了。
如果我们排除 INT_MAX 作为可能的输入(即我们不关心原始代码被困的情况),那么我们可以将 C 中的操作表示为 x < y+1 以用于有符号的 int 变量。在 C 中,有符号溢出是未定义的行为,因此优化编译器可以假设 y+1 不会溢出,因此 y!=INT_MAX。
// This C is not equivalent to your asm.
// It doesn't trap on y==0x7FFFFFFF, and it doesn't necessarily wrap like addiu
int sink;
void foo_nooverflow(int x, int y) {
if (x < y+1) {
sink = 0; // a store can't be done branchlessly
}
}
## gcc5.4 -O3
foo_nooverflow(int, int):
slt $4,$5,$4
beq $4,$0,$L4
因此 gcc/clang 转换条件 as Erik Eidt's answer suggests,以其他顺序进行比较并将 bne 交换为 beq 作为实现 jump if ($2 <= $3) 的一种方式,因为 MIPS 只有有限的 sXX 指令选择。
使用unsigned x, y 的版本也必须将addiu 与比较分开,然后使用不同的比较指令:sltu。 slt 是有符号(2 的补码)比较。
当任一输入的高位设置时,区别很重要。