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函数,当n10时,10存入[=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 值。一旦寄存器被设置,它就不会改变,除非一些机器代码指令改变它。因此,寄存器值具有连续性并且完全受机器代码程序控制。