我遇到了一个奇怪的情况,似乎对我来说很好,但我需要知道如何让它变得更好,或者如何生活。
我使用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中的一个符号,带有syscall指令和ret.系统调用返回值也是与正常函数返回值相同的寄存器。
000103f0 <syscall_enter>:
syscall_enter():
103f0: 00000073 ecall
103f4: 00008067 ret
在此之前,我必须创建20多个函数,以涵盖使用整数和带有编译器障碍的指针进行系统调用的所有各种方法。当我想添加一个使用浮点值的函数时,它会说调用是模糊的,因为整数和浮点可以来回转换。所以,我可以开始为函数添加唯一的名称,或者用更好的方法解决这个问题。老实说,这让人很恼火,也让原本很好的体验黯然失色。我真的很喜欢能够在"两边"都使用C++。
编译器生成的指令看起来不错。它是日航和日航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,那么您最终将调用错误的系统调用。
这是我现在使用的函数。非常感谢@PeterCodes的帮助。
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函数垃圾邮件解决方案的主要原因是,如果你有两个函数,其中一个采用整数参数,另一个采用浮点参数,那么函数调用将是模糊的,现在你需要开始考虑调用哪个函数。我已经用float和integral参数混合测试了这个解决方案,它可以正常工作。一个缺点是它将浮点参数放入64位寄存器中,因此在系统调用过程中速度会稍微慢一些。
同样,还有一个C++解决方案!