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)


ra寄存器
ra 是 "Return Address" 的缩写,在RISC-V中对应的是 x1 寄存器。它的主要作用是存储函数调用的返回地址。
- 功能:
- 存储函数调用后应该返回的地址
- 使程序能够在函数执行完毕后正确地返回到调用点
- 工作原理:
- 当使用JAL(Jump and Link)指令调用函数时,ra寄存器会自动被设置为当前指令地址+4(即下一条指令的地址)
- 函数执行完毕后,程序可以通过跳转到
ra中存储的地址来返回到调用点
- 使用场景:
- 函数调用:在调用函数前,JAL指令会自动将返回地址存储在
ra中 - 函数返回:函数结束时,通常使用ret指令(实际上是
JALR x0, 0(ra)的别名)来返回
- 函数调用:在调用函数前,JAL指令会自动将返回地址存储在
- 示例:
Text Only 1 2 3 4 5 6 7 8
main: # 其他代码 jal ra, some_function # 调用函数,ra被设置为返回地址 # 函数返回后继续执行的代码 some_function: # 函数代码 ret # 使用ra中的地址返回到调用点 -
重要性:
- 使用寄存器存储返回地址比使用内存更快
- 简化了函数调用和返回的机制
- 允许嵌套函数调用(通过在函数开始时保存ra到栈上)
-
注意事项:
- 如果函数内部会调用其他函数,需要在函数开始时保存ra的值,结束时恢复
- 在一些调用约定中,ra被归类为"调用者保存"寄存器,意味着调用函数负责保存和恢复它的值
- 调用者如何保存?
- 类似于
sw ra, 0(sp)的形式
- 类似于
Instruction Support for Functions¶
jal¶
JAL是"Jump and Link"的缩写,它是一个无条件跳转指令,主要用于函数调用。
- 功能:
- 跳转到指定地址
- 将返回地址(下一条指令的地址)保存到目标寄存器中
- 格式:
JAL rd, offsetrd:目标寄存器,通常是x1(ra,返回地址寄存器)offset:相对于当前PC的偏移量
- 操作:
PC = PC + offset(跳转到新地址)rd = PC + 4(保存返回地址)
- 用途:
- 主要用于函数调用
- 可以实现远距离跳转(±1MB范围内)
jalr¶
JALR是"Jump and Link Register"的缩写,用于基于寄存器的跳转。
- 功能:
- 跳转到一个基址寄存器加上立即数偏移的地址
- 将返回地址保存到目标寄存器中
- 格式:
JALR rd, offset(rs1)rd:目标寄存器,用于保存返回地址rs1:基址寄存器offset:12位有符号立即数
- 操作:
PC = (rs1 + offset) & ~1(跳转到计算出的地址,最低位置0)rd = PC + 4(保存返回地址)
- 主要用途:
- 函数返回(当rd为x0时)
- 间接跳转
- 实现更复杂的跳转逻辑
jr是jalr的特例
JR实际上是JALR(Jump and Link Register)指令的一个特殊用法。在RISC-V中,没有单独的JR指令,而是使用JALR来实现相同的功能。
- 功能:
- 跳转到寄存器中存储的地址
- 格式:
JALR x0, 0(rs)- x0:零寄存器(丢弃返回地址)
- rs:包含跳转目标地址的源寄存器
- 操作:
- PC = rs + 0(跳转到rs中的地址)
- x0 = PC + 4(返回地址被丢弃)
- 用途:
- 常用于函数返回
- 实现间接跳转
jal + jr¶
JAL和JR的配合使用
这两个指令通常配合使用来实现函数调用和返回:
-
函数调用:
Text Only 1JAL ra, function # 调用函数,保存返回地址到ra(x1) -
函数返回:
Text Only 1JALR x0, 0(ra) # 相当于JR ra,返回到调用点
Function Call Example¶

Nested Calls and Register Convention¶
| C | |
|---|---|
1 2 3 | |
- Something called
sumSquare, nowsumSquareis callingmult - So there’s a value in
rathat sumSquare wants to jump back to, but this will be overwritten by the call tomult - Need to save
sumSquarereturn address before call tomult– 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:
- Preserved across function call
- Caller can rely on values being unchanged
- callee返回后肯定不会破坏这些rsg,因此我(caller)不需要对其进行保护
- 我们称之为:
callee-saved - sp, gp, tp: "saved registers"
s0 - s11(s0is alsofp)
- 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
- Argument/return registers

caller/callee
只需要记住一件事:callee-saved只包含 sp 和 s0-s11