誰說不能與龍一起跳舞:Clang / LLVM (2)
對於Driver,這是幾乎所有編譯原理教材都會忽略的內容,因為這是一個實際工程問題,而非編譯理論問題。如你編譯器的選項如何設置,應該怎麼設置,你給用戶的是什麼介面,應該怎麼使用這些介面等問題,都是屬於做編譯器產品時應該考慮的問題,而非編譯原理教材本身應該講解的範疇。然而,Driver卻是實際編譯器的一個非常重要的部分,它也是最直接面向程序員使用者、最影響用戶體驗的一部分。如你在開發你的產品時選用的編譯器是GCC,你使用了很多GCC編譯器選項,當你想要移植到其它平台或者支持更多編譯器時,往往不是你的代碼需要做更改,而是你的編譯器選項需要做更改。如你使用了-std=c++11來開啟C++11,然而一些編譯器卻不是叫這個,如在AIX的IBM編譯器,叫做-qlanglvl=extended0x,在Windows下的MSVC也不識別-std=c++11這個選項。那麼,事到如今,各個平台都幾乎有一個事實統治的編譯器,很多時候若你要在這個平台下要與它競爭、與它搶用戶,一個很重要的地方就是保持與它的兼容,其中一大塊就是Driver,保證用戶在不修改編譯選項的時候,也能完成相同的功能,從而順利完成編譯器的遷移。於是乎,無論是IBM的XL編譯器,還是這篇文章談論的Clang,若它們想要在Linux下搶用戶,一個目標就是要保證與GCC的兼容性。而由於IBM編譯器的歷史比較悠久,以前採用的一直是-q風格的選項(如-qlanglvl=對應於-std=),所以IBM甚至提供了一個Driver叫做gxlC來保證你可以使用GCC的選項來調用XL編譯器,當然現在IBM編譯器越做越好,默認編譯器已經高度兼容GCC,在擁抱了Clang以後,我可以很自信的說IBM編譯器在編譯Linux開源軟體時,不需要更改任何編譯選項了,這也是抱了Clang的大腿,而且這個大腿足夠粗,感謝偉大的Clang!咳咳...回歸話題,這裡說到了gxlC來保證可以使用GCC的選項來調用XL編譯器,其實Clang也使用過類似的東西,這就是clang-cl。它可以讓Clang開啟微軟VC++的cl Driver模式,讓用戶可以使用微軟的cl選項來調用Clang編譯器,不過Clang在Windows的支持不是我的關注重點,所以我將更多的談論Clang本身及其默認的Driver上。然而說了這麼多,大家其實也可以發現Driver雖然在編譯原理里提到的不多,但是在實際編譯器開發中卻是非常重要的一個部分。
那麼,接下來我們看看Clang的Driver設計,那就是這一張圖:

這張圖的橘色部分除了代表圖片左上角提及的Input / Output,更是代表了Clang Driver裡面的具體數據結構,如你所見的ArgList;綠色部分除了代表 Driver Functions,也代表了Driver的具體走向流程;藍紫色部分則是一些重要的輔助類。那麼,我們先從第一個步驟Parse講起。
- Parse的作用是選項解析,負責把用戶傳入過來的命令行選項解析成一個一個的參數,放入到Arg實例中。
// a.cnint main(){}n
然後我們很簡單的使用clang a.c -I/My/Hello/World來編譯,但是我們使用-###選項(它需要放在所有選項的前面),它可以列印Parse後的結果,所以命令是clang -### a.c -I/My/Hello/World.


// a.cppn#include <iostream>nnint main()n{n std::cout << "Hello World!" << std::endl;n}n
若是clang a.cpp 或者 clang -x c++ a.cpp,你會發現一堆的鏈接錯誤,如:


然而我們在使用clang++時則不需要,因為在Driver層已經幫你做好了。所以,在C與C++識別上,編譯器不僅會根據傳入的文件類型,也會根據你調用的是clang還是clang++來做處理,是兩方面。有同學可能會說,那麼我每次就自動加上-lc++好了?我會回答這樣不好。很多時候我們在調用C++時包括的可能不僅僅C++標準庫,如我們IBM C++編譯器,我們可能還有ibm c++標準庫等,甚至還有非C++標準庫,如其他的C++輔助庫等,所以何必自尋煩惱呢?C程序使用clang,C++程序使用clang++就好,麻煩事交給編譯器開發人員吧。
而在正式進入第二個流程前,我想再講解一個特別的選項 -cc1. 這個選項非常重要,若不理解也很難理解後面的代碼,因為在driver的main處理時,其本質會走入cc1_main. 那麼cc1是什麼呢?簡單的來說就是有了cc1,就走入到Clang的前端了。在這裡,將會有獨特的選項和行為,如-emit-obj選項,這會告訴Clang進行emit object file。但是,這個選項你在外層是無法使用的,如clang -emit-obj a.c,會說不識別emit-obj選項,因為這是Clang前端的,而不是Clang Driver的,你需要clang -cc1 -emit-obj a.c才可以。我在很久以前回答過一個有關cc1的問題,我以代碼的方式闡述過:Clang裡面真正的前端是什麼? - 編譯器 . 無論如何,即使現在不理解cc1,也需要記住clang -cc1是Clang前端,clang是Clang Driver。
接下來讓我們看Driver的第二個流程:Pipeline.
- Pipeline的作用則是根據具體的編譯選項,構建不同的Compiler Action。





- Bind的作用則在於Tool與Filename的選擇提供
那麼,我們也可以使用-ccc-print-bindings來列印Bind後的結果。

首先,我們可以看到編譯選擇的是clang, 然後鏈接選擇的是darwin::Linker。然後是不是很吃驚彙編過程和彙編器去哪裡了?其實在Mac平台下,Clang使用了內置彙編器,integrated-as。在產生LLVM IR以後,調用了內置彙編器,然後直接生成.o,好處就是減掉了生成彙編文件和調用目標彙編器的開銷。怎麼讓這個彙編出來呢?那就是使用-fno-integrated-as,告訴clang不要使用內置彙編器。

Clang的做法我是非常喜歡的,為每一個平台、操作系統、架構提供抽象的ToolChain。如Clang目前沒有支持AIX,然而在上一次我為Clang實現AIX的支持時,我需要提供AIX整個的工具鏈支持,那麼在這一塊,我只需要創建出來我自己的AIXToolChain,然後繼承於它的抽象基類,實現相關的函數(如assembler, linker等),那麼整個這一套我就做好了,所以Clang的這一設定具有非常好的擴展性。
- Translate的作用則是處理工具的選項參數翻譯


- Execute的作用:執行整個編譯。

推薦閱讀:
※為什麼Apple的Clang生成的LLVM IR比開源的Clang生成的IR要讀者友好?
※如何評價Clang with Microsoft CodeGen?
※windows下如何使用clang來編譯c++14項目?
