为什么 MIPS 不在 $v0 和 $v1 中使用额外的函数参数
Why MIPS doesn't take additional function arguments in $v0 and $v1
根据 MIPS 文档,函数输出存储在 $v0
-$v1
(最多 64 位)中,函数参数在 $a0
-[=13 中给出=],其中任何其他参数都写入堆栈。
既然允许函数覆盖 $v0
-$v1
的值,那么在 $v0
上传递函数第五个参数(如果存在的话)不是更好吗?
在这种情况下使用堆栈的动机是什么?
您说得对,$v
寄存器可用于传递参数。
MIPS 有时会更新调用约定,例如:“MIPS EABI 32 位调用约定”,重新定义了原来的 $t
寄存器中的 4 个,</code>-<code>
,作为附加参数寄存器,总共传递最多 8 个整数参数。
我们还可以考虑 $at
又名 </code> — 汇编程序临时 — 在参数传递点也可用。</p>
<p>但是,对象模型调用,例如那些涉及 vtable、thunk 和其他存根(例如长调用,也许是跨库 (DLL) 调用)的那些可能需要一个或两个临时的可用寄存器,因此不一定最好使用 <em>every one</em> 用于参数的临时寄存器。</p>
<h2>讨论</h2>
<p>总的来说,除此之外,我不确定他们为什么不干脆去掉大部分 <code>$t
寄存器(和 $v
寄存器)并将它们全部设为 $a
寄存器——这些只会在需要时使用,否则那些未使用的参数寄存器将起到与 $t
寄存器相同的作用。参数越多,临时寄存器越少——尽管在调用者和被调用者中都是如此——但我认为可以做出权衡,而不是像当前 ABI 那样保证临时寄存器的最小数量更大。
尽管如此,如果没有一些最低限度的临时寄存器,您有时会最终使用内存,将已经计算的参数溢出到内存中,以便有空闲寄存器来计算最后几个参数,只需要重新加载那些将值溢出回寄存器。如果发生这种情况,还不如首先将其中一些参数传递到内存中,特别是因为被调用者可能还必须将一些参数存储到内存中(例如,被调用者不是叶函数,并且参数是进一步调用后需要)。
8 个参数寄存器可能已经在有用曲线的逐渐变细的末端,因此过去添加更多可能在实际代码库上可以忽略不计 returns。
此外,一种语言可以 invent/define 自己的调用约定:这些调用约定是 C 语言互操作性的标准。当确定不需要这种语言互操作性时,甚至 C 编译器也可以使用自定义调用约定,当我们知道函数实现的更多细节(即它们的内部寄存器使用)而不仅仅是函数签名时,我们也可以在汇编中这样做。
很好地收集了各种调用约定的详细信息:
https://www.dyncall.org/docs/manual/manualse11.html
附录:
让我们假设一台只有 2 个寄存器的机器,将它们称为 A 和 B,并且它使用两者来传递参数。假设第一个参数被计算到 A 中(如果需要,使用 B 寄存器作为暂存器)。在计算第二个参数的值时,对于 B,它可能 运行 超出临时寄存器,特别是如果该实际参数的表达式很复杂。当寄存器用完时,我们会将一些东西溢出到内存中,比如已经计算出的 A。现在可以使用该额外寄存器计算 B 的参数。但是,现在在内存中的 A 参数值需要在调用之前 return 返回到 A 寄存器。因此,这比在内存中传递 A 更糟糕 b/c 调用者必须同时执行存储和加载,而在内存中传递仅意味着存储。
现在,被调用方可能还必须将参数存储到内存中(各种可能的原因)。这意味着另一个存储到内存中。因此,总的来说,如果上述场景与这个场景重合,那么一个存储、一个加载和另一个存储——与内存参数传递形成对比,调用者只有一个存储。
根据 MIPS 文档,函数输出存储在 $v0
-$v1
(最多 64 位)中,函数参数在 $a0
-[=13 中给出=],其中任何其他参数都写入堆栈。
既然允许函数覆盖 $v0
-$v1
的值,那么在 $v0
上传递函数第五个参数(如果存在的话)不是更好吗?
在这种情况下使用堆栈的动机是什么?
您说得对,$v
寄存器可用于传递参数。
MIPS 有时会更新调用约定,例如:“MIPS EABI 32 位调用约定”,重新定义了原来的 $t
寄存器中的 4 个,</code>-<code>
,作为附加参数寄存器,总共传递最多 8 个整数参数。
我们还可以考虑 $at
又名 </code> — 汇编程序临时 — 在参数传递点也可用。</p>
<p>但是,对象模型调用,例如那些涉及 vtable、thunk 和其他存根(例如长调用,也许是跨库 (DLL) 调用)的那些可能需要一个或两个临时的可用寄存器,因此不一定最好使用 <em>every one</em> 用于参数的临时寄存器。</p>
<h2>讨论</h2>
<p>总的来说,除此之外,我不确定他们为什么不干脆去掉大部分 <code>$t
寄存器(和 $v
寄存器)并将它们全部设为 $a
寄存器——这些只会在需要时使用,否则那些未使用的参数寄存器将起到与 $t
寄存器相同的作用。参数越多,临时寄存器越少——尽管在调用者和被调用者中都是如此——但我认为可以做出权衡,而不是像当前 ABI 那样保证临时寄存器的最小数量更大。
尽管如此,如果没有一些最低限度的临时寄存器,您有时会最终使用内存,将已经计算的参数溢出到内存中,以便有空闲寄存器来计算最后几个参数,只需要重新加载那些将值溢出回寄存器。如果发生这种情况,还不如首先将其中一些参数传递到内存中,特别是因为被调用者可能还必须将一些参数存储到内存中(例如,被调用者不是叶函数,并且参数是进一步调用后需要)。
8 个参数寄存器可能已经在有用曲线的逐渐变细的末端,因此过去添加更多可能在实际代码库上可以忽略不计 returns。
此外,一种语言可以 invent/define 自己的调用约定:这些调用约定是 C 语言互操作性的标准。当确定不需要这种语言互操作性时,甚至 C 编译器也可以使用自定义调用约定,当我们知道函数实现的更多细节(即它们的内部寄存器使用)而不仅仅是函数签名时,我们也可以在汇编中这样做。
很好地收集了各种调用约定的详细信息: https://www.dyncall.org/docs/manual/manualse11.html
附录:
让我们假设一台只有 2 个寄存器的机器,将它们称为 A 和 B,并且它使用两者来传递参数。假设第一个参数被计算到 A 中(如果需要,使用 B 寄存器作为暂存器)。在计算第二个参数的值时,对于 B,它可能 运行 超出临时寄存器,特别是如果该实际参数的表达式很复杂。当寄存器用完时,我们会将一些东西溢出到内存中,比如已经计算出的 A。现在可以使用该额外寄存器计算 B 的参数。但是,现在在内存中的 A 参数值需要在调用之前 return 返回到 A 寄存器。因此,这比在内存中传递 A 更糟糕 b/c 调用者必须同时执行存储和加载,而在内存中传递仅意味着存储。
现在,被调用方可能还必须将参数存储到内存中(各种可能的原因)。这意味着另一个存储到内存中。因此,总的来说,如果上述场景与这个场景重合,那么一个存储、一个加载和另一个存储——与内存参数传递形成对比,调用者只有一个存储。