File tree Expand file tree Collapse file tree 5 files changed +113
-12
lines changed
Expand file tree Collapse file tree 5 files changed +113
-12
lines changed Original file line number Diff line number Diff line change @@ -90,8 +90,12 @@ export default defineConfig({
9090 link : "/零.二版/語義分析:類型檢查.md" ,
9191 } ,
9292 {
93- text : "精五真言生成(一)施術" ,
94- link : "/零.二版/精五真言生成(一)施術.md" ,
93+ text : "精五真言生成(一)棧與區域變數" ,
94+ link : "/零.二版/精五真言生成(一)棧與區域變數.md" ,
95+ } ,
96+ {
97+ text : "精五真言生成(二)術的施展" ,
98+ link : "/零.二版/精五真言生成(二)術的施展.md" ,
9599 } ,
96100 ] ,
97101 } ,
Original file line number Diff line number Diff line change 1-
2-
3- ## 零.二版引入
4-
1+ ## 術
52### 術
63函式
74
85### 外術
96外部函式
107
8+ ### 施者
9+ caller
10+
11+ ### 受者
12+ callee
13+
14+ ## 符號
1115### 基括號
1216〖〗
Original file line number Diff line number Diff line change 44
55但零.二版引入了術之後,一切都變了,術內宣告的變數都是區域變數;而程式碼在施術後會跳到另一個術裡執行,完成後又回到原本的術裡。
66
7- ## 棧與術
7+ ## 區域變數
88
9- 由於在咒執行的過程中 ,一個術可能被施展多次,最簡單的例子就是遞迴術,術中有術,層層嵌套。由於執行次數可能取決於法咒的輸入,編譯器無法知曉究竟一個術在法咒執行過程中究竟會執行幾次,因此不可能在編譯期就將記憶體分配完 。
9+ 咒執行的過程中 ,一個術可能被施展多次,最簡單的例子就是遞迴術,術中有術,層層嵌套。由於執行次數可能取決於法咒的輸入,編譯器無法知曉究竟一個術在法咒執行過程中究竟會執行幾次,因此不可能在編譯期就將樹中區域變數的記憶體分配完 。
1010
1111那只好在執行期動態分配記憶體了,分配到哪裡呢?[ 老樣子] ( ../零.一版/精五真言生成.md ) ,放到棧上,畢竟術的施展天然就像棧這種資料結構。
1212
4141
4242當計算機執行術時,會將計算機的咒指針(program counter)指向術的開頭,隨著咒指針遞增,術就一行行執行下去了,當甲術執行到要施展乙術後,乙必須先記錄當下的施者(caller)——也就是甲——施術時的咒指針,在乙術結束時,才能夠再跳回甲施乙術後的下一條真言執行。
4343
44- ### 臨時變數
44+ ## 臨時變數
4545注意到` 乙 ` 術的最後一行 ` 曰(宇+宙+洪+荒) ` ,回憶在[ 零.一版] ( ../零.一版/精五真言生成.md ) 中,計算算式時,吾人會將計算機當成一個堆疊機來用,在棧上開出更多空間以存放計算的中間結果。
4646
4747這就意味著單純採用堆疊機,且不進行優化,是沒法僅在棧中開四個變數的空間就完成計算。
4848
4949這有幾種不同的作法:
5050
51- #### 編譯器計算出臨時變數需要的空間
51+ ### 編譯器計算出臨時變數需要的空間
5252例如,編譯器能夠直接計算出需要幾個臨時變數,例如可以將原` 乙 ` 術在中間碼就轉換成如下邏輯
5353
5454```
6868```
6969則可以配出七個整數的空間。(此非最優解)
7070
71- #### 棧動態增長
71+ ### 棧動態增長
7272也可以如零.一版一樣,一邊計算一邊把臨時結果壓入棧,實作可能簡單一些。但最終仍要把棧恢復到舊貌,也就是說仍然要記錄棧到底增長了多少,或者採用 ` fp ` 暫存器來記錄當下的棧底為多少。
7373
7474臨時增長棧也會導致用 ` sp ` 來索引區域變數的位址會不太穩定,以 ` fp ` 來索引實作會比較容易。(棧臨時增長時,` sp ` 動,但` fp ` 不動)
@@ -194,7 +194,6 @@ gcc 可以用 `-fomit-frame-pointer` 來調控是否採用 `fp` 的棧形式,`
194194 ret # jr ra
195195
196196```
197- 全在暫存器就算完了。
198197
199198再來看看乙那段將宇、宙、洪、荒相加的算式,若編譯器好好優化,會怎麽計算:
200199
Original file line number Diff line number Diff line change 1+ 在上一章討論了區域變數如何存放在棧中,在[ 零.一版] ( ../零.一版/精五真言生成.md ) 中提及了全域變數如何生成。現在,還剩下一種變數沒有討論——** 參數** 。
2+
3+ ## 精五真言施術約定
4+ 精五有 32 個通用暫存器,施術時 ` call 術 ` 這個偽指令做的不過是將咒指針(program counter)跳躍到` 術 ` 所在位址,把就位址放到 ` ra ` 暫存器而已。
5+
6+ 施者(caller)如何傳遞資訊給受者(callee)呢?其實怎樣都可以,無論是丟到棧上的某個位置還是丟到特定暫存器都可以。總之** 講好就好** 。如果不同編譯器不按照同個約定來傳遞參數,那這些編譯器編譯出來的術就無法互相調用了。
7+
8+ 精五的[ 標準施展約定] ( https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf ) 規定在整數情況下,使用 ` a0 ` ~ ` a7 ` 暫存器來傳遞變數。這些暫存器吾人稱之為參數暫存器。(a 即是 argument 的縮寫)
9+
10+ 而術結束後,會將其歸值(return value)放在 ` a0 ` 與 ` a1 ` ,歸值若僅一個字長,只要看 ` a0 ` 就好了。
11+
12+ 貧道用 gcc 編譯個簡單的 C 程式(音界咒當前版本也依賴類似外術),來看看是否按照這個約定就能呼叫到它了。
13+
14+ ## 編譯外術(外部函式)
15+
16+ 將以下 C 代碼存入 ` 曰.c ` 檔案中。
17+
18+ ``` c
19+ #include < stdio.h>
20+ #include < inttypes.h>
21+
22+ int64_t print_int (int64_t number) {
23+ printf("%" PRId64 "\n", number);
24+ return 111;
25+ }
26+ ```
27+
28+ 執行以下指令來編譯該檔案
29+ ```sh
30+ riscv64-unknown-elf-gcc -c 曰.c -o 曰.o
31+ riscv64-unknown-elf-objcopy --redefine-sym print_int=曰 曰.o
32+ ```
33+
34+ C 語言不支援非 ASCII 的字符,得先編譯出目的檔 ` 曰.o ` 之後,再將其符號從 ` print_int ` 抽換成 ` 曰 ` 。
35+
36+ ## 施術範例
37+
38+ 再將以下檔案存到 ` 施展.S ` 。
39+
40+ ``` assembly
41+ .section .text
42+ .global main
43+
44+ main:
45+
46+ li a0, 17
47+ call 曰
48+ call 曰
49+
50+ li a7, 93 # ecall號碼93表示退出
51+ li a0, 0
52+ ecall
53+ ```
54+
55+ 用以下指令組譯 ` 施展.S ` 並鏈結` 曰.o `
56+ ```
57+ riscv64-unknown-elf-gcc 施展.S 曰.o
58+ ```
59+ 再以 qemu 執行生成的執行檔 ` a.out `
60+
61+ ```
62+ qemu-riscv64 a.out
63+ ```
64+ 會看到終端輸出
65+ ```
66+ 17
67+ 111
68+ ```
69+ 第一行 ` 17 ` 正是吾人放進 ` a0 ` 的值,而第二行的 ` 111 ` 恰好就是曰的歸值,第一個 ` call 曰 ` 結束之後,` a0 ` 已經被改為 ` 111 ` ,立刻再施展一次 ` 曰 ` ,果然輸出了 ` 111 ` 。
70+
71+ 看來 gcc 確實按照這個約定在傳遞參數與回傳值,音界咒編譯器也遵照約定,以在未能完全自舉之前方便與塵界之術互動。
72+
73+ ## 儲存參數
74+
75+ 既然在術開始之前,參數就已被施者填入 ` a0 ` ~ ` a7 ` 之中,那能否每次需要讀取參數時就直接使用暫存器呢?不能,因為術有可能還要去調用其他術,這是又得修改 ` a0 ` ~ ` a7 ` 來傳遞參數了。
76+
77+ 還是只能老方法全都存到棧裡了。
78+
79+ 如果不需要再調用其他術,大可以就放在參數暫存器裡,但零.二版音界咒的實作先不做這個優化。
80+
81+ 來看個範例:
82+
83+ ``` 音界
84+ 術.甲(子、丑)【
85+ 元.天=1
86+ 元.地=1
87+ 元.玄=1
88+ 元.黃=1
89+ 】
90+ ```
91+
92+ 執行後,棧應該要是
93+
94+ ![ 加入參數的精五棧圖解] ( ../image/參數精五棧圖解.png )
You can’t perform that action at this time.
0 commit comments