MIPS 中具有嵌套调用的子程序
Subroutines with nested calls in MIPS
此程序旨在将字符串中的所有小写字母替换为字符 *
.
我遇到的问题是子程序的嵌套调用。 IE。一些相同的 $t
和 $a
寄存器正在不同的子程序中使用。因此,当在另一个子例程中调用一个子例程时,caller 子例程的寄存器会被弄乱。
.data
str: .asciiz "WindOnTheHill"
.text
la $a0, str # start of the string
li $a1, '*'
jal ReplaceAllLower
#la $a0, str # start of the string
jal PrintStr
jal Exit
ReplaceAllLower:
# backup return address
addi $sp, $sp, -12 # create space for 3 words
# (3*4=12 bytes) on the stack
# (push) for $ra
sw $ra, 0($sp) # backup return address $ra
# protect arguments from change
sw $a0, 4($sp) # backup string address
sw $a1, 8($sp) # backup char
# get string length
jal StrLen # obtain string length
move $t0, $v0 # backup string length
# retrieve argument values
lw $a1, 8($sp) # restore char
lw $a0, 4($sp) # restore string address
move $t1, $a0 # obtain string address
move $t2, $a1 # obtain char
li $t3, 0 # loop counter
while:
bgt $t3, $t0, end_while
jal IsLower
beq $t0, 1, lower_case
j not_lower_case
lower_case:
sb $t2, ($a0)
not_lower_case:
addi $a0, $a0, 1 # increment address
addi $t3, $t3, 1 # increment loop counter
j while
end_while:
move $a0, $t1
# restore stack
lw $ra, 0($sp) # restore $ra
addi $sp, $sp, 16 # return the space on the stack(pop)
# return
jr $ra
IsLower:
lb $t0, ($a0) # obtain the character
li $t1, 97 # 'a' - character
li $t2, 122 # 'z' - character
bge $t0, $t1, con1_fulfilled #bigger tha or equal to 0
j con1_not_fulfilled
con1_fulfilled:
ble $t0, $t2, con2_fullfilled #less than or equal to 9
j con2_not_fulfilled
con2_fullfilled:
li $v0, 1
j return
con1_not_fulfilled:
con2_not_fulfilled:
li $v0, 0
return:
# return
jr $ra
StrLen:
move $a1, $a0 # start of string
# run a loop
li $t0, '[=10=]' # null character
li $t1, 0 # prepare the counter
start_loop:
lb $v0, ($a0) # obtain the 1st character
beq $v0, $t0, end_loop # exit loop if '[=10=]'-char found
addi $t1, $t1, 1 # increment counter
addi $a0, $a0, 1 # increment address
j start_loop # iterate again
end_loop:
move $a0, $a1 #restore string address
move $v0, $t1 # return value
# return
jr $ra
PrintStr:
li $v0, 4
syscall
# return
jr $ra
Exit:
# push $s0 on stack
addi $sp, $sp, -4 # create 4-bytes on the stack
sw $s0, ($sp) # cpy $s0 to stack
#terminate program
li $v0, 10
syscall
# free stack
addi $sp, $sp, 4
# return
jr $ra
注:暂时先不要关注算法
所以,我的问题是,
我应该使用什么技术来解决这个问题,因为很难事先知道将来会调用哪个子程序(库可以随着时间的推移自行扩展)?
一些约定是必需的,如果所有子程序都遵守它们,就不会有任何问题,例如寄存器被调用过程破坏。
对于 MIPS,普遍接受的调用约定是:
* 寄存器 $t0-7
是 "temporary" 并且可以在没有预防措施的情况下使用。如果一个过程想要在函数调用中保留其中的一些,则它有责任保存它们 ("caller saved")。
* 寄存器 $s0-7
("saved registers") 不能在没有预防的情况下使用。如果一个过程想要使用其中的一些,它必须在使用前保存它们并在 return ("callee saved")
时恢复它们的值
调用约定还有其他重要方面,例如在寄存器 $a0-$a3
中传递第一个参数,使用 $v0-$v1
作为 return 值等。它们还精确某些寄存器的作用,例如堆栈指针 (sp
) 或帧指针 (fp
)。 this document 是一个很好的总结,但您可以在互联网上轻松找到更多详细信息。
保存寄存器是用 call stack 完成的。它是一种保存所有保留信息的数据结构。
在函数的开始,必须在栈中预留一些space用于所有需要保留的信息。然后将要使用的寄存器s0-s7
保存在栈中。如果函数是非终端函数(即调用另一个函数),returned 地址也会被保存。
在调用函数之前,将需要保存的临时或参数寄存器($t0-7
或$a0-3
)写入堆栈。如果需要,参数被写入寄存器 $a0-3
或堆栈。并调用该函数。
调用函数return后,保留的临时寄存器被恢复
和函数returns之前,需要恢复保存的$s0-7
寄存器和return地址寄存器($ra
), 堆栈 space 被释放并且调用 jr $ra
.
如果所有程序都遵守这些调用约定,就不会有任何问题。编译器尊重这些约定,但它们依赖于 OS 和体系结构。
此程序旨在将字符串中的所有小写字母替换为字符 *
.
我遇到的问题是子程序的嵌套调用。 IE。一些相同的 $t
和 $a
寄存器正在不同的子程序中使用。因此,当在另一个子例程中调用一个子例程时,caller 子例程的寄存器会被弄乱。
.data
str: .asciiz "WindOnTheHill"
.text
la $a0, str # start of the string
li $a1, '*'
jal ReplaceAllLower
#la $a0, str # start of the string
jal PrintStr
jal Exit
ReplaceAllLower:
# backup return address
addi $sp, $sp, -12 # create space for 3 words
# (3*4=12 bytes) on the stack
# (push) for $ra
sw $ra, 0($sp) # backup return address $ra
# protect arguments from change
sw $a0, 4($sp) # backup string address
sw $a1, 8($sp) # backup char
# get string length
jal StrLen # obtain string length
move $t0, $v0 # backup string length
# retrieve argument values
lw $a1, 8($sp) # restore char
lw $a0, 4($sp) # restore string address
move $t1, $a0 # obtain string address
move $t2, $a1 # obtain char
li $t3, 0 # loop counter
while:
bgt $t3, $t0, end_while
jal IsLower
beq $t0, 1, lower_case
j not_lower_case
lower_case:
sb $t2, ($a0)
not_lower_case:
addi $a0, $a0, 1 # increment address
addi $t3, $t3, 1 # increment loop counter
j while
end_while:
move $a0, $t1
# restore stack
lw $ra, 0($sp) # restore $ra
addi $sp, $sp, 16 # return the space on the stack(pop)
# return
jr $ra
IsLower:
lb $t0, ($a0) # obtain the character
li $t1, 97 # 'a' - character
li $t2, 122 # 'z' - character
bge $t0, $t1, con1_fulfilled #bigger tha or equal to 0
j con1_not_fulfilled
con1_fulfilled:
ble $t0, $t2, con2_fullfilled #less than or equal to 9
j con2_not_fulfilled
con2_fullfilled:
li $v0, 1
j return
con1_not_fulfilled:
con2_not_fulfilled:
li $v0, 0
return:
# return
jr $ra
StrLen:
move $a1, $a0 # start of string
# run a loop
li $t0, '[=10=]' # null character
li $t1, 0 # prepare the counter
start_loop:
lb $v0, ($a0) # obtain the 1st character
beq $v0, $t0, end_loop # exit loop if '[=10=]'-char found
addi $t1, $t1, 1 # increment counter
addi $a0, $a0, 1 # increment address
j start_loop # iterate again
end_loop:
move $a0, $a1 #restore string address
move $v0, $t1 # return value
# return
jr $ra
PrintStr:
li $v0, 4
syscall
# return
jr $ra
Exit:
# push $s0 on stack
addi $sp, $sp, -4 # create 4-bytes on the stack
sw $s0, ($sp) # cpy $s0 to stack
#terminate program
li $v0, 10
syscall
# free stack
addi $sp, $sp, 4
# return
jr $ra
注:暂时先不要关注算法
所以,我的问题是,
我应该使用什么技术来解决这个问题,因为很难事先知道将来会调用哪个子程序(库可以随着时间的推移自行扩展)?
一些约定是必需的,如果所有子程序都遵守它们,就不会有任何问题,例如寄存器被调用过程破坏。
对于 MIPS,普遍接受的调用约定是:
* 寄存器 $t0-7
是 "temporary" 并且可以在没有预防措施的情况下使用。如果一个过程想要在函数调用中保留其中的一些,则它有责任保存它们 ("caller saved")。
* 寄存器 $s0-7
("saved registers") 不能在没有预防的情况下使用。如果一个过程想要使用其中的一些,它必须在使用前保存它们并在 return ("callee saved")
调用约定还有其他重要方面,例如在寄存器 $a0-$a3
中传递第一个参数,使用 $v0-$v1
作为 return 值等。它们还精确某些寄存器的作用,例如堆栈指针 (sp
) 或帧指针 (fp
)。 this document 是一个很好的总结,但您可以在互联网上轻松找到更多详细信息。
保存寄存器是用 call stack 完成的。它是一种保存所有保留信息的数据结构。
在函数的开始,必须在栈中预留一些space用于所有需要保留的信息。然后将要使用的寄存器s0-s7
保存在栈中。如果函数是非终端函数(即调用另一个函数),returned 地址也会被保存。
在调用函数之前,将需要保存的临时或参数寄存器($t0-7
或$a0-3
)写入堆栈。如果需要,参数被写入寄存器 $a0-3
或堆栈。并调用该函数。
调用函数return后,保留的临时寄存器被恢复
和函数returns之前,需要恢复保存的$s0-7
寄存器和return地址寄存器($ra
), 堆栈 space 被释放并且调用 jr $ra
.
如果所有程序都遵守这些调用约定,就不会有任何问题。编译器尊重这些约定,但它们依赖于 OS 和体系结构。