【龍書筆記】編譯器內部實現流程初探

上篇龍書筆記對編譯器在程序構建中的作用做了整體的黑盒介紹,本篇筆記的目的是拆開這個盒子,對編譯器內部實現流程做說明。

1. Phases of a compiler
從整體來看,編譯器內部流程可以分爲2大類:分析(analysis)和綜合(synthesis)。
在analysis階段,編譯器將源碼分解爲一系列片段併爲它們構建語法結構(grammatical structure),然後利用這些語法結構來創建源碼的中間表示(intermediate representation)。該階段會檢查源碼的語法及語義正確性,此外還會爲源碼建立後續會用到的符號表(symbol table)。
而synthesis階段則是用由aynlysis階段生成的中間表示來構造目標程序。
在專業術語上,analysis階段被稱爲編譯器前端(front end),而synthesis階段被稱爲編譯器後端(back end)
編譯器內部流程的每個階段(phase)的任務都是把一種表示(輸入)轉換爲另一種表示(輸出),一個典型的編譯器內部階段示意圖如下:

幾點說明如下:
1) 某些編譯器在前端和後端之間引入了一個與機器無關的代碼優化階段(該階段通常被稱爲middle end),是否實現了middle end與具體的編譯器有關
2) 上圖中,從lexical analyzer到intermediate code generator均屬於front end;而machine-independent code optimizer是可選的middle end階段;從code generator到最後的階段均屬back end
3) 在編譯器具體實現中,上圖中的某些步驟可能會被合併
4) 某些編譯器生成的中間表示碼會被精心設計以使之具備這樣的接口特性:爲某個特定語言設計實現的編譯器前端生成的中間碼能作爲“接口層”供爲某類特定目標機器設計實現的編譯器後端讀取來生成那類目標機器上運行的目標程序。
這種
接口特性帶來的好處是:通過組合多個爲不同語言實現的編譯器前端和一個爲某目標機器實現的編譯器後端,我們可以構建出能編譯多種源碼語言的編譯器,它編譯完的目標程序可以在某類特定目標機器上運行;同理,通過組合一個爲某種語言實現的編譯器前端和多個爲不同特定目標機器實現的編譯器後端,我們可以構建出能編譯某個源碼語言的編譯器,它編譯完的目標程序可以在不同的目標機器上運行(需要在編譯選項中指定目標機器類型)。Linux下常用的GCC編譯器工具鏈就具備這種能力。
5) 符號表存儲了源碼的關鍵信息(比如:對於變量來說,關鍵屬性包括變量的存儲位置、變量類型及其作用域;對函數來說,關鍵屬性包括參數個數、參數類型、傳參方式及返回值類型),所以它在編譯器的各個階段均會被用到下面開始對每個階段的細節做展開說明。

2. Lexical Analysis
該階段被稱爲詞法分析(lexical analysis)或掃描(scanning)。詞法分析器讀取源碼字符流並把它們分組成被稱爲lexemes的有效序列,對於每個lexeme,詞法分析器以<token-name, attribute-value>格式輸出一個token。其中,token-name是個抽象符號,語法分析階段會被用到;而attribute-value指向該token在符號表中對應的實體,該實體在語義分析和目標機器碼生成階段會被用到。
舉個例子,假設源碼中有下面一行代碼:
position = initial + rate * 60
則語法分析器處理後,會輸出成如下格式:
<id,1> <=> <id,2> <+> <id,3> <*> <60>
其中,每個<>表示分組後的1個lexeme;變量名(即變量標識符)被映射成<id,n>格式的lexeme;而常量和操作符由於不需要attribute-value,故它們均被映射成只有token-name且token-name即是其字符本身的lexeme。

3. Syntax Analysis
該階段被稱爲語法分析(syntax analysis)或解析(parsing)。語法分析器parser使用由詞法分析器生成的tokens來創建中間表示樹(tree-like intermediate representation)以描述這些token流的語法結構。這個“中間表示樹”常被稱爲語法樹(syntax tree),其根節點或子樹的根節點通常代表一個操作(operation),這些節點的葉子節點表示該操作對應的操作數。此外,語法樹需要反映出操作優先級。
備註:這些文字描述比較抽象,文末會給出一個實例示意圖來幫助理解。

4. Semantic Analysis
該階段被稱爲語義分析。語義分析器藉助語法樹和符號表來檢查源碼與語言規範的語義一致性,此外,它還收集類型信息並將其存入語法樹或符號表以便後續使用。
語義分析最重要的任務就是做類型檢查(type checking),此外,在語言規範允許的情況下,它會根據實際情況對某些變量做隱式類型轉換。

5. Intermediate Code Generation
該階段被稱爲中間碼生成階段,它爲源碼顯式生成一個low-level或machine-like的中間表示,我們可以把這個“中間表示”理解爲可以在抽象機器(abstract machine,即中間表示碼與具體的物理機器無關)上運行的程序。
中間表示碼需滿足2個重要性質:1) 易於產生;2) 易於轉換成目標機器碼
一種典型的中間表示碼是"three-address code",它是一種類似於彙編指令的表示方法,可以查看wikipedia這裏的說明來了解。

6. Code Optimization
該階段是與機器無關的代碼優化階段,它會嘗試在上一階段生成的中間表示碼基礎上,生成更高效的中間表示碼。

7. Code Generation
該階段以源碼的中間表示碼作爲輸入,然後將中間表示碼映射成目標語言。若目標語言是機器碼,則該階段需要爲每個變量選擇寄存器或內存地址,以便中間表示碼中的指令被轉換成可以實現等價任務的機器指令。
該階段的一個關鍵任務就是爲變量分配合理的寄存器。

以上就是一個典型的編譯器需要實現的處理流程。
下面是本文第2節提到的那個實例在編譯器各個階段的轉換示意圖,它可以幫助新手加深對編譯器實現流程的理解。

【參考資料】
1. <Compilers Principles, Techniques, & Tools>即龍書第1.2節
2. Wikipedia - Symbol Table 
3. Wikipedia - Compiler  

======================= EOF =======================

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