有個著名的問題:mommy, where do compilers comefrom?要解決這個問題,首先來看看t-diagram。可以將編譯器用乙個t形圖來表示:
---------
| s t |
--- ---
| i |
---其中,s表示souce language,t表示target language,i表示implementationlanguage。
根據這個圖,可以得到兩種組合:
1、 由i實現的將s翻譯成a的編譯器和將a翻譯成t的編譯器聯合起來工作,可以實現s到t的編譯。這不是很有意思的組合。
------------------
| s a | a t |
--- ------ ---
| i | | i |
--- ---
2、由i實現的將s翻譯成t的編譯器(compiler c1),由h實現的將i翻譯成k的編譯器的組合(compilerc2)。這個組合比較有意思:可以用c2去編譯c1(將其實現語言從i翻譯成k),這樣就得到由k實現的將s翻譯成t的編譯器(compilerc3):
--------- ---------
| s t | | s t |
--- ---------- ---> --- ---
| i | i k | | k |
------- --- ---
| h |
---這便是我們所感興趣的:編譯器的編譯器。
而乙個程式要在某台機器上執行時,其實現語言必須與相應的機器語言匹配。例如在x86平台上,能執行的程式只能是x86機器語言:
------------------
\ program /
\ /
-------------
| x86 |
-------------
| x86 |
-------------
讓我們將t形圖和上圖聯絡起來看,以c為例(假定已有x86的c編譯器):
-------- -------- ---------
\ hello / \ hello / \ hello /
----------------------- -------
| c | c x86| x86 | ---> | x86 |
--------- ---------- -------
|x86| | x86 |
---- -------
上圖表示用c寫的hello程式可以在c「機器」上執行,然而這不是我們想要的——在x86機器上執行。於是使用實現語言為x86的編譯器(其可執行**)編譯形成x86語言,這樣就可以執行了。
根據上面的第2條(編譯器的編譯器)以及程式的執行,我們可以得出,要想得到在m機器上執行的s語言編譯器,可以使用s語言來編寫(用m語言也可以,然而太麻煩)。但還是需要用m語言寫乙個具有很小功能的s編譯器,然後用其去編譯加入了更多功能的編譯器,然後用編譯出來的更多功能的編譯器去編譯,這樣就得到了最終需要的編譯器,這便是自舉。
--------- ---------
| s m | | s m |
--- ---------- ---> --- ---
| s | s m | | m |
------- --- ---
| m |(微型s編譯器)
---用s編寫s的編譯器還有乙個好處,那就是方便移植,並且能產生交叉編譯器。假定要編譯k機器語言的s編譯器,只需要將目標語言替換成k,然後用已有的s語言編譯器來編譯:
--------- ---------
| s k | | s k |
--- ---------- -> --- ---
| s | s m | | m |(執行在m機器上的編譯器,即交叉編譯器)
------- --- ---
| m |
---有了交叉編譯器,就可以編譯在k上執行的s編譯器了:
--------- ---------
| s k | | s k |
--- ---------- -> --- ---
| s | s k | | k |
------- --- ---
| m |
---
這個知識實在是簡單又基礎,主要是覺得很有意思才在這裡說說。整個gcc(編譯器及交叉編譯器)就是用自舉和移植產生的,對此rms有詳細的說明。——當然,也有很多編譯器不是這樣寫出來的——用另一種語言編寫的。可是,「mom,where does the compiler needed come from?」
實在不願意貼圖了,就用字元代替了,懶:)
compiler construction principles and practice, by kenneth c.louden
"mommy, where do compilers come from?", by anonymous.
copyleft (c) 2007, 2008 raof01.
早期的cpu全部是cisc架構,它的設計目的是要用最少的機器語言指令來完成所需的計算任務。比如對於乘法運算,在cisc架構的cpu上,您可能需要這樣一條指令:muladdra, addrb就可以將addra和addrb中的數相乘並將結果儲存在addra中。將addra,addrb中的資料讀入暫存器,相乘和將結果寫回記憶體的操作全部依賴於cpu中設計的邏輯來實現。這種架構會增加cpu結構的複雜性和對cpu工藝的要求,但對於編譯器的開發十分有利。比如上面的例子,c程式中的a*=b就可以直接編譯為一條乘法指令。今天只有intel及其相容cpu還在使用cisc架構。
risc架構要求軟體來指定各個操作步驟。上面的例子如果要在risc架構上實現,將addra,addrb中的資料讀入暫存器,相乘和將結果寫回記憶體的操作都必須由軟體來實現,比如:mov a, addra; mov b,addrb; mul a, b; str addra,a。這種架構可以降低cpu的複雜性以及允許在同樣的工藝水平下生產出功能更強大的cpu,但對於編譯器的設計有更高的要求。
編譯器自舉
語言的編譯器自舉是某個語言成熟標誌 今天看到乙個新詞 自舉 在知乎上看到輪子哥的解答,很清楚 你想創造一門v語言而且用v語言來寫v編譯器的話,你得按照下面的方法做 1 用c 把那個編譯器 a 寫出來,順便留下很多測試用例。2 用v語言把那個編譯器寫 b 出來,用a.exe來編譯b,修改直到所有測試用...
ucc編譯器(x86移植)
之前寫過一篇ucc的文章,也就是這一篇。這篇文章對ucc的流程說了挺多,但是怎麼把ucc移植到新的cpu上面,卻沒有說很多,後來自己又看了一下 發現還是有不少新的收穫。emit.c檔案是真正的後端入口,所有的彙編檔案的整理 組織部分都是這裡完成的。當然這部分只是框架的內容,告訴我們乙個大概,全域性變...
編譯方舟編譯器
環境 mac os parallels desktop 安裝ubuntu 16.04 安裝基礎包 sudo apt get y install openjdk 8 jdk git core gnupg flex bison gperf build essential zip curl zlib1g ...