Reputation: 121
I've read several posts about addiu and I think it would be helpful for me to understand if I knew some examples of specific situations/cases when it's better to use addiu over addi and vice-versa (and why it's better).
This post indicates dealing with addresses and addition with addresses it is important to use addiu to prevent an overflow "trap." But then it seems to say that the trap is not even really beneficial, so why should I use addi at all instead of addiu? Why would we use addiu instead of addi?
My professor just seems to use them interchangeably in an effort to remind us addiu exists maybe??? But it ends up sort of confusing me at times wondering "Do I really need to use addiu in this case or are they just using it for 'fun'?"
Upvotes: 3
Views: 3285
Reputation: 364458
so why should I use addi at all instead of addiu?
Generally you shouldn't, except as kind of an assert()
that there was no 2's complement signed overflow in an addition. You might possibly want that in cases where you have signed values that you expect to not overflow, especially when writing in asm by hand. (Compilers never use add
, always addu
.)
The instructions are literally identical apart from that, including sign-extension of the 16-bit immediate for addiu
. (Unlike ori
and other booleans which zero-extend. MIPS does require zero-extension for some instructions, but sign-extension for addiu
means it doesn't also need a subiu
opcode to subtract small integers.)
In a program for your own use, having your program faulting on bad inputs may be better than letting something wrap that's not supposed to. If you were writing for Linux MIPS instead of MARS, you could maybe install a signal handler for SIGFPE (arithmetic exception) so you could at least print a human-friendly error message like "signed overflow detected, aborting". In MARS I assume you'd just get dropped into the debugger.
The only reason add
and addi
exist at all (instead of just the normal addu
and addiu
, which do what most ISAs like x86 and ARM call add
) is integer overflow detection.
Integer overflow detection is a hard problem. (See also https://en.wikipedia.org/wiki/Integer_overflow). ISAs have made various attempts at providing hardware support for it that would let software (especially in high-level compiled languages) routinely use it. MIPS's add
/sub
instructions are one such attempt, but don't cover cases like left-shift. So high-level languages that wanted to provide overflow checking on every operation would need a separate mechanism. Or they might just only use the other mechanism and not add
/sub
at all.
As a contrast to MIPS, consider an ISA like x86 or ARM: they have a FLAGS or status register with a signed-overflow bit, and a way to do a conditional branch if the last instruction set that bit. x86 even has an into
(trap if OF is set in FLAGS) instruction.
But MIPS doesn't have a flags register at all, only integer registers. So there's nowhere to record the fact that an addition overflowed, except via an add-and-branch instruction like add
. Without spending any bits to encode a branch target for the overflow case, the only real choice is a trap / exception.
(Well, another easier-to-use choice would have been a special register that you can set, but then context switches would have to save/restore it. And it would have to work like a JAL to record which add faulted. This might have required microcode, which MIPS avoided. Or at least more complex handling of that special case because it's like an exception but not an exception, and can't be detected as early a conditions like beq
because it does need a full 32-bit add result.)
addu
?I don't know, maybe they were hardware people, not software, and were overly optimistic that they would usher in a new age of checked arithmetic and make many integer overflow bugs a thing of the past.
With hindsight from now, giving the add
mnemonic to the standard non-trapping versions that compilers always use would have made the most sense. Then you'd need another name for the trapping versions. Maybe addt
(trapping) or addc
(add-checked). But addc
is terrible because of confusion with add-with-carry on other ISAs, commonly adc
. adds
(add-signed) is a possibility. Naming is hard!
Basics: there are a few obvious cases where you need to use addu
:
For (potentially) large unsigned integers, it's obviously important to use addu
. 0x7fffffff + 1
is nothing special for unsigned. But it is 2's complement signed overflow from INT_MAX to INT_MIN so add
would trap.
For pointers, it's potentially important to use addu
. Unless you're using a "high half kernel" memory model where it's impossible for one array to start below 0x80000000
and end above it.
Below is my first version of answering this. Parts of it are redundant with the above. But I think not all. (TODO: finish editing this; I don't have time now but I think it's more useful to post now than to sit on this until later.)
when it's better to use addiu over addi and vice-versa (and why it's better).
Use add
/ addi
ONLY if you specifically want the machine to trap (aka fault, raise an exception) on 2's complement signed overflow (e.g. wraparound like INT_MAX + 1 becoming INT_MIN).
Most of the time nobody ever wants this, but maybe if you were writing by hand in asm and wanted to defend against some integer-overflow bugs you might use add
, if raising an exception is better than continuing with a bad value. Of course this is only available for add
, not left-shift by more than 1 or other instructions. So if you really want comprehensive integer overflow checking in some defensive code, you need another mechanism anyway.
And for this to be a good idea, you'd probably want to install an interrupt handler for that hardware exception. (Or in user-space under an OS, a signal handler for SIGFPE, the POSIX signal for arithmetic exceptions.)
Or during development, maybe you'd want integer overflow to just abruptly stop on an add instruction that you were expecting not to overflow. i.e. like an assert
. Compilers don't because it would be weird to only have checks sometimes, not checked integer arithmetic for everything. But by hand possibly better than nothing.
One place where integer-overflow bugs can often happen is memory-allocation size calculations. (e.g. resulting in a small allocation and the program goes off the end of it can be a DOS bug, or a memory-overwrite bug depending on the allocator). But those often use unsigned integers; it's not an error to do 0x7fffffff + 1 = 0x80000000
with unsigned.
addu
/ addiu
addu
is MIPS's normal add instruction.
As Difference between add and addu points out, the names are misleading. The u
possibly corresponds with signed overflow being undefined-behaviour in C, but unsigned overflow being well-defined as wraparound. But as you know, 2's complement addition is the same binary operation as plain unsigned binary addition. addi
merely checks for signed overflow.
(Not that C compilers will ever use add
or addi
: they don't want to make code that ever faults. Signed overflow is UB, but that doesn't mean they must do anything in particular. Also, after optimization it's not rare to make asm that has temporary values that don't exist in the C abstract machine. Doing w+x+y+z as (w+x)+(y+z)
could have signed overflow even if (((w+x)+y)+z)
doesn't; binary addition is truly associative regardless of signed overflow.)
MIPS32r6 even removed addi
, leaving only the non-faulting addiu
. (So if you want to trap on signed overflow, you can still put the operand in a register and use add
.) This is listed in the bullet list on Wikipedia of removed infrequently-used instructions as integer overflow trapping instructions with 16-bit immediate That should tell you something about how much real-world use addi
ever gets.
My professor just seems to use them interchangeably in an effort to remind us addiu exists maybe???
Hard to judge from that whether that's really true, or whether they have subtle reasons that you haven't picked up on. e.g. using addi
whenever it's definitely safe?
Or whenever they're thinking about an integer as signed, even if code is only holding non-negative values in it? e.g. for(int i=0 ; i<100; i++)
is a signed int
as far as the C abstract machine is concerned.
In situations where signed overflow is known to be impossible (e.g. after an lui
), it doesn't really matter. But IMO, still always use addiu
unless you want. To me, as a human reading code, seeing add
/ addi
would ideally be a sign that we intentionally want signed-overflow checking. But in SO questions, more usually beginners just used add
because they didn't think about or even know about addu
. So it forces you to look for the risk of "false positive" trap bugs: where add
could fault when you don't want it to.
I don't think there are any real MIPS CPUs where add
/addi
are slower than addu
/ addiu
. Probably not; any lw
or sw
can fault depending on register inputs so a MIPS pipeline needs to be able to efficiently handle instructions that can potentially fault.
In the common case they never fault; you optimize the pipeline for that and it barely matters how inefficient it actually is to take the fault. (Or maybe it does matter some on MIPS with software TLB-miss handling; that could happen fairly frequently, much moreso than page faults).
Upvotes: 2