MIPS 中的非保留寄存器究竟何时被清除?
When exactly are non-preserved registers cleared in MIPS?
下面是以 MIPS 表示的斐波那契值。
fib: addi $sp, $sp, -24
sw $ra, 16($sp)
sw $a0, 20(sp) # recursive calls will overwrite original $a0
sw $s0. 0($sp) # holds fib(n-1)
# end prologue
slti $t0, $a0, 4 # fib(i) = i for i = 1, 2, 3; fib(0) = 0 by C code
beq $t0, $zero, L1
addi $v0, $a0, 0 # see prior comment (assumes $a0 non-negative integer)
j exit
# fib(n) = fib(n-1) + fib(n-2)
L1: addi $a0, $a0, -1
jal fib
addi $s0, $v0, 0 # $s0 = fib(n-1) <-----how can use $v0?
addi $a0, $a0, -1
jal fib # upon return, $v0 holds fib(n-2)
add $v0, $v0, $s0
exit: # unwind stack and return
lw $s0, 0($sp)
lw $a0, 20($sp)
lw $ra, 16($sp)
addi $sp, $sp, 24
jr $ra
但是这里有些地方我不太明白。据我所知,$s
以外的寄存器的值在函数结束时消失
查看fib函数,当n
为1
或0
时,1
或0
存入[=17的值=].之后,当函数结束时,$v0
的值不应该也被删除吗?所以,在调用fib(n-2)
之前,fib(n-1)
的return值被删除了,所以我认为$s
中要保存的代码应该写在[=22=中] 函数。
然而,在上面的代码中,fib(n-1)
函数的 return 值被下一个 fib(n-2)
函数使用。我不知道这怎么可能。
非保留寄存器具体保留多久,什么时候删除?
请注意,您引用的 fib
遵循自定义和非标准调用约定。我不推荐它来了解调用保留与调用破坏寄存器。
在此部分中:
L1: addi $a0, $a0, -1
jal fib
addi $s0, $v0, 0 # $s0 = fib(n-1) <-----how can use $v0?
addi $a0, $a0, -1 <------------- **HERE** --------
jal fib # upon return, $v0 holds fib(n-2)
在标记为 **HERE**
的行中,代码预计 $a0
在函数调用后仍然存在。这是对正确 MIPS 调用约定的非标准和虚伪的说明。
虽然此代码有效,但它不遵循 MIPS 调用约定寄存器用法。它进行了自定义更改,只有了解调用者和被调用者的实现才有可能。对于一般情况,这种想法是错误的。
exit: # unwind stack and return
lw $s0, 0($sp)
lw $a0, 20($sp) <----------- **ALSO**
lw $ra, 16($sp)
addi $sp, $sp, 24
jr $ra
标记为 **ALSO**
的部分是代码正在为调用者恢复 $a0
的地方! 这是非常规的,并且在一般情况下案例不可靠。一般情况下,没有人会依赖 $a0
被保留,所以没有人会费心去恢复它。
这是递归 fib
的正确(且更有效)版本:
fib:
beq $a0, [=12=], return0 # goto return 0 if n == 0
li $v0, 1 # load $v0 with 1 for comparison and return
beq $a0, $v1, return # if n == 1 return leaving 1 in $v0
addiu $sp, $sp -8 # allocate two words of stack space
sw $ra, 4($sp) # save $ra
sw $a0, 0($sp) # save n
addi $a0, $a0, -1
jal fib # fib(n-1)
lw $a0, 0($sp) # restore $a0 for next call to fib
sw $v0, 0($sp) # store return value of fib(n-1) in stack
addi $a0, $a0, -2
jal fib # fib(n-2)
lw $t0, 0($sp) # reload return value from fib(n-1)
add $v0, $t0, $v0 # fib(n-1)+fib(n-2)
lw $ra, 4($sp) # restore our return address
addiu $sp, $sp, 8 # release stack
jr $ra # and use return address
return0:
li $v0, 0
return:
jr $ra
这个版本实际上遵循了MIPS调用约定。它不会将 $a0
转换为调用保留寄存器,而是在第二次递归调用之前而不是在结语中重新加载 $a0
。
这个版本不使用 $s
寄存器,因为它们在这个特定函数的环境中实际上是一个缺点。而是使用堆栈内存。 $s
寄存器版本会(稍微)长一些,因为将涉及相同数量的加载和存储(但出于保留 $s 寄存器的不同目的),尽管会有一个额外的寄存器到寄存器复制指令需要让它更长。
As far as I know, the values of the registers other than $s disappear when the function ends.
所有寄存器都是永久性的,程序中的所有机器代码都可以访问这些寄存器,例如,包括 $t、$a、$v 和 $s 寄存器。
寄存器值仅通过执行以它们为目标的机器代码指令来更改。您是否被允许(或应该)这样做是软件约定的问题,它告诉我们如何在一个可能具有数千个函数的程序中共享仅仅 31 个寄存器。
根据软件协议,$a0
用于传递第一个(整数或指针)参数,$v0
用于return函数的(整数或指针)return 值。一旦寄存器被设置,它就不会改变,除非一些机器代码指令改变它。因此,寄存器值具有连续性并且完全受机器代码程序控制。
下面是以 MIPS 表示的斐波那契值。
fib: addi $sp, $sp, -24
sw $ra, 16($sp)
sw $a0, 20(sp) # recursive calls will overwrite original $a0
sw $s0. 0($sp) # holds fib(n-1)
# end prologue
slti $t0, $a0, 4 # fib(i) = i for i = 1, 2, 3; fib(0) = 0 by C code
beq $t0, $zero, L1
addi $v0, $a0, 0 # see prior comment (assumes $a0 non-negative integer)
j exit
# fib(n) = fib(n-1) + fib(n-2)
L1: addi $a0, $a0, -1
jal fib
addi $s0, $v0, 0 # $s0 = fib(n-1) <-----how can use $v0?
addi $a0, $a0, -1
jal fib # upon return, $v0 holds fib(n-2)
add $v0, $v0, $s0
exit: # unwind stack and return
lw $s0, 0($sp)
lw $a0, 20($sp)
lw $ra, 16($sp)
addi $sp, $sp, 24
jr $ra
但是这里有些地方我不太明白。据我所知,$s
以外的寄存器的值在函数结束时消失
查看fib函数,当n
为1
或0
时,1
或0
存入[=17的值=].之后,当函数结束时,$v0
的值不应该也被删除吗?所以,在调用fib(n-2)
之前,fib(n-1)
的return值被删除了,所以我认为$s
中要保存的代码应该写在[=22=中] 函数。
然而,在上面的代码中,fib(n-1)
函数的 return 值被下一个 fib(n-2)
函数使用。我不知道这怎么可能。
非保留寄存器具体保留多久,什么时候删除?
请注意,您引用的 fib
遵循自定义和非标准调用约定。我不推荐它来了解调用保留与调用破坏寄存器。
在此部分中:
L1: addi $a0, $a0, -1
jal fib
addi $s0, $v0, 0 # $s0 = fib(n-1) <-----how can use $v0?
addi $a0, $a0, -1 <------------- **HERE** --------
jal fib # upon return, $v0 holds fib(n-2)
在标记为 **HERE**
的行中,代码预计 $a0
在函数调用后仍然存在。这是对正确 MIPS 调用约定的非标准和虚伪的说明。
虽然此代码有效,但它不遵循 MIPS 调用约定寄存器用法。它进行了自定义更改,只有了解调用者和被调用者的实现才有可能。对于一般情况,这种想法是错误的。
exit: # unwind stack and return
lw $s0, 0($sp)
lw $a0, 20($sp) <----------- **ALSO**
lw $ra, 16($sp)
addi $sp, $sp, 24
jr $ra
标记为 **ALSO**
的部分是代码正在为调用者恢复 $a0
的地方! 这是非常规的,并且在一般情况下案例不可靠。一般情况下,没有人会依赖 $a0
被保留,所以没有人会费心去恢复它。
这是递归 fib
的正确(且更有效)版本:
fib:
beq $a0, [=12=], return0 # goto return 0 if n == 0
li $v0, 1 # load $v0 with 1 for comparison and return
beq $a0, $v1, return # if n == 1 return leaving 1 in $v0
addiu $sp, $sp -8 # allocate two words of stack space
sw $ra, 4($sp) # save $ra
sw $a0, 0($sp) # save n
addi $a0, $a0, -1
jal fib # fib(n-1)
lw $a0, 0($sp) # restore $a0 for next call to fib
sw $v0, 0($sp) # store return value of fib(n-1) in stack
addi $a0, $a0, -2
jal fib # fib(n-2)
lw $t0, 0($sp) # reload return value from fib(n-1)
add $v0, $t0, $v0 # fib(n-1)+fib(n-2)
lw $ra, 4($sp) # restore our return address
addiu $sp, $sp, 8 # release stack
jr $ra # and use return address
return0:
li $v0, 0
return:
jr $ra
这个版本实际上遵循了MIPS调用约定。它不会将 $a0
转换为调用保留寄存器,而是在第二次递归调用之前而不是在结语中重新加载 $a0
。
这个版本不使用 $s
寄存器,因为它们在这个特定函数的环境中实际上是一个缺点。而是使用堆栈内存。 $s
寄存器版本会(稍微)长一些,因为将涉及相同数量的加载和存储(但出于保留 $s 寄存器的不同目的),尽管会有一个额外的寄存器到寄存器复制指令需要让它更长。
As far as I know, the values of the registers other than $s disappear when the function ends.
所有寄存器都是永久性的,程序中的所有机器代码都可以访问这些寄存器,例如,包括 $t、$a、$v 和 $s 寄存器。
寄存器值仅通过执行以它们为目标的机器代码指令来更改。您是否被允许(或应该)这样做是软件约定的问题,它告诉我们如何在一个可能具有数千个函数的程序中共享仅仅 31 个寄存器。
根据软件协议,$a0
用于传递第一个(整数或指针)参数,$v0
用于return函数的(整数或指针)return 值。一旦寄存器被设置,它就不会改变,除非一些机器代码指令改变它。因此,寄存器值具有连续性并且完全受机器代码程序控制。