5.11 Quản lý biến cục bộ và gọi hàm sử dụng stack pointer và frame pointer
Thủ tục con có thể sử dụng stack để tính toán các biểu thức (5.8), thanh ghi sp có thể thay
đổi nên khó sử dụng thanh ghi này để quản lý biến cục bộ cố định. Để quản lý biến cục
bộ một cách dễ dàng, MIPS sử dụng thanh ghi fp làm địa chỉ nền cố định để quản lý biến
cục bộ trong quá trình thực thi.
Hình bên minh họa việc dùng thanh ghi fp để quản lý biến cục bộ với các giả sử sau:
• Thủ tục cha gọi thủ tục con;
• Thủ tục cha sử dụng các thanh ghi tạm $t0,
$t3 trước khi gọi thủ tục con;
• Thủ tục con sử dụng các thanh ghi $s0, $s3,
$s5;
• Thủ tục con gồm 4 biến cục bộ a, b, i, j
Các chú ý khi một thủ tục gọi một thủ tục con dùng stack pointer và frame pointer:
Gọi thủ tục con
(thực hiện bởi chương trình gọi):
1. Push vào stack các thanh ghi $t0-$t9 cần lưu giá trị. Thủ tục con có thể thay đổi các thanh
ghi này.
2. Gán giá trị vào các tham số của thủ tục con $a0-$a3.
3. Gọi thủ tục con sử dụng jal.
Phần đầu thủ tục con
(Trong thủ tục con):
4. Push $ra vào stack.
5. Push thanh ghi $fp của thủ tục gọi
6. Push vào stack các thanh ghi $s0-$s7 nếu thủ tục con này có thể thay đổi chúng.
7. Khởi tạo $fp = $sp – không gian cần cho biến cục bộ (4*số biến cục bộ).
8. Khởi tạo $sp = $fp
Thân thủ tục con:
9. Thủ tục con có thể thay đổi các thanh ghi T, A hoặc S (nếu như S đã được lưu ở bước 5).
10. Thủ tục con tham khảo đến biến cục bộ sử dụng offset($fp)
11. Thủ tục con tự do push, pop phần tử vào stack
12. Nếu thủ tục con này gọi thủ tục con khác thì nó phải tuân thủ các chú ý này.
Phần cuối thủ tục con
(Thực hiện trước khi trở về chương trình gọi):
13. Đưa các giá trị cần trả về vào $v0-$v1
14. $sp = $fp + không gian cần cho biến cục bộ
15. Pop ra khỏi stack (theo thứ tự ngược) các thanh ghi $s0-$s7 đã lưu ở bước 5.
16. Pop ra khỏi stack địa chỉ trở về $ra.
17. Pop ra khỏi stack địa chỉ trở về $ra
18. Trở về chương trình gọi sử dụng jr $ra.
Phục hồi điều khiển
sau khi trở về từ chương trình con (thực hiện bởi chương trình gọi):
19.
Pop ra khỏi stack (theo thứ tự ngươc) các thanh ghi $t0-$t9 đã được push vào stack ở
bước 1.
Thiết lập các tham số trong menu Simulator -> Settings:
Bare Machine OFF,
Allow Pseudo Instructions ON, Load Trap File ON, Delayed
Branches ON,
Delayed Loads ON, Mapped IO ON, Quiet OFF.
# main()
# {
# int a, b; // a: 0($fp), b: 4($fp)
# write("enter an int:")
# read( a );
# b = fact( a );
# write("factorial is:")
# print( b );
# }
.text
.globl
main
main:
addiu
$sp,$sp,-4
sw
$ra,0($sp)
# 1. Push return address
addiu
$sp,$sp,-4
sw
$fp,0($sp)
# 2. Push caller's
frame pointer
# 3. No S registers to push
addiu $fp,$sp,-8
# 4. $fp = $sp - space_for_variables
addu
$sp,$fp,$0
# 5. $sp = $fp
li
$v0,4
#
write("enter
an
int:")
la
$a0,prompt1
syscall
li
$v0,5
#
read(
a
)
syscall
#
subroutine
call
# 1. No T registers to push
addu
$a0,$v0,$0
# 2. Put argument into $a0
jal
fact
# 3.
Jump and link to subroutine
nop
#
return
from
subroutine
# 1. No T registers to restore
sw
$v0,4($fp)
# b = fact( a )
li
$v0,4
#
write("factorial
is:")
la
$a0,prompt2
syscall
lw
$a0,4($fp)
# print( b )
li
$v0,1
syscall
#
epilog
# 1. No return value
addu
$sp,$sp,8
# 2. $sp = $fp + space_for_variables
# 3.
No S registers to pop
lw
$fp,0($sp)
# 4. Pop $fp
lw
$ra,4($sp)
# 5. Pop $ra
addu
$sp,$sp,8
jr
$ra
#
return
to
OS
nop
.data
prompt1: .asciiz "enter an int:"
prompt2: .asciiz "factorial is:"
# int fact( int n )
# {
# if ( n <= 1 )
# return 1;
# else
# return n*fact(n-1);
# }
.text
.globl
fact
fact:
addiu $sp,$sp,-8
# adjust stack for 2 items
sw
$ra,0($sp)
# save return address
sw
$a0,4($sp)
#
save
argument
#addi
$t0,$0,2
slti
$t0,$a0,2
#
test for n < 2
beq
$t0,$0,else
addi
$v0,$0,1
# if so, result is 1
addiu $sp,$sp,8
# pop 2 items from stack
jr
$ra
#
and
return
nop
else:
addi
$a0,$a0,-1
# else decrement n
jal
fact
#
recursive
call
nop
lw
$a0,4($sp)
#
restore original n
lw
$ra,0($sp)
# and return address
addi
$sp,$sp,8
# pop 2 items from stack
mul
$v0,$v0,$a0
#
multiply to get result
jr
$ra
#
and
return
nop