1. 程式人生 > >編譯器二:LLVM和GCC的區別

編譯器二:LLVM和GCC的區別

文章來源:http://lionelliu.com/?p=1771

最近在Mac OS X Mountain Lion下用Xcode進行開發,發現在編譯選項裡有如下所示的這兩種編譯器:一個是Apple LLVM compiler 4.2,另外一個是LLVM GCC 4.2。

Xcode Compiler Setting

近幾年一直聽人說LLVM比GCC好,但是我一直沒有時間研究這二者的差別。由此問題出發,我又給自己丟擲了很多疑問:

  • cc, c89, c99是什麼?有何區別?
  • gcc, g++, cpp, gpp又是什麼?
  • LLVM與GCC區別大嗎?
  • Apple LLVM compiler 4.2和LLVM GCC 4.2有何區別?
  • LLVM GCC 4.2到底是LLVM還是GCC?

接下來讓我們一起補補歷史課。

CC, C89, C99

Unix誕生之後,很多公司都開發了自己的Unix系統並且使用了自己專門的編譯器。這樣就導致在不同的Unix系統上,想編譯C語言程式碼就需要使用不同的命令。於是POSIX標準Commands and Utilities中就規定了將CC作為不同編譯器的統一命令介面,並且也規定了CC命令需要提供哪些必須的引數。

隨著後續ISO C標準的確定,POSIX標準又規定分別將C89C99作為ISO C的介面,而CC則繼續作為非標準C的介面。但實際上後續大多數C語言編譯器都實現了ISO C標準,所以POSIX標準規定後續應將CC這一歷史遺留的命令取消。

GCC, G++, CPP, GPP

隨著開源運動的興起,自由軟體基金會開發了自己的開源免費的C語言編譯器GNU C Compiler,簡稱GCC。GCC中提供了C Preprocessor這個C語言的前處理器,簡稱CPP。後來GCC又加入了對C++等其它語言的支援,所以他的名字也改為GNU Compiler Collection。G++則是專門用來處理C++語言的。在GNU的官方手冊中,有一個章節叫做G++ and GCC介紹了這二者的區別。G++是GCC編譯器集合的一個前端。關於前端、後端的概念下面有更詳細的介紹。而GPP呢,這個名字比較特殊,如果你用的是Linux系統,可能並沒有這個命令。但是在某些特殊的系統下,例如DOS,是無法建立G++這樣帶有特殊符號的檔名的。所以按照DJGPP編譯器的做法,GPP其實就是G++。

LLVM與GCC

回顧GCC的歷史,雖然它取得了巨大的成功,但開發GCC的初衷是提供一款免費的開源的編譯器,僅此而已。可後來隨著GCC支援了越來越多的語言,GCC架構的問題也逐漸暴露出來。但GCC到底有什麼問題呢?我們一起看看這篇文章:The Architecture of Open Source Applications: LLVM。LLVM的優點也正是GCC的缺點。

傳統編譯器

傳統編譯器的工作原理基本上都是三段式的,可以分為前端(Frontend)、優化器(Optimizer)、後端(Backend)。前端負責解析原始碼,檢查語法錯誤,並將其翻譯為抽象的語法樹(Abstract Syntax Tree)。優化器對這一中間程式碼進行優化,試圖使程式碼更高效。後端則負責將優化器優化後的中間程式碼轉換為目標機器的程式碼,這一過程後端會最大化的利用目標機器的特殊指令,以提高程式碼的效能。

SimpleCompiler

事實上,不光靜態語言如此,動態語言也符合上面這個模型,例如Java。Java Virtual Machine也利用上面這個模型,將Java程式碼翻譯為Java bytecode。

這一模型的好處是,當我們要支援多種語言時,只需要新增多個前端就可以了。當需要支援多種目標機器時,只需要新增多個後端就可以了。對於中間的優化器,我們可以使用通用的中間程式碼。

Retargetable Compiler

這種三段式的結構還有一個好處,開發前端的人只需要知道如何將原始碼轉換為優化器能夠理解的中間程式碼就可以了,他不需要知道優化器的工作原理,也不需要了解目標機器的知識。這大大降低了編譯器的開發難度,使更多的開發人員可以參與進來。

雖然這種三段式的編譯器有很多有點,並且被寫到了教科書上,但是在實際中這一結構卻從來沒有被完美實現過。做的比較好的應該屬Java和.NET虛擬機器。虛擬機器可以將目標語言翻譯為bytecode,所以理論上講我們可以將任何語言翻譯為bytecode,然後輸入虛擬機器中執行。但是這一動態語言的模型並不太適合C語言,所以硬將C語言翻譯為bytecode並實現垃圾回收機制的效率是非常低的。

GCC也將三段式做的比較好,並且實現了很多前端,支援了很多語言。但是上述這些編譯器的致命缺陷是,他們是一個完整的可執行檔案,沒有給其它語言的開發者提供程式碼重用的介面。即使GCC是開源的,但是原始碼重用的難度也比較大。

LLVM

LLVM最初是Low Level Virtual Machine的縮寫,定位是一個虛擬機器,但是是比較底層的虛擬機器。它的出現正是為了解決編譯器程式碼重用的問題,LLVM一上來就站在比較高的角度,制定了LLVM IR這一中間程式碼表示語言。LLVM IR充分考慮了各種應用場景,例如在IDE中呼叫LLVM進行實時的程式碼語法檢查,對靜態語言、動態語言的編譯、優化等。

LLVM Compiler

從上面這個圖中我們發現LLVM與GCC在三段式架構上並沒有本質區別。LLVM與其它編譯器最大的差別是,它不僅僅是Compiler Collection,也是Libraries Collection。舉個例子,假如說我要寫一個XYZ語言的優化器,我自己實現了PassXYZ演算法,用以處理XYZ語言與其它語言差別最大的地方。而LLVM優化器提供的PassA和PassB演算法則提供了XYZ語言與其它語言共性的優化演算法。那麼我可以選擇XYZ優化器在連結的時候把LLVM提供的演算法連結進來。LLVM不僅僅是編譯器,也是一個SDK。

Pass Linkage

Apple LLVM compiler 4.2和LLVM GCC 4.2

現在我們可以回答本文最前面我遇到的那個問題了。Apple LLVM compiler 4.2是一個真正的LLVM編譯器,前端使用的是Clang,基於最新的LLVM 3.2編譯的。LLVM GCC 4.2編譯器的核心仍然是LLVM,但是前端使用的是GCC 4.2編譯器。從LLVM的下載頁面可以看出,LLVM從1.0到2.5使用的都是GCC作為前端,直到2.6開始才提供了Clang前端。