$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 调用者必须同时执行存储和加载,而在内存中传递仅意味着存储。
现在添加被调用者可能还必须将参数存储到内存的情况(各种可能的原因)。这意味着另一个存储到内存。所以,总的来说,如果上面的场景与这个场景一致,那么一个存储、一个加载和另一个存储——与内存参数传递相比,调用者只有一个存储。