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 subr ! at the time of call nop ! delay slot
jmpl %o0, %o7 ! same as call %o0Here, 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
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, 4In 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, %spExample: 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
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.
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