【问题标题】:How to simplify MIPS ADD 1 / SLT / BNE into fewer Instructions?如何将 MIPS ADD 1 / SLT / BNE 简化为更少的指令?
【发布时间】:2019-10-01 03:14:26
【问题描述】:

如何将这些 MIPS 指令减少到更少的指令?

addi  $8, $3, 1
slt   $9, $2, $8
bne   $9, $0, End

【问题讨论】:

  • 也许sle $9, $2, $3 ; bne $9, $0, End
  • 可能是<= 而不是++<。有一个伪指令bltble,但这并不能真正算作更少的指令。还有bgez作为真正的硬件指令,也许sub/bgez也可以像sle/bne一样工作。或许不是,sle 肯定是更明显的一个。

标签: assembly mips micro-optimization


【解决方案1】:

如何简化这些 MIPS 指令?

slt $9, $3, $2
beq $9, $0, End

这是一种推理方法。你想做的事:

if ( $2 < $3+1 ) goto End;

我们将其转换为删除添加:

if ( $2 <= $3 ) goto End;

但我们在 MIPS 上没有 &lt;=,因此,我们反转条件否定它。这种双重否定抵消了,所以仍然代表相同的逻辑。这将删除比较的相等部分:

if ( ! ( $2 > $3 ) ) goto End;

现在,我们交换操作数...因为 MIPS 也没有 &gt;

if ( ! ( $3 < $2 ) ) goto End;

(注意:操作数的这种交换不会否定条件:对于这种交换,我们在翻转运算符时保持相等组件相同(这里,不存在);而在前面步骤的否定中,我们 翻转运算符并更改其相等组件。)

好消息是我们可以只用两条指令执行此操作,因为否定可以折叠到分支指令中,使用 beq (branch on false) 而不是 bne (分支为真)。

实际上,如果你使用ble伪指令,你会得到与上面相同的两个指令序列。

ble $2, $3, End

顺便说一句,sle 是一个糟糕的选择,具体取决于您的标准。

MIPS 没有 sle 作为指令,它是一个伪指令:

sle $9, $2, $3 生成:

slt $9, $3, $2    # generate the opposite condition
ori $1, $0, 0x1   # generate the constant 1
sub $9, $1, $9    # generate 1 - "the opposite condition"

正如您所看到的,它添加了几条额外的指令来制作我们应该为 sle 获得的确切的 10 答案,您仍然需要添加一个分支指令,这样就可以4条指令! (并且我们可以在该扩展的初始指令之后分支为 false。)也没有“反向减法立即数”,因此 R 型减法与单独生成的常量一起使用。

【讨论】:

  • 即使有可能的签名环绕,这种转换是否有效? (即addiuaddi 是否安全?)我认为不是:&lt;= INT_MAX&lt; INT_MIN 有很大不同。所以也许值得一提的是,addi 在那个环绕上出错了,我们复制了那个输入的行为(因为这个版本不能引发整数溢出异常)。
  • 我查过,编译器不会做这个优化,因为它不安全,除非你排除y==INT_MAX,或者当签名溢出是UB。
【解决方案2】:

首先,您可以使用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 &lt; $3+1),那么 INT_MAX 仍然是一个特例。如果 x &lt;= 0x7FFFFFFF 对所有 x 都为真,但 x &lt; -0x8000000 对所有 x 都为假(32 位 2 的补码,这是 MIPS slt 实现的)。 x &lt; y+1 等效于 x &lt;= 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。)

(addaddu 恰好与 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 &lt; 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 &lt;= $3) 的一种方式,因为 MIPS 只有有限的 sXX 指令选择。


使用unsigned x, y 的版本也必须将addiu 与比较分开,然后使用不同的比较指令:sltuslt 是有符号(2 的补码)比较。

当任一输入的高位设置时,区别很重要。

【讨论】:

    猜你喜欢
    • 2012-01-10
    • 2012-01-09
    • 1970-01-01
    • 2021-09-15
    • 1970-01-01
    • 2014-03-04
    • 1970-01-01
    • 2021-11-29
    • 1970-01-01
    相关资源
    最近更新 更多