Recursion in CS232: Implementing a Recursive Function in MIPS and C, Study notes of Computer Architecture and Organization

The concept of recursion in computer science, specifically in the context of the cs232 spring 2006 course. It explains the importance of saving registers in recursive functions and provides an example of a recursive function in c that calculates the sum of integers from n to k. The document then goes on to explain how to implement this recursive function in mips, including saving callee- and caller-saved registers on the stack and calling the function recursively. Finally, it provides a problem for the reader to convert mips code into an equivalent c function.

Typology: Study notes

Pre 2010

Uploaded on 03/16/2009

koofers-user-n2e
koofers-user-n2e 🇺🇸

10 documents

1 / 6

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
CS232 Spring 2006 Discussion 2: Recursion Jan 30-31, 2006
In lecture, we have talked about how to write functions. Unlike in C, calling a function in
MIPS requires more than just invoking it. Before calling a function, the function making the
call (known as the caller) needs to save values of registers that the function may use and
overwrite (these are known as caller-saved registers). The called function (known as the callee)
also needs to save values of some registers, which are appropriately named callee-saved registers.
Following the register-saving convention becomes even more critical when using recursive func-
tions, which is the topic of this discussion.
Some Definitions
Arecursive function is a function that calls itself. To ensure that the self-calling eventually
terminates, there are two parts to every recursive function, the base case and the recursive case.
The base case defines the condition that terminates the recursion. The recursive case calls the
function recursively, but with different input(s) that come progressively closer to the base case
condition. Reaching the base case results in termination of the recursion and unwinding of the
stack of recursive calls.
Example
Let’s write a recursive function that does simple math. Given two integers nand k, where
nk, find the sum of integers from nto k, inclusive (e.g. if n= 2 and k= 4, our program
will add 2 + 3 + 4).
Recursion in high level languages
First, let’s try to write this function in our favorite C-like language. There are two inputs to
this function, so the signature is:
int mySum(int n, int k);
The function proceeds to count numbers from nup to kby incrementing nby 1 each time. The
recursive case performs the addition using a recursive call:
return n + mySum (n + 1, k);
The base case of the algorithm occurs when n=k. Note that we’re assuming that nkin the
beginning.
if(n == k)
return n;
So, here’s the complete function:
int mySum (int n, int k) {
if (n == k)
return n;
return n + mySum (n + 1, k);
}
It is short and elegant, which is one of the appeals of recursive functions.
1
pf3
pf4
pf5

Partial preview of the text

Download Recursion in CS232: Implementing a Recursive Function in MIPS and C and more Study notes Computer Architecture and Organization in PDF only on Docsity!

In lecture, we have talked about how to write functions. Unlike in C, calling a function in MIPS requires more than just invoking it. Before calling a function, the function making the call (known as the caller ) needs to save values of registers that the function may use and overwrite (these are known as caller-saved registers). The called function (known as the callee) also needs to save values of some registers, which are appropriately named callee-saved registers.

Following the register-saving convention becomes even more critical when using recursive func- tions, which is the topic of this discussion.

Some Definitions

A recursive function is a function that calls itself. To ensure that the self-calling eventually terminates, there are two parts to every recursive function, the base case and the recursive case. The base case defines the condition that terminates the recursion. The recursive case calls the function recursively, but with different input(s) that come progressively closer to the base case condition. Reaching the base case results in termination of the recursion and unwinding of the stack of recursive calls.

Example

Let’s write a recursive function that does simple math. Given two integers n and k, where n ≤ k, find the sum of integers from n to k, inclusive (e.g. if n = 2 and k = 4, our program will add 2 + 3 + 4).

Recursion in high level languages

First, let’s try to write this function in our favorite C-like language. There are two inputs to this function, so the signature is:

int mySum(int n, int k);

The function proceeds to count numbers from n up to k by incrementing n by 1 each time. The recursive case performs the addition using a recursive call:

return n + mySum (n + 1, k);

The base case of the algorithm occurs when n = k. Note that we’re assuming that n ≤ k in the beginning.

if(n == k) return n;

So, here’s the complete function:

int mySum (int n, int k) { if (n == k) return n; return n + mySum (n + 1, k); }

It is short and elegant, which is one of the appeals of recursive functions.

Implementing recursion in MIPS

Now let’s write the MIPS code, doing tiny steps, eventually arriving at the correct solution.

  1. Review register conventions and name variables. First, a reminder about registers: $a0-$a3 are used to pass parameters to functions, while $v0 and $v1 are used for return values. In our case, $a0 corresponds to n, $a1 is k and the return value will be stored in $v0.
  2. Convert the code for the base case. Converting the base case (lines 2 & 3 of C code) is easy, but not trivial. Remember to “invert” the test, i.e. check if n 6 = k, because the label must point to the else (recursive) case:

mySum: bne $a0, $a1, recurse move $v0, $a jr $ra

Note that, since the base case does not have any function calls, it is not necessary to save/restore any values to the stack. The recursive case (line 4 of C code) is more complex and requires multiple steps:

  1. Save callee- and caller-saved registers on the stack. How much stack space has to be allocated? Callee-saved registers are the $s-registers and $ra. Other registers are caller-saved. Re- cursive functions are both caller and callee, so we need to save all registers. Even if we’re sure that some registers will not change (e.g. $a1), it’s still good programming practice to save them.

recurse: sub $sp, $sp, 12 # allocate stack space: 3 values * 4 bytes each sw $ra, 0($sp) sw $a0, 4($sp) sw $a1, 8($sp) # add this just for completeness

  1. Call mySum recursively. Before we can perform the addition (line 4 of C code), we need to obtain the result of the function call. Since $a1 already has the right value, we only need to modify $a0 and call mySum.

addi $a0, $a0, 1 jal mySum

  1. Clean up the stack and return the result. To perform the addition, we need to use the old value of $a0 (n, not n + 1), which we fortunately stored on the stack.

lw $a0, 4($sp) add $v0, $v0, $a

  1. Save callee- and caller-saved registers on the stack.
  2. Call fib recursively.
  3. Call fib recursively again.
  4. Clean up the stack and return the result.

2 Converting MIPS to C

In this problem, you will convert MIPS code into a high-level language like C.

Guidelines

  1. You can use any C-like high-level language. We will not deduct points for sytax errors, unless they alter the meaning of your code.
  2. You must not use gotos. You must translate conditional and unconditional jumps into high-level constructs such as if-then-else and switch statements, for and while loops, etc.
  3. You must infer the correct function prototype for each function, i.e. you must specify the return type and type of each argument for every function.
  4. For arrays, you must translate accessing array elements using array indexing, not using pointer arithmetic. For example:

loop: ... lw $t0, 0($a0) add $a0, $a0, 4 ... j loop

should be translated as:

while (...) { /* or, for(...) / ... t0 = a0[i]; / index array element / i++; / increment index */ ... }

and must not be translated as:

while (...) { /* or, for(...) */ ... t0 = a0; / dereference the pointer / a0 = a0 + 4; / increment the pointer */ ... }