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, offset
rd
:目标寄存器,通常是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 1
JAL ra, function # 调用函数,保存返回地址到ra(x1)
-
函数返回:
Text Only 1
JALR x0, 0(ra) # 相当于JR ra,返回到调用点
Function Call Example¶
Nested Calls and Register Convention¶
C | |
---|---|
1 2 3 |
|
- Something called
sumSquare
, nowsumSquare
is callingmult
- So there’s a value in
ra
that sumSquare wants to jump back to, but this will be overwritten by the call tomult
- Need to save
sumSquare
return 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
(s0
is 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