To cover the standard procedure calling conventions for the SPARC.
After completing this lab, you will be able to write assembly language procedures that:
In most cases, you will not want to write entire programs in assembly language. Instead, you will want to write most of the program in a high-level language (like C) and only write a few procedures in assembly language--the procedures that cannot be easily optimized in the high-level language or that need to take advantage of special features provided by the machine.
In this lab, we complete our presentation of the SPARC application binary interface (ABI). The SPARC ABI is a set of conventions that are expected to be followed by all compilers and assembly language programmers. These conventions cover the uses of registers and the structure of the stack frame. If you follow the conventions specified by the SPARC ABI in your assembly language procedures, it will be possible to call your procedures from procedures written in high-level languages. You will also be able to call procedures written in high-level languages from your assembly language procedures.
In Lab 10 we covered the portion of the SPARC ABI that deals with optimized leaf procedures. In Lab 11 we covered the conventions related to register usage for procedures that are not implemented as optimized leaf procedures. In this lab we cover the conventions related to the allocation and structure of stack frames. Throughout this lab we will assume that we are not implementing an optimized leaf procedure.
The stack pointer is stored in register %o6. This register can also be referenced using the alias %sp. Due to the overlap of register windows, the stack pointer for the calling procedure is always available in register %i6. In SPARC terminology, the previous stack pointer is called the frame pointer and can be accessed using the alias %fp. The stack grows from addresses with larger numbers to addresses with smaller numbers. As such, allocation of a stack frame is implemented by subtracting a value from the current stack pointer (actually, this usually done by adding a negative number to the stack pointer).
Figure 12.1: Stack frame allocation in the SPARC ABI
As we noted in the previous two labs, registers %o0-%o5 are used for the first six parameters passed to a procedure. If a procedure has more than 6 parameters, the remaining parameters are passed on the stack. SPARC procedures do not push parameters (beyond the sixth parameter) onto the procedure call stack. Instead, they allocate space in their stack frame for the parameters and copy parameters into this space. This means that the called procedure will find its parameters (beyond the sixth) in the caller's stack frame. The called procedure can access these parameters using the frame pointer (%fp) with positive offsets.
As a minimum, every procedure that is not implemented as an optimized leaf procedure (i.e., any procedure that executes a save instruction) must allocate a stack frame of at least 64 bytes. This space will be used to store the input and local registers (%i0-%i7 and %l0-%l7) allocated by this procedure should you run out of register windows in a later procedure call.
Every non-leaf procedure must allocate an additional 7 words (28 bytes) in its stack frame. The first word of this space is used to store a ``hidden'' parameter. The hidden parameter is used for procedures that return structured values. Procedures that return simple values use %i0 (the caller's %o0) to return the result. However, if a procedure returns a structured value, the result may not fit in a register. In this case, the calling procedure must allocate space for the return value (probably in its stack frame). The calling procedure then puts the address of this space into the hidden parameter before making the call.
The remaining 6 words can be used by the called procedure to store the first six arguments (the ones passed in %o0-%o5). In most cases, the called procedure will be able to access these parameters in the registers %i0-%i5 and will not need to store them in the caller's stack frame. However, if the called procedure needs to take the address of a parameter, it needs to store the parameter into memory (you can't take the address of a register).
In addition to the regions that we have discussed, a procedure may allocate additional stack space for: alignment (the stack pointer should always be a multiple of 8), outgoing parameters (beyond the sixth parameter), automatic local arrays and other automatic local variables that don't fit in the local registers %l0-%l7, temporaries, and floating point registers. Figure 12.2 illustrates the organization of a SPARC stack frame.
Figure 12.2: Stack frame organization
int add7( int p1, int p2, int p3, int p4, int p5, int p6, int p7 ) { return p1 + p2 + p3 + p4 + p5 + p6 + p7; }
In this case, the parameters p1-p6 will be in registers %i0-%i5. The parameter p7 will be in the caller's stack frame at offset 92 (that is, %fp + 92).
.text add7: save %sp, -64, %sp ! this is a leaf procedureld [%fp+92], %l0 ! we'll eventually need p7
add %i0, %i1, %i0 ! add in p2 add %i0, %i2, %i0 ! add in p3 add %i0, %i3, %i0 ! add in p4 add %i0, %i4, %i0 ! add in p5 add %i0, %i5, %i0 ! add in p6
ret restore %i0, %l0, %o0 ! add in p7
int test( int x1, int x2 ) { int l1, l2;l1 = x1 + x2; l2 = x2 - x1; return add7( x1, x2, l1, l2, l1+l2, l2-l1, l2+l2 ); }
.text test: save %sp, -(92+4), %sp ! allocate the minimum stack frame ! (includes a 4 byte ``alignment'' pad)add %i0, %i1, %l0 ! l1 = x1 + x2; sub %i1, %i0, %l1 ! l2 = x2 - x1;
mov %i0, %o0 ! first parameter mov %i1, %o1 ! second parameter mov %l0, %o2 ! third parameter mov %l1, %o3 ! fourth parameter add %l0, %l1, %o4 ! fifth parameter sub %l1, %l0, %o5 ! sixth parameter
add %l1, %l1, %l2 ! temp = l2+l2 call add7 st %l2, [%sp+92] ! seventh parameter (delay slot)
ret restore %g0, %o0, %o0 ! return the result to caller's %o0
It is also common to access local variables stored in the stack using negative offsets from the frame pointer.
void read10( ) { int i; int a[10];for( i = 0 ; i < 10 ; i++ ) { a[i] = read_int(); }
for( i = 9 ; i >= 0 ; i- ) { write_int( a[i] ); } }
In this case, we will use %l0 for i (scaled by 4) and the array a will be stored in the local space starting at %fp-40.
.text add7: save %sp, -(92+4*10+4), %sp ! we need 40 words for the arraysub %fp, 40, %l1 ! l1 points to the start of the array
clr %l0 ! i = 0
top1: call read_int nop st %o0, [%l1+%l0] ! a[i] = read_int();
inc %l0, 4 ! increment i += 4 cmp %l0, 40 ! i < 10*4 bl top1 nop
mov 36, %l0 ! i = 9*4 top2: ld [%l1+%l0], %o0 ! write_int( a[i] ) call write_int nop
deccc %l0, 4 ! i -= 4 bge top2 ! i >= 0 nop
ret restore