詳解三大編譯器:gcc、llvm 和 clang

編譯器一般構成

傳統的編譯器通常分為三個部分,前端(frontEnd),優化器(Optimizer)和後端(backEnd)。在編譯過程中,前端主要負責詞法和語法分析,將源代碼轉化為抽象語法樹;優化器則是在前端的基礎上,對得到的中間代碼進行優化,使代碼更加高效;後端則是將已經優化的中間代碼轉化為針對各自平臺的機器代碼。

GCC

GCC(GNU Compiler Collection,GNU編譯器套裝),是一套由 GNU 開發的編程語言編譯器。GCC 原名為 GNU C 語言編譯器,因為它原本只能處理 C語言。GCC 快速演進,變得可處理 C++、Fortran、Pascal、Objective-C、Java, 以及 Ada 等他語言。

詳解三大編譯器:gcc、llvm 和 clang

LLVM

LLVM (Low Level Virtual Machine,底層虛擬機) 提供了與編譯器相關的支持,能夠進行程序語言的編譯期優化、鏈接優化、在線編譯優化、代碼生成。簡而言之,可以作為多種編譯器的後臺來使用。

蘋果公司一直使用 GCC 作為官方的編譯器。GCC 作為一款開源的編譯器,一直做得不錯,但 Apple 對編譯工具會提出更高的要求。原因主要有以下兩點:

其一,是 Apple 對 Objective-C 語言(包括後來對C語言)新增很多特性,但 GCC 開發者並不買Apple的賬——不給實現,因此索性後來兩者分成兩條分支分別開發,這也造成 Apple 的編譯器版本遠落後於 GCC 的官方版本。其二,GCC 的代碼耦合度太高,很難獨立,而且越是後期的版本,代碼質量越差,但 Apple 想做的很多功能(比如更好的 IDE 支持),需要模塊化的方式來調用 GCC,但 GCC一直不給做。

編譯器大神 Chris Lattner 橫空出世

2000年,本科畢業的 Chris Lattner 像中國多數大學生一樣,按部就班地考了GRE,最終前往UIUC(伊利諾伊大學厄巴納香檳分校),開始了艱苦讀計算機碩士和博士的生涯。在這階段,他不僅周遊美國各大景點,更是翻爛了《Compilers: Principles, Techniques, and Tools》,成了GPA滿分(4.0) 牛人,並不斷地研究探索關於編譯器的未知領域,發表了一篇又一篇的論文,。他在碩士畢業論文裡提出了一套完整的在編譯時、鏈接時、運行時甚至是在閒置時優化程序的編譯思想,直接奠定了LLVM 的基礎。LLVM 在他念博士時更加成熟,使用GCC 作為前端來對用戶程序進行語義分析產生IF(Intermidiate Format),然後 LLVM 使用分析結果完成代碼優化和生成。這項研究讓他在2005年畢業時就成為了業界小有名氣的編譯器專家,他也因此早早地被Apple 盯上,最終成為其編譯器項目的骨幹。

剛進入 Apple,Chris Lattner 就大展身手:首先在 OpenGL 小組做代碼優化,把 LLVM運行時的編譯架在 OpenGL 棧上,這樣OpenGL 棧能夠產出更高效率的圖形代碼。如果顯卡足夠高級,這些代碼會直接扔入GPU 執行。但對於一些不支持全部OpenGL特性的顯卡(比如當時的Intel GMA卡),LLVM 則能夠把這些指令優化成高效的 CPU指令,使程序依然能夠正常運行。這個強大的 OpenGL 實現被用在了後來發佈的Mac OS X 10.5上。同時,LLVM的鏈接優化被直接加入到 Apple 的代碼鏈接器上,而 LLVM-GCC也被同步到使用 GCC4.0 代碼。

詳解三大編譯器:gcc、llvm 和 clang

LLVM2.0 - Clang

Apple 吸收Chris Lattner的目的要比改進GCC代碼更具野心 -- Apple 打算從零開始寫 C、C++、Objective-C語言的前端 Clang,完全替代掉GCC。

Clang 是LLVM的前端,可以用來編譯C,C++,ObjectiveC等語言。Clang則是以LLVM為後端的一款高效易用,並且與IDE 結合很好的編譯前端。

Clang 只支持C,C++和Objective-C三種語言。2007年開始開發,C編譯器最早完成,而由於Objective-C 只是C語言的一個簡單擴展,相對簡單,很多情況下甚至可以等價地改寫為C語言對Objective-C運行庫的函數調用,因此在2009年時,已經完全可以用於生產環境。C++ 在後來也得到了支持。

詳解三大編譯器:gcc、llvm 和 clang

GCC 和 Clang 對比

  • Clang特性

速度快:通過編譯 OS X 上幾乎包含了所有 C 頭文件的 carbon.h 的測試,包括預處理 (Preprocess),語法 (lex),解析 (parse),語義分析 (Semantic Analysis),抽象語法樹生成 (Abstract Syntax Tree) 的時間,Clang 比 GCC 快2倍多。

內存佔用小:Clang 內存佔用是源碼的 130%,Apple GCC 則超過 10 倍。

診斷信息可讀性強:其中錯誤的語法不但有源碼提示,還會在錯誤的調用和相關上下文的下方有~~~~~和^的提示,相比之下 GCC 的提示很天書。

兼容性好:Clang 從一開始就被設計為一個API,允許它被源代碼分析工具和 IDE 集成。GCC 被構建成一個單一的靜態編譯器,這使得它非常難以被作為 API 並集成到其他工具中。

Clang有靜態分析,GCC沒有。

Clang使用BSD許可證,GCC使用GPL許可證。

詳解三大編譯器:gcc、llvm 和 clang

  • GCC 優勢

支持 JAVA/ADA/FORTRAN

GCC 支持更多平臺

GCC 更流行,廣泛使用,支持完備

GCC 基於 C,不需要 C++ 編譯器即可編譯

GCC、LLVM 和 Clang 如何選擇?

目前不推薦使用老的GCC4.2,因為蘋果不會維持它了,而且LLVM-GCC看起來會更好。在項目中途改編譯選項可是一個大變動,需要慎重。

對新的項目而言,LLVM-GCC 看起來應該是個安全的選擇,蘋果公司認為它夠穩定夠成熟,所以才把它當做Xcode 4的預設選項。而且,既然選項使用的是GCC parser,向後兼容性應該沒問題。

LLVM-GCC是個安全的選項,但並不是指Clang/LLVM比較不安全,只是成熟度還沒那麼高效了。

總結 - 再探LLVM

回顧GCC的歷史,雖然它取得了巨大的成功,但開發GCC的初衷是提供一款免費的開源編譯器,僅此而已。可後來隨著GCC支持了越來越多的語言,GCC架構的問題也逐漸暴露出來。但GCC到底有什麼問題呢?LLVM的優點也正是GCC的缺點。傳統編譯器工作的時候前端負責解析源代碼,檢查語法錯誤,並將其翻譯為抽象的語法樹(Abstract Syntax Tree)。優化器對這一中間代碼進行優化,試圖使代碼更高效。後端則負責將優化器優化後的中間代碼轉換為目標機器的代碼,這一過程後端會最大化的利用目標機器的特殊指令,以提高代碼的性能。事實上,不光靜態語言如此,動態語言也符合上面這個模型,例如Java。JVM也利用上面這個模型,將Java代碼翻譯為Java bytecode。這一模型的好處是,當我們要支持多種語言時,只需要添加多個前端就可以了。當需要支持多種目標機器時,只需要添加多個後端就可以了。對於中間的優化器,我們可以使用通用的中間代碼。

這種三段式的結構還有一個好處,開發前端的人只需要知道如何將源代碼轉換為優化器能夠理解的中間代碼就可以了,他不需要知道優化器的工作原理,也不需要了解目標機器的知識。這大大降低了編譯器的開發難度,使更多的開發人員可以參與進來。雖然這種三段式的編譯器有很多優點,並且被寫到了教科書上,但是在實際中這一結構卻從來沒有被完美實現過。做的比較好的應該屬Java和.NET虛擬機。虛擬機可以將目標語言翻譯為bytecode,所以理論上講我們可以將任何語言翻譯為bytecode,然後輸入虛擬機中運行。但是這一動態語言的模型並不太適合C語言,所以硬將C語言翻譯為bytecode並實現垃圾回收機制的效率是非常低的。GCC也將三段式做的比較好,並且實現了很多前端,支持了很多語言。但是上述這些編譯器的致命缺陷是,他們是一個完整的可執行文件,沒有給其它語言的開發者提供代碼重用的接口。即使GCC是開源的,但是源代碼重用的難度也比較大。

LLVM最初的定位是比較底層的虛擬機。它的出現正是為了解決編譯器代碼重用的問題,LLVM一上來就站在比較高的角度,制定了LLVM IR這一中間代碼表示語言。LLVM IR充分考慮了各種應用場景,例如在IDE中調用LLVM進行實時的代碼語法檢查,對靜態語言、動態語言的編譯、優化等。從上面這個圖中我們發現LLVM與GCC在三段式架構上並沒有本質區別。LLVM與其它編譯器最大的差別是,它不僅僅是Compiler Collection,也是Libraries Collection。舉個例子,假如說我要寫一個X語言的優化器,我自己實現了PassX算法,用以處理X語言與其它語言差別最大的地方。而LLVM優化器提供的PassA和PassB算法則提供了X語言與其它語言共性的優化算法。那麼我可以選擇X優化器在鏈接的時候把LLVM提供的算法鏈接進來。LLVM不僅僅是編譯器,也是一個SDK。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前端。

如果你下載 LLVM 的代碼,那麼它就是一個IR到ARM/機器碼的編譯器。比如bin/opt就是對IR的優化器,bin/llc就是IR->ASM的翻譯,bin/llvm-mc就是彙編器。如果你再從http://llvm.org下載Clang,那麼就有了C->IR的翻譯以及完整的編譯器Driver。GDB是GNU的調試器。只要編譯器支持DWARF格式,就可以用GDB調試。


分享到:


相關文章: