jal 指令將控制權轉移到函數,即跳轉並保存返回地址到 ra(返回地址 register。jr ra 指令把控制權返回給呼叫者,並釋放所使用的資源(例如 register 和 stack 空間)。基本的指令支持:
jal(Jump And Link)指令來跳轉並同時保存下一條指令的地址到 ra,這是一個關鍵的指令,能夠實現程序流程的控制。jal 會將當前 PC(程式計數器)的值加上 4,並將其保存到 ra,然後跳轉到指定的目標地址。jr ra 指令跳回到返回地址,繼續執行呼叫程序中的下一條指令。為什麼要使用 jr 而不是 j:
jr ra 的原因是因為一個函數可能會從多個不同的地方被呼叫。如果使用固定的跳轉指令 j,那麼函數只能返回到一個固定的位置。jr 指令允許從 ra register 中讀取返回地址,確保函數可以正確返回到呼叫的源位置。例子:使用 jal 和 jr:
jal ra, function_label  # 跳轉到 function_label 並將返回地址存入 ra
...
function_label:
  # 函數的邏輯在此處
  jr ra  # 從 ra 中讀取返回地址並返回
stack 的使用:
sp(stack pointer)register 會指向當前 stack 的頂部。當需要存儲數據到 stack 中時,sp 會遞減,為數據騰出空間;當數據被取回時,sp 會遞增,釋放空間。例子:使用 stack 保存和恢復數據:
addi sp, sp, -8   # 為 stack 分配 8 字節空間
sw ra, 0(sp)      # 保存 ra 到 stack 頂
sw s0, 4(sp)      # 保存 s0 到 stack 頂下一個位置
...
lw ra, 0(sp)      # 從 stack 中恢復 ra
lw s0, 4(sp)      # 從 stack 中恢復 s0
addi sp, sp, 8    # 釋放 stack 空間
jr ra             # 返回到呼叫者
嵌套函數調用(Nested Calls):
ra 和 sp 的內容可能會被改寫,因此需要將這些重要的寄存器內容保存到 stack 中,以確保函數之間的數據不會互相干擾。ra 和其他重要的 register,那麼它必須在函數返回後恢復這些 register。register 分類:
Leaf Procedures:
ra,因為它們不會跳轉到其他函數,因此返回地址不會被覆蓋。這樣可以簡化 register 的管理。Non-leaf Procedures:
ra 的內容,因此需要將 ra 保存到 stack 中,以確保返回時能夠回到正確的位置。非葉子程序還需要管理 stack,確保每次調用函數時,register 和 stack 的狀態都能夠正確恢復。例子:葉子程序:
ra:addi a0, a0, 1    # 將 a0 增加 1
jr ra             # 返回呼叫者
例子:非葉子程序:
ra 和 s0:addi sp, sp, -8   # 為 stack 分配 8 字節空間
sw ra, 0(sp)      # 保存 ra 到 stack
sw s0, 4(sp)      # 保存 s0 到 stack
jal ra, subroutine   # 調用子程序
lw ra, 0(sp)      # 恢復 ra
lw s0, 4(sp)      # 恢復 s0
addi sp, sp, 8    # 釋放 stack 空間
jr ra             # 返回呼叫者
Caller-Saved Registers(呼叫者保存的 register):
t0-t6 和 a0-a7,這些 register 通常用於傳遞參數或作為暫時性的數據存儲。Callee-Saved Registers(被調用者保存的 register):
s0-s11,它們在函數之間應保持不變。為什麼區分這兩類 register:
例子:
s0 是被調用者保存的 register,因此被調用者在使用它之前會先保存它的值。addi sp, sp, -4   # 為 stack 分配 4 字節空間
sw s0, 0(sp)      # 保存 s0 到 stack
# 使用 s0 執行一些操作
lw s0, 0(sp)      # 從 stack 中恢復 s0
addi sp, sp, 4    # 釋放 stack 空間
jr ra             # 返回呼叫者
遞歸函數的特點:
ra 和其他 register 的值都會發生變化。因此,遞歸函數必須小心管理 stack,以確保每次返回時 register 的內容能夠正確恢復。例子:計算階乘的遞歸函數:
factorial:
  addi sp, sp, -8    # 為 stack 分配空間
  sw ra, 0(sp)       # 保存 ra
  sw a0, 4(sp)       # 保存參數 a0
  addi t0, a0, -1    # t0 = a0 - 1
  bge t0, zero, recurse   # 如果 a0 >= 1,則跳轉到 recurse
  li a0, 1           # 返回 1(base case)
  addi sp, sp, 8     # 釋放 stack
  jr ra              # 返回
recurse:
  jal ra, factorial  # 遞歸呼叫 factorial
  lw a0, 4(sp)       # 恢復原參數 a0
  mul a0, a0, t0     # 計算 a0 * t0
  lw ra, 0(sp)       # 恢復 ra
  addi sp, sp, 8     # 釋放 stack
  jr ra              # 返回