【问题标题】:Why MIPS doesn't take additional function arguments in $v0 and $v1为什么 MIPS 在 $v0 和 $v1 中不接受额外的函数参数
【发布时间】:2021-12-13 00:10:28
【问题描述】:

根据 MIPS 文档,函数输出存储在 $v0-$v1(最多 64 位)中,函数参数在 $a0-$a3 中给出,其中任何其他参数都写入堆栈。

既然允许函数覆盖$v0-$v1 的值,那么在$v0 上传递函数的第五个参数(如果存在)不是更好吗?

在这种情况下使用堆栈的动机是什么?

【问题讨论】:

标签: mips mips32 mars-simulator


【解决方案1】:

$v 寄存器可用于传递参数是对的。

MIPS 有时会更新调用约定,例如:“MIPS EABI 32 位调用约定”,重新定义了原始 $t 寄存器中的 4 个,$8-$11,作为附加参数寄存器, 总共传递最多 8 个整数参数。


我们还可以考虑 $at aka $1 — 汇编器温度 — 也可用于参数传递。

但是,对象模型调用,例如那些涉及 vtables、thunk 和其他存根(例如长调用、可能是跨库 (DLL) 调用)的那些可能需要一个或两个可用的暂存器,因此不一定最好使用 每个参数的暂存寄存器。

讨论

一般来说,除此之外,我不确定他们为什么不直接删除大部分 $t 寄存器(和 $v 寄存器)并将它们全部设为 $a 寄存器 - 这些只会是在需要时使用,否则那些未使用的参数寄存器将用于与$t 寄存器相同的目的。参数越多,临时寄存器就越少——虽然在调用者和被调用者中——但我认为可以进行权衡,而不是像当前 ABI 那样保证更大的最小临时寄存器数量。

不过,如果没有最低数量的暂存寄存器,您有时最终会使用内存,将已计算的参数溢出到内存中,以便有空闲寄存器来计算最后几个参数,而只需要重新加载那些溢出的值回到寄存器。如果发生这种情况,不妨首先在内存中传递其中一些参数,特别是因为被调用者可能还必须将一些参数存储到内存中(例如,被调用者不是叶函数,参数是进一步调用后需要)。

8 个参数寄存器可能已经处于有用曲线的末端,因此在此之后添加更多可能对实际代码库的回报可以忽略不计。

此外,一种语言可以发明/定义自己的调用约定:这些调用约定是 C 语言互操作性的标准。当确定不需要这种语言互操作性时,即使 C 编译器也可以使用自定义调用约定,当我们知道函数实现的更多细节(即它们的内部寄存器使用)而不仅仅是函数签名时,我们也可以在汇编中这样做。


很好地收集了有关各种调用约定的集合详细信息: https://www.dyncall.org/docs/manual/manualse11.html


附录:

让我们假设一台机器只有 2 个寄存器,分别称为 A 和 B,它使用这两个寄存器来传递参数。假设第一个参数被计算到 A 中(如果需要,使用 B 寄存器作为暂存器)。在计算第二个参数的值时,对于 B,它可能会用完暂存寄存器,特别是如果该实际参数的表达式很复杂。当寄存器用完时,我们会将一些东西溢出到内存中,比如已经计算的 A。现在可以使用那个额外的寄存器来计算 B 的参数。但是,现在在内存中的 A 参数值需要在调用之前返回到 A 寄存器。因此,这比在内存中传递 A 更糟糕 b/c 调用者必须同时执行存储和加载,而在内存中传递仅意味着存储。

现在添加被调用者可能还必须将参数存储到内存的情况(各种可能的原因)。这意味着另一个存储到内存。所以,总的来说,如果上面的场景与这个场景一致,那么一个存储、一个加载和另一个存储——与内存参数传递相比,调用者只有一个存储。

【讨论】:

  • 我不确定我是否完全遵循。假设我们要编写一个有 5 个参数的函数(如果也使用了这些 $t 寄存器,则为 9 个)。我们有两个选择:要么使用当前发生的堆栈,要么使用其中一个暂存寄存器。在后一种情况下,如果函数不需要所有暂存寄存器,我们的代码就会变得更有效率。如果是这样,它需要将一个存储到内存中。但这不会与首先在堆栈上传递参数一样花费我们吗?通过保持这个额外的暂存寄存器空闲并使用堆栈,我们可以获得什么?
  • 见我的附录,以获得解释。
猜你喜欢
  • 1970-01-01
  • 2015-08-21
  • 2021-04-03
  • 1970-01-01
  • 1970-01-01
  • 2021-09-30
  • 1970-01-01
  • 1970-01-01
  • 2021-09-27
相关资源
最近更新 更多