在 RISC-V Assembly 的主函数中使用“保存的”寄存器

问题描述

假设以下简单的 main 函数用 RISC-V 汇编编写:

.globl main
main:
     addi s3,zero,10 #Should this register (s3) be saved before using?

由于 s3一个“保存的寄存器”,所以应该遵循过程调用约定,因此在使用之前应该将该寄存器压入堆栈。然而,通过查看源文件,没有其他程序使用过这个寄存器,将寄存器保存到堆栈中似乎是多余的。

我的问题是,这些类型的寄存器是否应该在每次使用前保存,即使这意味着编写更多(冗余)代码只是为了遵守调用约定?有时可以忽略这些约定以提高性能吗?

在上面的例子中,是否应该保存寄存器,因为不知道 main 的调用者是否一直在使用 s3 寄存器?

解决方法

是的,main 是一个函数,它具有您返回的真实调用者,并且该调用者可能正在使用 s3 做某事。

除非您的 main 永远不会返回,要么是无限循环,要么仅通过调用 exit 或系统调用退出。如果你再也没有回来,你就不需要能够恢复调用者的状态,甚至不需要找到回去的路(通过一个返回地址)。

因此,如果调用 exit 而不是从 main 返回同样方便,那么这样做可以避免保存任何内容。

当然,这也适用于 main 没有什么可以返回的情况,因此返回甚至不是一种选择。例如如果它是内核或其他独立代码的入口点。


另外,我希望你明白,每次使用前都保存意味着每个使用它们的函数一次,而不是单独围绕每个单独的块。还有not saving call-clobbered registers around each function call; just let them die

有时可以忽略这些约定以提高性能吗?

是的,如果您将详细信息隐藏在您无法控制的任何代码中。

如果您将小的私有辅助函数视为一个大函数的实际一部分,那么它们可以使用“私有”自定义调用约定。 (即使你确实调用/返回而不是仅仅跳转到它们,如果你想避免在多个调用点内联它们)

有时这只是利用了额外保证,当您知道您正在调用的函数时。例如它实际上并没有破坏它的一些输入 arg 寄存器。当您调用自己时,这在递归中很有用:foo(int *p,int a) 自我调用可能会利用 p 仍处于未修改的同一个寄存器中,而不必将 p 保留在其他地方以便在调用返回后使用,就像调用“未知”函数一样,您不能假设调用约定不能保证的任何内容。

或者如果你在你的实际私有递归函数前面有一个公开可见的包装器,你可以设置一些常量,或者甚至让递归函数将一个寄存器视为一个静态变量,而不是将指针传递给某些内存中的共享状态。 (这不再是纯粹的递归,只是一个循环,它使用 asm 堆栈来跟踪一些碰巧包含跳转地址的历史记录。)