Skip to content

Commit dda2021

Browse files
committed
介紹 RISC-V 的棧
1 parent d57ffbe commit dda2021

File tree

5 files changed

+94
-1
lines changed

5 files changed

+94
-1
lines changed

book/.vitepress/config.mts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ export default defineConfig({
8787
text: "語義分析:類型檢查",
8888
link: "/零.二版/語義分析:類型檢查.md",
8989
},
90+
{
91+
text: "精五真言生成(一)施術",
92+
link: "/零.二版/精五真言生成(一)施術.md",
93+
},
9094
],
9195
},
9296
{

book/image/精五棧圖解.png

65.2 KB
Loading

book/image/精五棧圖解fp.png

85.8 KB
Loading
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
執行檔所定義的事情,不外乎**數據如何存放****程式如何執行**
2+
3+
在零.一版中,所有的變數都是全域變數,程式執行也是一行一行往下。
4+
5+
但零.二版引入了術之後,一切都變了,術內宣告的變數都是區域變數;而程式碼在施術後會跳到另一個術裡執行,完成後又回到原本的術裡。
6+
7+
## 棧與術
8+
9+
由於在咒執行的過程中,一個術可能被施展多次,最簡單的例子就是遞迴術,術中有術,層層嵌套。由於執行次數可能取決於法咒的輸入,編譯器無法知曉究竟一個術在法咒執行過程中究竟會執行幾次,因此不可能在編譯期就將記憶體分配完。
10+
11+
那只好在執行期動態分配記憶體了,分配到哪裡呢?[老樣子](../零.一版/精五真言生成.md),放到棧上,畢竟術的施展天然就像棧這種資料結構。
12+
13+
```音界
14+
術.甲()【
15+
16+
元.天=1
17+
元.地=1
18+
元.玄=1
19+
元.黃=1
20+
21+
乙()
22+
23+
術.乙()【
24+
25+
元.宇=1
26+
元.宙=1
27+
元.洪=1
28+
元.荒=1
29+
30+
曰(宇+宙+洪+荒) // 曰是外術(外部函式)
31+
32+
```
33+
34+
以下分別圖示剛施展甲術,以及甲術呼叫乙術之後的棧的樣子。
35+
36+
圖左為剛施展甲術,未施展乙術;圖右為甲術施展乙術後:
37+
38+
![精五棧圖解](../image/精五棧圖解.png)
39+
40+
圖中的區域變數對應甲、乙術中宣告的變數,很直觀能理解。但每個術在棧中還額外佔用了一塊空間「返回位址」,這是做什麼的呢?
41+
42+
當計算機執行術時,會將計算機的咒指針(program counter)指向術的開頭,隨著咒指針遞增,術就一行行執行下去了,當甲術執行到要施展乙術後,乙必須先記錄當下的施者(caller)——也就是甲——施術時的咒指針,在乙術結束時,才能夠再跳回甲施乙術後的下一條真言執行。
43+
44+
### 臨時變數
45+
注意到``術的最後一行 `曰(宇+宙+洪+荒)` ,回憶在[零.一版](../零.一版/精五真言生成.md)中,計算算式時,吾人會將計算機當成一個堆疊機來用,在棧上開出更多空間以存放計算的中間結果。
46+
47+
這就意味著單純採用堆疊機,且不進行優化,是沒法僅在棧中開四個變數的空間就完成計算。
48+
49+
這有幾種不同的作法:
50+
51+
#### 編譯器計算出臨時變數需要的空間
52+
例如,編譯器能夠直接計算出需要幾個臨時變數,例如可以將原``術在中間碼就轉換成如下邏輯
53+
54+
```
55+
術.乙()【
56+
57+
元.宇=1
58+
元.宙=1
59+
元.洪=1
60+
元.荒=1
61+
62+
元.和1=宇+宙
63+
元.和2=洪+荒
64+
元.和=和1+和2
65+
66+
曰(和)
67+
68+
```
69+
則可以配出七個整數的空間。(此非最優解)
70+
71+
#### 棧動態增長
72+
也可以如零.一版一樣,一邊計算一邊把臨時結果壓入棧,實作可能簡單一些。但最終仍要把棧恢復到舊貌,也就是說仍然要記錄棧到底增長了多少,或者採用 `fp` 暫存器來記錄當下的棧底為多少。
73+
74+
使用 `fp` 的棧會形如:
75+
76+
![精五棧圖解](../image/精五棧圖解fp.png)
77+
78+
但無論如何,臨時增長棧就意味著修改 `sp`,在執行期改暫存器會造成額外開銷,因此在實務中不太會採用這種策略。
79+
80+
gcc 可以用 `-fomit-frame-pointer` 來調控是否採用 `fp` 的棧形式,`fp`在棧中構成一個鏈結串列,故採用 `fp` 能輕鬆的追蹤施術鏈(函式調用鏈)。
81+
82+
方便除錯也是設計 `fp` 的主要目的,只是剛好用了它能讓實作棧動態增長變得容易些罷了。
83+
84+
不過要計算臨時變數也挺麻煩,先以記錄 `fp` 的方式來實作施術吧。
85+
86+
## 術的真言形式
87+
88+
來看看上述的甲、乙兩術翻成真言會是什麼樣子:
89+

book/零.二版/設計與概述.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@
132132

133133
## 外術(外部函式)
134134

135-
支援 `打印整數(數)` 此一外術,其接受一個整數作為參數,執行後會將該整數送進標準輸出。
135+
支援 `(數)` 此一外術,其接受一個整數作為參數,執行後會將該整數送進標準輸出。
136136

137137
## 法咒執行流程
138138

0 commit comments

Comments
 (0)