问题描述
我目前正在UC Berkeley的CS61C实验室工作。 根据幻灯片,应保存调用方保存的寄存器,包括ra(返回地址),然后再调用另一个函数,然后再恢复。
但是在本课程的示例代码中,我发现ra与s0,s1(被调用者保存的寄存器)一起存储。
那么这两种寄存器有什么区别?我们是否应该根据调用约定对它们进行区别对待?
解决方法
ra
和sp
不太适合呼叫者/被呼叫者的分类(又名暂存/保留)。这些都是出于特殊目的传递给函数的参数,并且请注意,这些值/概念在C编程中不直接可见。
ra
是一个参数(要返回的位置)从呼叫者传递到被呼叫者。 sp
是一个参数(可以在其下分配堆栈存储器的位置)从调用方传递到被调用方。
ra
是在结尾中恢复的,不是为了调用者的缘故,而是因为jr ra
指令立即需要它。如果将保存的ra
值恢复为t0
(仍然是0(sp)
),则代码会很好地运行 1 ,唯一需要的调整是{ {1}}然后将执行jr
)。
jr t0
也必须还原,但这是给调用方的,以便可以在调用方的位置找到调用方的堆栈帧内存,即sp
指向的位置返回值应与初始调用时的返回值相同。
我们是否应该根据调用约定对它们进行区别对待?
对它们进行处理,因为需要通过定义它们的工作方式来对待它们,而不是严格按照调用者或被调用者的保存方式进行。
有些文本会将sp
放入呼叫者保存的分类中,而另一些文本放入被呼叫者保存的分类中,但这两种方法都过于简单了,因为真正的答案是ra
展示了两种分类的质量,两者都没有/特别准确。
在序言/结尾中,ra
随被调用方保存而直观地显示,因为它是与其他被调用方保存寄存器一起保存和恢复的,但这是一条红色的鲱鱼,因为要保留的值是被调用方的参数将需要返回给调用者,而其他被调用者保存的寄存器的初始值对被调用者而言则没有任何意义,仅被保存即可将其还原为原始值。不过,一旦保存了返回地址值,ra
就会成为临时寄存器,这意味着如果需要额外的寄存器,它可以用作调用方保存寄存器(因为它不能用作被调用方保存寄存器,根据定义,专门用于拨打电话。
ra
以便函数可以返回给调用它的任何人。如果函数调用另一个函数,则ra
寄存器将必定被重新利用,因此将其原始值保存在序言中并在结语中进行恢复是有意义的,因为这是我们所能达到的最高效率。返回时,呼叫者不依赖ra
。
(在序言中保存ra
并在使用后恢复有效地将返回地址视为从传入的ra
值初始化的内存变量。调用(在调用之前定义并在调用之后使用):被调用者保存,调用者保存和内存变量。我们可以将ra
视为调用者保存寄存器来保存和恢复调用,但是在函数中只有相同的效率只有一个调用不在循环中-否则调用者保存方法对非叶子函数的效率将较低。此分析与其他调用者保存寄存器的用法类似。)
ra
来传达可用堆栈和使用中堆栈之间的边界,如果更改了边界,则需要在返回时恢复该边界。 (从某种意义上讲,sp
类似于in&out参数-尽管有与特定目的相关的限制:它传入来,供被调用方用于堆栈分配,然后传递回调用方,还原为原始参数。 )
不用于将参数传递给被调用方的参数寄存器是调用方保存(临时)寄存器。但是,由于传递的那些已经初始化,因此与简单的调用者保存寄存器在本质上是不同的。
sp
与其他传递的参数不同,因为此参数仅用于一个目的,即最后一个ra
,远远超出了函数的主体。因此,我们无需了解功能主体就能知道保持该价值的最佳方法。其他传递的参数以特定于函数的方式完全在函数体内使用,因此它们的最佳保存/还原是自定义的。
1 模数硬件返回地址预测