ECE/CS 314: Computer Organization
Fall 2003
Calling Conventions
 
 
 

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-Saved”registers. 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.
  Questions?
   
 
Contact Alan Demers