Image goes here
MIP Function Definitions and Calls
CptS 260 - Intro to Computer Architecture
Washington State University

Function (or sub-routine or procedure) calls

For each particular processor architecture, hardward architects and software designers cooperatively design one (or more) register and stack use conventions for function calls. These conventions are then implemented in compilers for many languages allowing programs in different languages to interoperate.

It's important to know the conventions so you can write interoperable assembly programs as well as understand what is going on in HLL programs if you ever have to use a low-level debugger with them.

(BTW, did anyone attend Scott Robinson's debugging lecture on Wednesday? I thought it was very helpful in introducing not only use of the gdb debugger but also tools for managing source code, test frameworks, and compilation/build. One thing he stressed was "build or find tools that let you do all of your building, or all of your testing, or all of your checkin steps in ONE command line interaction (or button click)." Good advice.)

Fundamentally, talking about agreement between caller and callee: caller sets thing up in the way expected by the callee; when callee returns, things are as expected by the caller.

First point: return to the correct point in the code.

caller:
    ...
    (move arguments to parameter registers)
    la $ra,afterCall
    j proc
afterCall:
    (rest of the program)

proc:
    (body of the called procedure)
    jr $ra
but hw architects from early on have recognized the importance of procedure call and provide jump-and-link instructions to make them smaller and faster:
    jal proc
stores the address of the next instruction in register $ra. MIPS also has jalr Rd,Rs where the destination address is in register Rd and the return address is stored into the programmer-specified Rs instead of implicitly into $ra.

Second point: parameters: $a0-$a3.

Third point: results: $v0-$v1

Fourth point: other processor state: $sp, $fp, $ra, $gp + callee-save registers. We have talked about caller-save and callee-save register conventions. The agreement is that a called procedure must restore all of these to their state at the time of the call if it changes them. How can it do that? Where do the registers get saved? (Student suggests fixed location, show why it won't work for nested/recursive calls.) A stack is not much more complicated.

Re-entrant code: code that can be executing multiple times "at once". Achieved by using *no* global writable data locations within the procedure.

Typical procedure entry code is therefore like this:

proc:
proc_prolog:
    addi    $sp, $sp, -16    # -16 (framesize) depends on the amount of space needed
    sw      $ra, 12($sp)     # offset must be <= framesize-4
    sw      $fp, 8($sp)      # store the caller's fp
    sw      $gp, 4($sp)      # store caller's gp
    sw      $s0, 0($sp)      # make $s0 available for our use
    mov	    $fp, $sp         # fp is now ours
    ...                      # code is free to change $ra, $gp, $s0
proc_epilog:
    lw      $s0, 0($sp)
    lw      $gp, 4($sp)
    lw      $fp, 8($sp)
    addi    $sp, $sp, 16     # reverse the effect of 1st instruction
    jr      $ra
Note that some processors have instructions for storing multiple registers in a single operation, in which case one often just stores all the callee-save registers rather than worrying about which are actually used.

Full example: factorial(n), with success indicator in $v0 (1 - ok, 0 - problem), and answer in $v1; assume parameter in $a0.

main:
    li      $a0, 10
    jal     fac
    move    $a0, $v1
    li      $v0, 1
    syscall

    li      $v0, 10         # exit
    syscall            
fac:
    bltz    $a0, Problem
    addi    $t1, $a0, -13
    bgtz    $t1, Problem

fac_prolog:
    addi    $sp, $sp, -16
    sw      $ra, 12($sp)
    sw      $s0, 8($sp)
    slti    $t0, $a0, 2      # $t0 = $a0 < 2 ? 1 : 0
    beqz    $t0, Go
    li      $v0, 1
    li      $v1, 1
    b       fac_epilog

Go:
    move     $s0, $a0         # hold onto n
    addi    $a0, $a0, -1
    jal     fac              # could be jal fac_prolog; why?
    mult    $v1, $s0
    mflo    $v1
    li      $v0, 1

fac_epilog:
    lw      $s0, 8($sp)
    lw      $ra, 12($sp)
#   li      $v0, 1           # common success indicator
    addi    $sp, $sp, 16
    jr      $ra

Problem:
    li      $v0, 0
    jr      $ra


Note that this code is slightly different from what is in the book (p. 70): uses registers for arguments and results; fixes two typos in the book. The line that says addiu $sp,$sp,-16 is wrong as is the line that says bgtz $t1,Prob.

One more thing to note: what if there are more than 4 arguments or two results? Answer: they are passed on the stack, above (at higher addresses than) the *caller's $sp.

(c) 2004-2006 Carl H. Hauser           E-mail questions or comments to Prof. Carl Hauser