At the hardware / machine-code level of ISA design, MIPS doesn't have a subi
/ subiu
instruction. That makes sense because MIPS doesn't have a FLAGS register.
There's no carry flag that would record the difference between adding a negative or subtracting a positive, like there is on many other architectures (x86, ARM, and many other less RISCy architectures). Thus spending an extra opcode (and the transistors to decode it) makes no sense.
Adding a negated immediate doesn't change the signed-overflow detection of addi
/ subi
. You get signed overflow when you add two numbers with the same sign and the result has the opposite sign. Or when subtracting z = x - y
, if x and y have opposite signs, and z
and x
have opposite signs, that's subtraction overflow (where you want subi
to trap.) y
is the immediate, so implementing it as z = x + y_negated
makes addi
's overflow-detection work.
Of course normally you (or a C compiler) would just use addiu
/ subiu
because you don't want to trap, and would rather have wrap-around as the signed-overflow behaviour, unless you compiled with -fsanitize=undefined-behavior
or something.
At the asm source level, it can be implemented as a pseudo-instruction for convenience, as your quote from the NIOS II manual shows. (Or even as a macro, on assemblers that don't support the pseudo-instruction.)
The only time when a hardware subi
/ subiu
would save an instruction is when you wanted to add 32768
/ subtract -32678
. (Notice the NIOS II manual pointing out that subi
supports immediates in the -32767 .. 32768
range, opposite from the normal signed 16-bit 2's complement -32768 .. 32767
)
The most-negative number in 2's complement is an anomaly, and its negative takes an extra bit to represent correctly. i.e. -(0x8000)
overflows to 0x8000
in the 16-bit immediate. Any decent assembler that implements subi
as a pseudo-instruction should warn about this, or warn about using an immediate outside the signed-16-bit range for addi
/ addiu
.
addiu
sign-extends its immediate to 32 bits, the same as addi
. The "unsigned" is a misnomer. Why would we use addiu instead of addi?. Signed and unsigned addition are the same binary operation for 2's complement machines. The naming kind of matches C signed-overflow being undefined behaviour, but note that undefined doesn't require faulting. Think of addi
as overflow-checked signed addition, and only use it when you specifically want that.
Fun fact: ori
and other booleans do zero-extend their immediate, so li $t0, val
can expand to only a single instruction for val = -32768 .. 65535
, using either addiu $t0, $zero, signed_int16
or ori $t0, $zero, uint16
.