詳解三大編譯器:gcc、llvm 和 clang
編譯器一般構(gòu)成
傳統(tǒng)的編譯器通常分為三個(gè)部分,前端(frontEnd),優(yōu)化器(Optimizer)和后端(backEnd)。在編譯過程中,前端主要負(fù)責(zé)詞法和語法分析,將源代碼轉(zhuǎn)化為抽象語法樹;優(yōu)化器則是在前端的基礎(chǔ)上,對(duì)得到的中間代碼進(jìn)行優(yōu)化,使代碼更加高效;后端則是將已經(jīng)優(yōu)化的中間代碼轉(zhuǎn)化為針對(duì)各自平臺(tái)的機(jī)器代碼。
GCC
GCC(GNU Compiler Collection,GNU編譯器套裝),是一套由 GNU 開發(fā)的編程語言編譯器。GCC 原名為 GNU C 語言編譯器,因?yàn)樗局荒芴幚?C語言。GCC 快速演進(jìn),變得可處理 C++、Fortran、Pascal、Objective-C、Java, 以及 Ada 等他語言。
LLVM
LLVM (Low Level Virtual Machine,底層虛擬機(jī)) 提供了與編譯器相關(guān)的支持,能夠進(jìn)行程序語言的編譯期優(yōu)化、鏈接優(yōu)化、在線編譯優(yōu)化、代碼生成。簡(jiǎn)而言之,可以作為多種編譯器的后臺(tái)來使用。
蘋果公司一直使用 GCC 作為官方的編譯器。GCC 作為一款開源的編譯器,一直做得不錯(cuò),但 Apple 對(duì)編譯工具會(huì)提出更高的要求。原因主要有以下兩點(diǎn):
其一,是 Apple 對(duì) Objective-C 語言(包括后來對(duì)C語言)新增很多特性,但 GCC 開發(fā)者并不買Apple的賬——不給實(shí)現(xiàn),因此索性后來兩者分成兩條分支分別開發(fā),這也造成 Apple 的編譯器版本遠(yuǎn)落后于 GCC 的官方版本。其二,GCC 的代碼耦合度太高,很難獨(dú)立,而且越是后期的版本,代碼質(zhì)量越差,但 Apple 想做的很多功能(比如更好的 IDE 支持),需要模塊化的方式來調(diào)用 GCC,但 GCC一直不給做。
編譯器大神 Chris Lattner 橫空出世
2000年,本科畢業(yè)的 Chris Lattner 像中國多數(shù)大學(xué)生一樣,按部就班地考了GRE,最終前往UIUC(伊利諾伊大學(xué)厄巴納香檳分校),開始了艱苦讀計(jì)算機(jī)碩士和博士的生涯。在這階段,他不僅周游美國各大景點(diǎn),更是翻爛了《Compilers: Principles, Techniques, and Tools》,成了GPA滿分(4.0) 牛人,并不斷地研究探索關(guān)于編譯器的未知領(lǐng)域,發(fā)表了一篇又一篇的論文,。他在碩士畢業(yè)論文里提出了一套完整的在編譯時(shí)、鏈接時(shí)、運(yùn)行時(shí)甚至是在閑置時(shí)優(yōu)化程序的編譯思想,直接奠定了LLVM 的基礎(chǔ)。LLVM 在他念博士時(shí)更加成熟,使用GCC 作為前端來對(duì)用戶程序進(jìn)行語義分析產(chǎn)生IF(Intermidiate Format),然后 LLVM 使用分析結(jié)果完成代碼優(yōu)化和生成。這項(xiàng)研究讓他在2005年畢業(yè)時(shí)就成為了業(yè)界小有名氣的編譯器專家,他也因此早早地被Apple 盯上,最終成為其編譯器項(xiàng)目的骨干。
剛進(jìn)入 Apple,Chris Lattner 就大展身手:首先在 OpenGL 小組做代碼優(yōu)化,把 LLVM運(yùn)行時(shí)的編譯架在 OpenGL 棧上,這樣OpenGL 棧能夠產(chǎn)出更高效率的圖形代碼。如果顯卡足夠高級(jí),這些代碼會(huì)直接扔入GPU 執(zhí)行。但對(duì)于一些不支持全部OpenGL特性的顯卡(比如當(dāng)時(shí)的Intel GMA卡),LLVM 則能夠把這些指令優(yōu)化成高效的 CPU指令,使程序依然能夠正常運(yùn)行。這個(gè)強(qiáng)大的 OpenGL 實(shí)現(xiàn)被用在了后來發(fā)布的Mac OS X 10.5上。同時(shí),LLVM的鏈接優(yōu)化被直接加入到 Apple 的代碼鏈接器上,而 LLVM-GCC也被同步到使用 GCC4.0 代碼。
LLVM2.0 - Clang
Apple 吸收Chris Lattner的目的要比改進(jìn)GCC代碼更具野心 -- Apple 打算從零開始寫 C、C++、Objective-C語言的前端 Clang,完全替代掉GCC。
Clang 是LLVM的前端,可以用來編譯C,C++,ObjectiveC等語言。Clang則是以LLVM為后端的一款高效易用,并且與IDE 結(jié)合很好的編譯前端。
Clang 只支持C,C++和Objective-C三種語言。2007年開始開發(fā),C編譯器最早完成,而由于Objective-C 只是C語言的一個(gè)簡(jiǎn)單擴(kuò)展,相對(duì)簡(jiǎn)單,很多情況下甚至可以等價(jià)地改寫為C語言對(duì)Objective-C運(yùn)行庫的函數(shù)調(diào)用,因此在2009年時(shí),已經(jīng)完全可以用于生產(chǎn)環(huán)境。C++ 在后來也得到了支持。
GCC 和 Clang 對(duì)比
- Clang特性
速度快:通過編譯 OS X 上幾乎包含了所有 C 頭文件的 carbon.h 的測(cè)試,包括預(yù)處理 (Preprocess),語法 (lex),解析 (parse),語義分析 (Semantic Analysis),抽象語法樹生成 (Abstract Syntax Tree) 的時(shí)間,Clang 比 GCC 快2倍多。
內(nèi)存占用小:Clang 內(nèi)存占用是源碼的 130%,Apple GCC 則超過 10 倍。
診斷信息可讀性強(qiáng):其中錯(cuò)誤的語法不但有源碼提示,還會(huì)在錯(cuò)誤的調(diào)用和相關(guān)上下文的下方有~~~~~和^的提示,相比之下 GCC 的提示很天書。
兼容性好:Clang 從一開始就被設(shè)計(jì)為一個(gè)API,允許它被源代碼分析工具和 IDE 集成。GCC 被構(gòu)建成一個(gè)單一的靜態(tài)編譯器,這使得它非常難以被作為 API 并集成到其他工具中。
Clang有靜態(tài)分析,GCC沒有。
Clang使用BSD許可證,GCC使用GPL許可證。
- GCC 優(yōu)勢(shì)
支持 JAVA/ADA/FORTRAN
GCC 支持更多平臺(tái)
GCC 更流行,廣泛使用,支持完備
GCC 基于 C,不需要 C++ 編譯器即可編譯
GCC、LLVM 和 Clang 如何選擇?
目前不推薦使用老的GCC4.2,因?yàn)樘O果不會(huì)維持它了,而且LLVM-GCC看起來會(huì)更好。在項(xiàng)目中途改編譯選項(xiàng)可是一個(gè)大變動(dòng),需要慎重。
對(duì)新的項(xiàng)目而言,LLVM-GCC 看起來應(yīng)該是個(gè)安全的選擇,蘋果公司認(rèn)為它夠穩(wěn)定夠成熟,所以才把它當(dāng)做Xcode 4的預(yù)設(shè)選項(xiàng)。而且,既然選項(xiàng)使用的是GCC parser,向后兼容性應(yīng)該沒問題。
LLVM-GCC是個(gè)安全的選項(xiàng),但并不是指Clang/LLVM比較不安全,只是成熟度還沒那么高效了。
總結(jié) - 再探LLVM
回顧GCC的歷史,雖然它取得了巨大的成功,但開發(fā)GCC的初衷是提供一款免費(fèi)的開源編譯器,僅此而已??珊髞黼S著GCC支持了越來越多的語言,GCC架構(gòu)的問題也逐漸暴露出來。但GCC到底有什么問題呢?LLVM的優(yōu)點(diǎn)也正是GCC的缺點(diǎn)。傳統(tǒng)編譯器工作的時(shí)候前端負(fù)責(zé)解析源代碼,檢查語法錯(cuò)誤,并將其翻譯為抽象的語法樹(Abstract Syntax Tree)。優(yōu)化器對(duì)這一中間代碼進(jìn)行優(yōu)化,試圖使代碼更高效。后端則負(fù)責(zé)將優(yōu)化器優(yōu)化后的中間代碼轉(zhuǎn)換為目標(biāo)機(jī)器的代碼,這一過程后端會(huì)最大化的利用目標(biāo)機(jī)器的特殊指令,以提高代碼的性能。事實(shí)上,不光靜態(tài)語言如此,動(dòng)態(tài)語言也符合上面這個(gè)模型,例如Java。JVM也利用上面這個(gè)模型,將Java代碼翻譯為Java bytecode。這一模型的好處是,當(dāng)我們要支持多種語言時(shí),只需要添加多個(gè)前端就可以了。當(dāng)需要支持多種目標(biāo)機(jī)器時(shí),只需要添加多個(gè)后端就可以了。對(duì)于中間的優(yōu)化器,我們可以使用通用的中間代碼。這種三段式的結(jié)構(gòu)還有一個(gè)好處,開發(fā)前端的人只需要知道如何將源代碼轉(zhuǎn)換為優(yōu)化器能夠理解的中間代碼就可以了,他不需要知道優(yōu)化器的工作原理,也不需要了解目標(biāo)機(jī)器的知識(shí)。這大大降低了編譯器的開發(fā)難度,使更多的開發(fā)人員可以參與進(jìn)來。雖然這種三段式的編譯器有很多優(yōu)點(diǎn),并且被寫到了教科書上,但是在實(shí)際中這一結(jié)構(gòu)卻從來沒有被完美實(shí)現(xiàn)過。做的比較好的應(yīng)該屬Java和.NET虛擬機(jī)。虛擬機(jī)可以將目標(biāo)語言翻譯為bytecode,所以理論上講我們可以將任何語言翻譯為bytecode,然后輸入虛擬機(jī)中運(yùn)行。但是這一動(dòng)態(tài)語言的模型并不太適合C語言,所以硬將C語言翻譯為bytecode并實(shí)現(xiàn)垃圾回收機(jī)制的效率是非常低的。GCC也將三段式做的比較好,并且實(shí)現(xiàn)了很多前端,支持了很多語言。但是上述這些編譯器的致命缺陷是,他們是一個(gè)完整的可執(zhí)行文件,沒有給其它語言的開發(fā)者提供代碼重用的接口。即使GCC是開源的,但是源代碼重用的難度也比較大。
LLVM最初的定位是比較底層的虛擬機(jī)。它的出現(xiàn)正是為了解決編譯器代碼重用的問題,LLVM一上來就站在比較高的角度,制定了LLVM IR這一中間代碼表示語言。LLVM IR充分考慮了各種應(yīng)用場(chǎng)景,例如在IDE中調(diào)用LLVM進(jìn)行實(shí)時(shí)的代碼語法檢查,對(duì)靜態(tài)語言、動(dòng)態(tài)語言的編譯、優(yōu)化等。從上面這個(gè)圖中我們發(fā)現(xiàn)LLVM與GCC在三段式架構(gòu)上并沒有本質(zhì)區(qū)別。LLVM與其它編譯器最大的差別是,它不僅僅是Compiler Collection,也是Libraries Collection。舉個(gè)例子,假如說我要寫一個(gè)X語言的優(yōu)化器,我自己實(shí)現(xiàn)了PassX算法,用以處理X語言與其它語言差別最大的地方。而LLVM優(yōu)化器提供的PassA和PassB算法則提供了X語言與其它語言共性的優(yōu)化算法。那么我可以選擇X優(yōu)化器在鏈接的時(shí)候把LLVM提供的算法鏈接進(jìn)來。LLVM不僅僅是編譯器,也是一個(gè)SDK。Apple LLVM compiler 4.2是一個(gè)真正的LLVM編譯器,前端使用的是Clang,基于最新的LLVM 3.2編譯的。LLVM GCC 4.2編譯器的核心仍然是LLVM,但是前端使用的是GCC 4.2編譯器。從LLVM的下載頁面可以看出,LLVM從1.0到2.5使用的都是GCC作為前端,直到2.6開始才提供了Clang前端。
如果你下載 LLVM 的代碼,那么它就是一個(gè)IR到ARM/機(jī)器碼的編譯器。比如bin/opt就是對(duì)IR的優(yōu)化器,bin/llc就是IR->ASM的翻譯,bin/llvm-mc就是匯編器。如果你再從http://llvm.org下載Clang,那么就有了C->IR的翻譯以及完整的編譯器Driver。GDB是GNU的調(diào)試器。只要編譯器支持DWARF格式,就可以用GDB調(diào)試。