CLASS 19

Difference between an open and closed subroutine

In case of an open subroutine, it is like a macro call, where the contents of the subroutine replace the call to the subroutine. Similar to the (inline) function call in case of C and C++.

In case of a closed subroutine, there is only one part of code, and a call to the subroutine results in a jump to the memory location where the code is placed. Similar to ordinary functions and subroutines in C and C++.

What could be the Advantages and Disadvantages of open and closed subroutines?

There are two instructions for branching into the subroutine: call and jmpl. (ba cannot be used.. why?)


call

The call instruction is used when the subroutine name is known at assembly time. The call instruction has a label, and it branches to the label, storing the value of %pc into %o7. It is followed by a delay slot. e.g.
call  subr    ! at the time of call
nop           ! delay  slot

jmpl

The jmpl instruction is used whenever the address of the subroutine has to be computed. A typical jump statement is like:
jmpl   %o0, %o7    ! same as call %o0
Here, the address of the routine being called is stored in %o0, and the return address (current value of %pc) is stored in %o7.

The return from a subroutine is also a jmpl instructions of the format:

jmpl %i7  + 8, %g0  ! next instruction from the called one.

The typical instructions that are executed when a function is called are:

At the time of call: 
call  subr    ! at the time of call
nop           ! delay  slot

In the subroutine...
subr: save %sp, ...., %sp

At the end of the subroutine:
jmpl  %i7 + 8, %g0
restore

Arguments to the subroutine

The arguments can be after the call instruction (as in Fortran), and can be accessed using their locations as offsets from the return location. e.g.

  call  add
  nop
  .word 3, 4
In the subroutine, the arguments will be used as shown below:
add:    save 	%sp, -64, %sp
        ld 	[%i7 + 8], %i0
        ld 	[%i7 + 12], %i1
        add 	%i1, %i0, %i0
        jmpl  	%i7 + 16, %g0
        restore

But there are disadvantages of this approach, as no recursion is possible. The other option is to use the concept of register sets, which was discussed earlier.

We can use registers %o0 to %o5 (6 registers) to pass on six values to the new subroutine ( where they will be stored in registers %i0 to %i5). But for more arguments than that, they have to be stored on the stack. Hence the save command at the start of the function will have to modified accordingly.

As we have seen in the previous examples, 64 bytes are reserved on the stack for register window saving. Further, 4 bytes are now needed for a pointer to an address where a structure may be returned by the function. After that, 24 bytes are reserved by convention for the first six arguments. After that, more space can be reserved for local variables on the stack. The typical save command will now have to be modified as:

       .global sub_name
sub_name:
       save %sp, -(64 + 4 + 24 + local) & -8, %sp
Example: Consider the following C program:
int example(int a, int b, char c);
{
  int x, y;
  short ary[128];
  register int i, j;
  x = a + b;
  i = c + 64;
  ary[i] = c + a;
  y = x * a;
  j = x + i;
  return x + y;
}

int main()
{
  int r;
  r = example(3, 5, 4);
  printf("%d\n", r);        /* same as cin in C++ */
}
Let us write the assembly code corresponding to the following C program.
define(a_r, i0)    ! a_r in %i0
define(b_r, i1)    ! b_r in %i1
define(c_r, i2)    ! c_r in %i2
define(x_s, -4)   
define(y_s, -8)
define(ary_s, -264)  
define(i_r,  l0)
define(j_r, l1)

/* Save command for the  example function will be : 
   save %sp, (-64 - 4 - 24 - 264) & -8, %sp  */

        .global _example

_example: 
        save    %sp, -360, %sp
        add     %a_r, %b_r, %o0       !   x = a + b
        st      %o0, [%fp + x_s]
        add     %c_r, 64, %i_r        !   i = c + 64
        add     %a_r, %c_r, %o0       !   ary[i] = c + a
        sll     %i_r, 1, %o1
        add     %fp, ary_s, %o2
        sth     %o0, [%o1 + %o2]
        ld      [%fp + x_s], %o0      !   y = x * a
        call    .mul
        mov     %a_r, %o1
        st      %o0, [%fp + y_s]
        ld      [%fp + x_s], %o0      !   j = x + i
        add     %i_r, %o0, %j_r
        ld      [%fp + x_s], %o0      !   return x + y
        ld      [%fp + y_s], %o1
        ret
        restore %o0, %o1, %o0

Return Values

A subroutine that returns a value is called a function.

Value returned is stored in %o0 of the calling function, which is %i0 in the called function before the restore instruction is executed.

A function in C and C++ can also return a structure. These can be implemented by storing pointer to the structure on the stack.

Example:

struct point {
  int x, y;
};

struct point  zero()
{
  struct point local;
  local.x = 0;
  local.y = 0;
  return local;
}

int main()
{
  struct point x1, x2;
  x1 = zero();
  x2 = zero();
}
In this case, the function zero returns a structure, as can be seen from the code. The main function reserves space on the stack to store the entire structures x1 and x2. But at the time of return from function zero(), the entire structure is not returned, but only a pointer to the structure is returned back to the main function. We will not look at this program in further detail at this time.

Subroutines with many Arguments

Upto six arguments can be passed to another function without use of the stack. But if more than 6 arguments are required, then the remaining ones must be stored on the stack. e.g.

If we have a function call with 8 arguments as shown below:

 foo(1,2,3,4,5,6,7,8);
The function returns the sum of the 8 arguments as shown below:
int foo(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8)
{  
   return ( a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8) ; 
}
Here, we need extra space on the stack for the last two arguments. The corresponding assembly code is given below:
        ! code for the main function

	define(arg7_s, 4)
	define(arg8_s, 8)

        .global _main

_main:  save    %sp, -64, %sp
        add     %sp, -8, %sp          ! space for 2 args. on stack
        mov     8, %o0                ! load args in reverse
        st      %o0, [%sp + arg8_s]
        mov     7, %o0
        st      %o0, [%sp + arg7_s]
        mov     6, %o5
        mov     5, %o4
        mov     4, %o3
        mov     3, %o2
        mov     2, %o1
        call    _foo
        mov     1, %o0
        sub     %sp, -2 * 4 & -8, %sp   !release space on stack
        ret
        restore

        ! code for the subroutine foo

        define(a8_s, arg8_s)
        define(a7_s, arg7_s)
        define(a6_r, i5)
        define(a5_r, i4)
        define(a4_r, i3)
        define(a3_r, i2)
        define(a2_r, i1)
        define(a1_r, i0)

        .global foo

foo:    save    %sp, (-92 & -8), %sp
        ld        [%fp + a8_s], %o0       !the eighth argument
        ld        [%fp + a7_s], %o1       !the seventh argument
        add     %o0, %o1, %o0
        add     %a6_r, %o0, %o0           !the sixth argument
        add     %a5_r, %o0, %o0           !the fifth argument
        add     %a4_r, %o0, %o0           !the fourth argument
        add     %a3_r, %o0, %o0           !the third argument
        add     %a2_r, %o0, %o0           !the second argument
        ret
        restore %o0, %a1_r, %o0           !the first argument

For class 20 notes, click here

For more information, contact me at tvohra@mtu.edu