As RISC-V calling convention documentation says:
When primitive arguments twice the size of a pointer-word are passed on the stack, they are naturally aligned. When they are passed in the integer registers, they reside in an aligned even-odd register pair, with the even register holding the least-significant bits. In RV32, for example, the function void foo(int, long long) is passed its first argument in a0 and its second in a2 and a3. Nothing is passed in a1.
Why not just use a1
and a2
instead of a2
and a3
? So we can pass more arguments by registers.
Usually, this kind of thing is done so that straightforward code can flush all the registers to memory and get what you'd expect as if the memory had been used in the first place to pass nicely aligned parameters — this is typically important with varargs functions.
However, we should note that neither clang nor gcc follow this directly for non-varargs functions. They both use a0
for the int
and a1
, a2
for the long long
(where a1
is the low order and a2
the high order of the long long
).
long long square(int num, long long foo) {
return foo + 100;
}
results in
square:
addi a0, a1, 100
sltu a1, a0, a1
add a1, a1, a2
ret
clang: https://godbolt.org/z/9Pez4r
gcc: https://godbolt.org/z/b4dMsr
Only if we do varargs do we see the compilers skip a1
:
long long square(int num, ...);
int test () {
square ( 100, (long long) 200 );
}
results in
test:
addi a0, zero, 100
addi a2, zero, 200
mv a3, zero
tail square