寫編譯器需要把彙編語言學到什麼程度就夠用了?入門到進階有什麼好書值得讀?
首先要看題主寫什麼編譯器。
編譯器的本質是把一個程序從源語言A的形式翻譯為保持了足夠語義的目標語言B的形式;其中A與B可以是同一種語言也可以是不同的語言。
而作為一個編譯器,B並不一定要是一種實際有硬體實現的機器語言/彙編才算數,還有許多其它可能。目標語言可以是一種中高級語言,例如C、C++甚至JavaScript、Java;
也可以是一種虛擬的目標架構,例如JVM位元組碼、MSIL、CPython位元組碼、LLVM IR等等;
目標甚至還可以是諸如用XML描述的樹形結構。當然很常見的情況還是以實際硬體的機器語言/彙編為目標,但不要忽視了還有大量編譯器是以別的東西為目標的。於是,要實現一個編譯器,正確理解自己要支持的源語言和目標語言,就對了。
以JVM位元組碼為目標的話,就以JVM規範中的指令集規定為參考;以x86、ARM為目標的話,就以各自的指令集手冊為參考。就算是以實際硬體的機器語言/彙編為目標,「彙編」也不是一門語言,而是每個平台都不一樣,甚至同一平台的不同彙編器所支持的「彙編語言」也都不一樣。要入門都各自有不同的入門材料,而最終手邊要常備的還是各自的手冊(ISA手冊以及/或者彙編器手冊)。
要說要了解到什麼程度為之「夠用」,其實在剛開始的時候寫個入門級編譯器可能只會用到目標平台上的很少量的指令。這樣的話只要隨便找點資料了解一下這個目標平台上的彙編語言的基本形式是怎樣的(多少地址、有多少寄存器各自是啥用途、有什麼特殊功能),了解基本的 calling convention 或者說 ABI 規定是怎樣的,然後就可以搜搜看指令集列表看有哪些指令是自己需要的,然後就可以開搞了。後面需要啥信息可以慢慢讀手冊來發掘。
現在要偷懶的話有很多辦法。其中一個就是借LLVM所支持的目標平台對應的TD文件來了解這個目標平台的特徵。例如說這是MIPS32r6的:lib/Target/Mips/MicroMips32r6InstrInfo.td
=======================================
話說寫一個編譯器的時候,默認/最主要支持的目標平台是什麼有可能對編譯器的內部設計有深遠的影響。
有些編譯器可能只支持一個目標平台,有些可能支持多個目標平台但最初是從某個有特點的平台開始的。如果這個編譯器是個關注優化的實現,那裡面就有許多地方會受影響。例如說,ARM的許多指令都支持條件執行(predicated execution),像這樣的源語言代碼:if (cond) {
do A
do B
do C
}
可以被編譯為這樣的形式:
do A if cond
do B if cond
do C if cond
要充分發揮這種條件執行的功能,編譯器中端的IR里最好能在優化過程中要考慮好支持「執行斷言」的語義。有些編譯器IR的基本形式就是把操作跟它所依賴的執行斷言關聯在一起顯式表達出來,這就非常合適。
而在後端做具體目標平台的代碼生成的部分,目標平台的特徵也會影響框架的基本設計。例如說,當主要目標平台都是用三地址代碼的形式(例如各種RISC)時,code generator的設計可能就會用三地址的形式來表示抽象的指令。於是這樣的框架要retarget到一個二地址的指令集(例如x86)就會很彆扭。
大家要是有興趣看過Sun的Java ME的一個JVM實現,CDC HotSpot Implementation(也叫做CVM),它的JIT編譯器後端就是有一個通用框架來支持RISC系的目標的;而x86目標平台的實現則完全在這個框架之外自己開了一套,正是因為上述原因。還有假如說要支持的目標平台中有像Itanium那種不常規的,那也會影響優化編譯器的內部設計。有些原本沒有支持Itanium、後來才加上Itanium支持的編譯器,本來主要優化都在中端做的,後端只做些常規的指令調度、寄存器分配、窺孔優化和最終代碼生成;在加上Itanium支持後,後端硬生生給加了一大堆不跟其它平台共用的優化,而且有不少代碼其實跟中端部分有重複,但無奈原本後端框架的設計無法跟中端復用優化演算法,只好重複。
=======================================
順帶: @vczh vc大大的回答提到x86用來檢測數組越界的指令。那是這個bound:x86 Instruction Set Reference但其實像C#和Java底下的執行引擎(CLR、各JVM實現)很少用這個指令的。通常就是用普通的unsigned cmp來做比較。這是因為像C#和Java的數組下標的下界都是固定為0,為上界必須是一個非負數,所以用一個unsigned cmp正好可以一次過完整下界0和上界的測試:(偽代碼)
// if (index &>= 0 index &< length)
// is converted into:
if (index u&< length)
而不會去用那個bound指令,因為它的形式限制太大,指定的內存布局一定要把上下界都顯式放在連續的內存里,對C#和Java來說就浪費了——下界0是已知的常量,根本不用放內存里存著。
另外回頭有空補充個八卦。vc大大的回答下面的評論有人問為啥GCC開發者吐槽鯨書(Steven Muchnick的《Advanced Compiler Design and Implementation》)和龍書,以及吐槽鏈接:ListOfCompilerBooks - GCC Wiki其實吧…龍書可能被業界從業人員們吐槽得還真是挺多的。而鯨書被吐槽的沒那麼多。具體原因回頭再寫…誰告訴你寫編譯器必須要彙編語言的,拖出去罰他重讀編譯原理一百遍。
編譯器前端幾乎不需要彙編知識,你需要懂的是中間語言,FE -&> IR. 編譯器後端的平台無關優化也幾乎不需要,IR -&> IR。編譯器後端的代碼生成部分,更重要的是ISA手冊,直接參考手冊就好了,IR -&> DAG -&> AS / OBJ. 如果特別想看看彙編,google一個 assembly quick tutorial,快速看一下就好了,可以幫助你以後理解程序,以及偶爾寫寫內聯彙編(提高逼格)。
先隨便找本書看看,懂得寫幾個簡單的程序之後,就去刷intel的那本手冊。======忘記說為什麼要刷intel的手冊了。原因有3:
1、這樣你才知道有多少指令是,雖然你可以不用,但是用了更好的,譬如說x86就有一個檢查int是否在一對int的範圍內的指令,可以用來檢查數組越界什麼的。
2、每一代都有新增加的指令,譬如MMX、SSE、AVX什麼的。這些你並不能忽略,因為現在64位在windows上的calling convention都要求你用SSE的寄存器來存放浮點數什麼的。3、也是最重要的,必須學會如何寫機器碼呀!不太多,總之幾乎不需要你用彙編寫一個完整的函數。
但如果你關注的是後端,你確實需要意識到源程序經過你的編譯器會變成怎樣的彙編。寫體系結構無關的優化部分不需要懂太多彙編;寫code generator肯定需要看廠商手冊,不過懂處理器微體系結構比彙編更加重要一些。搜索廠商手冊的過程中一定已經找到很多write paper, programming guide之類的材料了,不需要看什麼書。
如果你的目標語言是彙編,那還是需要學的,如果目標語言是其他的,比如把C++編譯為Java,前端就占很大一部分,但是兩種語言和一些實現模式和原理也是需要懂的
推薦閱讀:
※自己寫的COFF文件格式和編譯器生成的.obj文件一樣嗎?
※「Monokai」這個詞是什麼意思?
※如何評價Tinyfool在swiftcon大會上發言跑題被罵一事?
※為什麼好多編程「牛人」不喜歡用Microsoft Visual Studio?
※隨著IDE的不斷升級,普通開發者的手工編碼是否會被完全取代?