iT邦幫忙

2024 iThome 鐵人賽

DAY 20
0

以下展示一份精五真言生成的實作。

這是生成器的定義,其接收一個語法樹,把精五真言輸入到真言檔。變數集可以在符號檢查時取得。

pub struct O真言生成器 {
    真言檔: File,
    語法樹: O語法樹,
    變數集: HashSet<String>,
}

impl O真言生成器 {

    pub fn 生成(&mut self) -> io::Result<()> {
        self.生成數據段()?;
        self.生成代碼段()
    }

	...
}

真言生成分為「數據段」跟「代碼段」分開實作。

數據段(變數儲存)

數據段的生成頗為簡單,為每個變數在 .section .data 製造一相應的標籤,以 .quad 指定 64 位元空間。

fn 生成數據段(&mut self) -> io::Result<()> {
	writeln!(self.真言檔, ".section .data")?;
	self.生成變數標籤()
}
fn 生成變數標籤(&mut self) -> io::Result<()> {
	for 變數 in &self.變數集 {
		writeln!(self.真言檔, "{}:", 變數)?;
		// 初始值為 0
		// 初始值是多少不重要,通過符號檢查,代表每個變數使用前都會先賦值
		writeln!(self.真言檔, "\t.quad 0")?;
	}
	self.換行()
}

(代碼段)運算

代碼段就相對複雜許多,原理已在前文解釋,道友們可閱讀註解來幫助理解。

這裡並沒有真的先轉換到後序表示法再生成代碼,而是對算術樹做後序遍歷的同時就把代碼給生成了。

fn 生成代碼段(&mut self) -> io::Result<()> {
	writeln!(self.真言檔, ".section .text")?;
	// 編譯器會將某些 .data 段的變數存放到 .sdata 段
	// .sdata 段的數據可以直接用 gp 暫存器的相對位址得到
	// 會快一個指令,但 gp 初始化需要導引
	// 因此此處採用 main 而非 _start
	// gcc 編譯時不加 -nostdlib 參數,讓 gcc 生成 _start 協助引導
	writeln!(self.真言檔, ".global main")?;
	self.換行()?;
	writeln!(self.真言檔, "main:")?;
	let 語法樹 = &self.語法樹;

	for 句 in &語法樹.句 {
		match 句 {
			O句::變數宣告(變數宣告) => Self::賦值(&mut self.真言檔, &變數宣告)?,
			O句::算式(算式) => Self::計算(&mut self.真言檔, &算式)?,
		}
	}
	writeln!(self.真言檔, "# 結束")?;
	writeln!(self.真言檔, "\tli a7, 93")?; // RISCV Linux 中 exit 系統呼叫編號是 93
	writeln!(self.真言檔, "\tmv a0, t0")?; // a0 = t0
	writeln!(self.真言檔, "\tecall")?; // 執行系統呼叫 exit(t0)
	Ok(())
}

fn 賦值(真言檔: &mut File, 變數宣告: &O變數宣告) -> io::Result<()> {
	Self::計算(真言檔, &變數宣告.算式)?;
	writeln!(真言檔, "# 賦值給 {}", &變數宣告.變數名)?;
	writeln!(真言檔, "\tsd t0, {}, s1", &變數宣告.變數名) // 存入變數所在記憶體
}

// 計算結束後,結果置於 t0
fn 計算(真言檔: &mut File, 算式: &O算式) -> io::Result<()> {
	match 算式 {
		O算式::二元運算(二元運算) => {
			Self::計算(真言檔, 二元運算.左.as_ref())?;
			Self::計算(真言檔, 二元運算.右.as_ref())?;
			Self::二元運算(真言檔, &二元運算.運算子)
		}
		O算式::數字(數) => Self::數字入棧(真言檔, 數),
		O算式::變數(變數) => Self::變數入棧(真言檔, 變數),
	}
}
// 結束時,t0 = 數
fn 數字入棧(真言檔: &mut File, 數: &i64) -> io::Result<()> {
	writeln!(真言檔, "# {} 入棧", 數)?;

	writeln!(真言檔, "\taddi sp, sp, -8")?; // 增加棧 64 位元的空間
	writeln!(真言檔, "\tli t0, {}", 數)?; // 將 t0 設為「數」
	writeln!(真言檔, "\tsd t0, 0(sp)") // t0 放入棧頂
}
// 結束時,t0 = 變數
fn 變數入棧(真言檔: &mut File, 變數: &String) -> io::Result<()> {
	writeln!(真言檔, "# 變數「{}」入棧", 變數)?;

	writeln!(真言檔, "\taddi sp, sp, -8")?; // 增加棧 64 位元的空間
	writeln!(真言檔, "\tld t0, {}", 變數)?; // t0 = *(i64*)變數
	writeln!(真言檔, "\tsd t0, 0(sp)") // t0 放入棧頂
}
// 結束時,t0 = 二元運算結果
fn 二元運算(真言檔: &mut File, 運算子: &O運算子) -> io::Result<()> {
	writeln!(真言檔, "# {:?}", 運算子)?;

	writeln!(真言檔, "\tld t1, 0(sp)")?; // t1 = 棧頂
	writeln!(真言檔, "\taddi sp, sp, 8")?; // 縮小棧
	writeln!(真言檔, "\tld t0, 0(sp)")?; // t0 = 棧頂

	match 運算子 {
		O運算子::加 => {
			writeln!(真言檔, "\tadd t0, t0, t1")?;
		}
		O運算子::減 => {
			writeln!(真言檔, "\tsub t0, t0, t1")?;
		}
		O運算子::乘 => {
			writeln!(真言檔, "\tmul t0, t0, t1")?;
		}
		O運算子::除 => {
			writeln!(真言檔, "\tdiv t0, t0, t1")?;
		}
	}

	writeln!(真言檔, "\tsd t0, 0(sp)") // t0 放入棧頂
}

組譯執行

riscv64-unknown-elf-gcc {{target}}.S  # 不加 -nostdlib 參數
qemu-riscv64 a.out

上一篇
零.一版精五真言生成(二)堆疊機
下一篇
零.一版優化之常數折疊
系列文
離塵指引.卷之一.試結丹:程式語言自舉31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言