|
MIPS Calling Conventions
The lecture slides were intended to develop calling conventions step-by-step,
so most of the examples there do not conform to the full MIPS calling conventions.
Some students seem not to have realized this, so
by popular demand, here is a summary of the calling conventions we expect you to use
in homework problems and assembly language programming projects.
These are the rules for how registers should be used and how stack frames
should be laid out in memory.
You should treat this page,
or equivalently the material in the textbook section A.6,
as the truth.
Register Use
For convenience, we repeat the register usage conventions here:
- $0 ($0) always 0
- $1 ($at) the Assembler Temporary used by the assembler in expanding
pseudo-ops.
- $2-$3 ($v0-$v1) these registers contain the Returned Value of a subroutine;
if the value is only 32 bits long only $v0 is significant.
- $4-$7 ($a0-$a3) the Argument registers, contain the first 4 argument
values for a subroutine call.
- $8-$15,$24,$25 ($t0-$t9) the Temporary or Caller-Savedregisters.
A subroutine is free to modify these, so if a program depends on their values
it must save and restore them (in the stack frame) around subroutine calls.
- $16-$23 ($s0-$s7) the Callee-Saved registers.
A subroutine is required to preserve the values of these registers,
so if it changes them it must save the original values on entry
(in the prolog code) and restore them on exit (in the epilog code).
- $26-$27 ($k0-$k1) the Kernel ReservedDO NOT USE.
- $28 ($gp) the Globals Pointer used for addressing static global variables.
For now, ignore this.
- $29 ($sp) the Stack Pointer.
- $30 ($fp/$s8) the Frame Pointer, if needed (this was discussed briefly
in lecture). Programs that do not use an explicit frame pointer
(e.g., everything assigned in ECECS314) can use register $30 as another
callee-saved register.
- $31 ($ra) the Return Address in a subroutine call.
A couple of additional rules that do not obviously follow from the above:
- The value of the stack pointer is required to be multiple of 8 at all times.
This ensures that a 64-bit data object can be pushed on the stack
without generating an address alignment error at run-time.
This implies the size of every stack frame must be a multiple of 8;
technically, this requirement applies even to leaf procedures
as discussed below.
- The argument registers $a0-$a3 are considered caller-saved: a subroutine
is allowed to change the values of any of the argument registers
without saving/restoring them.
- The first four words at the top of the stack are argument slots --
memory locations reserved to store the four arguments $a0-$a3.
Arguments beyond the fourth are passed in memory immediately after that;
so (as illustrated below) a subroutine that saves $a0-$a3 into these argument
slots can then treat all its arguments as a 1-dimensional
array in memory. The argument slots are allocated by the caller, but all four
slots are required, even if the caller knows it is passing fewer than
four arguments. Thus, on entry a subroutine may legallyl store all
of the argument registers into the argument slots if desired.
Leaf vs Nonleaf Procedures
The MIPS calling conventions distinguish 3 different classes of procedures:
- Simple Leaf procedures do not call any other procedures,
do not use any memory space on the stack (either for local variables
or to save/restore callee-saved registers).
Such procedures do not require a stack frame,
consequently never need to change $sp.
- Leaf with Data are leaf procedures (i.e. do not call any other procedures)
that require stack space, either for local variables or
as save areas for callee-save registers.
Such procedures push a stack frame (the size of which should be a
multiple of 8 as discussed above).
However, $ra is not saved in the stack frame; and in general
the layout of the frame is up to you.
- Nonleaf procedures are those that call other procedures.
The stack frame of a nonleaf procedure,
reading from top to bottom (higher to lower memory addresses)
contains:
- one word of space for the saved $ra value.
- space to save any callee-saved registers the procedure
may change.
- space to save any caller-saved registers this procedure
needs to save/restore around calls to other procedures.
- space for any in-memory local variables used by the procedure.
- possibly an unused word of space to ensure the size of the
entire stack frame is a multiple of 8.
- enough argument slots for any call this procedure
may make to other procedures (that is, the maximum number
of arguments used in any call).
Below are examples for each of these cases.
A call with more than 4 arguments
The first 4 arguments are in registers $a0 through $a3 ,
and the 5th argument is only on the stack.
# call f(0,1,2,3,4)
li $a0,0 # arg0
li $a1,1 # arg1
li $a2,2 # arg2
li $a3,3 # arg3
li $t0,4 # arg3 value
sw $t0,16($sp) # store in arg3 slot
jal f # jump to callee and link
A Nonleaf Procedure
The called function f computes a fairly complicated expression:
int f(int a, int b, int c, int d, int e) {
int temp = g(b, c);
return a + g(temp, g(d, e));
}
This function makes several calls to g with various arguments.
Recall that the argument registers $a0 through $a4
are considered caller-save, that is, their values may be changed
by a call to the external function g .
Thus, it is necessary for f to save at least some of them.
In addition, f will allocate space for variable temp
in its stack frame.
So the stack frame will look like:
The displacements printed at the left are relative to the initial stack pointer
when f is entered, or relative to framesz($sp) after
f has pushed its frame onto the stack.
The assembly language code for f looks like
f:
# prolog
addiu $sp,$sp,-framesz # push frame
sw $ra,framesz-4($sp) # save $ra
sw $a0,framesz($sp) # save a
sw $a3,framesz+12($sp) # save d
# x = g(b,c)
add $a0,$0,$a1 # put b in $a0
add $a1,$0,$a2 # put c in $a1
jal g # call g
sw $v0,framesz-8($sp) # put result in temp
# call g(d,e)
lw $a0,framesz+12($sp) # put d in $a0
lw $a1,framesz+16($sp) # put e in $a0
jal g # call g
# call g(x, g(d,e))
lw $a0,framesz-8($sp) # put temp in a0
add $a1,$0,$v0 # put prev function result in a1
jal g # call g
# add a to result
lw $t0,framesz($sp) # get a
add $v0,$v0,$t0 # add a to function result
# epilog
lw $ra,framesz-4($sp) # restore $ra
addiu $sp,$sp,framesz # pop frame
jr $ra # return
This is admittedly long,
but not terribly complicated if you take it one comment at a time.
A Simple Leaf Function
Consider the simple function
int g( int x, int y ) {
return (x + y);
}
This function does not call any other fuunction,
so it does not need to save $ra .
Also, it does not require any temporary storage.
Thus, it can be written with no stack manipulation at all.
Here it is:
g:
add $v0,$a0,$a1 # result is sum of args
jr $ra # return
Because it has no local data, this function does no stack manipulation at all.
A Leaf Function With Data
Now let's make g a little more complicated:
int g( int x, int y ) {
int a[32];
... (calculate using x, y, a);
return a[0];
}
This function does not call any other fuunction,
so it does not need to save $ra ,
but, it does require space for the array a .
Thus, it must push a stack frame.
Here is the code:
g:
addiu $sp,$sp,(-128) # push space for array a
. . .
lw $v0,0($sp) # result is a[0]
addiu $sp,$sp,128 # pop space for array a
jr $ra # return
Because this is a leaf function, there is no need to save/restore $ra
and no need to leave space for argument slots.
|