至此,貧道已經展示了零.一版音界咒的分詞、剖析、語義檢查,若程式能通過這些步驟,代表它完全合法。接下來,就可以生成目標碼了,在本指引中,貧道會示範如何生成精五真言(RISC-V)。
熟悉 C 語言的道友想必都曉得,全域變數與區域變數在程序執行時是放在不同位置。全域變數會放在數據段(data section),在整個程序執行的過程都佔用一塊記憶體空間;而區域變數則是則是存放在函式調用時臨時開的棧禎。
零.一版音界咒的變數可以視做全域的,畢竟該版本尚無函式調用的概念。
很容易就能用精五真言設定全域變數,一下是範例數據段.S
:
.section .data
甲:
.word 50 # .word 表示開闢 32 位元的空間,該空間的值為 50
.section .text
.global _start
_start:
lw t0, 甲 # t0 = *(u32*)甲
li a7, 93 # RISCV Linux 中 exit 系統呼叫編號是 93
mv a0, t0 # a0 = t0
ecall # 執行系統呼叫 exit(t0)
同樣交叉編譯並以 qemu 模擬
riscv64-unknown-elf-gcc -nostdlib 數據段.S # 編譯後應得 a.out 檔案
qemu-riscv64 a.out # qemu-riscv64 並非單單模擬裸機,還實作了部分系統呼叫
echo $? # 可以看到上一個程序的結束碼是 50
數據段.S
首先在數據段 .section .data
,中用 .word
開闢 32 位元的空間,前方的甲
是一個標籤,在後續程式段的真言中會被代換為該空間的位址。
再來看 _start
的第一行 lw t0, 甲
,lw 是 load word 的縮寫,效果是從位址甲開始讀取 32 位元放進 t0 暫存器。 lw
執行完後, t0
就是 50
了,最後 mv a0, t0
把程序結束碼設為 50。
與 lw
相對應,sw
(store word)能夠將暫存器的值寫入某個記憶體位址,但 sw
要多加一個參數,sw t0, 甲, rt
, rt
可以是任意通用暫存器。
接三個參數的 sw rd, 標籤, rt
是偽指令,組譯後會變為兩條指令:
auipc rt, 標籤[31:12]
sw rd, 標籤[31:12](rt)
其大概意思是用先把 rt 的值弄成成標籤的位址,再把 rt 所在位址的 32 位元存進 rd 。
那為何 lw rd, 標籤
就不需要額外暫存器來幫忙存位址呢?它也是會編譯成兩條真言的偽指令吧! 因為rd
在載入時能重複用。
auipc rd, 標籤[31:12] # 反正等等 rd 的值等等也要被改了,順便當位址用
lw rd, 標籤[31:12](rd) # rd 既是記憶體位址,又是要被寫入的暫存器
sw
的 rd 要是重複用,還沒把它的值寫到記憶體,自己就先被汙染了:
auipc rd, 標籤[31:12] # rd 的值已被汙染
sw rd, 標籤[31:12](rd) # 把被汙染的值寫到記憶體
所以 sw rd, 標籤, rt
的第三個參數 rt 省不了。