Skip to content

函数调用


一、堆栈🧀

函数调用:

  1. 参数传递 → 放进 a0–a7
  2. 跳转jal
  3. 分配空间 → 用 stack(sp
  4. 执行函数
  5. 返回值 → 放进 a0
  6. 返回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
1
2
3
4
5
6
高地址
│   s1  ← sp + 4
│   s0  ← sp + 0
sp(当前)
  • 中间结果用 s0 / s1
  • 最终结果放 a0

2、嵌套函数🧀

1
2
3
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
  • 嵌套调用 → 必须存 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 还


三、内存分配🧀

  • 局部变量:优先寄存器,不够才用栈
  • 全局变量:在静态内存区(不是寄存器)
  • 寄存器:只是临时加速用的