目標:把源代碼變成目標代碼前端
1.若是源代碼在操做系統上運行:目標代碼就是「彙編代碼」。再經過彙編和連接的過程造成可執行文件,而後經過加載器加載到操做系統執行。算法
2.若是源代碼在虛擬機(解釋器)上運行:目標代碼就是「解釋器能夠理解的中間形式的代碼」,好比字節碼(中間代碼)IR、AST語法樹。數據結構
編譯過程能夠分爲這幾個階段,每一個階段作了必定的任務,層級的讓下一個階段進行。
架構
編譯器讀入源代碼,通過詞法分析器識別出Token,把字符串轉換成一個個Token。
Token的類型包括:關鍵字、標識符、字面量、操做符、界符等函數
好比下面的C語言代碼源文件,通過詞法分析器識別出的token有:int、foo、a、b、=、+、return、(){}等token工具
int foo(int a){ int b = a + 3; return b; }
爲何要這樣作呢,把代碼裏的單詞進行分類,編譯器後面的階段不就更好處理理解代碼了嘛!性能
每個程序代碼,實際上能夠經過樹這種結構表現出其語法規則。優化
語法分析階段把Token串,轉換成一個體現語法規則的、樹狀數據結構,即抽象語法樹AST。
AST樹反映了程序的語法結構。操作系統
好比下面對應的一段C語言代碼,對應的AST抽象語法樹以下所示:翻譯
int foo(int a){ int b = a + 3; return b; }
AST抽象語法樹
AST樹長成什麼樣,由語法的結構有關。
好比 上面C語言代碼中對函數的語法定義以下:語法分析器就按照語法定義進行解析,就是從上到下匹配的過程。
也就是先匹配function的規則,匹配函數類型type、函數名name、函數參數parameters、函數體
當匹配函數參數時,就去匹配parameters的規則
當匹配函數體時,函數體由一個個語句組成,就去匹配各個語句stmt的規則。
function := type name parameters functionBody parameters:= parameter* functionBody:= stmt returnStatement
生成 AST 之後,程序的語法結構就很清晰了,但這棵樹到底表明了什麼意思,咱們目前仍然不能徹底肯定,要在語義分析階段肯定。
爲何要把程序轉換成AST這麼一顆樹,由於編譯器不像人能直接理解語句的含義,AST樹更有結構性,後續階段能夠針對這顆樹作各類分析!
語義分析階段的任務:理解語義,語句要作什麼。
好比+號要執行加法、=號要執行賦值、for結構要去實現循環、if結構實現判斷。
因此語義階段要作的內容有:上下文分析(包括引用消解、類型分析與檢查等)
引用消解:找到變量所在的做用域,一個變量做用範圍屬於全局仍是局部。
類型識別:好比執行a+3,須要識別出變量a的類型,由於浮點數和整型執行不同,要執行不一樣的運算方式。
類型檢查:好比int b = a + 3,是否能夠進行定義賦值。等號右邊的表達式必須返回一個整型的數據、或則可以自動轉換成整型的數據,纔可以對類型爲整型的變量b進行復制。
好比以前的一段C語言代碼,通過語義分析後得到的信息(引用消解信息、類型信息),能夠在AST上進行標註,造成下面的「帶有標註的語法樹」,讓編譯器更好的理解程序的語義。
也會將這些上下文信息存入「符號表」結構中,便於各階段查詢上下文信息。
符號表是有層次的結構:咱們只須要逐級向上查找就能找到變量、函數等的信息(做用域、類型等)
接下來就能夠 解釋執行:實現一門解釋型的語言
Tip:編譯型語言須要生成目標代碼,而解釋性語言只須要解釋器去執行語義就能夠了。
實現AST的解釋器:在語法分析後有了程序的抽象語法樹,在語義分析後有了「帶有標註的AST」和符號表後,就能夠深度優先遍歷AST,而且一邊遍歷一邊執行結點的語義規則。整個遍歷的過程就是執行代碼的過程。
舉一個解釋執行的例子,好比執行下面的語義:
在編譯前端完成後(編譯器已經理解了詞法和語義),編譯器能夠直接解釋執行、或則直接生成目標代碼。對於不一樣架構的CPU,還須要生成不一樣的彙編代碼,若是對每一種彙編代碼作優化就很繁瑣了。因此咱們須要增長一個環節:生成中間代碼IR,統一優化後中間代碼,再去將中間代碼生成目標代碼。
中間代碼IR的兩個用途:解釋執行 、代碼優化
解釋執行:解釋型語言,好比Python和Java,生成IR後就能直接執行了,也就是前面舉出的例子。
優化代碼:好比LLVM等工具;在生成代碼後須要作大量的優化工做,而不少優化工做不必使用匯編代碼來作(由於不一樣CPU體系的彙編語言不一樣),而能夠基於IR用統一的算法來完成,下降編譯器適配不一樣CPU的複雜性。
一種方案:基於基本塊做代碼優化
分類:本地優化、全局優化、過程間優化
本地優化:可用表達式分析、活躍性分析
全局優化:基於控制流圖CFG做優化。
控制流圖CFG :是一種有向圖,它體現了基本塊以前的指令流轉關係,若是從BLOCK1的最後一條指令是跳轉到 BLOCK2, 就連一條邊,若是經過分析 CFG,發現某個變量在其餘地方沒有被使用,就能夠把這個變量所在代碼行刪除。
過程間優化:跨越函數的優化,多個函數間做優化
優化案例:
代數優化:
好比刪除「x:=x+0 」,乘法優化掉「x:=x乘以0」 能夠簡化成「x:=0」,乘法優化成移位運算:「x:=x*8」能夠優化成「x:=x<<3」。
常數摺疊:
對常數的運算能夠在編譯時計算,好比 「x:= 20 乘以 3 」能夠優化成「x:=60」
刪除公共子表達式:做「可用表達式分析」
x := a + b y := a + b //優化成y := x
拷貝傳播:做「可用表達式分析」
x := a + b y := x z := 2 * y //優化成z:= 2 * x
常數傳播:
x := 20 y := 10 z := x + y//優化成z := 30
死代碼刪除:做變量的「活躍性分析」
活躍性分析(優化刪除死代碼,沒用到的變量) 數據流分析:使用「半格理論」解決多路徑的V值計算集合問題,不在代碼下面集合的變量就是死代碼。
目標代碼生成,也就是生成虛擬機執行的字節碼,或則操做系統執行的彙編代碼
代碼生成的過程,其實很簡單,就是將中間代碼IR逐個翻譯成想要的彙編的代碼
那麼目標代碼生成階段的任務就有: