【问题标题】:Using "saved" registers in the main function at RISC-V Assembly在 RISC-V 程序集的主函数中使用“已保存”寄存器
【发布时间】:2021-06-11 09:22:20
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 是的,每次都应该保存它们,即使这意味着更多的代码。是的,main 的调用者(例如 libc 初始化代码)可以将s3 用于自己的目的。

标签: function assembly calling-convention riscv


【解决方案1】:

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

除非你的 main 永远不会返回,要么是一个无限循环,要么只通过调用 exit 或系统调用退出。如果您从不返回,则无需恢复调用者的状态,甚至无需找到返回的路(通过返回地址)。

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

这也适用于main 无法返回的情况,当然,返回甚至不是一个选项。例如如果它是内核或其他独立代码中的入口点。


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

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

是的,如果您不让任何您无法控制的代码看到详细信息。

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

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

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

【讨论】:

    猜你喜欢
    • 2023-03-29
    • 2020-08-27
    • 2021-02-09
    • 2020-04-18
    • 1970-01-01
    • 2021-05-03
    • 1970-01-01
    • 2015-08-18
    • 1970-01-01
    相关资源
    最近更新 更多