# RISC-V Procedures
> 資料來源:NCKU Jserv 教授開設之**計算機結構**課程-[RISC-V Procedures](https://docs.google.com/presentation/d/18F1vHY5Mv5E6WU6BvktXzkVbZrTZ3jQUbkWOjEPGE4I/edit#slide=id.p1)。
:::info
可回來查閱 **RISC-V Symbolic Register Names**。
:::
## RISC-V Function Call
### Six Fundamental Steps in Calling a Function
1. 將 **arguments** 放在函式可以存取的位置。
2. 將「控制權」轉交給函式。
3. 取得函式所需的(本地)儲存資源。
4. 執行函式所需的任務。
5. 將 **return value** 放在呼叫程式碼可以存取的位置,並恢復使用過的暫存器;釋放本地儲存資源。
6. 將「控制權」交回給原點,因為一個函式可能從程式的多個點被呼叫。
### RISC-V Function Call Conventions
- 暫存器比記憶體快,因此應善加利用它們。
- `a0` – `a7` (`x10` - `x17`):**8 個參數暫存器**,用來傳遞參數及兩個 return value(`a0` - `a1`)。
- `ra`:**1 個 return address 暫存器**,用來回到到原點(`x1`)。
- 另外還有 `s0`-`s1` (`x8`-`x9`) 和 `s2`-`s11` (`x18`-`x27`): saved 暫存器(稍後會進一步解釋)。
### RISC-V Function Call Instructions
- **呼叫函式**:使用 jump and link 指令(`jal`)。
- 「link」的意思是建立一個位址或連結,指向呼叫的地方,讓函式能夠返回到正確的位址。
- 「jump」到指定位址,並同時將下一條指令的位址儲存到 `rd` 暫存器中:
```c
jal rd, FunctionLabel
```
- 從**函式回傳**:使用 jump register 指令(`jr`)。
- 無條件跳躍到 `ra` 暫存器指定的位址:
```c
jr ra
```
- 組譯器的簡寫:`ret = jr ra`。
小總結:
事實上,只有兩個指令:
- `jal rd, Label` – **jump-and-link** `# rd = pc+4; pc += imm`
- `jalr rd, rs, imm` – **jump-and-link register** `# rd = pc+4; pc = R[rs1]+imm`
- 正如我們即將看到的,「天下沒有白吃的午餐」,因此可能**沒有足夠的位元**讓 Label 指向我們想跳躍的遠處。
- 使用 `jalr` 時,我們跳躍到暫存器 `rs` 的內容加上立即值 `imm`(就像基底指標加上偏移量),並且將 `rd` 設置為和 `jal` 相同的方式。
> 注意:`j`, `jr` 和 `ret` 是 pseudo-instructions!
### Stack
> 在呼叫函數之前,需要一個地方來**保存舊的值**,返回時還原它們,並刪除這些值。
理想的選擇是 **Stack**:一種 LIFO 的佇列。
- Push:將資料放入 Stack。
- Pop:從 Stack 中取出資料。
Stack 位於記憶體中,因此需要一個暫存器指向它。
RISC-V 中的 Stack Pointer 為 `sp`(`x2`)。
慣例上,Stack 從高位址向低位址增長,執行 Push 時減少 `sp`,執行 Pop 時增加 `sp`。
Stack **frame** 包含:
- Return「instruction」位址
- 參數(引數)
- 其他區域變數的空間
stack frame 是連續的記憶體區塊, Stack Pointer 會**指示 stack frame 的底部位置**。

當程式執行完畢後,stack frame 會從 Stack 中移除,釋放記憶體以供未來的 stack frame 使用。
## RISC-V Function Call Example
### C Code
```c
int Leaf (int g, int h, int i, int j)
{
int f;
f = (g + h) – (i + j);
return f;
}
```
> Parameter variables `g`, `h`, `i`, and `j` in argument registers `a0`, `a1`, `a2`, and `a3`, and `f` in `s0`.
Assume need one temporary register `s1`.
### Assembly Code
```c
Leaf: addi sp,sp,-8 # adjust stack for 2 items
sw s1, 4(sp) # save s1 for use afterwards
sw s0, 0(sp) # save s0 for use afterwards
add s0,a0,a1 # f = g + h
add s1,a2,a3 # s1 = i + j
sub a0,s0,s1 # return value (g + h) – (i + j)
lw s0, 0(sp) # restore register s0 for caller
lw s1, 4(sp) # restore register s1 for caller
addi sp,sp,8 # adjust stack to delete 2 items
jr ra # jump back to calling routine
```
### Stack Before, During, After Function

## Nested Calls and Register Conventions
### Nested Procedures
```c
int sumSquare(int x, int y) {
return mult(x, x) + y;
}
```
有個名為 `sumSquare` 的函式,現在 `sumSquare` 呼叫了 `mult` 函式。
此時,`sumSquare` 需要 return 的 addr. 儲存在 `ra` 中,但在呼叫 `mult` 時,`ra` 會被覆寫。
因此,在呼叫 `mult` 之前,必須**先將 `sumSquare` 的 return address 存放到 Stack 中**,以避免被覆寫。
### Register Conventions
- 呼叫者(Calle**R**):指發起呼叫的函式。
- 被呼叫者(Calle**E**):指被呼叫的函式。
> 當 Calle**E** 執行完畢 return 時,Calle**R** 需要知道哪些暫存器可能會被改變,哪些則保證不會被改變。
**暫存器慣例(Register Conventions)**:這是一組普遍接受的規則,說明在執行函式呼叫(`jal`)後,哪些暫存器會保持不變,哪些可能會被改變。
為了減少因暫存器溢出與恢復所造成的昂貴 load 和 store 操作,RISC-V 的 function-calling convention 將暫存器分為兩類:
1. 函式呼叫後,**保留的暫存器**:
- 呼叫者可以依賴其值不變。
- `sp`(堆疊指標)、`gp`(全域指標)、`tp`(執行緒指標)。
- Saved registers:`s0 - s11`(`s0` 也作為 `fp`,即框架指標),
2. 函式呼叫後,**不保留的暫存器**:
- 呼叫者無法依賴其值保持不變。
- Argument / Return registers:`a0 - a7`、`ra`(返回位址暫存器)。
- Temporary registers:`t0 - t6`。
### RISC-V Symbolic Register Names

> **Register**:Numbers hardware understands。
> **ABI Name**:Human-friendly symbolic names in assembly code.
## RV32 指令
