誰說不能與龍一起跳舞: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設計,那就是這一張圖:
這張圖來自於Clang官網,我將詳細的講解這張圖,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.c和選項-I/My/Hello/World. 在第一個紅框內,我們可以發現編譯器把-I/My/Hello/World拆開為了 -I 和 /My/Hello/World. 而這裡的-I 是 Clang支持的選項,在Clang內部使用的是OPT__I,隸屬於Option類。Clang對於選項使用了DSL專門處理,然後存為.td文件,再使用table-gen進行處理,轉為C++語言,隨後與其餘的C++代碼一起編譯。我會在後面的文章進行代碼分析的時候專門講解Option,並且我們會編寫我們的編譯選項,嵌套在Clang中。而/My/Hello/World就是-I所接收的值,這裡我們就可以發現Driver層做了處理,解析了我們傳進去的-I/My/Hello/World. 當然,相比-I/My/Hello/World, Driver對於我們傳進去的文件a.c則做了更多的處理,分別是-x c a.c. 那麼-x這個選項,我相信很多程序員是第一次見,這在Clang中是指定源代碼文件所需要的編譯語言。Clang Driver在識別到a.c為C語言文件後,則為-x選項加上了c這個值,表明在編譯C語言文件。那麼Clang Driver怎麼這麼聰明知道這是C語言文件呢?其實Clang Driver是個「蠢貨」,它就是根據文件後綴名.c來判定是C語言的。所以我們可以用-x c++ a.c來使用C++模式編譯。而若你有觀測過clang 和 clang++編譯的話,你會發現clang++本質就是clang,只是默認為C++編譯,所以所有相關選項都是針對C++。不信且看:你可以發現它其實就是指向的clang,而非單獨的clang++。那麼我們用clang a.cpp為何不能編譯C++程序呢?如下面的簡單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,你會發現一堆的鏈接錯誤,如:
但是若我們回顧C++程序的編譯流程,我們知道一個C++程序會經歷 編譯 -> 鏈接 這兩個大流程,而這裡鏈接錯誤則代表著我們已經正確的過了編譯期,但是是鏈接期錯誤了。而C和C++在鏈接期一個巨大的差別就是C++鏈接期需要鏈接C++標準庫,所以我們鏈接上C++標準庫即可,如:然而我們在使用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不要使用內置彙編器。
在我們後面代碼分析中,我們也會發現有一個cc1as_main的東西,這跟彙編過程息息相關。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項目?