L10 RISC-V Procedures
RISCV Function
Six Fundamental Steps in Calling a Function
RISC-V Function Call Conventions
Instruction Support for Functions
这个图片之前有三个图片,都是介绍图中before的内容,实际上riscv不用before的方式,我们看这个图就好了。
关于jal的进一步介绍
在RISC-V指令中,rd
是 目标寄存器(destination register) 的缩写,表示指令执行后存储结果的寄存器。例如:
- 在
jal rd, Label
指令中,rd
是存储返回地址(当前程序计数器加上 4)的位置,即rd = pc + 4
,然后程序跳转到Label
所指的地址。 - 在
jalr rd, rs, imm
指令中,rd
同样存储返回地址,rs
是基地址寄存器,imm
是立即数,程序计数器(pc
)将被设为R[rs] + imm
。
简单来说,rd
是存储操作结果的寄存器,在跳转类指令中通常保存跳转前的地址,以便之后可以返回原来的位置继续执行。
Summary of Instruction Support
红框部分解释了 j
、jr
和 ret
是伪指令(pseudoinstructions),即它们并不是 RISC-V 中的原生指令,而是用其他指令组合实现的简化写法。
其中,j
指令其实是用 jal x0, Label
实现的,即:
jal
是 jump and link 指令,x0
表示零寄存器,因此不会保存跳转返回地址;Label
是跳转目标地址。
这意味着j Label
本质上是跳转到Label
而不会保存跳转前的程序计数器地址,也就是一个没有返回地址存储的跳转。
唯一的
jal是唯一的原生指令
Where Are Old Register Values Saved to Restore Them After Function Call?
stack
RSICV Function Call Example
Example
Stack Before, During, After Function
Nested Calls and Register Conventions
What If a Function Calls a Function? Recursive Function Calls?
好问题,看看DDCA?Lab7manual#todo3
好的我们回到课程。
register conventions
第一部分(Register Conventions 1/2):
- CalleR:调用函数,即正在调用另一个函数的函数。
- CalleE:被调用的函数,即被调用执行任务的函数。
- 当被调用函数执行完并返回时,调用函数需要知道哪些寄存器的值可能发生了变化,哪些寄存器的值保证不变。
- Register Conventions:指的是一套约定,用于规定在函数调用(如使用
jal
指令)后,哪些寄存器的值必须保持不变,哪些寄存器的值可以被更改。
第二部分(Register Conventions 2/2):
RISC-V 函数调用约定将寄存器分为两类,以减少昂贵的寄存器溢出和恢复操作:
- 函数调用后保存的寄存器:
- 调用方可以依赖这些寄存器的值不变。
- 包括堆栈指针(
sp
)、全局指针(gp
)、线程指针(tp
)、以及“保存寄存器”s0
至s11
(其中s0
也称为帧指针fp
)。
- 函数调用后不保存的寄存器:
- 调用方不能依赖这些寄存器的值保持不变。
- 包括参数/返回值寄存器
a0-a7
,返回地址寄存器ra
,以及“临时寄存器”t0-t6
。
关于s0-s11:
“保存寄存器” s0
至 s11
(也称为 caller-saved registers 或 saved registers)在 RISC-V 的函数调用约定中主要用于保存跨函数调用需要保持的值。
具体来说,它们的作用是:
- 调用方依赖:调用函数(CalleR)可以依赖这些寄存器的值在被调用函数(CalleE)执行完后保持不变。因此,如果被调用函数需要使用这些寄存器,它必须先将当前值保存(通常保存在栈中),在函数返回前再恢复原值。
- 典型用途:这些寄存器通常用于保存全局变量、跨多个函数调用的局部变量、或者一些需要在多个函数中保持不变的值。由于函数执行时可以依赖它们的值不变,因此减少了重复加载或保存数据的开销。
- s0/fp 的特殊作用:
s0
也被用作帧指针(frame pointer),用于跟踪函数调用栈中的帧起始位置,特别是在需要动态栈分配或调试的场景下,帧指针可以帮助管理和访问局部变量、参数等。
总结起来,s0
至s11
的主要职责是在跨函数调用期间保存需要保持不变的值,以保证调用方函数的正确执行。
关于t和a:
a0-a7
和 t0-t6
寄存器在 RISC-V 的调用约定中被称为 不保存寄存器(caller-saved registers),即调用方不能依赖它们在函数调用后保持原值。因此:
a0-a7
(参数/返回值寄存器):- 这些寄存器用于传递函数参数和返回值。
- 在函数调用时,调用方通过这些寄存器传递参数;函数执行完毕后,返回值也存放在这些寄存器中。
- 由于它们属于不保存的寄存器,如果调用方需要保留这些寄存器的值,就必须在调用函数之前将其保存到栈中(或其他位置),调用完毕后再从栈中恢复。
t0-t6
(临时寄存器):- 这些寄存器用于存放临时值,函数调用之间不会保存它们的内容。
- 如果调用方有重要的数据存储在这些寄存器中,在函数调用之前必须保存(通常是推入栈中),并在调用结束后恢复。