在語法樹中,算式
是一棵二元樹,要從樹形轉換為循序、線性的真言並不是一目瞭然的事情。若先將算術二元樹轉換為後序表達式,計算的順序會比較明瞭一些。道友們在煉氣(學習編程基礎、資料結構)時,想必對後序表達式不陌生,後序拜訪二元樹就能得到。
後序拜訪(樹) {
後序拜訪(樹.左子樹)
後序拜訪(樹.右子樹)
打印(樹.值)
}
舉個例子:
+
/ \
* 5
/ \
3 -
/ \
4 2
後序拜訪後,會打印出:
3 4 2 - * 5 +
這時計算的順序就很清楚了,由左而右是 - * + ,得到順序後,把每個數字放進一個暫存器,可以生成真言
li t0, 3
li t1, 4
li t2, 2
sub t1, t1, t2
mul t0, t1, t1
li t1, 5
add t0, t1, t1
然而算術樹可能會很高很畸形,導致暫存器的數量不夠用,這時就得把數字放進記憶體暫時儲存了。
道友們煉氣時應該都有印象,藉助資料結構「棧(堆疊)」可以很輕鬆的計算後序表達式,若需要複習,可以看看下方的逐步操作回憶一下:
[3] # 遇數字 3,壓入棧
[3, 4] # 遇數字 4,壓入棧
[3, 4, 2] # 遇數字 2,壓入棧
[3, 2] # 遇運算 -,計算 4 - 2 = 2,將結果壓入棧
[6] # 遇運算 *,計算 3 * 2 = 6,將結果壓入棧
[6, 5] # 遇數字 5,壓入棧
[11] # 遇運算 +,計算 6 + 5 = 11,將結果壓入棧
把所有運算都存到棧裡是很慢的,畢竟一次記憶體存取可能比是存取暫存器要慢上幾十乃至上百倍,即使快取命中也要好幾個 cycle 才拿得回來,但堆疊機實現起來容易。就先暫且把暫存器分配問題丟到一邊,來看看在精五真言中如何模擬堆疊機。
精五(RISC-V)架構的棧一般來說是從高位址往低位址成長的。棧頂位址由暫存器 sp 記錄,addi sp, sp, -8
可擴大棧,相反地,addi sp, sp, 8
會縮小棧。
以下精五真言將數字 3 壓入棧
addi sp, sp, -8 # 擴大棧 64 位元(8 位元組 = 64 位元)
li t0, 3 # t0 = 3
sd t0, 0(sp) # sd 將 64 位元 t0 存到棧頂
最後來看上述算術樹轉成堆疊機操作的完整精五真言:
# 3 入棧
addi sp, sp, -8
li t0, 3
sd t0, 0(sp)
# 4 入棧
addi sp, sp, -8
li t0, 4
sd t0, 0(sp)
# 2 入棧
addi sp, sp, -8
li t0, 2
sd t0, 0(sp)
# 減
ld t1, 0(sp) # t1 = 棧頂
addi sp, sp, 8 # 棧頂縮小 64 位元
ld t0, 0(sp) # t0 = 棧頂
sub t0, t0, t1 # t0 = t0 - t1
sd t0, 0(sp) # sd 將 64 位元 t0 存到棧頂
# 乘
ld t1, 0(sp)
addi sp, sp, 8
ld t0, 0(sp)
mul t0, t0, t1
sd t0, 0(sp)
# 5 入棧
addi sp, sp, -8
li t0, 5
sd t0, 0(sp)
# 加
ld t1, 0(sp)
addi sp, sp, 8
ld t0, 0(sp)
add t0, t0, t1
sd t0, 0(sp)