Should we define a complete RISC-V SBI-call calling convention?
洛佳 Luo Jia
RISC-V SBI provides an interface via enironment call to provide platform agnostic features, which is similiar to operating system's syscalls. According to current SBI specification, this environment call feature is same as RISC-V function calls except (1) control transfer instruction is `ecall` other than `call`, (2) extension ID and function (if applicable) ID should be stored into `a7` and `a6` respectively. Current SBI implementations implement this feature by trap handlers, it by now always store all callee saved registers after stack swap, and jump to the actual handler function which follows RISC-V standard function call ABI to perform the SBI feature.
This process is conventional but could loss some of its performance when we target to extreme performance. It always saves all callee saved registers whenever the handler function need or don't need to use them. When the handler does not use the saved registers, it result in redundant register saves and reloads.
If it's possible should we define a complete SBI-call calling convention to solve this problem, by these following features:
- caller always store parameters in a0-a7 registers without touching the stack, even if parameter count is greater than two
- caller transfer instruction using `ecall`
- callee store all the registers other than a0-a7, x0, sp, tp, gp if used by function as temporary value, so that for compability caller do not need to save them (specifically, ra and t0-t6 if used should be preserved and saved by callee in this calling convention)
- callee will use `ret` instruction to return to interrupt handle wrapper (any other good ideas on this?)
- caller will use a0-a1 registers as return value, return value does not come from stack
Another issue comes from incompleteness of current definition of SBI calling convention. The version 0.3 SBI spec only defines how to transfer control and fill in parameters, but how other registers should be saved remains undefined as is mentioned in https://github.com/riscv-non-isa/riscv-sbi-doc/issues/35 . To make it complete we reduce ambiguity, and it would be a standard developers may refer to when implementing them in higher programming languages like Rust or C.
To raise an example of how code is compiled under this calling convention, I wrote a gist page for this idea: https://gist.github.com/luojia65/e6a31563d99aa566a8c61ffd6bbc2d70 .
By saving only used registers, we get around from always having to save and reload all registers. It saves time when we need exterme performance on SBI calls. If possible, this SBI-call calling convention should be added to RISC-V SBI specification to make current SBI-call definition complete.