C++ 函数调用到 RISC-V 系统调用

C++ function call into RISC-V system calls

我有一个奇怪的情况,似乎很适合我,但我需要知道如何让它变得更好或如何生活。

我正在使用 C++ 作为游戏引擎的编译脚本语言。 RISC-V 系统调用 ABI 与 C 函数调用约定相同,不同之处在于系统调用编号使用 A7 而不是第 8 个整数或指针参数。是的,你知道这是怎么回事。看:

extern "C" long syscall_enter(...);

template <typename... Args>
inline long syscall(long syscall_n, Args&&... args)
{
    asm volatile ("li a7, %0" : : "i"(syscall_n));
    return syscall_enter(std::forward<Args>(args)...);
}

而 syscall_enter 只是 .text 中带有系统调用指令和 ret 的符号。系统调用 return 值也是与普通函数 return.

相同的寄存器
000103f0 <syscall_enter>:
syscall_enter():
   103f0:       00000073                ecall
   103f4:       00008067                ret

在此之前,我必须创建 20 多个函数来涵盖使用整数和指针进行系统调用的所有各种方式,并带有编译器屏障,当我想添加一个采用浮点值的函数时,它会说该调用不明确,因为整数和浮点数可以来回转换。所以,我可以开始为函数添加唯一的名称,或者只是用更好的方法解决这个问题。老实说,这令人恼火,并使原本出色的体验蒙上了一层阴影。我真的很喜欢能够在 "both sides".

上使用 C++

编译器生成的指令似乎没问题。它是 JAL 和 JALR syscall_enter,很好。编译器似乎有点混乱,但我不介意多一条指令。

   10204:       1f500793                li      a5,501
   10208:       00078893                mv      a7,a5
   1020c:       00000513                li      a0,0
   10210:       1e0000ef                jal     ra,103f0 <syscall_enter>

以及中心相机位置:

   100d4:       19600793                li      a5,406
   100d8:       00078893                mv      a7,a5
   100dc:       000127b7                lui     a5,0x12
   100e0:       4207b587                fld     fa1,1056(a5) # 12420 <_exit+0x2308>
   100e4:       22b58553                fmv.d   fa0,fa1
   100e8:       010000ef                jal     ra,100f8 <syscall_enter>

又是一条额外的移动指令。看起来不错。 API 已经被大量使用,还有一个线程 API 可以与之配合使用。

现在,有更好的方法吗?我想不出更好的方法来加载带有数字的 a7,然后强制编译器设置函数调用,而不进行实际的函数调用。我正在考虑为系统调用号使用模板参数,但我不太确定其余部分。也许我们可以将参数的数量限制为 7?当有整数和浮点参数时它不会正确,但这很好。堆栈存储结构很容易通过。

经过一些测试,我决定使用这个:

extern "C" long syscall_enter(...);

template <typename... Args>
inline long syscall(long syscall_n, Args&&... args)
{
    // This will prevent some cases of too many arguments,
    // but not a mix of float and integral arguments.
    static_assert(sizeof...(args) < 8, "There is a system call limit of 8 integer arguments");
    // The memory clobbering prevents reordering of a7
    asm volatile ("li a7, %0" : : "i"(syscall_n) : "a7", "memory");
    return syscall_enter(std::forward<Args>(args)...);
    asm volatile("" : : : "memory");
}

应该够了。不需要系统调用函数垃圾邮件。检查计数参数不是最佳的,因为它应该只阻止使用第 8 个整数寄存器(这意味着计数整数、指针和引用参数)。但它会阻止某些情况。

这有两个问题。

首先是您没有告诉编译器您正在使用 a7,因此它可能会尝试将其他内容放在那里,从而导致代码不正确。您需要将 a7 添加到 asm 的 clobbers 列表中:

asm volatile ("mv a7, %0" : : "r"(syscall_n) : "a7");

第二个是 asm 语句未连接到调用,因此编译器可能会重新排序,特别是在 asm mv 指令和调用之间移动其他代码。如果发生这种情况并且相关代码修改了 a7,您最终将调用错误的系统调用。

这是我现在使用的功能。非常感谢@PeterCordes 的所有帮助。

extern "C" long syscall_enter(...);

template <typename... Args>
inline long apicall(long syscall_n, Args&&... args)
{
    // This will prevent some cases of too many arguments,
    // but not a mix of float and integral arguments.
    static_assert(sizeof...(args) < 8, "There is a system call limit of 8 integer arguments");
    // The memory clobbering prevents reordering of a7
    asm volatile ("li a7, %0" : : "i"(syscall_n) : "a7", "memory");
    return syscall_enter(std::forward<Args>(args)...);
    asm volatile("" : : : "memory");
}

对我来说效果很好。同样,避免使用 syscall-function-spam 解决方案的主要原因是,如果你有 2 个函数,其中一个接受整数参数,另一个接受浮点参数,那么函数调用将是不明确的,现在你需要开始考虑调用哪个函数。我已经使用浮点数和整数参数的组合测试了这个解决方案,它可以正常工作。一个缺点是它将浮点参数放入 64 位寄存器,因此在系统调用期间它会慢一点。

同样,有一个 C++ 解决方案!