关于否定 mips 中的符号整数?

About negate a sign-integer in mips?

我正在考虑如何对 mips32 中的有符号整数取反。我的直觉是使用 2 的补码定义,例如:(假设 $s0 是要取反的数字)

nor $t0, $s0, $s0   ; 1's complement
addiu $t0, $t0, 1   ; 2's = 1's + 1

然后我意识到可以这样做:

sub $t0, $zero, $s0

所以...有什么区别?哪个更快? IIRC sub 将尝试检测溢出,但这会使速度变慢吗?最后,还有其他方法吗?

subu $t0, $zero, $s0 是最好的方法,也是编译器所做的。

在任何给定的 MIPS 实现中,大多数简单的 ALU 指令 (add/sub/and/nor) 具有相同的性能。用 1 条简单指令而不是 2 条简单指令完成相同的工作是代码大小、延迟和吞吐量的胜利。

更少的指令并不总是更好,但是作为经典 RISC ISA 的 MIPS 除了 mult / div / rem 之外没有很多“慢”指令.

如果您想要 -x-1,那么 您可以使用 a 2's complement identity.

将其优化为单个 nor $t0, $zero, $s0

sub 而不是 subu 会在 -INT_MIN 上引发异常,您避免在 nor/add 版本中使用 addiu。您应该始终使用 subadd 指令的 u 版本,除非您特别 想要 签名溢出以引发异常。 C 编译器总是使用 u 版本。 (在 C 中,有符号溢出是未定义的行为1。)

int neg(int x) { return -x; }

On the Godbolt compiler explorer,MIPS gcc11.2 -O3 -fno-delayed-branch 完全按照我们的预期编译它:

neg(int):
    subu    ,[=11=],
    jr      
    nop              # filling the branch delay slot for

询问编译器通常是找到在 asm 中做事的有效方法的好方法。 (GCC 总是制作与真正的 MIPS CPUs 兼容的 asm,而 GAS 不同于 MARS/SPIM 经典的 MIPS 汇编器。另见 Tweak mips-gcc output to work with MARS


IIRC sub will try to detect overflow, but would this make is slower?

没有。在无一例外的情况下,据我所知,subsubu 具有相同的性能 CPUs.

CPUs 针对常见情况进行了大量优化。在正常代码中很少发生例外情况,因此例外情况需要相当多的周期。因此 CPU 核心只需要在将任何错误结果写回寄存器文件或存储到 cache/memory 之前检测到异常。在任何 MIPS 流水线的执行和回写之间至少有几个流水线阶段。

在有符号溢出的情况下,ALU可以在与结果相同的周期内产生溢出信号。带有被大多数指令更新的“标志”寄存器的 ISA 作为 add 指令正常操作的一部分一直这样做:如果软件想在 x86 或 ARM 上对有符号溢出做一些特殊的事情,他们会在溢出标志上使用条件分支(x86 上为 OF,ARM 上为 V)。 MIPS 的特殊之处在于,除了对有符号溢出采取异常外,很难做任何事情。


脚注 1: 未定义行为意味着它允许出错,但 不需要 出错,而且通常人们宁愿它没有'吨。编译器希望能够优化和引入创建 C 抽象机中永远不存在的临时值的转换,因此他们必须避免在这样做时出错。始终使用 subu 是一种很好的方法,因此它不需要跟踪操作和输入值是否是 C 抽象机中会发生的值。但在这种情况下,使用 sub.

是合法的

UB 的另一个含义是允许编译器假设 -x 结果没有溢出到 INT_MIN,因此 x 之前和之后不可能是 INT_MIN.

因此,如果您将此操作作为求绝对值的一部分,则需要避免使用 0U - x 在进行无符号减法之前将 x 转换为无符号,从而产生无符号结果.在像 MIPS 这样的 2 的补码机器上,将 signed int 强制转换为相同宽度的 unsigned 是免费的,只需使用不变的位模式即可。 return x<0 ? 0U - x : x;

为此,2 的补码位运算 有用的,GCC 将其与 -march=mips32r3 及更高版本一起使用。 (IDK 为什么它认为分支在 CPU 上更好,例如 -march=r14000,一个 4-wide 乱序执行 CPU。)

uabs(int):
        sra     ,,31          # broadcast the sign bit
        xor     ,,          # ~x      or x
        subu    ,,          # ~x-(-1) or x
        jr      
        nop