MIPS_primery_zadach / dandamudi05gtr guide risc processors programmers engineers
.pdf
196 |
Guide to RISC Processors |
Temporary Storage of Data The stack can be used as a scratchpad to store data on a temporary basis. For example, suppose that a procedure (say, main) calls another procedure test, which uses some of the callee-save registers (say, $s0, $s1, and $s2). Before using these registers, test must save the contents of these registers so that it can restore them before returning control to main. The stack can be used for this purpose as shown below:
test:
#save $s0, $s1, and $s2 registers on the stack
sub |
$sp,$sp,12 # reserve 12 bytes of stack |
sw |
$s0,0($sp) # save registers |
sw |
$s1,4($sp) |
sw |
$s2,8($sp) |
# now these three registers can be used
. . .
. . .
#restore $s0, $s1, and $s2 registers from the stack
lw |
$s0,0($sp) |
lw |
$s1,4($sp) |
lw |
$s2,8($sp) |
add |
$sp,$sp,12 |
As demonstrated in this example, the stack is frequently used as a scratchpad to save and restore registers. The necessity often arises when we need to free up a set of registers so they can be used by the current code. This is often the case with nonleaf procedures as our later examples show.
Transfer of Control The previous discussion concentrated on how we, as programmers, can use the stack to store data temporarily. The stack is also used to transfer control. In particular, when a nonleaf procedure is called, the return address of the instruction is stored on the stack so that the control can be transferred back to the calling program. We give an example of a nonleaf procedure later (see Example 11.2 on page 202). Also see Chapter 16 for a related discussion.
Parameter Passing Another important use of the stack is to act as a medium to pass parameters to the called procedure. The stack is extensively used by high-level languages to pass parameters. This is what we discuss next with an example.
Parameter Passing via the Stack
To demonstrate how we can use the stack to pass parameters, we redo the sum example discussed before (see page 189). But this time, we pass the three integers via the stack. The program computes the sum of three integers. The main procedure requests three integers from the user and passes them to the find_sum procedure via the stack. As in the previous sum procedure, it returns the sum in $v0.
Chapter 11 • Procedures and the Stack |
197 |
The input numbers are requested using a read loop consisting of lines 27–33 (see Program 11.2). We use $t0 to maintain the loop count. Because we are reading three integers, we initialize $t0 to 3 (line 26). The system call on line 29 places the input number in $v0. The instructions on lines 30 and 31 push this value onto the stack. Then, we decrement the loop count (line 32) and jump back to read more if the loop count is not zero. The remainder of the main procedure is similar to that in Program 11.1.
Program 11.2 The sum procedure uses stack-based parameter passing
1: # Find the sum of three numbers |
SUM_STACK.ASM |
2:#
3:# Objective: Finds the sum of three integers.
4: |
# |
To demonstrate stack-based |
5: |
# |
parameter passing. |
6:# Input: Requests three numbers from the user.
7:# Output: Outputs the sum.
8:#
9:################### Data segment ###################
10:.data
11:prompt:
12: |
.asciiz |
"Please enter three numbers: \n" |
13:sum_msg:
14: |
.asciiz |
"The sum is: " |
15:newline:
16: .asciiz "\n" 17:
18:################### Code segment ###################
19:.text
20:.globl main
21:main:
22: |
la |
$a0,prompt |
# prompt user for input |
23:li $v0,4
24:syscall
26: |
li |
$t0,3 |
# loop count = 3 |
27: |
read_more: |
|
|
28: |
li |
$v0,5 |
# read 1st number into $a1 |
29: |
syscall |
# number is in $v0 |
|
30: |
sub |
$sp,$sp,4 |
# reserve 4 bytes on stack |
31: |
sw |
$v0,($sp) |
# store the number on stack |
32: |
sub |
$t0,$t0,1 |
# decrement loop count |
33: |
bnez |
$t0,read_more |
# if not zero, read more |
34: |
|
|
|
35:jal find_sum
36:move $t0,$v0
198 |
|
|
Guide to RISC Processors |
38: |
la |
$a0,sum_msg |
# write sum message |
39:li $v0,4
40:syscall
42: |
move $a0,$t0 |
# output sum |
43:li $v0,1
44:syscall
46: la $a0,newline # write newline
47:li $v0,4
48:syscall
50: |
li |
$v0,10 |
# exit |
51: |
syscall |
|
|
52: |
|
|
|
53:#------------------------------------------------
54:# FIND_SUM procedure receives three integers
55:# via the stack and returns their sum in $v0.
56:#------------------------------------------------
57:find_sum:
58: |
li |
$v0,0 |
# |
$v0 = 0 (sum in $v0) |
59: |
li |
$t0,3 |
# |
loop iteration count |
60:sum_loop:
61: |
lw |
$t1,($sp) |
# pop into $t1 |
62: |
add |
$sp,$sp,4 |
# |
63:add $v0,$v0,$t1
64:sub $t0,$t0,1
65:bnez $t0,sum_loop
66: jr $ra
We also use a loop to add the numbers. This sum loop consists of lines 60–65. Because we want to return the sum in $v0, we use this register to keep the sum. It is initialized to zero on line 58. As in the main procedure, we use $t0 for the loop count. The two instructions on lines 61 and 62 implement the stack pop operation. The value in $t1 is added to our sum in $v0. At the end of the loop, the final sum is in $v0 as in the previous sum example. Notice that at the end of the sum loop, the stack is cleared of the three values passed onto the procedure.
One main difference between this program and the previous one is that we can use a loop to read the numbers and to add them up. When we use registers, this is not possible. To understand the flexibility of the stack-based parameter passing, consider adding 20 numbers instead of 3. In the stack version, all we have to do is change the loop count initialization from 3 to 20 (on lines 26 and 59).
Chapter 11 • Procedures and the Stack |
199 |
Preserving Calling Procedure State
It is important to preserve the contents of the registers across a procedure call. The necessity for this is illustrated by the following code.
. . .
li |
$s0,50 |
repeat: |
|
call |
compute |
|
. . . |
subu |
$s0,$s0,1 |
bnez |
$s0,repeat |
|
. . . |
The code invokes the compute procedure 50 times. The $s0 register maintains the number of remaining iterations. Now suppose that the compute procedure uses $s0 during its execution. Then, when compute returns control to the calling program, $s0 would have changed, and the program logic would be incorrect. To preserve the contents of $s0, it should be saved. Of course, we use the stack for this purpose.
Which Registers Should Be Saved?
The answer to this question is simple: save those registers that are used by the calling procedure but changed by the called procedure. This leads to the following question. Which procedure, the calling or the called, should save the registers?
If the calling procedure is to save the necessary registers, it needs to know the registers used by the called procedure. This causes two serious difficulties:
1.Program maintenance would be difficult because, if the called procedure were modified later on and a different set of registers used, every procedure that calls this procedure would have to be modified.
2.Programs tend to be longer because if a procedure is called several times, we have to include the instructions to save and restore the registers each time the procedure is called.
For these reasons, we prefer to make the called procedure responsible for saving the registers that it uses and restoring them before returning to the calling procedure. This also conforms to modular program design principles.
This strategy works fine from the logical standpoint but may not be the most efficient one. To see why this might be the case, assume that the called procedure uses 10 registers. However, none of these registers is used by the calling procedure. In this case, we would be wasting resources and time in saving and restoring these 10 registers. To solve this problem, MIPS divides the registers into two groups:
1.Caller-save registers: As the name implies, these registers are saved by the caller. In MIPS, $t0 to $t9 registers are used as caller-save registers. These registers
200 |
Guide to RISC Processors |
can be overwritten by the called procedure without any concern. If the caller keeps something useful in these registers, it has to take care of preserving their contents across procedure calls.
2.Callee-save registers: These registers are saved by the called procedure. In MIPS, registers $s0 to $s8 are used as callee-save registers. These registers, if used by the called procedure, must be preserved by the called procedure. This is often done by pushing these registers onto the stack and restoring them before returning from the procedure.
Illustrative Examples
We have looked at some simple procedure examples. Now we give some more examples to further illustrate the concepts discussed here.
Example 11.1 Compute Fibonacci function.
The Fibonacci sequence of numbers is defined as
fib(1) = 1, fib(2) = 1,
fib(n) = fib(n − 1) + fib(n − 2) for n > 2.
In other words, the first two numbers in the Fibonacci sequence are 1. The subsequent numbers are obtained by adding the previous two numbers in the sequence. Thus,
1, 1, 2, 3, 5, 8, 13, 21, 34, . . .
is the Fibonacci sequence of numbers.
The program shown in Program 11.3 requests a number n > 0 and outputs fib(n). For example, if n = 9, the program outputs 34. If n ≤ 0, an error message is displayed and the user is requested to enter another valid number (lines 33–37). Thus, we always call the Fibonacci procedure with n > 0.
Program 11.3 An example to compute Fibonacci number
1: # Computes Fibonacci number |
FIB_LOOP.ASM |
2:#
3:# Objective: Computes the Fibonacci number.
4: # |
Uses iteration. |
5:# Input: Requests a number n.
6:# Output: Outputs Fibonacci number fib(n).
7:#
8:# $a0 - number n is passed via this register
9:# $v0 - fib(n) is returned
10:#
Chapter 11 • Procedures and the Stack |
201 |
11:################### Data segment ###################
12:.data
13:prompt:
14: |
.asciiz |
"Please enter a number n>0: \n" |
15:error_prompt:
16: |
.asciiz |
"Not a valid number!\n" |
17:out_msg:
18: |
.asciiz |
"Fib(n) = " |
19:newline:
20: |
.asciiz |
"\n" |
21: |
|
|
22:################### Code segment ###################
23:.text
24:.globl main
25:main:
26: |
la |
$a0,prompt |
# prompt user for input |
27:li $v0,4
28:syscall
30: |
li |
$v0,5 |
# read n into $v0 |
|
|
31: |
syscall |
|
|
|
|
32: |
|
|
|
|
|
33: |
bgtz |
$v0,number_OK |
# |
number is OK if |
n>0 |
34: |
la |
$a0,error_prompt |
# |
output error message |
|
35:li $v0,4
36:syscall
37: |
b |
main |
38: |
|
|
39:number_OK:
40:move $a0,$v0
41: |
jal |
find_fib |
# fib(n) returned in $v0 |
42: |
move |
$t0,$v0 |
|
43: |
|
|
|
44: |
la |
$a0,out_msg |
# write output message |
45:li $v0,4
46:syscall
48: |
move $a0,$t0 |
# output fib(n) |
49:li $v0,1
50:syscall
52: la $a0,newline # write newline
53:li $v0,4
54:syscall
56: |
li |
$v0,10 |
# exit |
57:syscall
202 |
Guide to RISC Processors |
58:
59:#------------------------------------------------
60:# FIND_FIB receives an integer n>0 in $a0
61:# and returns fib(n) in $v0
62:# $t0: holds the second last fib value
63:# $v0: holds the last fib value
64:# $t1: used to compute the next fib value
65:#------------------------------------------------
66:find_fib:
67: |
li |
$v0,1 |
# if n = |
1 |
or n = 2 |
68: |
ble |
$a0,2,fib_done # return |
1 |
|
|
69: |
|
|
|
|
|
70:# last fib value is in $v0
71: |
li |
$t0,1 |
# $t0 = second last fib value |
72: |
|
|
|
73:loop1:
74: |
add $t1,$t0,$v0 |
# compute the next fib value |
75:move $t0,$v0 # shift the last & 2nd last
76: |
move |
$v0,$t1 |
# |
fib numbers |
77: |
sub |
$a0,$a0,1 |
# n = n-1 |
|
78: |
bgt |
$a0,2,loop1 |
# loop if n>2 |
|
79: |
|
|
|
|
80:fib_done:
81: jr $ra
The find_fib procedure returns 1 for n ≤ 2. For higher n values, it computes the Fibonacci number using the loop (lines 73–78). Notice that we use add, rather than addu, so that for large n, arithmetic overflow can be generated. For example, if n = 50, we see the following error message from SPIM.
Exception 12 [Arithmetic overflow] occurred and ignored
In this example, we have used a loop to compute fib(n). In Chapter 16, we rewrite this example using recursion.
Example 11.2 Finds the range of three numbers.
This is a simple program to explain the basics of nonleaf procedures in the MIPS assembly language. The objective is to find the range of three numbers. For example, if the three numbers are 5, 10, and 12, the range is computed as 12 − 5 = 7.
The main program requests three integers and passes them to find_range procedure. It in turn calls two procedures: find_min and find_max. Each procedure returns a value: minimum or maximum. Registers are used for parameter passing as well as to return the result. Registers $a1, $a2, and $a3 are used to pass the three integers. Each procedure returns its result in $v0.
Chapter 11 • Procedures and the Stack |
203 |
It is important to notice the difference between find_range and find_max or find_min procedures. Because the find_range procedure is a nonleaf procedure, we need to save the return address in $ra before invoking other procedures. We do this on lines 63 and 64. We restore the $ra register on lines 71 and 72. The two leaf procedures are simple and straightforward to understand.
Program 11.4 A simple nonleaf procedure example
1: # Finds the range of three numbers |
RANGE.ASM |
2:#
3:# Objective: Finds the range of three integers. To
4: # |
demonstrate writing nonleaf procedures. |
5:# Input: Requests three numbers.
6:# Output: Outputs the range.
7:#
8:# $a0, $a1, $a2 - three numbers are passed
9:# $v0 - range is returned
10:#
11:################### Data segment #######################
12:.data
13:prompt:
14: |
.asciiz |
"Please enter three numbers: \n" |
15:range_msg:
16: |
.asciiz |
"The range is: " |
17:newline:
18: .asciiz "\n" 19:
20:################### Code segment #######################
21:.text
22:.globl main
23:main:
24: |
la |
$a0,prompt |
# prompt user for input |
25:li $v0,4
26:syscall
28: |
li |
$v0,5 |
# read 1st number into $a0 |
29:syscall
30:move $a0,$v0
32: |
li |
$v0,5 |
# read 2nd number into $a1 |
33:syscall
34:move $a1,$v0
36: |
li |
$v0,5 |
# read 3rd number into $a2 |
37:syscall
38:move $a2,$v0
204 |
Guide to RISC Processors |
39:
40:jal find_range
41:move $t0,$v0
43: |
la |
$a0,range_msg |
# write range message |
44:li $v0,4
45:syscall
47: |
move $a0,$t0 |
# output range |
48:li $v0,1
49:syscall
51: |
la |
$a0,newline |
# write newline |
52:li $v0,4
53:syscall
55: |
li |
$v0,10 |
# exit |
56: |
syscall |
|
|
57: |
|
|
|
58:#--------------------------------------------------------
59:# FIND_RANGE receives three integers in $a0, $a1, and $a2
60:# and returns the range in $v0
61:#--------------------------------------------------------
62:find_range:
63: |
sub |
$sp,$sp,4 |
# save $ra |
64: |
sw |
$ra,0($sp) |
|
65: |
|
|
|
66:jal find_min
67:move $t0,$v0
68:jal find_max
69:sub $v0,$v0,$t0
71: |
lw |
$ra,0($sp) |
# restore $ra |
72:add $sp,$sp,4
73:jr $ra
74:
75:#--------------------------------------------------------
76:# FIND_MIN receives three integers in $a0, $a1, and $a2
77:# and returns the minimum of the three in $v0
78:#--------------------------------------------------------
79:find_min:
80:move $v0,$a0
81:ble $v0,$a1,min_skip_a1
82:move $v0,$a1
83:min_skip_a1:
84:ble $v0,$a2,min_skip_a2
85:move $v0,$a2
Chapter 11 • Procedures and the Stack |
205 |
86:min_skip_a2:
87:jr $ra
89:#--------------------------------------------------------
90:# FIND_MAX receives three integers in $a0, $a1, and $a2
91:# and returns the maximum of the three in $v0
92:#--------------------------------------------------------
93:find_max:
94:move $v0,$a0
95:bge $v0,$a1,max_skip_a1
96:move $v0,$a1
97:max_skip_a1:
98:bge $v0,$a2,max_skip_a2
99:move $v0,$a2
100:max_skip_a2:
101:jr $ra
Example 11.3 Reverses a given string.
This example performs string reversal. For example, if the input string is pals, it gives slap as the output string. We do this reversal in place (i.e., within the input string array). The algorithm is simple: we maintain two pointers front and back. Initially front and back point to the first and last characters of the string, respectively. After exchanging these two characters, front is advanced by one character and back is decremented by one to point to the previous character. We exchange the characters pointed to by these pointers. We repeat this process until front ≥ back. The algorithm is summarized below:
string_reverse(string) front := 0
back := 0
while ((string[back] =linefeed) AND (string[back] =NULL)) back := back + 1
end while
back := back − 1 while (front < back)
string[front] string[back] {Exchange the two characters} end while
end string_reverse
Program 11.5 uses this algorithm to reverse a string. user for a string and passes it to the string_reverse the string reverse procedure, it prints the reversed string.
The main program prompts the procedure. After returning from
