关于否定 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
。您应该始终使用 sub
和 add
指令的 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?
没有。在无一例外的情况下,据我所知,sub
与 subu
具有相同的性能 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
我正在考虑如何对 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
。您应该始终使用 sub
和 add
指令的 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?
没有。在无一例外的情况下,据我所知,sub
与 subu
具有相同的性能 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