本文参考自RVOS的讲义。
函数调用中有关寄存器的编程约定
寄存器名 | ABI(Application Binary Interface)名(编程用名) | 用途约定 | 谁负责在函数调用过程中维护这些寄存器 |
---|---|---|---|
x0 |
zero |
读取时总为0 , 写入时不起任何效果 |
不适用 |
x1 |
ra |
存放 函数返回地址 (return address) | Caller |
x2 |
sp |
存放 栈指针 (stack pointer) | Callee |
x5~x7, x28~x31 |
t0~t2, t3~t6 |
临时(temporary)寄存器,Callee可能会使用这些寄存器,所以Callee不保证这些寄存器中的值在函数调用过程中保持不变,这意味着对于Caller来说,如果需要的话,Caller需要自己在调用Callee之前保存临时寄存器中的值 | Caller |
x8~x9, x18~x27 |
s0~s1, s2~s11 |
保存(saved)寄存器,Callee需要保证这些寄存器的值在函数返回后仍然维持函数调用之前的原值,所以一旦Callee在自己的函数中会用到这些寄存器则需要在栈中备份并在退出函数时进行恢复 | Callee |
x10, x11 |
a0, a1 |
参数(argument)寄存器,用于在函数调用过程中保存第一个和第二个参数, 以及在函数返回时传递返回值 | Caller |
x12~x17 |
a2~a7 |
参数(argument)寄存器, 如果函数调用时需要传递更多的参数,则可以用这些寄存器,但注意用于传递参数的寄存器最多只有8个(a0~a7 ),如果还有更多的参数则要利用栈 |
Caller |
函数调用过程的函数跳转和返回指令
伪指令 | 等价指令 | 描述 | 示例 |
---|---|---|---|
jal offset |
jal x1, offset |
跳转到offset 指定位置,返回地址保存在x1(ra) |
jal foo |
jalr rs |
jalr x1, 0(rs) |
跳转到rs 中的值所指定的位置,返回地址保存在x1(ra) |
jalr s1 |
j offset |
jal x0, offset |
跳转到offset 指定位置, 不保存返回地址 |
j loop |
jr rs |
jalr x0, 0(rs) |
跳转到rs 中值所指定的位置, 不保存返回地址 |
jr, s1 |
call offset |
auipc x1, offset[31 : 12] + offset[11] jalr x1, offset[11:0](x1) |
长跳转调用函数 | call foo |
tail offset |
auipc x6, offset[31 : 12] + offset[11] jalr x0, offset[11:0](x6) |
长跳转 尾调用 | tail foo |
ret |
jalr x0, 0(x1) |
从Callee返回 | ret |
函数调用的常见程序
函数调用的一些标准操作(由编译器实现):
- 函数起始部分 (Prologue):
- 减少
sp
的值,根据本函数中使用saved寄存器的情况以及local变量的多少开辟栈空间; - 将saved寄存器的值保存到栈中;
- 如果函数中还会调用其他的函数,则将
ra
寄存器的值保存到栈中。
- 减少
- 函数执行体;
- 函数退出部分 (Epilogue):
- 从栈中恢复saved寄存器;
- 如果需要的话,从栈中恢复
ra
寄存器; - 增加
sp
的值,恢复到进入本函数之前的状态; - 调用
ret
返回。
示例一:
1 | # Calling Convention |
示例二(嵌套函数):
1 | # Calling Convention |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Shuojiang的博客!
评论