Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

MIPS_primery_zadach / dandamudi05gtr guide risc processors programmers engineers

.pdf
Скачиваний:
81
Добавлен:
11.05.2015
Размер:
1.39 Mб
Скачать

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

Соседние файлы в папке MIPS_primery_zadach