函数调用
一、堆栈
函数调用:
- 参数传递 → 放进
a0–a7 - 跳转 →
jal - 分配空间 → 用 stack(
sp) - 执行函数
- 返回值 → 放进
a0 - 返回 →
jr ra
Danger
会出现问题,如果我嵌套调用函数,那么我的 ra 会被重复赋值,也就是跳转回去的索引丢失了。索引丢失也就是返回地址无法找回。
结论:在调用下一个函数之前,把 ra 存到栈中。因为栈在内存中空间很大可以存很多很多地址,不像寄存器非常有限。
1、一般函数
| int f = (g + h) - (i + j);
|
| # 函数:f = (g + h) - (i + j)
# 参数:
# a0 = g
# a1 = h
# a2 = i
# a3 = j
# 返回:
# a0 = f
Leaf:
# -----------------------------
# ① 开栈(分配 8 字节)
# 因为要用 s0、s1(每个 4 字节)
# -----------------------------
addi sp, sp, -8
# -----------------------------
# ② 保存 s 寄存器
# s寄存器属于 callee-saved
# 只要用了,就必须恢复
# -----------------------------
sw s1, 4(sp)
sw s0, 0(sp)
# -----------------------------
# ③ 计算 (g + h)
# s0 = a0 + a1
# -----------------------------
add s0, a0, a1
# -----------------------------
# ④ 计算 (i + j)
# s1 = a2 + a3
# -----------------------------
add s1, a2, a3
# -----------------------------
# ⑤ 做减法
# a0 = (g+h) - (i+j)
# 返回值必须放在 a0
# -----------------------------
sub a0, s0, s1
# -----------------------------
# ⑥ 恢复 s寄存器(非常重要)
# 把调用前的值还回去
# -----------------------------
lw s0, 0(sp)
lw s1, 4(sp)
# -----------------------------
# ⑦ 关栈(释放空间)
# -----------------------------
addi sp, sp, 8
# -----------------------------
# ⑧ 返回调用者
# -----------------------------
jr ra
|
| 高地址
│
│ s1 ← sp + 4
│ s0 ← sp + 0
↓
sp(当前)
|
2、嵌套函数
| int sumSquare(int x, int y) {
return mult(x,x) + y;
}
|
| sumSquare:
# -----------------------------
# ① 开栈(分配 8 字节空间)
# 用来存:
# - ra(返回地址)
# - y(a1)
# -----------------------------
addi sp, sp, -8
# -----------------------------
# ② 保存 ra(回 main 的地址)
# 因为后面 jal mult 会覆盖 ra
# -----------------------------
sw ra, 4(sp)
# -----------------------------
# ③ 保存 y(a1)
# 因为后面要改 a1 = x
# -----------------------------
sw a1, 0(sp)
# -----------------------------
# ④ 准备调用 mult(x, x)
# 当前:
# a0 = x
# a1 = y
# 变成:
# a0 = x
# a1 = x
# -----------------------------
mv a1, a0
# -----------------------------
# ⑤ 调用 mult
# jal 做两件事:
# 1. ra = 返回到下一行
# 2. PC 跳到 mult
# -----------------------------
jal mult
# -----------------------------
# ⑥ mult 返回后:
# a0 = mult(x, x)
# a1 已经不可靠(被改过)
# 所以恢复 y
# -----------------------------
lw a1, 0(sp)
# -----------------------------
# ⑦ 做最终计算:
# a0 = mult(x,x) + y
# 返回值必须放在 a0
# -----------------------------
add a0, a0, a1
# -----------------------------
# ⑧ 恢复 ra(回 main 的地址)
# 如果不恢复,后面 jr ra 会跳错地方
# -----------------------------
lw ra, 4(sp)
# -----------------------------
# ⑨ 关栈(释放 8 字节)
# -----------------------------
addi sp, sp, 8
# -----------------------------
# ⑩ 返回调用者(main)
# PC = ra
# -----------------------------
jr ra
|
二、寄存器约定
| 寄存器 | ABI 名称 | 描述 | 谁负责保存 |
| x0 | zero | 恒为 0 | - |
| x1 | ra | 返回地址 | 调用者(Caller) |
| x2 | sp | 栈指针 | 被调用者(Callee) |
| x3 | gp | 全局指针 | - |
| x4 | tp | 线程指针 | - |
| x5 | t0 | 临时寄存器 / 备用链接寄存器 | 调用者 |
| x6–7 | t1–t2 | 临时寄存器 | 调用者 |
| x8 | s0 / fp | 保存寄存器 / 帧指针 | 被调用者 |
| x9 | s1 | 保存寄存器 | 被调用者 |
| x10–11 | a0–a1 | 函数参数 / 返回值 | 调用者 |
| x12–17 | a2–a7 | 函数参数 | 调用者 |
| x18–27 | s2–s11 | 保存寄存器 | 被调用者 |
| x28–31 | t3–t6 | 临时寄存器 | 调用者 |
判断寄存器保存规则
a/t/ra 会被改 → caller 存
s/sp 不能变 → callee 还
三、内存分配
- 局部变量:优先寄存器,不够才用栈
- 全局变量:在静态内存区(不是寄存器)
- 寄存器:只是临时加速用的