According to the MIPS documentation, functions output is stored in $v0
-$v1
(up to 64 bits), and the function arguments are given in $a0
-$a3
, where any additional arguments are written to the stack.
Since the function is allowed to overwrite the values of $v0
-$v1
, wouldn't it be better to pass the function fifth argument (if such exist) on $v0
?
What is the motivation for using the stack in this case?
You are right that the $v
registers are available to be used to pass parameters.
MIPS has, at times, updated the calling convention, for example: the "MIPS EABI 32-bit Calling Convention", redefines 4 of the original $t
registers, $8
-$11
, as additional argument registers, to pass up to 8 integer arguments in total.
We might also consider that $at
aka $1
— the assembler temp — is also available at the point for parameter passing.
However, object model invocations, e.g. those involving vtables, thunks and other stubs such as long calls, perhaps cross library (DLL) calls, can require an available register or two that are scratch, so it would not necessarily be best to use every one of the scratch registers for arguments.
In general, other than that I'm not sure why they don't just get rid of most of the $t
registers (and $v
registers) and make them all $a
registers — these would only be used when needed, and otherwise those unused argument registers would serve the same purpose as $t
registers. The more parameters, the fewer scratch registers — though in both caller and callee — but I think tradeoff can be made instead of guaranteeing some larger minimum number of scratch registers as in current ABIs.
Still, without some bare minimum number of scratch registers, you would sometimes end up using memory, spilling already computed arguments to memory in order to have free registers to compute the last couple of parameters, only to have to reload those spilled values back into registers. If that were to happen, might as well have passed some of them in memory in the first place, especially since the callee may also have to store some of the arguments to memory anyway (e.g. the callee is not a leaf function, and parameters are needed after further calls).
8 argument registers is probably already on the tapering end of the curve of usefulness, so past thereabouts adding more probably has negligible returns on real code bases.
Also, a language can invent/define its own calling convention: these calling conventions are the standard for C language interoperability. Even the C compiler can use custom calling conventions when it is certain that such language interoperability is not required, as we can also do in assembly when we know more details about function implementations (i.e. their internal register usages) than just the function signature.
Nicely collected set details on various calling conventions: https://www.dyncall.org/docs/manual/manualse11.html
Addendum:
Let's assume a machine with only 2 registers, call them A & B, and it uses both to pass parameters. Let's say a first parameter is computed into A (using B register as scratch if needed). In computing the value of the 2nd parameter, for B, it may run out of scratch registers, especially if the expression for that actual argument is complicated. When out of registers, we spill something to memory, here let's say, the already computed A. Now the parameter for B can be computed with that extra register. However, the A parameter value, now in memory, needs to return back to the A register before the call. Thus, this is worse than passing A in memory b/c the caller has to do both a store and a load, whereas passing in memory means just the store.
Now add to that situation that the callee may have to store the parameter to memory as well (various possible reasons). That means another store to memory. So, in total, if the above scenario coincides with this one, then a store, a load and another store — contrasted with memory parameter passing, which would have just the one store by the caller.