我正在学习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
~a3
,f
为$s0
,返回值存储在$v0
中。
此时,由于条件问题,f
存储在$s
中,而$s
需要保留,所以我了解到 $s0
的值存储在堆栈中,然后 $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
寄存器更好的位置。
在第一个函数中,使用 $s
寄存器来存储局部变量 f
是愚蠢的 - f
应该放在 中>$v0
。为什么?两个原因:
$v0
可用,并且从第一次使用f
到最后一次使用该函数所做的任何事情都不会破坏,并且,- 函数以
return f;
结束,这意味着在函数结束时,f
的值需要位于$v0
.
因此,将其放在第一位是有意义的,并且避免了使用 $s
寄存器的开销,而且不需要在最后复制到 $v0
中.
在第二个函数中,它在循环内调用swap
,所以,然后
- 假设函数调用会清除所有参数寄存器(它们是调用破坏集的一部分)
sort
本身将清除作为该函数调用的参数传递部分的$a0
和$a1
。
变量i
、j
、v
和n
均在函数调用之前定义并使用后。 i++
既是 i
的一种用法,也是它的另一种定义——该变量应该在函数调用后继续存在。 j--
也是如此。 v
和 n
都是在函数入口处定义并重复使用的,因此显然它们也必须在 swap
函数调用(或者下一次迭代)中幸存下来循环将不起作用)。
由于这 4 个变量使用得相当频繁,$s
寄存器是很好的候选者,因为替代方案内存需要将额外的加载和存储插入到循环体中。
权衡是在序言和尾声中分别对 $s
寄存器进行一次保存和恢复,而不是在循环体中进行加载和存储,并且如果循环执行多次这是一笔不错的交易。
当我们将逻辑变量映射到物理存储时,这些变量不一定是永久的,因此有时逻辑变量“存在”(映射到)代码的一部分中的物理存储与另一部分中的不同物理存储。
关于assembly - 如何确定是否应保留寄存器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69413034/