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++ 解决方案!
我有一个奇怪的情况,似乎很适合我,但我需要知道如何让它变得更好或如何生活。
我正在使用 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++ 解决方案!