【編譯原理系列】目的碼生成與優化
阿新 • • 發佈:2021-01-25
程式碼生成器的主要任務
一、指令選擇
- 選擇適當的目標機指令來實現中間表示(IR)語句,
- 例如:三地址語句 x = y + z
目的碼: LD R0, y /* 把y的值載入到暫存器R0中*/ ADD R0, R0, z /* z加到R0上*/ ST x, R0 /* 把R0的值儲存到x中*/
- 如上,直接進行生成,會產生一些冗餘指令。目的碼需要進一步優化。
二、暫存器分配和指派:
- 把哪個值放在哪個暫存器中
三、指令排序:
- 按照什麼順序來安排指令的執行
一個簡單的目標機模型
三地址機器模型:
- 載入、儲存、運算、跳轉等操作
- 記憶體按位元組定址
- n個通用暫存器
R
0
,
R
1
,
…
,
R
n
−
1
R_0, R_1, …, R_{n-1}
- 假設所有的運算分量都是整數
- 指令之間可能有一個標號
目標機器的主要指令
-
載入指令 LD dst, addr
- LD r, x
- LD r1, r2
-
儲存指令 ST x, r
-
運算指令 OP dst, src1, src2
-
無條件跳轉指令 BR L
-
條件跳轉指令 Bcond r, L
- 例: BLTZ r, L
定址模式
-
變數名a
- 例如:LD R1, a,即R1 = contents(a),將地址 a 中的內容放到暫存器 R1
-
a(r),a是一個變數,r是一個暫存器
- 例如:LD R1, a(R2),即R1 = contents(a + contents(R2))
-
c(r),c是一個整數
- 例如:LD R1, 100(R2),即R1 = contents(contents(R2) + 100),表示暫存器 R2 中存放的地址,加上整數c,其表示的地址所存放的內容。
-
∗r,在暫存器r的內容所表示的位置上存放的記憶體位置
- 例如:LD R1, *R2,即R1 = contents(contents(contents(R2))),這是間接定址模式
-
∗c(r),在暫存器r中內容加上c後所表示的位置上存放的記憶體位置
- 例如:LD R1, *100(R2),即R1 = contents(contents(contents(R2) + 100))
-
KaTeX parse error: Expected ‘EOF’, got ‘#’ at position 1: #̲c,
- 例如LD R1, #100,即R1 = 100。
指令選擇
運算語句的目的碼
-
三地址語句:x = y - z
-
目的碼:
LD R1 , y // R1 = y LD R2 , z // R2 = z SUB R1 , R1 , R2 // R1 = R1 - R2 ST x , R1 // x = R1
-
儘可能避免使用上面的全部四個指令,如果:
- 所需的運算分量已經在暫存器中了
- 運算結果不需要存放回記憶體
陣列定址語句的目的碼
- 三地址語句:b = a[ i ](a是一個實數陣列,每個實數佔8個位元組)
- 目的碼:
LD R1 , i // R1 = i MUL R1 , R1, 8 // R1=R1 * 8 LD R2 , a(R1) // R2=contents ( a + contents(R1) ) ST b , R2 // b = R2
- 三地址語句:a [ j ] = c(a是一個實數陣列,每個實數佔8個位元組)
- 目的碼:
LD R1 , c // R1 = c LD R2 , j // R2 = j MUL R2 , R2 , 8 //R2 = R2 * 8 ST a(R2) , R1 // contents(a+contents(R2))=R1
指標存取語句的目的碼
- 三地址語句:x = *p
- 目的碼:
LD R1, p // R1 = p LD R2, 0 (R1) // R2 = contents ( 0 + contents (R1) ) ST x , R2 // x = R2
- 三地址語句:*p = y
- 目的碼:
LD R1 , p // R1 = p LD R2 , y // R2 = y ST 0(R1), R2 //contents ( 0 + contents ( R1 ) ) = R2
條件跳轉語句的目的碼
- 三地址語句:if x < y goto L
- 目的碼:
LD R1 , x // R1 = x LD R2 , y // R2 = y SUB R1 , R1 , R2 // R1=R1 - R2 BLTZ R1 , M // if R1 < 0 jump to M
- M是標號為L的三地址指令所產生的目的碼中的第一個指令的標號。
過程呼叫和返回的目的碼
靜態區儲存分配
- 方法呼叫
- 三地址語句:call callee
- 目的碼
ST callee.staticArea, #here + 20 //即callee的活動記錄在靜態區中的起始位置 BR callee.codeArea, //即calle的目的碼在程式碼區中的起始位置
- 方法返回
- 三地址語句:return
- 目的碼
BR * callee.staticArea // 間址,返回地址在 callee 棧幀底部
棧式儲存分配
- 方法呼叫
- 三地址語句:call callee
- 目的碼
ADD SP, SP, #caller.recordsize ST 0(SP), #here+16 // 把返回地址壓到被調過程的棧幀底部 BR callee. code Area
- 方法返回
- 三地址語句:return
- 目的碼
被呼叫過程:BR* 0(SP) 呼叫過程:SUB SP, SP, #caller.recordsixe
暫存器的選擇
三地址語句的目的碼生成
對每個形如x = y op z的三地址指令I,執行如下動作:
- 呼叫函式 getreg(I)來為x、y、z選擇暫存器,把這些暫存器稱為 R x R y R z R_x R_y R_z RxRyRz
- 如果 R y R_y Ry 中存放的不是y ,則生成指令“LD Ry , y′ ′”。y′ 是存放y的記憶體位置之一
- 類似的,如果Rz中存放的不是z,生成指令“LD Rz , z ′ ”
- 生成目標指令“OP Rx , Ry , Rz ”
暫存器描述符和地址描述符
暫存器描述符(register descriptor):
- 記錄每個暫存器當前存放的是哪些變數的值
地址描述符(address descriptor):
- 記錄執行時每個名字的當前值存放在哪個或哪些位置
- 該位置可能是暫存器、棧單元、記憶體地址或者是它們的某個集合
- 這些資訊可以存放在該變數名對應的符號表條目中
基本塊的收尾處理
對於一個在基本塊的出口處可能活躍的變數x
- 如果它的地址描述符表明它的值沒有存放在x的記憶體位置上,則生成指令“ST x, R” ( R是在基本塊結尾處存放x值的暫存器)
管理暫存器和地址描述符
對於指令LDR, x":
- 修改R的暫存器描述符,使之只包含x
- 修改x的地址描述符,把R作為新增位置加入到x的位置集合中
- 從任何不同於x的地址描述符中刪除R
對於指令"OP Rx, Ry ,Rz,":
- 修改Rx的暫存器描述符,使之只跑含x
- 從任何不同於Rx的暫存器描述符中刪除x
- 修改x的地址描述符,使之只包含位置Rx
- 從任何不同於x的地址描述符中刪除Rx
對於指令“ST x, R”:
- 修改x的地址描述符,使之包含自己的記憶體位置
對於複製語句x=y,如果需要生成載入指令"LD Ry, y’"則:
- 修改Ry的暫存器描述符,使之只包含y
- 修改y的地址描述符,把Ry作為新增位置加入到y的位置集合中
- 從任何不同於y的變數的地址描述符中刪除Ry
- 修改Ry的暫存器描述符,使之也包含x
- 修改x的地址描述符,使之只包含Ry
窺孔優化
-
窺孔(peephole)是程式上的一個小的滑動視窗
-
窺孔優化是指在優化的時候,檢查目標指令的一個滑動視窗(即窺孔) ,並且只要有可能就在窺孔內用更快或更短的指令來替換視窗中的指令序列。
-
也可以在中間程式碼生成之後直接應用窺孔優化來提高中間表示形式的質量。
具有窺孔優化特點的程式變換的例子
- 冗餘指令刪除
- 例如:消除冗餘的載入和儲存指令
- 例如:消除不可達程式碼,一個緊跟在無條件跳轉之後的不帶標號的指令可以被刪除。
- 控制流優化
- 在程式碼中出現跳轉到跳轉指令的指令時,某些條件下可以使用一個跳轉指令來代替。
- 如果不再有跳轉到L1的指令,並且語句L1: goto L2之前是一個無條件跳轉指令,則可以刪除該語句。
- 代數優化
- 代數恆等式:消除窺孔中類似於x=x+0或x=x*1的運算指令。
- 強度削弱:
對於乘數(除數)是2的冪的定點數乘法(除法) ,用移位運算實現代價比較低;
除數為常量的浮點數除法可以通過乘數為該常量倒數的乘法來求近似值。
- 機器特有指令的使用
- 充分利用目標系統的某些高效的特殊指令來提高程式碼效率。
- 例如:INC指令可以用來替代加1的操作。