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 # 返回