如何确定是否应保留寄存器

How to determine if a register should be preserved

正在学习MIPS,正在尝试用MIPS表达C语言写的代码

其中,我在研究应该保留的寄存器时,我认为我是单纯从寄存器的类型来决定是否保留的。

int leaf_example (int g, int h, int i , int j)
{
    int f;
    f = (g+h)-(i+j);
    return f;
 }

比如有上面的代码,还有条件g~j$a0~a3f$s0,return 值存储在 $v0.

此时因为条件问题,f存入$s需要保留$s,所以理解为[=17的值=] 存储在堆栈中,然后 $s 稍后恢复。

void sort(int v[],int n)
{
    int i, j;
    for (i = 0 i < n; i++){
        for (j = i - 1; j >= 0 && v[j] > v[j + 1]; j-=1){
            swap(v, j);
        }
    }
}

但是,当有像上面这样的代码时,在MIPS中表示如下:

为什么sort()函数中作为参数进来的两个值要分开存放,在$s中没有存放时再使用呢?我不知道如何确定应该保留哪些寄存器。

在上面的代码中,我想知道为什么需要保留$a0 $a1。也就是说,如果您能告诉我如何区分代码中应保留哪些值,我将不胜感激。

在 C 或伪代码中,我们有生命周期有限的逻辑变量,它们按作用域来来去去(例如局部变量和参数),而在汇编中我们有物理存储。当我们将算法翻译成汇编语言时,我们必须将逻辑变量映射到物理存储中。

为变量选择的特定存储必须满足其生命周期和使用要求。按照软件惯例,MIPS 将寄存器(物理存储)细分为调用保留寄存器和调用破坏寄存器。

我们需要做的是分析变量及其使用方式。您想回答以下问题:对于每个变量,它所持有的值是在函数调用之前定义的,还是在函数调用之后使用的。如果任何变量的答案是肯定的,那么该变量必须存储在一个保留的位置,该位置将在函数调用后继续存在,并且这些位置是 $s 寄存器或(本地)堆栈内存。但无论哪种方式,直接涉及内存,在后一种情况下,在使用 $s 寄存器的前一种情况下,必须保留这些寄存器本身以遵循约定。

如果代码多次使用变量(通过动态计数),就像循环中经常出现的情况,$s 寄存器是有利和可取的。否则,如果变量仅使用一次(或在动态路径上意外或对性能不重要),例如,内存可能是它们比 $s 寄存器更好的位置。


在第一个函数中,为局部变量 f 使用 $s 寄存器会很愚蠢 — f 应该放在 $v0 中。为什么?两个原因:

  • $v0 可用,并且不会被此函数从第一次使用 f 到最后一次使用时所做的任何事情所破坏,并且,
  • 函数以return f;结束,也就是说在函数结束时,f的值需要在$v0.

所以,把它放在首位是有道理的,并且避免了使用 $s 寄存器的开销,而且不需要在最后复制到 $v0


在第二个函数中,它在循环中调用swap,所以,then

  • 假定函数调用会清除所有参数寄存器(它们是调用破坏集的一部分)
  • sort 本身将消除 $a0$a1 作为进行该函数调用的参数传递部分。

变量ijvn都是函数调用前定义,函数调用后使用。 i++ 既是 i 的一种用法也是另一种定义——该变量应该在函数调用后继续存在。 j-- 也一样。 vn 都是在函数入口时定义并重复使用的,因此显然它们也必须在 swap 函数调用后继续存在(否则循环的下一次迭代将不起作用)。

由于这 4 个变量使用得相当频繁,$s 寄存器是很好的候选者,作为替代方案,内存需要在循环体中插入额外的加载和存储。

权衡是在序言和尾声中对 $s 寄存器进行一次保存和恢复,而不是在循环体中进行加载和存储,如果循环执行多次,那就是一笔不错的交易。


当我们将逻辑变量映射到物理存储时,这些变量不一定是永久性的,因此有时逻辑变量在代码的一部分中“存在”(映射到)不同的物理存储。