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

时间:2020-04-10 19:57:53

标签: c++ system-calls inline-assembly riscv

我有一个奇怪的情况,似乎对我来说很好,但是我需要知道如何使它变得更好或如何生活。

我正在使用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 ++。

由编译器生成的指令似乎还不错。它是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");
}

就足够了。不需要syscall函数发送垃圾邮件。检查计数参数不是最佳方法,因为它只能阻止使用第8个整数寄存器(这意味着对整数,指针和参考参数进行计数)。但这可以防止某些情况。

2 个答案:

答案 0 :(得分:2)

这有两个问题。

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

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

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

答案 1 :(得分:1)

这是我现在正在使用的功能。非常感谢@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 ++解决方案!