程序呼叫
程式是由可重複使用的零件組成的:一個算平方根的函式、一個畫按鈕的函式。呼叫其中一個,就像派一位同事去辦件事,並期待他辦完回到你座位旁。程序呼叫(procedure call)就是機器層級上「跳到某函式的程式碼、執行它、再返回到呼叫之後的那一道指令」的動作,好讓原本的工作從中斷處接續下去。
核心訣竅在於記住該回到哪裡。普通的跳躍只管前往某處、忘了自己從哪來;呼叫則必須留下線索。在 RISC-V 上,呼叫指令 jal(jump and link)一次做兩件事:跳到函式,並把下一道指令的位址(返回位址,return address)存進一個暫存器。函式做完後,一道返回指令(ret,其實是 jalr 跳回那個存起來的位址)就跳回來了。引數通常透過約定好的暫存器傳遞,函式也把結果留在約定好的暫存器,於是呼叫者與被呼叫者能來回交接工作,互不踩到對方。
程序呼叫之所以重要,是因為它讓大型程式保持井然有序:每個函式都是一份你能單獨推理的小契約。但這套機制很脆弱。若某函式自己又發出呼叫,它必須先把返回位址存起來(否則內層呼叫會把它覆蓋掉,它就永遠回不了家)。誰該存什麼、引數放哪、堆疊如何管理,這些規則由呼叫慣例(calling convention)明文規定,正是它讓不同人、甚至不同編譯器寫的程式碼能彼此可靠地呼叫。
呼叫者這端: li a0, 9 # 把引數 9 放進 a0 jal ra, isqrt # 呼叫 isqrt;ra <- 下一行的位址 # ... 返回後,結果就在 a0 裡 isqrt: # 被呼叫者 # ... 計算,把答案留在 a0 ... ret # 跳回存起來的返回位址
jal 跳轉並存下返回位址;ret 跳回。這裡引數與結果都走 a0。
jal 把返回位址存進暫存器,而非堆疊。一個自己又會呼叫別人的函式,必須先把那個暫存器存到堆疊上,否則就會找不到回家的路。