在上一章討論了區域變數如何存放在棧中,在零.一版中提及了全域變數如何生成。現在,還剩下一種變數沒有討論——參數。
精五真言施術約定
精五有 32 個通用暫存器,施術時 call 術
這個偽指令做的不過是將咒指針(program counter)跳躍到術
所在位址,把就位址放到 ra
暫存器而已。
施者(caller)如何傳遞資訊給受者(callee)呢?其實怎樣都可以,無論是丟到棧上的某個位置還是丟到特定暫存器都可以。總之講好就好。如果不同編譯器不按照同個約定來傳遞參數,那這些編譯器編譯出來的術就無法互相調用了。
精五的標準施展約定規定在整數情況下,使用 a0
~ a7
暫存器來傳遞變數。這些暫存器吾人稱之為參數暫存器。(a 即是 argument 的縮寫)
而術結束後,會將其歸值(return value)放在 a0
與 a1
,歸值若僅一個字長,只要看 a0
就好了。
貧道用 gcc 編譯個簡單的 C 程式(音界咒當前版本也依賴類似外術),來看看是否按照這個約定就能呼叫到它了。
編譯外術(外部函式)
將以下 C 代碼存入 曰.c
檔案中。
#include <stdio.h>
#include <inttypes.h>
int64_t print_int(int64_t number) {
printf("%" PRId64 "\n", number);
return 111;
}
執行以下指令來編譯該檔案
riscv64-unknown-elf-gcc -c 曰.c -o 曰.o
riscv64-unknown-elf-objcopy --redefine-sym print_int=曰 曰.o
C 語言不支援非 ASCII 的字符,得先編譯出目的檔 曰.o
之後,再將其符號從 print_int
抽換成 曰
。
施術範例
再將以下檔案存到 施展.S
。
.section .text
.global main
main:
li a0, 17
call 曰
call 曰
li a7, 93 # ecall號碼93表示退出
li a0, 0
ecall
用以下指令組譯 施展.S
並鏈結曰.o
riscv64-unknown-elf-gcc 施展.S 曰.o
再以 qemu 執行生成的執行檔 a.out
qemu-riscv64 a.out
會看到終端輸出
17
111
第一行 17
正是吾人放進 a0
的值,而第二行的 111
恰好就是曰的歸值,第一個 call 曰
結束之後,a0
已經被改為 111
,立刻再施展一次 曰
,果然輸出了 111
。
看來 gcc 確實按照這個約定在傳遞參數與回傳值,音界咒編譯器也遵照約定,以在未能完全自舉之前方便與塵界之術互動。
儲存參數
既然在術開始之前,參數就已被施者填入 a0
~ a7
之中,那能否每次需要讀取參數時就直接使用暫存器呢?不能,因為術有可能還要去調用其他術,這是又得修改 a0
~ a7
來傳遞參數了。
還是只能老方法全都存到棧裡了。
如果不需要再調用其他術,大可以就放在參數暫存器裡,但零.二版音界咒的實作先不做這個優化。
來看個範例:
術.甲(子、丑)【
元.天=1
元.地=1
元.玄=1
元.黃=1
】
執行後,棧應該要是