函数调用约定中寄存器的保存问题
Published on 2024-03-29 09:49 in 分类: Compiler with dutrmp19
分类: Compiler

函数调用约定中寄存器的保存问题

函数调用约定中寄存器的保存问题
calling convention

Created: 2024-03-25T17:03+08:00
Published: 2024-03-29T09:50+08:00
Categories: Compiler

一个方便的在线查看汇编网站:Compiler Explorer

函数调用时,caller 和 callee 的寄存器保存问题。

callee 至少能看到:

  1. local variables:自己的局部变量
  2. function arguments:调用自己的函数参数
  3. return address: 要返回到的 caller 的地址

基于 risc-v(32-bits) gcc(trunk),执行过程中:

  1. local variables 由 stack pointer(sp 寄存器)索引
  2. function arguments 由 frame pointer(fp 寄存器,也叫做 s0)索引
  3. caller 的信息,如 return address 和 caller 的 fp 在 frame pointer 下面,sp 指向的栈区高地址

例子

int foo(int p0, int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8, int p9, int p10) {
int a = 1;
int b = 2;
return p0 + p1 + a;
}
int main() {
foo(0,1,2,3,4,5,6,7,8,9,10);
return 0;
}

对应汇编如下:

# call foo(0,1,2,3,4,5,6,7,8,9,10) in main():
li a5,10
sw a5,8(sp)
li a5,9
sw a5,4(sp)
li a5,8
sw a5,0(sp)
li a7,7
li a6,6
li a5,5
li a4,4
li a3,3
li a2,2
li a1,1
li a0,0
call foo(int, int, int, int, int, int, int, int, int, int, int)
  1. 函数参数压栈的顺序是从后往前,10 先被压栈,然后是 9 ……
  2. 把 foo 的参数放到 sp 上,sp[0] 就是 倒数第一个参数,sp[1] 是倒数第二个参数,……

这样 foo 作为 callee,看到的内存和寄存器的场景就是:

  1. 前 8 个参数在寄存器上
  2. sp 指向了函数第 8 个以后的参数

说明现在 callee 的参数是由 sp 索引的

risc-v 32-bits gcc(trunk) 这样处理 fp 和 sp:

  1. local variables 由 stack pointer 索引
  2. function arguments 由 frame pointer 索引
  3. caller 的 ra 等存储在 sp 指向的栈区高地址

callee 要执行 fp=sp,这样就可以用 fp 索引调用参数了,
但是现在 foo 看到的 fp 是前文 main 的函数参数索引,
如果直接执行 fp=sp,原来 fp 的值就被覆盖了,所以要把 fp 保存起来。
这就是 foo 函数开头的逻辑:

  1. 存储 caller 的 ra 和 fp
  2. 修改 fp 和 sp 方便自己调用

foo 最后的逻辑就是把 fp、ra 恢复到 main 调用前的状态

caller-callee

foo(int, int, int, int, int, int, int, int, int, int, int):
addi sp,sp,-64 # save a stack for local variables
sw ra,60(sp)
sw s0,56(sp)
addi s0,sp,64 # adjust fp to previous stack pointer to index arguments
sw a0,-36(s0)
sw a1,-40(s0)
sw a2,-44(s0)
sw a3,-48(s0)
sw a4,-52(s0)
sw a5,-56(s0)
sw a6,-60(s0)
sw a7,-64(s0)
li a5,1
sw a5,-20(s0)
li a5,2
sw a5,-24(s0)
lw a4,-36(s0)
lw a5,-40(s0)
add a4,a4,a5
lw a5,-20(s0)
add a5,a4,a5
mv a0,a5 # result in a0
lw ra,60(sp)
lw s0,56(sp) # restore register fp for caller
addi sp,sp,64 # restore register sp for caller
jr ra

caller-save 和 callee-save

callee-save 的意思是说,如果 callee 想要用这个寄存器,那么就要预先保存好里面的值,用完了以后恢复。
比如 fp 和 sp 就是 callee-save,caller 将这两个寄存器交给 callee,callee 为了自己的方便,调整它们的位置,
但是最后返回的时候,fp 和 sp 要像调用 callee 之前一样。

所以,callee-save 的寄存器在函数调用前后保持不变。

caller-callee

如果您有任何关于文章的建议,欢迎评论或在 GitHub 提 PR

作者:dutrmp19
本文为作者原创,转载请在 文章开头 注明出处:https://www.cnblogs.com/dutrmp19/p/18103119
遵循 CC 4.0 BY-SA 版权协议


posted @   dutrmp19  阅读(44)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
历史上的今天:
2023-03-29 listening-list-for-children
点击右上角即可分享
微信分享提示