问题
I'm thinking about how to negate a signed-integer in mips32. My intuition is using definition of 2's complement like: (suppose $s0
is the number to be negated)
nor $t0, $s0, $s0 ; 1's complement
addiu $t0, $t0, 1 ; 2's = 1's + 1
then I realized that it can be done like:
sub $t0, $zero, $s0
so... what's the difference? Which is faster? IIRC sub will try to detect overflow, but would this make is slower? Finally, is there any other way to do so?
回答1:
subu $t0, $zero, $s0
is the best way, and is what compilers do.
On any given implementation of MIPS, most simple ALU instructions (add/sub/and/nor) have identical performance. Getting the same work done in 1 simple instruction instead of 2 simple instructions is a win for code-size, latency, and throughput.
Fewer instructions isn't always better, but MIPS being a classic RISC ISA doesn't have many "slow" instructions other than mult / div / rem.
sub
instead of subu
would raise an exception on -INT_MIN
, which you avoid using addiu
in the nor/add version. You should always use the u
version of sub
and add
instructions unless you specifically want signed overflow to raise an exception. C compilers always use the u
version. (In C, signed overflow is undefined behaviour. That means it's allowed to fault, but not required to fault, and generally nobody wants that. Compilers want to be able to optimize and introduce transformations that create temporary values that never exist in the C abstract machine, so they must avoid faulting when doing that.)
On the Godbolt compiler explorer, MIPS gcc5.4 -O3 compiles
int neg(int x) { return -x; }
into
neg(int):
j $31
subu $2,$0,$4 # in the branch delay slot
exactly like we'd expect. Asking a compiler is usually a good way to find efficient ways to do things in asm.
IIRC sub will try to detect overflow, but would this make is slower?
No. In the no-exception case, sub
has the same performance as subu
, as far as I know.
CPUs are heavily optimized for the common case. Taking an exception happens so rarely in normal code that it's fine for exceptions to take quite a lot of cycles. So the CPU core just has to detect an exception before any bad result is written back to the register file, or stored to cache/memory. There are at least a couple pipeline stages between Execute and Write-Back on any MIPS pipeline.
In the case of signed overflow, the ALU can produce the overflow signal in the same cycle as the result. (ISAs with a "flags" register that's updated by most instructions do this all the time as part of the normal operation of an add
instruction: if software wants to do something special on signed overflow on x86 or ARM, they'd use a conditional branch on the overflow flag (OF on x86, V on ARM). MIPS is special in that it's difficult to do anything other than take an exception on signed overflow.)
来源:https://stackoverflow.com/questions/53568440/about-negate-a-sign-integer-in-mips