ECE 272 Laboratory - Lab 6

ECE L272 Lab 6
Subroutine parameters

Objectives

Background

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.

Assignment - due [see schedule]

  1. Try the assignment below


THIS IS THE SPECIFICATION OF WHAT THE ASSEMBLY FUNCTION THAT NEED TO BE WRITTEN WILL DO. DO NOT TYPE THIS CODE IN.

/* 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 */

THIS IS THE C CODE WHICH ACTS AS A DRIVER FOR THE ASSEMBLY FUNCTION BY CALLING IT.

lab6drv.c - the following code
----- 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 -----

THIS IS THE STUB OF THE ASSEMBLY CODE TO SOLVE THE PROBLEM. YOU WILL GET SOMETHING LIKE THIS DURING YOUR PRACTICAL EXAM. YOU MUST FILL IN THE BLANKS TO COMPLETE THE PROBLEM.

lab6asm.s - the following code
----- BEGIN ASSEMBLY CODE STUB -----
        .globl  Factorial
        .type   Factorial,@function
Factorial:
        /* put code here */

        /* prolog */

return:
        /* epilog */
        ret
----- END ASSEMBLY CODE -----

THIS IS WHAT YOUR OUTPUT SHOULD LOOK LIKE.

[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.

THIS IS THE SOLUTION TO THE PROBLEM WITH THE STUBS FILLED IN. YOU SHOULD TRY AND DO THE PROBLEM WITHOUT LOOKING AT THIS ANSWER.

lab6asm-sol.s - the following code
----- 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 -----