sections in this module | City
College of San Francisco - CS270 Computer Architecture Module: MIPS-III (Procedures) |
module list |
Introduction to Procedures
Note: the term function
and procedure are used interchangeably in this discussion. The
traditional distinction is that a procedure doesnt return a value while
a function does. We will view the terms as synonymous.
In this section we will start to discuss procedures. We begin with the simplest case - a leaf function. A leaf function is a function that does not call anyone else - it simply does its work and returns. If the leaf function is simple, it does not need to use the stack. It often does not need to touch the stack at all.
Returning from a function
The simplest thing to discuss about a function is how to return. When a function is called, the address of the instruction following the call is automatically placed in a special register - the return result register, $ra.
We have been using $ra to return from main() recently, and this is
no different - just end the function with the return statement
jr $ra
Returning a function result
If your function returns a result, the result is placed in another special register - the return result register, $v0. Simply set $v0 to the function result and return. There are actually two function result registers, $v0 and $v1, but $v1 is seldom used.
Function arguments
On most machines, when a function is called, the arguments to the
function are placed on the stack by the calling function. (Remember, we
will refer to the calling function as the caller.) After the function call, the receiving function (the callee)
takes the arguments off the stack as they are needed. Let's look at
what this means for a leaf function that takes four arguments:
int foo (int a, int b, int c, int d)
higher address lower address |
other data for the caller |
<----- $sp at entry to foo |
fourth argument (d) 12($sp) |
||
third argument (c) 8($sp) |
||
second argument (b) 4($sp) |
||
first argument (a) 0($sp) |
Thus, at entry to foo, foo's first argument (a) would be located at
0($sp), and it's second argument at 4($sp). On most machines, then, foo
can extract the arguments from the stack using their offsets whenever
it likes. The stack location for the argument is called its home location.
In fact, if foo wants to store a modified value for b on the stack
later, it can use the home location allocated for it by its caller
(4($sp)).
MIPS keeps this general framework. There is always space allocated for arguments on the stack when a function is called. However, MIPS does an optimization here. Since most functions use all of their arguments, it is redundant for the caller to place them on the stack and then have the callee take them off again. So MIPS reserves four registers for the first four arguments and leaves the arguments in those registers! Thus, foo can use the pre-loaded register $a0 for the first argument (a), up to register $a3 for the fourth argument (d). These values are not placed on the stack by the caller, but, since there is a home location allocated for them, foo (the callee) can store them if it needs to.
Note that this creates a bit of a quandry - not all functions use four arguments: some use more and many use less. The convention on MIPS is this - the $a0-$a3 registers are always reserved for the first four arguments, and space is reserved to home them on the stack. Later arguments (starting with the fifth one) are placed on the stack in the correct relative position just like most architectures.
A first example
Let's look at an example of a simple function foo:
Here we will pretend that blahblah is something that requires us to save argument a in its home location (for practice). Here is how the code would look:
As you see, the first four arguments were in registers (and not on the stack - although space was allocated for them). The fifth (and later) arguments were on the stack, and we had to load them.
On other machines, only the amount of space required for arguments is allocated on the stack when functions are called. On MIPS, space for a minimum of four arguments is allocated on the stack whenever a function is called. (Later, we shall see that this actually simplifies our work.)
A second example
We have seen how to use arrays that are global. What if an array is passed as an argument. This is actually very simple - when an array is passed as an argument, the address of its first argument is passed. Thus, there is no difference between these two functions
int sumarr(int arr[], int nelems);
int sumarr(int *iptr, int nelems);
In both cases, the first argument is a pointer to the first array element. Let's go ahead and code one of these functions to sum an array, as its name implies:
First, convert it. Here we have adopted the rule of prefixing our
local labels with L and adopting a standard naming convention (although
our label names get long):
Now lets add the MIPS code:
As you can see, the register $a0 was already initialized with the
address of the start of the array - we just used it in each iteration.
Note that this code could be optimized, but the indexing operation was
left in for clarity.
Use of registers
Registers are global - there is only one set of registers. If you
have a register in-use when you call a function, and that function uses
that register, it overwrites your value.
Part of the procedure calling convention tells us what to do about preserving our registers. We will see that in the next section. Here we just introduce the simplest rule:
Whatever function is executing owns
these registers: the a registers, the t registers, the v registers, and
$ra. This means when you call a function, you must assume that all of those registers has been destroyed. It also means our leaf function could freely use those
registers, as you saw in the example above.
In the next section we will discuss the general situation - when a
function calls another. If that happened with our function foo, foo
would have to allocate room on the stack (create a stack frame) to hold
data which could be destroyed by the function it calls (it's return
address, for example). We will see how that works in the next
module. Note
that our function main(), which calls foo() is no longer a leaf
function. It now needs a stack frame, as we will discuss in the next
section.
Character strings in functions
In a previous section we discussed how an assignment of a character
string (a C-string) to a character pointer results in an initialization
of the string and the assignment of the address of the string to the
pointer variable. In that example, the character pointer was global.
Globals, of course, are rarely used in modern programming languages. Instead you would have a local variable that is of type char * that is initialized to a string such as at the beginning of this function:
The only difference here is that there is not a global variable to hold the pointer. Instead, the local variable is initialized at runtime to the address of the string, which would be generated on MIPS using a .asciiz directive as in the previous example.
Character buffers in functions
Until now, we have been using global static character buffers to
hold user input. It is a very bad idea (and is disallowed in most
programming languages of today) to allow a function to modify global
data. Once we start using the stack for local variables in the next
module, we will allocate room for character buffers there instead. You
will begin to see this in the sample files for the exercise sets and we
will discuss it further in the next section. After that it will be
class policy to not allow any function to modify global data.
Prev | This page was made entirely
with free software on linux: Kompozer, the Mozilla Project and Openoffice.org |
Next |