編譯原理實驗七:中間代碼生成器

實現一門語言的中間代碼生成器(4小時)

 

實驗目的

通過本次實驗,加深對中間代碼生成的理解,學會編制中間代碼生成器。

實驗任務

用C、JAVA或其他語言編寫一門語言的中間代碼生成器,所選實現語言應與之前語言保持一致。

實驗內容

  1. 實現中間代碼生成器,可以將任一源語言(源語言儘量與前期實驗中的源語言保持一致)轉化成三地址碼(或其他中間表示形式)。
  2. 準備2~3個測試用例,測試你的程序,並解釋生成的中間代碼。

 

源代碼下載和說明

鏈接:https://pan.baidu.com/s/1Ogf4447oPMrxHVJE8_hwmg

密碼:fc7f

運行方法:同實驗一TINY編譯器(這其實就是實驗一的工程)

 

說明:實驗七中間代碼生成器直接使用了TINY語言。在2018-2019年秋季學期,湖南大學編譯原理課首次將本實驗變爲必做(之前是選做,但由於難度太大,基本沒有學長學姐寫),故本實驗採用已有的代碼。

 

實驗知識點講解和函數源代碼分析

 

1、中間代碼生成的任務

 

中間代碼生成屬於編譯器前端結構的最後一部分,首先,編譯器前端讀入代碼,對代碼進行詞法分析,構建出符號序列,再將符號序列傳入語法分析,構造出語法樹;接下來的語義分析則是一個靜態檢查的過程,它判斷上下文的各個結點是否符合語法規則,並報錯,生成符號表,而接下來的中間代碼生成則也是對於語法樹進行操作,傳入一棵語法樹,從根結點,根據該節點的詞法屬性,分析詞法結點之間的邏輯,翻譯成合適的中間表示。

在TINY語言中,需要將NO_CODE標記位設置爲真,這樣就能夠輸出中間代碼的生成結果。

2、實現TINY語言的中間代碼生成器

 

本次實驗要求實現一箇中間代碼生成器,則我們採用的方法是增量編程,在之前所構造好的TINY前期組件基礎之上,構建中間代碼的生成部分。

 

2.1 TINY語言的中間代碼生成結果——TM CODE

 

TINY語言可以被翻譯爲一個適用於TM虛擬機環境的代碼表示:TM CODE。TM CODE其實是類似於彙編指令的程序語言,但是何其不同的地方在於,TM CODE可以在爲TINY語言所構建的虛擬機中運行,它模擬了彙編代碼中的一些特性,比如說寄存器操作,它在CODE.H頭文件中定義了幾個寄存器的值(地址),使得這樣的一種基於寄存器操作的類彙編語言能夠執行。

T機的模擬程序直接從一個文件中讀取彙編代碼並執行它,因此應避免將由彙編語言翻譯爲機器代碼的過程複雜化。但是,這個模擬程序並非是一個真正的彙編程序,它沒有符號地址或標號。因此,TINY編譯器必須仍然計算跳轉的絕對地址。此外爲了避免與外部的輸入/輸出例程連接的複雜性,TM機有內部整型的I/O設備;在模擬時,它們都對標準設備讀寫。

下圖展示了TM CODE的詳細定義:

我們注意到裝入操作中有3個地址模式並且是由不同的指令給出的:LDC是“裝入常量”,LD是“由存儲器裝入”,而LDA是“裝入地址”。另外,該地址通常必須給成“寄存器+偏差”值。例如“10(1)”(上面代碼的第2條指令),它代表在將偏差10加到寄存器1的內容中計算該地址。(因爲在前面的指令中,0已被裝入到寄存器1中,這實際是指絕對位置10)。我們還看到算術指令MUL和ADD可以是“三元”指令且只有寄存器操作數,其中可以單獨確定結果的目標寄存器。

 

2.2 MAIN函數調用入口及文件預處理

 

如圖代碼展示了MAIN函數的文件預處理和中間代碼生成的調用入口:

第一步是文件的預處理。

strcspn函數的作用是,在pgm字符串中查找到第一個“.”,並返回它之前的所有字符作爲子串,這樣做的目的在於,我們傳入給編譯器的文件是一個文件,我們中間代碼的輸出結果也需要保存在一個文件中,輸出文件在這裏這樣做,是爲了保持和輸入文件同名。

strncpy函數的作用是拷貝字符串,這裏用於將strcspn提取的文件名存入字符串供輸出文件使用。

接下來程序使用提取的文件名創建了一個.TM文件,作爲中間代碼的輸出,並打開它,賦予其“w”寫的權限,並執行中間代碼生成的後續操作。

【這裏大家注意一下,運行完程序之後,文件夾裏面會出現一個.TM結尾的文件,用記事本打開,就是其生成的TM CODE,也就是運行結果】

第二步就是中間代碼生成,它調用了codeGen函數,傳入了語法樹根結點以及需要寫入的文件,進行後續操作,下面代碼所展示的是該函數:

這個代碼主要是調用了emitRM,emitComment函數插入了TM虛擬機的初始化指令,其中這些函數傳入的都是mp、ac等定義好的寄存器,這個在code.H中有詳細定義:

TM虛擬機不是一開始就能運行中間代碼的,需要一些初始化的條件,在插入完這些指令之後,就會到達一個正式的cGen的代碼生成過程,待到cGen函數執行完畢,繼續需要插入一條停機指令HALT代表代碼執行完畢。

 

2.3 代碼生成的具體過程

 

下圖展示的是cGen函數的代碼:

可以看到,傳入的語法樹在一邊遍歷的同時,檢查結點的類型,TINY語言中分爲兩種語句結點,一種是帶有關鍵字的保留語句,一種是表達式,比如賦值或者是算式(總之,不帶有保留關鍵字),這兩種情況分開考慮。

 

1、getStmt——分析含有保留關鍵字語句的函數

該函數的作用主要是處理TINY語言中所包含的五個關鍵字——if,repeat,assign,read,write。

這裏以if作爲一個例子來分析說明這個函數需要做的工作:if結點包括三個子結點,if本身的判斷表達式、then、以及else(通常,else可以被省略)。我們對於每個if的子結點遞歸分析(因爲if的子結點可能也會是一個表達式,比如if的條件判斷,這樣的話就需要對它進行遞歸分析)。

用savedLoc變量記錄遞歸的返回位置,待分析完這一條路徑之後,就可以找到函數在哪裏被調用了,在調用的過程中,由於各個節點都需要遞歸地訪問,因此這裏在處理下一個節點的時候,使用了emitSkip這個函數,用於跳過並保存當前點的位置,以便於函數最後的返回工作。【這個地方,也叫作回填】

其他的處理也是類似的,比如在repeat語句裏面,repeat包含的是兩個結點,一個是repeat它本身,第二個是與之對應的until條件,同if一樣,分爲兩塊進行分別的一個遞歸處理。

其他三個關鍵字分別是assign,read,write。這三個的處理比較簡單,因爲他們的語句結構決定了他們只有一個子結點,因此,直接處理子結點就可以了。

如何處理子結點呢?

如圖所示,我們獲取了結點的類型,也能夠獲取結點的邏輯關係,此時,只要調用剛剛提到的函數emit,就可以將這條指令寫到輸出文件中,使得最後的TM虛擬機能夠執行完成。

 

2、getExp——處理表達式

 

處理表達式結點的邏輯比較簡單,如果表達式的結點類型是ID或者數字,那麼直接使用LD命令加載它即可。

如果結點是一個運算符,那麼據我們所知,運算符是由子結點構成的,它的子結點就是運算的兩個數字,或者是ID字符。我們需要使用LD命令將子結點裏面的具體數字或字符讀取出來,再根據運算符的類型構造相應的TM code命令。

下圖展示的是小於和等於的命令TM code:

運行結果

 

輸入數據:(文件名:SAMPLE.TNY)

{ Sample program
  in TINY language -
  computes factorial
}
read x; { input an integer }
if 0 < x then { don't compute if x <= 0 }
  fact := 1;
  repeat
    fact := fact * x;
    x := x - 1
  until x = 0;
  write fact  { output factorial of x }
end

輸出:(文件名:SAMPLE.TM)

* Standard prelude:
  0:     LD  6,0(0) 	load maxaddress from location 0
  1:     ST  0,0(0) 	clear location 0
* End of standard prelude.
  2:     IN  0,0,0 	read integer value
  3:     ST  0,0(5) 	read: store value
* -> if
* -> Op
* -> Const
  4:    LDC  0,0(0) 	load const
* <- Const
  5:     ST  0,0(6) 	op: push left
* -> Id
  6:     LD  0,0(5) 	load id value
* <- Id
  7:     LD  1,0(6) 	op: load left
  8:    SUB  0,1,0 	op <
  9:    JLT  0,2(7) 	br if true
 10:    LDC  0,0(0) 	false case
 11:    LDA  7,1(7) 	unconditional jmp
 12:    LDC  0,1(0) 	true case
* <- Op
* if: jump to else belongs here
* -> assign
* -> Const
 14:    LDC  0,1(0) 	load const
* <- Const
 15:     ST  0,1(5) 	assign: store value
* <- assign
* -> repeat
* repeat: jump after body comes back here
* -> assign
* -> Op
* -> Id
 16:     LD  0,1(5) 	load id value
* <- Id
 17:     ST  0,0(6) 	op: push left
* -> Id
 18:     LD  0,0(5) 	load id value
* <- Id
 19:     LD  1,0(6) 	op: load left
 20:    MUL  0,1,0 	op *
* <- Op
 21:     ST  0,1(5) 	assign: store value
* <- assign
* -> assign
* -> Op
* -> Id
 22:     LD  0,0(5) 	load id value
* <- Id
 23:     ST  0,0(6) 	op: push left
* -> Const
 24:    LDC  0,1(0) 	load const

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章