跳转至

Lecture 10 RISC-V Procedures

RISC-V Function Call Conventions

  • Registers faster than memory, so use them
  • a0 – a7 (x10 - x17): eight argument registers to pass parameters and two return values (a0 - a1)
  • ra: one return address register to return to the point of origin (x1)
  • Also s0 - s1 (x8 - x9) and s2 - s11 (x18 - x27): saved registers (more about those later)

alt text

alt text

ra寄存器

ra 是 "Return Address" 的缩写,在RISC-V中对应的是 x1 寄存器。它的主要作用是存储函数调用的返回地址。

  1. 功能:
    • 存储函数调用后应该返回的地址
    • 使程序能够在函数执行完毕后正确地返回到调用点
  2. 工作原理:
    • 当使用JAL(Jump and Link)指令调用函数时,ra寄存器会自动被设置为当前指令地址+4(即下一条指令的地址)
    • 函数执行完毕后,程序可以通过跳转到 ra 中存储的地址来返回到调用点
  3. 使用场景:
    • 函数调用:在调用函数前,JAL指令会自动将返回地址存储在 ra
    • 函数返回:函数结束时,通常使用ret指令(实际上是 JALR x0, 0(ra) 的别名)来返回
  4. 示例:
    Text Only
    1
    2
    3
    4
    5
    6
    7
    8
    main:
        # 其他代码
        jal ra, some_function  # 调用函数,ra被设置为返回地址
        # 函数返回后继续执行的代码
    
    some_function:
        # 函数代码
        ret  # 使用ra中的地址返回到调用点
    
  5. 重要性:

    • 使用寄存器存储返回地址比使用内存更快
    • 简化了函数调用和返回的机制
    • 允许嵌套函数调用(通过在函数开始时保存ra到栈上)
  6. 注意事项:

    • 如果函数内部会调用其他函数,需要在函数开始时保存ra的值,结束时恢复
    • 在一些调用约定中,ra被归类为"调用者保存"寄存器,意味着调用函数负责保存和恢复它的值
    • 调用者如何保存?
      • 类似于 sw ra, 0(sp) 的形式

Instruction Support for Functions

jal

JAL是"Jump and Link"的缩写,它是一个无条件跳转指令,主要用于函数调用。

  1. 功能:
    • 跳转到指定地址
    • 将返回地址(下一条指令的地址)保存到目标寄存器中
  2. 格式: JAL rd, offset
    • rd:目标寄存器,通常是x1(ra,返回地址寄存器)
    • offset:相对于当前PC的偏移量
  3. 操作:
    • PC = PC + offset(跳转到新地址)
    • rd = PC + 4(保存返回地址)
  4. 用途:
    • 主要用于函数调用
    • 可以实现远距离跳转(±1MB范围内)

jalr

JALR是"Jump and Link Register"的缩写,用于基于寄存器的跳转。

  1. 功能:
    • 跳转到一个基址寄存器加上立即数偏移的地址
    • 将返回地址保存到目标寄存器中
  2. 格式: JALR rd, offset(rs1)
    • rd:目标寄存器,用于保存返回地址
    • rs1:基址寄存器
    • offset:12位有符号立即数
  3. 操作:
    • PC = (rs1 + offset) & ~1(跳转到计算出的地址,最低位置0)
    • rd = PC + 4(保存返回地址)
  4. 主要用途:
    • 函数返回(当rd为x0时)
    • 间接跳转
    • 实现更复杂的跳转逻辑
jrjalr的特例

JR实际上是JALR(Jump and Link Register)指令的一个特殊用法。在RISC-V中,没有单独的JR指令,而是使用JALR来实现相同的功能。

  1. 功能:
    • 跳转到寄存器中存储的地址
  2. 格式: JALR x0, 0(rs)
    • x0:零寄存器(丢弃返回地址)
    • rs:包含跳转目标地址的源寄存器
  3. 操作:
    • PC = rs + 0(跳转到rs中的地址)
    • x0 = PC + 4(返回地址被丢弃)
  4. 用途:
    • 常用于函数返回
    • 实现间接跳转

jal + jr

JAL和JR的配合使用

这两个指令通常配合使用来实现函数调用和返回:

  1. 函数调用:

    Text Only
    1
    JAL ra, function  # 调用函数,保存返回地址到ra(x1)
    

  2. 函数返回:

    Text Only
    1
    JALR x0, 0(ra)  # 相当于JR ra,返回到调用点
    

Function Call Example

alt text

Nested Calls and Register Convention

C
1
2
3
int sumSquare(int x, int y) { 
    return mult(x,x) + y; 
} 
  • Something called sumSquare, now sumSquare is calling mult
  • So there’s a value in ra that sumSquare wants to jump back to, but this will be overwritten by the call to mult
  • Need to save sumSquare return address before call to mult – again, use stack

Register Conventions

motivation

When callee returns from executing, the caller needs to know which registers may have changed and which are guaranteed to be unchanged.

Register Conventions: A set of generally accepted rules as to which registers will be unchanged after a procedure call (jal) and which may be changed

Implementation

To reduce expensive loads and stores from spilling and restoring registers, RISC-V function-calling convention divides registers into two categories:

  1. Preserved across function call
    • Caller can rely on values being unchanged
    • callee返回后肯定不会破坏这些rsg,因此我(caller)不需要对其进行保护
    • 我们称之为:callee-saved
    • sp, gp, tp: "saved registers" s0 - s11 (s0 is also fp)
  2. Not preserved across function call
    • Caller cannot rely on values being unchanged
    • callee返回后或许会破坏这些rsg,因此我(caller)要想在调用callee后重用,需要在调用callee前保存这个值
    • 我们称之为:caller-saved
      • Argument/return registers a0 - a7, ra
      • temporary registers t0-t6

alt text

caller/callee

只需要记住一件事:callee-saved只包含 sps0-s11