編譯原理

coderlwz發表於2024-08-10

步驟

  • 詞法分析
  • 語法分析
  • 語義分析與中間程式碼產生
  • 優化
  • 目的碼生成

文法

  • 3型文法:正則文法,用於描述程式設計語言詞法的有效工具
  • 2型文法:上下型無關文法,描述程式語法的有效工具

產生式

A -> B
B -> BC|C
C -> 0|1|2|3|4|5|6|7|8|9

推導與規約

A -> aBc
B -> b
==================
aBc是abc的歸約
abc是aBc的推導

規範推導/規約

規範推導:最右推導

規範規約:最左規約

無符號串
=》數字串
=》數字串+數字
=》數字串6
=》數字6
=》56

句型

由產生式到最終狀態之間的中間串

例如<數字>9

句子

產生式最終匹配的終態

例如56是文法無符號整數的一個句子

詞法分析

有限自動機

DFA

確定有限自動機:每個狀態在接收下一個輸入,只會轉移到唯一一個下一個狀態

complier-dfa
L(M):代表由FA能夠識別的所有字串的總體

NFA

非確定有限自動機,自動機中含有某些狀態,在接收下一個輸入後,會轉移到多於一個的狀態

complier-nfa

NFA確定化

每個NFA都存在一個DFA,使得L(NFA) = L(DFA),也就是說等價

NFA的確定化就是,從NFA轉變為DFA的過程

  • c-closure(state):c-閉包,從一個狀態轉移不需要通過輸入可以直接轉移到下一個狀態的集合
  • move(state, action):從狀態state,通過action,轉換到的下一個狀態的集合
  • a弧轉換的閉包:I = c-closure(move(state, action))

子集構造法

complier-nfa2dfa
然後把I列表標記為狀態,並畫出DFA

在NFA->DFA後,得到的DFA並不是最簡的,必須通過DFA最簡化處理,來提高分析的效率

DFA最簡化
  • 多餘狀態:從開始狀態出發無法到達的狀態
  • 死狀態:從此狀態出發無法到達最終狀態的狀態
  • 無關狀態:多餘狀態+死狀態
  • 等價狀態(不可區別狀態):當兩個狀態,輸入任意action,都轉移到相同的狀態時,代表這兩個狀態是等價的。

簡化步驟為:

  • 消除無關狀態
  • 合併等價狀態
正則與NFA的轉換
R->NFA

complier-r2nfa
例子

complier-r2nfa-sample

NFA-R

逆過程

語法分析

自頂向下語法分析

消除左遞迴
直接左遞迴
P->Pa|b => ba+
	P ->bM
	M -> aM|空
間接左遞迴
A -> Bc|d
B -> aA|Ab
=>
	A -> aAc|Abc|d
	=> 
		A -> (aAc|d)M
		M -> bcM|空
First集

若X->a....則a加入X的First集中,如果X->空,則空加入到X的First集中

X->amm|bk|空
	=> First(X) = {a,b,空}
X->Ym|b
Y->a|d|空
	=> First(X) = {a, d, m, b}
Follow集
  • 若A->aBC,則First(C)加入Follow(B)
  • 若A->aB,則Follow(A)加入Follow(B)
LL(1)語法分析

complier-ll1
L:從左向由掃描

L:最左推導

1:向前看1個字元

對文法G的句子進行確定的自頂向下語法分析的充要條件是

產生式A->B|C滿足

  • 如果B和C都不能推匯出空,則FIrst(B)交First(C)為空集
  • B和C最多隻有一個可以推匯出空
  • 如果B可以推匯出空,則First(C)交Follow(A為空

LL(1)文法不能由二義性,也不能含有左遞迴,對LL(1)文法的所有句子都可以進行自頂向下的語法分析

並不是所有語言都可以用LL(1)來描述

自下而上的語法分析

FirstVT(T)

非終結符T的最左終結符集合

  • If T->a or T->Ra => a屬於FirstVt(T)
  • if b belongs to FirstVt(R) => b and T->R... 屬於FirstVt(T)
LastVt(T)

非終結符T的最右終結符集合

  • if T->....a or T->....aR => a屬於LastVt(T)
  • if b belongs to LastVt(R) and T->...R => b屬於LastVt(T)
最左素語

complier-leftest

  • 建立一個棧
  • 從左到右掃描表示式
  • 通過出棧比較相鄰兩個個操作符的優先順序
  • 如果如果兩個操作符優先順序相等或者前一個後一個優先順序高則規約前一個操作符
  • 出棧比較相鄰兩個操作符
  • 如果第一個操作符比第二個操作符低,則再取一個操作符,如果第二個操作符等於或者高於第三個操作符,則規約第二個操作符,否則繼續入棧
產生式->NFA
E->aA|bB
A->cA|d
B->cB|d

complier-g2nfa
NFA->DFA

通過子集構造法

complier-g2nfa2dfa

LR(0)語法分析
  • 移進專案:圓點之後為終結符的專案,A->a.bc
  • 待約專案:圓點之後為非終結符,A->a.Bb
  • 歸約專案:圓點之後沒有遠點,A->a.
  • 接受專案:對於文法G(S),有S->Q.

complier-dfa2table
通過從左向右掃描句子,通過下一輸入,決定從Si歸約到Sj,並最終到達終態

SLR(1)語法分析

LR(0)文法,如果遇到在一個狀態中,同時出現移進和規約的衝突,則會讓LR(0)無法判斷此步驟應該如何處理

complier-ll0dame
S3狀態,已經滿足歸約條件,則無論下一個字元是什麼,都需要進行歸約操作;但是如果下一個字元是‘,’,則應該i進行移進操作

SLR(1)為了解決移進歸約衝突,則遇到衝突時向前看一個字元,來解決衝突

complier-slr1

LR(1)語法分析

complier-slrdame
當某個狀態遇到移進歸約衝突,並且下一個移進的符號,與歸約符號的下一個符號相同,則無法使用SLR(1)進行分析

SLR(1)通過Follow(A)來計算,而LR(1)通過First(A.next)來決定來縮小範圍

complier-lr1
LR(1)過程:

  • 如果A->a.BM, B->r,m=First(M),則記為B->r,m

中間程式碼

優點

  • 編譯程式的邏輯結構更加簡單明確
  • 利於再不同的目標機器上實現同一種語言
  • 利於進行與機器無關的優化
  • 可以用於解析

形式

  • 字尾式
  • 圖表示:抽象語法樹/DAG圖
  • 三地址程式碼:三元式/四元式/間接三元式
字尾式
a+b*(c-d)+e/(c-d)
=>
abcd-*+ecd-/+
抽象語法樹

內部節點代表操作符,葉子節點為運算元

complier-abtree

DAG圖

把抽象語法樹的公共部分進行合併,可以減少公共表示式的計算

complier-dag

三地址碼

可以看作抽象語法樹或者DAG的一種線性表示

complier-dag2three

三地址碼詳解

四元式
特點
  • 佔用空間多
  • 易於優化
result = arg1 op arg2
=>
(op, arg1 arg2, result)
三元式
特點
  • 佔用空間少
  • 由於臨時變數跟i緊密關聯,導致難以優化
result = arg1 op arg2
=>
(i) (op, arg1, arg2)

用i位置來儲存臨時結果

左值與右值
x[i]=y =>
(0) ([]=, x, i)
(1) (assign, (0), y)
y=x[i] =>
(0) (=[], x, i)
(1) (assign, y, (0))
間接三元式
特點
  • 佔用空間多
  • 易於優化

complier-fourExp

陣列翻譯

LOC(A[i]) = base + (i-low)*w
          = (base-low*w) + i*w
		  = conspart + varpart
Loc(A[i][j]) = base + ((i-low1)*n2+j-low)*w
             = (base-(low1*n2+low2)*w)+(i*n2+j)*w

可見任意維度的陣列元素的計算都可以分為不變計算和可變計算組成,在編譯時首先計算出conspart,可以達到優化的目的

條件語句的翻譯

文法
S -> if(E) S1|if(E) S1 else S2

complier-if

拉鍊回填

對於E.true的未來地址,在三地址碼中,先通過打標誌的方式,在後續確定下地址後,再回填到標誌的位置

迴圈語句的翻譯

文法
S -> while(E) S1

complier-while

符號表

complier-table

儲存

變長欄位通過指標連結到另外的記憶體區儲存

查詢

線性查詢

通過順序查詢

自適應線性表:通過新增一列,通過LRU演算法,構成連結串列,最新訪問的元素會出現在鏈頭

二叉樹

建立樹索引

hash

建立hash索引

名字作用域

complier-name

巢狀結構的符號表特徵
  • 由於函式的執行是先執行,後結束,所以適合用棧的方式來儲存
  • 在函式對應的符號表中指定display表,代表此函式可用符號表的首地址
  • 在符號表中引入指標previous,來連線上一個符號的首地址

執行時儲存空間組織

complier-store

活動記錄

用於管理函式變數的資訊

complier-activestore

棧式儲存

complier-activestroe-exapmle

過程進入和返回

通過變更top和sp指標,實現活動記錄的棧式處理

靜態鏈實現區域性變數的訪問

complier-staticlink
complier-displaytable

靜態鏈

指向直接外層函式的首地址

動態鏈

指向上一層函式的首地址

display表

所有外層函式的首地址

優化

區域性優化

基於基本塊範圍內的優化

  • 刪除公共子表示式
  • 刪除無用程式碼
  • 合併已知變數
  • 交換語句位置
  • 代數變換/強度削弱

DAG優化

  1. 三地址碼轉換為DAG

complier-three2dag
2. DAG重寫三地址碼

complier-dagrewritethree
3. 刪除無用變數

complier-removeunused

迴圈優化

程式碼外提
  • 求出迴圈L的所有不變運算
  • 檢查步驟1的不變運算A=const是否滿足:
    • s是否是L的所有出口必經節點,或者A在離開L後不再活躍
    • A在L的其他地方沒有再賦值
    • 對S涉及的A的引用都是在此A賦值後才到達
  • 對滿足以上條件的A=const進行外提
強度消弱
  • 對於迴圈L有I = I+C,且有T=K*I+C2,其中C,K,C2為不變數,則T可以進行強度削弱,把乘法轉換為加法
  • 削弱後,迴圈中會出現無用賦值,可刪除
  • 下標變數的地址計算很耗時,可以使用強度削弱
刪除歸納變數

對於迴圈變數i,由於L中有其他變數A是跟I有線性關係的,可以用A來代替i在迴圈的控制,以減少對迴圈變數的計算

相關文章