Implementing Procedures in MIPS

Using the stack

(see PDF notes)

Translating Leaf Procedures

Consider this C procedure add2

int add2(int x, int y) {
  return x + y;
}

Translate this procedure call to MIPS assembly, assuming f is in $s0

int f = add2(3, 4);

Solution:

li $a0, 3
li $a1, 4
jal add2
move $s0, $v0

Now translate the add2 procedure itself to MIPS assembly. Solution:

add2:
  add $v0, $a0, $a1
  jr $ra

Group Problem

Translate this procedure to MIPS assembly:

int max(int x, int y) {
  if (x > y) {
    return x;
  } else {
    return y;
  }
}

Solution:

max:
  ble $a0, $a1, else  # If x is not greater than y, go to else
  move $v0, $a0       # We are going to return x
  j exit              # Go to the procedure exit
 else:
  move $v0, $a1       # We are going to return y
                      # Fall through to the procedure exit
 exit:
  jr $ra              # Return from the procedure

And also translate this call to MIPS assembly, assuming f is stored in $s0.

int f = max(5, 15);

Solution:

li $a0, 5
li $a1, 15
jal max
move $s0, $v0

Translating Non-Leaf Procedures

Now let’s look at this non-leaf procedure:

int max3(int x, int y, int z) {
  return max(x, max(y, z));
}

Let’s translate the procedure. Solution:

max3:
  addi $sp, $sp, -4
  sw $ra, 0($sp)
  jal max
  move $a0, $v0
  move $a1, $a2
  jal max
  lw $ra, 0($sp)
  addi $sp, $sp, 4
  jr $ra

Group Problem

Work with your groups to translate this non-leaf procedure. It happens to be recursive, but all the same rules still apply!

int fib(int n) {
  if (n < 2) {
    return n;
  } else {
    return fib(n-1) + fib(n-2);
  }
}

Solution:

fib:
  slti $t0, $a0, 2      # Is n less than 2?
  beq $t0, $zero, fib_else  # If n is not less than 2, go to else
  
  # base case: return n
  move $v0, $a0     # Put n in the return register
  jr $ra            # Return
  
fib_else:
  addi $sp, $sp, -12 # Make space to save ra, n, and fib(n-1)
  sw $ra, 8($sp)     # Save the return address
  sw $a0, 4($sp)     # Save n

  # recursive case
  addi $a0, $a0, -1  # Compute n-1
  jal fib            # Call fib(n-1)
  sw $v0, 0($sp)     # Save the result of fib(n-1) on the stack
  
  lw $a0, 4($sp)     # Load n from the stack
  addi $a0, $a0, -2  # Compute n-2
  jal fib            # Call fib(n-2)
  
  lw $t0, 0($sp)     # Load the result of fib(n-1) from the stack
  add $v0, $v0, $t0  # Compute fib(n-1) + fib(n-2)
  
  lw $ra, 8($sp)     # Load the return address from the stack
  addi $sp, $sp, 12  # Restore the stack
  jr $ra             # Return

Tail Recursion (if there’s time)

Consider this simple recursive procedure:

int termial(unsigned int n) {
  if(n == 0) {
    return 0;
  } else {
    return n + termial(n-1);
  }
}

We can rewrite termial to use a helper procedure:

int termial_helper(int n, int sum_so_far) {
  if (n == 0) {
    return sum_so_far;
  } else {
    return termial_helper(n-1, n + sum_so_far);
  }
}

int termial(int n) {
  return termial_helper(n, 0);
}

We can take advantage of the fact that termial_helper is tail-recursive and does not need to return back through each level of recursion; it would be fine if the last level of recursion (the base case) returned all the way back to the original caller. This is something we can do in assembly.

Remember that we use jal to remember where we must return, and jr $ra to return there. If we don’t ever need to come back after a call, we can just use j instead. That will leave $ra unmodified, so we can also skip saving and restoring that register.

termial_helper:
  # If n is not zero, go to the else case
  bne $a0, $zero, termial_helper_else
  
  # Return sum_so_far
  move $v0, $a1
  jr $ra
  
termial_helper_else:
  add $a1, $a1, $a0  # Add n to sum_so_far
  addi $a0, $a0, -1  # Subtract 1 from n
  j termial_helper   # Make our recursive tail call