Parameters are the primary means of communicating between subroutines. There are two main methods for passing parameters: On the stack, and in registers. The main issue is that both the caller and the callee must agree on exactly how the parameters will be passed, or havoc will ensue. Let's first consider passing parameters in registers. This is the simplest method, and is often the fastest, since the data is kept in registers. Very simply, the arguments to a procedure or function are stored in agreed-upon registers before the call, and the callee simply uses them right out of those registers. The to a procedure or function are stored in agreed-upon registers before the call, and the callee simply uses them right out of those registers. The callee does not need to save the registers used for parameter passing unless the caller requires that they remain unchanged (but this is rare). Here is an example:
extern int a,b;
foo()
{
bar(a);
}
bar(p1)
int p1;
{
b = b + p1;
}
Now in assembly language, we will pass the argument a in the %eax
register as follows:
foo:
pushl %ebp
movl %esp, %ebp
; begin foo
movl a, %eax
call bar
; end foo
movl %ebp, %esp
popl %ebp
ret
bar:
pushl %ebp
movl %esp, %ebp
; begin bar
addl %eax, b
; end bar
movl %ebp,%esp
popl %ebp
ret
In a similar manner, values can be returned from a function in a register
as follows:
extern int a,b;
foo()
{
b = bar(a);
}
bar(p1)
int p1;
{
return p1 + 2;
}
Here, AX is used both to pass in the argument, and to pass out the
return value, thus making the implementation of bar very simple.
foo:
pushl %ebp
movl %esp,%ebp
; begin foo
movl a, %eax
call bar
movl %eax, b
; end foo
movl %ebp,%esp
popl %ebp
ret
bar:
pushl %ebp
movl %esp, %ebp
; begin bar
addl 2, %eax
; end bar
movl %ebp, %esp
popl %ebp
ret
Using registers to pass arguments is clearly the most efficient, but what
happens when there are more arguments than registers, or if an argument
requires more than one register? Say if it were a structure? In this
case, the stack is the best place to pass the parameters. Passing
parameters on the stack is simple. The last thing we do before the call is
push the arguments onto the stack in the reverse order that they appear in
the high-level language program (this will be important later). When the
procedure is called, the callee's prolog code will position the base
pointer register a fixed distance from the arguments, which can then be
accessed in the same manner as the local variable space, only the arguments
will be at positive offsets from the base pointer. Here is an example:
extern int a,b,c,d,e;
foo()
{
e = bar(a, b, c, d);
}
bar(p1, p2, p3, p4)
int p1, p2, p3, p4;
{
return (p1 + p2) * (p3 + p4);
}
Here we will push all four arguments to the stack. We will still use the
%eax register to return the result. After the call returns,
we have to remember to pop the results from the stack, but since we no longer
care about the values, we can simply adjust the stack pointer by the size
of the arguments. Note that the first argument is at 4(%ebp) since we
have to skip over the old %ebp value and the return address (16 bits in
this case).
foo:
pushl %ebp
movl %esp, %ebp
; begin foo
pushl d
pushl c
pushl b
pushl a
call bar
addl 16, %esp ; remove arguments from stack
movl %eax, e
; end foo
movl %ebp, %esp
popl %ebp
ret
bar: pushl %ebp
movl %esp, %ebp
pushl %ebx
; begin bar
movl 8(%ebp), %eax ; p1
addl 12(%ebp), %eax ; p2
movl 16(%ebp), %ebx ; p3
addl 20(%ebp), %ebx ; p4
mull %ebx ; result in ax
; end bar
popl %ebx
movl %ebp, %esp
popl %ebp
ret
This mechanism is general and can be used in any situation that may arise.
Return values are normally put in a register (i.e. %eax). Some
languages like C and Modula allow complex types to be returned from a
function. This can be tricky to handle. In most cases the return value is
pushed onto the stack, and a pointer to it is returned in an agreed-upon
register.
Your assignment this week will again involve translating a C program into assembly language.
/* begin assignment specification code lab6asm.s */
/* Convert the procedure exactly as given using a local variable
where needed. */
int Factorial(int n)
{
if(n == 0 || n == 1)
return 1;
else
return n * Factorial(n-1);
}
/* end assignment specification code lab6asm.s */
----- BEGIN C CODE -----
/* begin driver code lab6drv.c */
/* This is the skeleton C program. It will input a number, then
* print the Factorial of that number.
* NOTE: You will need to use the stack for the local variables and
* return the answer in the appropriate register (see lecture about
* which one).
*/
#include <stdio.h>
int main(void);
int Factorial(int);
int main(void)
{
int i;
int number;
int answer;
int fib_subscript;
printf("Please enter a number: ");
scanf("%d",&number);
answer = Factorial(number);
printf("The factorial of %d is %d.\n", number, answer);
}
/* end driver code */
----- END C CODE -----
----- BEGIN ASSEMBLY CODE STUB -----
.globl Factorial
.type Factorial,@function
Factorial:
/* put code here */
/* prolog */
return:
/* epilog */
ret
----- END ASSEMBLY CODE -----
[euphoria]~...lab6 > ./lab6 Please enter a number: 0 The factorial of 0 is 1. [euphoria]~...lab6 > ./lab6 Please enter a number: 1 The factorial of 1 is 1. [euphoria]~...lab6 > ./lab6 Please enter a number: 2 The factorial of 2 is 2. [euphoria]~...lab6 > ./lab6 Please enter a number: 3 The factorial of 3 is 6. [euphoria]~...lab6 > ./lab6 Please enter a number: 4 The factorial of 4 is 24. [euphoria]~...lab6 > ./lab6 Please enter a number: 5 The factorial of 5 is 120. [euphoria]~...lab6 > ./lab6 Please enter a number: 6 The factorial of 6 is 720. [euphoria]~...lab6 > ./lab6 Please enter a number: 7 The factorial of 7 is 5040.
----- BEGIN ASSEMBLY CODE SOLUTION -----
.globl Factorial
.type Factorial,@function
Factorial:
/* put code here */
/* prolog */
pushl %ebp
pushl %ebx
movl %esp, %ebp
subl $4, %esp
/* get 'number' (the argument) from stack */
movl 12(%ebp), %ecx
/* ecx is the argument now */
cmp $0, %ecx
je ret_one
cmp $1, %ecx
je ret_one
/* save the variable locally so that after the function call we
* can perform our math */
movl %ecx, -4(%ebp)
/* decrement then call */
decl %ecx
pushl %ecx
call Factorial
movl -4(%ebp), %ecx
/* the return was placed in %eax so we just multiply by our local */
mull %ecx
jmp return
ret_one:
movl $1, %eax
return:
/* epilog */
movl %ebp, %esp
popl %ebx
popl %ebp
ret
----- END ASSEMBLY CODE -----