To introduce register windows.
After completing this lab, you will be able to use:
In this lab we introduce a more general procedure calling mechanism that uses overlapping register windows. In particular, we introduce:
To this point, we have used the %r names for the integer registers. From this point on, we will use the alternate names for these registers. The alternate names are shown in Table 11.1.
Table 11.1: Names for the integer registers
The alternate names reflect the uses of the registers in procedures based on register windows. The global registers (%g0-%g7) are shared by all procedures. The output registers (%o0-%o7) hold the values for parameters when calling another procedure. That is, they are outputs from the current procedure. The local registers (%l0-%l7) hold local values for the procedure. The input registers (%i0-%i7) hold the values of the parameters passed to the procedure. That is, they are inputs for the current procedure.
When you consider the relationship between the output and input registers, the trick is to make the caller's output registers the same as the called procedure's input registers. On the SPARC, this is done using overlapping register windows. Figure 11.1 illustrates the principle of overlapping register windows.
Figure 11.1: Overlapping register windows
All procedures share the global registers. The remaining registers, %o0-%o7, %l0-%l7, and %i0-%i7, constitute the register window. When a procedure starts execution, it allocates a set of 16 registers. The 16 new registers are used for the output and local registers. The input registers are overlapped with the caller's output registers. Figure 11.2 shows the details of overlapping of register windows, emphasizing the names for the registers.
Figure 11.2: Overlapping register names
In addition to the register names we have discussed, Figure 11.2 introduces two new names: %sp and %fp. The first of these, %sp, denotes the stack pointer. In an assembly language program, %sp is simply another name for %o6. Similarly, %fp denotes the frame pointer and is simply another name for %i6. We will discuss the special uses of these registers in the next lab when we consider stack frame organization.
An implementation of the SPARC integer unit can have between 40 and 520 integer registers: every implementation of the SPARC has 8 global registers along with 2 to 32 register sets and each register set has 16 registers ( and ). The number of registers window sets, NWINDOWS, is implementation dependent.
The current window pointer, CWP, identifies the current register window. The CWP is stored in the least significant five bits of the processor status register, PSR. The save operation allocates a new register set by decrementing the CWP. The restore operation deallocates a register set by decrementing the CWP. The names save and restore refer to the fact that the called procedure is ``saving'' and ``restoring'' the registers in the caller's register window.
To this point, we have illustrated the register sets as a linear array; however, the save and restore operations allocate and deallocate register sets in a circular fashion. Figure 11.3 shows six register sets arranged in a circular fashion with clockwise numbering.
Figure 11.3: A circular arrangement of register windows
Table 11.2 summarizes the SPARC save and restore operations. SPARC assemblers provide two instruction formats for save instructions and three formats for restore instructions. Both formats for the save instruction and the first two formats for the restore instruction use three explicit operands--two source operands and a destination operand. In the first format, both source operands are in registers. In the second format, one source operand is in a register, the other is specified using a small constant value. This constant may be positive or negative; however its 2's complement representation must fit in 13 bits. Its important to note, for both the save and restore operations, that the destination register is in the register window after the CWP has been modified. The restore instruction also has a format that doesn't have any operands.
Table 11.2: Saving and restoring register windows
The operands of the save instruction are commonly used to allocate space for a stack frame. We'll discuss the stack and stack frames at greater length in the next lab. However, to use the save and restore instructions correctly, you must allocate and maintain a runtime stack.
The runtime stack grows from higher to lower addresses. The stack pointer (%sp, aka %o6, aka %r14) should always be aligned on an address that is a multiple of 8. For now, we'll use the operands of the save instruction to subtract 96 ( ) from the stack pointer (%sp). The reason for this will be clearer in the next lab.
In most cases, you will simply use the restore instruction with no operands to restore the caller's register window. However, you can occasionally use other versions of this instruction to return a value from a procedure.
From the caller's perspective using register windows does not change anything about how it interacts with the called procedure. The caller still puts the outgoing parameters in registers %o0-%o5 (%r8-%r13) and expects the result in %o0 (%r8). Moreover, the caller may assume that registers %g0, %g2-%g7, %o6 (%sp), %l0-%l7, and %i0-%i7 will not be altered by the called procedure. Finally, the caller uses the call instruction, introduced in the previous lab, to transfer control to the called procedure.
From the perspective of the called procedure, things have changed quite a bit. First, the called procedure needs to save to the caller's register window. Second, the called procedure can modify the registers %g1, %i0-%i5, %l0-%l7, and %o0-%o6. (You could also modify %i7; however, %i7 holds the return address, so it's not a good idea to change it.) Third, the called procedure needs to restore the caller's register window just before it returns.
The instruction used to restore the caller's register window is usually put in the branch delay slot of the instruction used to return control to the caller. Because the caller's register window has not been restored when the called procedure issues the return instruction, the return instruction needs to use %i7 in calculating the return address instead of %o7 (%r15). SPARC assemblers provide the ret instruction for this purpose. Table 11.3 summarizes the call and return instructions.
Table 11.3: The SPARC call and ret operations
.text ! pr_str - print a null terminated string ! ! Temporaries: %i0 - pointer to string ! %o0 - character to be printed ! pr_str: save %sp, -96, %sp ! PROLOGUE - save the current window and ! allocate the minimum stack framepr_lp: ldub [%i0], %o0 ! load character cmp %o0, 0 ! check for null be pr_dn nop call pr_char ! print character nop ba pr_lp inc %i0 ! increment the pointer (branch delay)
pr_dn: ret ! EPILOGUE return from procedure and restore ! restore old window; no return value
.data str: .asciz "Hello, World!$$n".align 8 stack_top: . = . + 2048 stack_bot:
.text start: set stack_bot-96, %sp ! initialize the stack and allocate ! the minimum stack frame set str, %o0 ! initialize pointer to str call pr_str ! call print string nop ! (branch delay) end: ta 0
! fib - calculate the Nth Fibonacci number ! ! fib(N) = fib(N-1) + fib(N-2) ! fib(0) = fib(1) = 1fib: save %sp, -96, %sp ! PROLOGUE
cmp %i0, 1 bg fib_call ! call recursively nop
ret ! EPILOGUE restore %g0, 1, %o0 ! return 1
fib_call: call fib ! call with N-1 sub %i0, 1, %o0 ! (branch delay) mov %o0, %l0 ! %l0 = fib(N-1)
call fib ! call with N-2 sub %i0, 2, %o0 ! (branch delay)
ret ! EPILOGUE restore %l0, %o0, %o0 ! return fib(N-1) + fib(N-2)
Both the save and restore operations can generate exceptions. Before the CWP is modified, the bit in the WIM corresponding to the new value for the CWP is tested. If the bit in the WIM is 1, an exception is generated. For a save instruction, this causes a window overflow trap. For a restore instruction, this causes a window underflow trap.
These traps are normally handled by the operating system and are transparent to the application programmer. In tkisem these traps are handled by the rom code. We will discuss the code used to handle these traps in Lab 17.
We have introduced three new instructions in this lab: save, restore, and ret. The save and restore instructions are encoded as data manipulation instructions (the instruction formats are shown in Figure 9.3). The restore instruction with no operands is actually a synthetic instruction in which all of the operands are %g0. Table 11.4 summarizes the encodings of the op field for the save and restore instructions.
Table 11.4: Encoding op in save and restore instructions
Like the retl instruction, the ret instruction is a synthetic instruction, based on the jmpl instruction. The ret instruction is translated to jmpl %i7+8, %g0.
This lab presents a more general mechanism for procedures on the SPARC. Register windows provide easy access to a large collection of registers and can reduce the need to save registers in memory. While this mechanism has many advantages, there are several disadvantages to keep in mind. First, this mechanism only provides six registers for parameters. If you write a procedure with more than six parameters, you will need to to use the stack for any parameters beyond six. Second, most implementations only have 7 or 8 register sets. If your call sequence gets deeper than NWINDOWS (as it probably will in most recursive procedures), you are again forced to use the stack.
void draw_bitmap(char* bits, int w, int h, int x, int y)
The draw_bitmap procedure should make use of the draw_pixel procedure (from the previous lab).
void tile_bitmap(char* bits, int w, int h)
Don't forget to clip the border tiles appropriately.