What is the "relationship" between addi and subi?

21,753

Solution 1

I'm unaware that MIPS even has a proper subi instruction (though some environments may implement a macro for it).

Since you're subtracting an immediate value, you can just provide the negation of it to the addi instruction:

addi $r1, $r2, -42     ; equivalent to subi $r1, $r2, 42

The immediate operand is a two's complement value which means it's perfectly capable of being a negative number and the way that two's complement works means that you can add a negative number in an unsigned manner and that gives the same result as subtracting (since you wrap around).

For example, -42 in 16-bit two's complement is the unsigned value 65494. When you add 50 and 65494 wrapping around at 65536, you end up with:

     50
+ 65494 (ie, -42)
  -----
  65544 (overflow, so
- 65536  we wrap at 64K)
  -----
      8 (identical to "50 - 42")

Solution 2

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.

Share:
21,753

Related videos on Youtube

Niklas Rosencrantz
Author by

Niklas Rosencrantz

I'm as simple as possible but not any simpler.

Updated on August 21, 2020

Comments

  • Niklas Rosencrantz
    Niklas Rosencrantz over 3 years

    I'm supposed to answer this question. After some research it says that add and sub have the same opcode and differ only in the functional field. Is this the answer or something else?

    Update

    It's available in the Nios II CPU manual:

    subi
    subtract immediate
    Operation: rB ← rA – σ (IMMED)
    Assembler Syntax: subi rB, rA, IMMED
    Example: subi r8, r8, 4
    Description: Sign-extends the immediate value IMMED to 32 bits, subtracts it from the value of rA and then
                stores the result in rB.
    Usage: The maximum allowed value of IMMED is 32768. The minimum allowed value is
    –32767.
    Pseudo-instruction:
    © March 2009
    subi is implemented as addi rB, rA, -IMMED
    
  • Niklas Rosencrantz
    Niklas Rosencrantz over 11 years
    Thank you for the right answer. I also found a reference that my envirnoment (Nios II) actually has subi , I've updated the question and you can see how the instruction is according to the manual.
  • Peter Cordes
    Peter Cordes over 5 years
    More likely some assemblers would have it as a pseudo-instruction rather than a source-level macro. MIPS assemblers usually have a lot of pseudo-instructions; and there's even a register that's commonly reserved as a temporary for use by pseudo-instructions. (e.g. for compare-and-branch). And the manual entry in the question update describes it as a pseudo-instruction.
  • Peter Cordes
    Peter Cordes over 5 years
    At the hardware / machine-code level, it's because MIPS doesn't have a FLAGS register. So there's no carry flag that would record the difference between adding a negative or subtracting a positive, like there is on x86. Thus spending an extra opcode (and the transistors to decode it) makes no sense. At the asm source level, yes obviously it could be implemented as a pseudo-instruction for convenience, but it never takes extra instructions vs. addiu (unless you wanted to subtract -32768 but instead can only add 32767).
  • Peter Cordes
    Peter Cordes over 5 years
    What exactly are you claiming is the conspiracy? The lack of hardware subi? Or the lack of a subi pseudo-instruction in some assemblers? I just tried MARS, and it assembles subi $t1, $t2, 1234 to two instructions: first generating the constant in $at ($1), then a sub instruction (which of course is a real instruction). So MARS is dumb, and doesn't just negate the immediate for an addi. But it only does this with "extended pseudo-instructions" enabled, not basic pseudo-instructions that most assemblers support. That's why it emits such inefficient code.