【问题标题】:How to determine if a register should be preserved如何确定是否应保留寄存器
【发布时间】:2021-11-23 12:34:41
【问题描述】:

我正在学习 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,返回值存放在@987654333 @。

此时因为有问题,f存储在$s中,$s需要保留,所以我明白了$s0的值是存储在栈上然后@ 987654338@后来恢复了。

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。也就是说,如果您能告诉我如何区分代码中应该保留哪些值,我将不胜感激。

【问题讨论】:

  • 如果您在通话后需要您的注册器参数(或其他一些值),请将它们保存在某个地方,这样您就不会丢失唯一的副本。请参阅What are callee and caller saved registers?(这是不方便的术语:呼叫保留与呼叫破坏更清晰)。在叶子函数中,如果您用完了调用破坏的寄存器(如$t0..9),则仅使用调用保留寄存器(如$s0.. regs),因此它选择$s0..7 用于休假中的int f 本地变量函数是一个糟糕(低效)的选择,迫使您保存/恢复调用者的 $s0 值。
  • 这能回答你的问题吗? What are callee and caller saved registers? - 我认为我的回答确实涵盖了您所询问的所有内容。也相关:MIPS registers preservation
  • 非常感谢,虽然我浪费了很多时间我还是没看懂,但是感谢你我解决了它

标签: assembly mips cpu-registers calling-convention


【解决方案1】:

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

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

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

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


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

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

因此,将其放在首位是有意义的,并且避免了使用 $s 寄存器的开销,而且无需在最后复制到 $v0


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

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

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

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

权衡是在序言和尾声中分别保存和恢复到$s寄存器一次,而不是在循环体中进行加载和存储,如果循环执行多次,这是一个很好的交易。


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

【讨论】:

  • 非常感谢您的详细回复。我想我差不多明白了。如果是这样,作为排序参数的 $a0 将用于交换。由于 $a0 的值在 swap 函数结束时被删除,所以将 $a0 保存在 $s 寄存器中是对的,该寄存器不管函数调用如何都存储该值,然后在下一次 swap 调用之前调用它。 ?
  • 是的,变量v 从它开始的$a0 移动到$s 寄存器中。当然,$s 寄存器的原始值必须在入口和(在 v$a0 移动之前)保存,并在 sort 退出时恢复,因为一些调用者暂停并等待 sort要完成的功能,可能已经在使用该$s 寄存器。正是通过这种行为协议,$s 寄存器才能在函数调用中存活。
  • @ErikEidt:我应该重新提出这个问题吗?它不是完全重复的,也许我匆忙关闭它,特别是现在你已经写了一个从这个角度接近它的答案,它更适合这个问题。
  • @PeterCordes,好的,为什么不呢?
猜你喜欢
  • 1970-01-01
  • 2015-03-27
  • 2019-05-13
  • 2018-02-27
  • 1970-01-01
  • 1970-01-01
  • 2016-03-28
  • 2018-01-14
  • 1970-01-01
相关资源
最近更新 更多