編譯器設計-代碼生成
Compiler Design - Code Generation
代碼生成可以看作是編譯的最後階段。通過後代碼生成,優化過程可以應用到代碼上,但這可以看作是代碼生成階段本身的一部分。編譯器生成的代碼是一些低級編程語言(例如彙編語言)的目標代碼。我們已經看到,用高級語言編寫的源代碼被轉換爲低級語言,從而生成低級目標代碼,該目標代碼應至少具有以下屬性:
它應該具有源代碼的確切含義。
它應該在CPU使用和內存管理方面是高效的。
現在我們將看到如何將中間代碼轉換爲目標對象代碼(在本例中是彙編代碼)。
有向無環圖Directed Acyclic Graph
有向無環圖(DAG)是描述基本塊結構的工具,有助於看到基本塊之間的值流,並提供優化。DAG提供了對基本塊的簡單轉換。DAG可以理解爲:
葉節點表示標識符、名稱或常量。
內部節點表示運算符。
內部節點還表示表達式的結果或要存儲或分配值的標識符/名稱。
Example:
t0 = a + b
t1 = t0 + c
d = t0 + t1
窺視孔優化Peephole Optimization
這種優化技術在源代碼的本地工作,將其轉換爲優化的代碼。在本地,我們指的是手邊代碼塊的一小部分。這些方法可以應用於中間碼和目標碼。對一組語句進行分析,並檢查是否存在以下可能的優化:
冗餘指令消除Redundant instruction elimination
在源代碼級別,用戶可以執行以下操作:
在編譯級,編譯器搜索本質上多餘的指令。指令的多次加載和存儲可能具有相同的含義,即使其中一些指令已被刪除。例如:
移動x,R0
移動R0,R1
我們可以刪除第一條指令,將句子改寫爲:
MOV x, R1
無法訪問的代碼Unreachable
code
不可訪問代碼是程序代碼的一部分,由於編程結構的原因,它永遠不會被訪問。程序員可能不小心編寫了一段永遠無法訪問的代碼。
Example:
void add_ten(int x){ return x + 10; printf(“value of x is %d”, x);}
在這個代碼段中,printf語句永遠不會被執行,因爲程序控件在執行之前返回,因此printf可以被刪除。
控制優化流程
代碼中存在程序控件在不執行任何重要任務的情況下來回跳轉的實例。這些跳躍可以被移除。請考慮以下代碼塊:
…
MOV R1, R2GOTO L1…L1 : GOTO L2L2 : INC R1
在這段代碼中,標籤L1可以在將控件傳遞給L2時被移除。因此,控件不必跳到L1然後跳到L2,而是可以直接到達L2,如下所示:
…
MOV R1, R2GOTO L2…L2 : INC R1
代數表達式簡化Algebraic expression simplification
有時代數表達式可以變得簡單。例如,表達式a=a+0可以由自身替換,表達式a=a+1可以簡單地由INC a替換。
強度折減Strength reduction
有些操作會消耗更多的時間和空間。它們的“強度”可以通過用其他消耗更少時間和空間但產生相同結果的操作來代替它們而降低。
例如,x2可以替換爲x<1,這隻涉及一個左移位。雖然aa和a2的輸出是相同的,但是a2的實現效率要高得多。
訪問機器指令Accessing machine instructions
目標機器可以部署更復雜的指令,這些指令能夠更有效地執行特定操作。如果目標代碼能夠直接容納這些指令,不僅可以提高代碼質量,而且可以產生更有效的結果。
代碼生成器Code Generator
代碼生成器應瞭解目標計算機的運行時環境及其指令集。代碼生成器生成代碼時應考慮以下事項:
目標語言:代碼生成器必須知道要轉換代碼的目標語言的性質。這種語言可以幫助一些特定於機器的指令,幫助編譯器以更方便的方式生成代碼。目標計算機可以具有CISC或RISC處理器體系結構。
IR類型:中間表示有多種形式。它可以是抽象語法樹(AST)結構、反向波蘭符號或3地址碼。
指令選擇:代碼生成器以中間表示作爲輸入,並將其轉換(映射)爲目標機器的指令集。一個表示可以有多種方法(指令)來轉換它,因此代碼生成器有責任明智地選擇適當的指令。
寄存器分配:程序在執行期間有許多值要維護。目標機器的體系結構可能不允許將所有值保存在CPU內存或寄存器中。代碼生成器決定在寄存器中保留哪些值。此外,它還決定要用來保存這些值的寄存器。
指令排序:最後,代碼生成器決定指令的執行順序。它爲執行指令創建時間表。
描述符 Descriptors
代碼生成器在生成代碼時必須同時跟蹤寄存器(以獲取可用性)和地址(值的位置)。對於這兩種情況,使用以下兩個描述符:
寄存器描述符:寄存器描述符用於通知代碼生成器寄存器的可用性。寄存器描述符跟蹤存儲在每個寄存器中的值。每當在代碼生成過程中需要新的寄存器時,都會參考該描述符以獲得寄存器的可用性。
地址描述符:程序中使用的名稱(標識符)的值在執行時可能存儲在不同的位置。地址描述符用於跟蹤存儲標識符值的內存位置。這些位置可以包括CPU寄存器、堆、棧、存儲器或所述位置的組合。
代碼生成器會實時更新這兩個描述符。對於load語句LD R1,x,代碼生成器:
更新值爲x和的寄存器描述符R1
更新地址描述符(x)以顯示x的一個實例在R1中。
代碼生成Code Generation
基本塊由三個地址指令序列組成。代碼生成器將這些指令序列作爲輸入。
注意:如果一個名稱的值出現在多個位置(寄存器、緩存或內存),則寄存器的值將優先於緩存和主內存。同樣,緩存的值將優先於主內存。主存幾乎沒有任何偏好。
getReg:代碼生成器使用getReg函數來確定可用寄存器的狀態和名稱值的位置。getReg的工作原理如下:
如果變量Y已經在寄存器R中,則使用該寄存器。
否則,如果某個寄存器R可用,它將使用該寄存器。
否則,如果上述兩個選項都不可行,它將選擇需要最少加載和存儲指令數的寄存器。
對於指令x=y OP z,代碼生成器可以執行以下操作。假設L是保存y OP z的輸出的位置(最好是寄存器):
調用函數getReg,決定L的位置。
通過查詢y的地址描述符確定y的當前位置(寄存器或內存)。如果y當前不在寄存器L中,則生成以下指令將y的值複製到L:
MOV y’, L
其中y’表示y的複製值。
使用步驟2中用於y的相同方法確定z的當前位置,並生成以下指令:
OP z’, L
其中z’表示z的複製值。
現在L包含了y OP z的值,這個值是要賦給x的。所以,如果L是寄存器,更新它的描述符以表示它包含了x的值。更新x的描述符以表示它存儲在位置L。
如果y和z沒有進一步的用途,它們可以返回給系統。
其他的代碼結構,如循環和條件語句,則以一般的彙編方式轉換爲彙編語言。