Skip to content

Commit 1d108b9

Browse files
committed
精五真言生成實作
1 parent a272c8f commit 1d108b9

1 file changed

Lines changed: 143 additions & 0 deletions

File tree

book/零.一版/精五真言生成.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,146 @@ sd t0, 0(sp) # sd 將 64 位元 t0 存到棧頂
170170
add t0, t0, t1
171171
sd t0, 0(sp)
172172
```
173+
174+
## 實作
175+
以下展示一份精五真言生成的實作。
176+
177+
這是生成器的定義,其接收一個語法樹,把精五真言輸入到真言檔。變數集可以在符號檢查時取得。
178+
```rust
179+
pub struct O真言生成器 {
180+
真言檔: File,
181+
語法樹: O語法樹,
182+
變數集: HashSet<String>,
183+
}
184+
185+
impl O真言生成器 {
186+
187+
pub fn 生成(&mut self) -> io::Result<()> {
188+
self.生成數據段()?;
189+
self.生成代碼段()
190+
}
191+
192+
...
193+
}
194+
```
195+
真言生成分為「數據段」跟「代碼段」分開實作。
196+
197+
### 數據段(變數儲存)
198+
199+
數據段的生成頗為簡單,為每個變數在 `.section .data` 製造一相應的標籤,以 `.quad` 指定 64 位元空間。
200+
201+
```rust
202+
fn 生成數據段(&mut self) -> io::Result<()> {
203+
writeln!(self.真言檔, ".section .data")?;
204+
self.生成變數標籤()
205+
}
206+
fn 生成變數標籤(&mut self) -> io::Result<()> {
207+
for 變數 in &self.變數集 {
208+
writeln!(self.真言檔, "{}:", 變數)?;
209+
// 初始值為 0
210+
// 初始值是多少不重要,通過符號檢查,代表每個變數使用前都會先賦值
211+
writeln!(self.真言檔, "\t.quad 0")?;
212+
}
213+
self.換行()
214+
}
215+
```
216+
217+
### (代碼段)運算
218+
219+
代碼段就相對複雜許多,原理已在前文解釋,道友們可閱讀註解來幫助理解。
220+
221+
這裡並沒有真的先轉換到後序表示法再生成代碼,而是對算術樹做後序遍歷的同時就把代碼給生成了。
222+
223+
```rust
224+
fn 生成代碼段(&mut self) -> io::Result<()> {
225+
writeln!(self.真言檔, ".section .text")?;
226+
// 編譯器會將某些 .data 段的變數存放到 .sdata 段
227+
// .sdata 段的數據可以直接用 gp 暫存器的相對位址得到
228+
// 會快一個指令,但 gp 初始化需要導引
229+
// 因此此處採用 main 而非 _start
230+
// gcc 編譯時不加 -nostdlib 參數,讓 gcc 生成 _start 協助引導
231+
writeln!(self.真言檔, ".global main")?;
232+
self.換行()?;
233+
writeln!(self.真言檔, "main:")?;
234+
let 語法樹 = &self.語法樹;
235+
236+
forin &語法樹.句 {
237+
match 句 {
238+
O句::變數宣告(變數宣告) => Self::賦值(&mut self.真言檔, &變數宣告)?,
239+
O句::算式(算式) => Self::計算(&mut self.真言檔, &算式)?,
240+
}
241+
}
242+
writeln!(self.真言檔, "# 結束")?;
243+
writeln!(self.真言檔, "\tli a7, 93")?; // RISCV Linux 中 exit 系統呼叫編號是 93
244+
writeln!(self.真言檔, "\tmv a0, t0")?; // a0 = t0
245+
writeln!(self.真言檔, "\tecall")?; // 執行系統呼叫 exit(t0)
246+
Ok(())
247+
}
248+
249+
fn 賦值(真言檔: &mut File, 變數宣告: &O變數宣告) -> io::Result<()> {
250+
Self::計算(真言檔, &變數宣告.算式)?;
251+
writeln!(真言檔, "# 賦值給 {}", &變數宣告.變數名)?;
252+
writeln!(真言檔, "\tsd t0, {}, s1", &變數宣告.變數名) // 存入變數所在記憶體
253+
}
254+
255+
// 計算結束後,結果置於 t0
256+
fn 計算(真言檔: &mut File, 算式: &O算式) -> io::Result<()> {
257+
match 算式 {
258+
O算式::二元運算(二元運算) => {
259+
Self::計算(真言檔, 二元運算..as_ref())?;
260+
Self::計算(真言檔, 二元運算..as_ref())?;
261+
Self::二元運算(真言檔, &二元運算.運算子)
262+
}
263+
O算式::數字(數) => Self::數字入棧(真言檔, 數),
264+
O算式::變數(變數) => Self::變數入棧(真言檔, 變數),
265+
}
266+
}
267+
// 結束時,t0 = 數
268+
fn 數字入棧(真言檔: &mut File, 數: &i64) -> io::Result<()> {
269+
writeln!(真言檔, "# {} 入棧", 數)?;
270+
271+
writeln!(真言檔, "\taddi sp, sp, -8")?; // 增加棧 64 位元的空間
272+
writeln!(真言檔, "\tli t0, {}", 數)?; // 將 t0 設為「數」
273+
writeln!(真言檔, "\tsd t0, 0(sp)") // t0 放入棧頂
274+
}
275+
// 結束時,t0 = 變數
276+
fn 變數入棧(真言檔: &mut File, 變數: &String) -> io::Result<()> {
277+
writeln!(真言檔, "# 變數「{}」入棧", 變數)?;
278+
279+
writeln!(真言檔, "\taddi sp, sp, -8")?; // 增加棧 64 位元的空間
280+
writeln!(真言檔, "\tld t0, {}", 變數)?; // t0 = *(i64*)變數
281+
writeln!(真言檔, "\tsd t0, 0(sp)") // t0 放入棧頂
282+
}
283+
// 結束時,t0 = 二元運算結果
284+
fn 二元運算(真言檔: &mut File, 運算子: &O運算子) -> io::Result<()> {
285+
writeln!(真言檔, "# {:?}", 運算子)?;
286+
287+
writeln!(真言檔, "\tld t1, 0(sp)")?; // t1 = 棧頂
288+
writeln!(真言檔, "\taddi sp, sp, 8")?; // 縮小棧
289+
writeln!(真言檔, "\tld t0, 0(sp)")?; // t0 = 棧頂
290+
291+
match 運算子 {
292+
O運算子::=> {
293+
writeln!(真言檔, "\tadd t0, t0, t1")?;
294+
}
295+
O運算子::=> {
296+
writeln!(真言檔, "\tsub t0, t0, t1")?;
297+
}
298+
O運算子::=> {
299+
writeln!(真言檔, "\tmul t0, t0, t1")?;
300+
}
301+
O運算子::=> {
302+
writeln!(真言檔, "\tdiv t0, t0, t1")?;
303+
}
304+
}
305+
306+
writeln!(真言檔, "\tsd t0, 0(sp)") // t0 放入棧頂
307+
}
308+
```
309+
310+
### 組譯執行
311+
312+
```
313+
riscv64-unknown-elf-gcc {{target}}.S # 不加 -nostdlib 參數
314+
qemu-riscv64 a.out
315+
```

0 commit comments

Comments
 (0)