From a272c8fec653ec320d4e8b210424c4b28c36cbc5 Mon Sep 17 00:00:00 2001 From: MROS Date: Thu, 3 Oct 2024 23:35:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=8B=E7=B4=B9=E5=A0=86=E7=96=8A=E6=A9=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...37\350\250\200\347\224\237\346\210\220.md" | 109 +++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git "a/book/\351\233\266\357\274\216\344\270\200\347\211\210/\347\262\276\344\272\224\347\234\237\350\250\200\347\224\237\346\210\220.md" "b/book/\351\233\266\357\274\216\344\270\200\347\211\210/\347\262\276\344\272\224\347\234\237\350\250\200\347\224\237\346\210\220.md" index b37d6c9..4b70f7e 100644 --- "a/book/\351\233\266\357\274\216\344\270\200\347\211\210/\347\262\276\344\272\224\347\234\237\350\250\200\347\224\237\346\210\220.md" +++ "b/book/\351\233\266\357\274\216\344\270\200\347\211\210/\347\262\276\344\272\224\347\234\237\350\250\200\347\224\237\346\210\220.md" @@ -7,7 +7,7 @@ ### 精五真言的數據段 -很容易就能用精五真言設定全域變數,一下是範例`數據段.S`: +很容易就能用精五真言設定全域變數,以下是範例`數據段.S`: ```assembly .section .data @@ -32,7 +32,7 @@ qemu-riscv64 a.out # qemu-riscv64 並非單單模擬裸 echo $? # 可以看到上一個程序的結束碼是 50 ``` -`數據段.S` 首先在數據段 `.section .data` ,中用 `.word` 開闢 32 位元的空間,前方的`甲`是一個標籤,在後續程式段的真言中會被代換為該空間的位址。 +`數據段.S` 首先在數據段 `.section .data` 用 `.word` 開闢 32 位元的空間,前方的`甲`是一個標籤,在後續程式段的真言中會被代換為該空間的位址。 再來看 `_start` 的第一行 `lw t0, 甲`,lw 是 load word 的縮寫,效果是從位址甲開始讀取 32 位元放進 t0 暫存器。 `lw` 執行完後, `t0` 就是 `50` 了,最後 `mv a0, t0` 把程序結束碼設為 50。 @@ -65,3 +65,108 @@ sw rd, 標籤[31:12](rd) # 把被汙染的值寫到記憶體 所以 `sw rd, 標籤, rt` 的第三個參數 rt 省不了。 ## 運算 + +在語法樹中,`算式`是一棵二元樹,要從樹形轉換為循序、線性的真言並不是一目瞭然的事情。若先將算術二元樹轉換為後序表達式,計算的順序會比較明瞭一些。道友們在煉氣(學習編程基礎、資料結構)時,想必對後序表達式不陌生,後序拜訪二元樹就能得到。 + +```虛擬碼 +後序拜訪(樹) { + 後序拜訪(樹.左子樹) + 後序拜訪(樹.右子樹) + 打印(樹.值) +} +``` + +舉個例子: + +``` + + + / \ + * 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 壓入棧 +```assembly +addi sp, sp, -8 # 擴大棧 64 位元(8 位元組 = 64 位元) +li t0, 3 # t0 = 3 +sd t0, 0(sp) # sd 將 64 位元 t0 存到棧頂 +``` + +最後來看上述算術樹轉成堆疊機操作的完整精五真言: + +```assembly +# 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) +```