第2章 外 部 項 目
不包含于核心LLVM和Clang存儲庫的項目需要單獨下載下傳。在本章中,我們将介紹各種其他官方LLVM項目,并介紹如何建構和安裝它們。僅對核心LLVM工具感興趣的讀者可以跳過本章,或在需要時再翻閱。
在本章中,我們将介紹以下項目及其安裝方法:
- Clang 外部工具
- Compiler-RT
- DragonEgg
- LLVM測試套件
- LLDB
- libc++
除了本章所涉及的項目之外,還有兩個在本書範圍之外的官方LLVM項目:Polly(多面體優化器)以及lld(目前正在開發的LLVM連結器)。
預建構的二進制包不包含本章中提及的任何外部項目(Compiler-RT除外)。是以,與上一章不同,我們将僅介紹如何下載下傳源代碼并自行建構它們。
讀者不要指望本章介紹的項目與核心LLVM/Clang項目的成熟度相同,因為其中一些項目隻是實驗性的,或處于起步階段。
2.1 Clang外部項目介紹
LLVM中最引人注目的設計就是将後端與前端隔離為兩個獨立的項目,即LLVM核心和Clang。LLVM開始時是以LLVM中間表示(IR)為中心的一組工具,并且依賴于可自行修改的GCC将進階語言程式轉換為獨有的IR形式,并存儲在位碼(bitcode)檔案中。位碼是一個術語,它模仿了Java位元組碼的命名。Clang作為LLVM團隊專門設計的第一個前端,是LLVM項目的一個重要裡程碑,它有着與LLVM核心相同的代碼品質、清晰的文檔和庫組織結構。它不僅可以将C和C++程式轉換為LLVM IR,還可以作為靈活的編譯器驅動程式對整個編譯過程進行監督,以便盡可能保持與GCC的相容性。
我們後面會稱Clang為前端程式,而不是驅動程式,它負責将C和C++程式轉換為LLVM IR。Clang庫的一大亮點是可以用于編寫強大的工具,比如C++代碼重構工具和源代碼分析工具,進而使C++程式員可以自由地研究C++的熱點問題。Clang預包裝的一些工具可以幫助你了解如何利用這些庫,比如:
- Clang Check:它能夠執行文法檢查,還能應用快速修複以解決常見問題,還可以轉儲任何程式的内部Clang抽象文法樹(AST)表示。
- Clang Format:它包含一個工具和一個LibFormat庫,它們不僅可以縮進代碼,還可以将任何一部分C++代碼格式化為任何樣式,以符合LLVM編碼标準以及Google、Chromium、Mozilla或者WebKit的樣式指南。
clang-tools-extra存儲庫是建立在Clang之上的多個應用程式的集合,它們能夠讀取大型C或C++代碼庫,并執行各種代碼重構和分析。我們在下面列出這個包中的一些工具,但不是全部:
- Clang Modernizer:它是一個代碼重構工具,用于掃描C++代碼并更改舊樣式的結構,以符合較新标準(例如C++ 11标準)提出的更現代的樣式。
- Clang Tidy:它是一個錯誤檢查工具,用于檢查違反LLVM或Google編碼标準的常見程式設計錯誤。
- Modularize:它可以幫助你識别适合組成子產品的C++頭檔案,“子產品”是C++标準化委員會正在讨論的新概念(有關更多資訊,請參閱第10章)。
- PPTrace:它是一個簡單工具,用于跟蹤Clang C++預處理器的活動。
有關如何使用這些工具以及如何建構自己的工具的更多資訊,請參見第10章。
2.1.1 建構和安裝Clang外部工具
可以從
http://llvm.org/releases/3.4/clang-tools-extra-3.4.src.tar.gz擷取該項目的3.4版本的官方快照。如果想浏覽所有可用的版本,請通路
http://llvm.org/releases/download.html。如果想依靠LLVM建構系統輕松編譯這組工具,可以與核心LLVM和Clang的源代碼一起建構。為此,必須将源代碼目錄放入Clang源代碼樹中,如下所示:

還可以直接從官方的LLVM SVN存儲庫擷取資源:
從上一章得知,如果要擷取版本3.4的穩定源代碼,可以用tags/RELEASE_34/final替換trunk。或者,如果你喜歡使用GIT版本控制軟體,可以使用以下指令行下載下傳它:
将源代碼放入Clang樹後,必須參照第1章中的編譯操作說明,使用CMake或自動工具生成的配置腳本繼續操作。要測試安裝是否成功,請運作clang-modernize工具,如下所示:
2.1.2 了解Compiler-RT
Compiler-RT(RT指運作時)項目用于為硬體不支援的低級功能提供特定于目标的支援。例如,32位目标通常缺少支援64位除法的指令。Compiler-RT通過提供特定于目标并經過優化的功能來解決這個問題,該功能在使用32位指令的同時實作了64位除法。它提供相同的功能,是以是LLVM項目中libgcc的替代品。此外,它還具有對位址和記憶體清洗工具的運作時支援。你可以從
http://llvm.org/releases/3.4/compiler-rt-3.4.src.tar.gz下載下傳3.4版本的Compiler-RT,或者在
上查找更多版本。
它在基于LLVM的編譯工具鍊中是一個關鍵元件,是以上一章已經介紹了如何安裝Compiler-RT。如果你仍然沒有這個元件,請記住将其源代碼放入LLVM源代碼樹中的projects檔案夾内,如以下指令序列所示:
如果你願意,也可以使用它的SVN存儲庫:
除了SVN存儲庫,還可以通過GIT鏡像下載下傳:
2.1.3 實驗Compiler-RT
要檢視編譯器運作時庫啟動時的典型情況,可以編寫一個執行64位除法的C程式來做一個簡單的實驗:
如果你有64位x86系統,請使用你的LLVM編譯器來實驗以下兩個指令:
-m32标志訓示編譯器生成32位x86程式,而-S标志将用于在test-32bit.S中為此程式生成x86彙編語言檔案。如果檢視這個檔案,就會看到每當程式需要執行除法時都會有一個有趣的調用:
該函數由Compiler-RT定義,并示範了将在何處使用該庫。但是,如果省略-m32标志并使用64位x86編譯器,即與生成test-64bit.S彙編檔案的第二個編譯器指令一樣,則不會再看到需要Compiler- RT協助的程式,因為它可以通過單個指令完成除法運算:
2.2 使用DragonEgg插件
如前所述,LLVM項目初期依賴于GCC,沒有自己的C/C++前端。在那時,你需要下載下傳一個名為llvm-gcc的GCC源代碼樹并将其完整編譯,才能使用LLVM。由于編譯涉及完整的GCC軟體包,需要知道自己重建GCC所需的所有必要的GNU知識,是以這是一項非常耗時且棘手的任務。DragonEgg項目為利用GCC插件系統提出了一個聰明的解決方案,它将LLVM邏輯分離到它自己的一個更小的代碼樹中。這樣,使用者不再需要重建整個GCC包,而隻需建構一個插件,然後将其加載到GCC中即可。DragonEgg也是LLVM項目下唯一獲得GPL授權的項目。
即使Clang已經興起,DragonEgg至今仍然存在,因為Clang隻處理C和C++語言,而GCC能夠解析更多種類的語言。通過使用DragonEgg插件,可以使用GCC作為LLVM編譯器的前端,進而能夠編譯GCC支援的大多數語言,包括Ada、C、C++和FORTRAN,并且部分支援Go、Java、Objective-C和Objective-C++。
該插件用LLVM的相應部分替代GCC的中間和後端,并自動執行所有編譯步驟,能滿足你對一流的編譯器驅動程式的期望。圖2-1是這種新場景的編譯流程。
如果你願意,可以使用-fplugin-arg-dragonegg-emit-ir -S标志将編譯過程停止在LLVM IR生成階段,并使用LLVM工具分析和調查前端的結果,或者手動使用 LLVM工具完成編譯過程。我們将很快看到一個例子。
由于DragonEgg是一個LLVM分支項目,維護人員無法以維護LLVM主項目那樣的頻率更新該項目。在編寫本書時,DragonEgg最新的穩定版本是3.3版本,并且被綁定到LLVM 3.3的工具集中。是以,如果你生成LLVM位碼(使用LLVM IR寫在磁盤上的程式),則不能使用3.3以外版本的LLVM工具分析此檔案,也不能進行優化或繼續編譯。你可以在
http://dragonegg.llvm.org上找到DragonEgg官方網站。
2.2.1 建構DragonEgg
要編譯并安裝DragonEgg,請首先從
http://llvm.org/releases/3.3/dragonegg-3.3.src.tar.gz擷取源代碼。對于Ubuntu,請使用以下指令:
如果你希望從SVN庫中擷取最新但不穩定的源代碼,請使用以下指令:
對于GIT鏡像,請使用以下指令:
要執行編譯和安裝,需要提供LLVM安裝路徑。LLVM版本必須與正在安裝的DragonEgg版本相比對。假設使用與第1章中相同的安裝字首/usr/local/llvm,并假設GCC 4.6已安裝并存在于你的shell PATH變量中,則應使用以下指令:
請注意,該項目缺少自動工具或CMake項目檔案。你應該使用make指令直接建構。如果你的gcc指令已經提供了你需要的正确GCC版本,則可以在運作make時省略GCC = gcc-4.6字首。建構後将生成名為dragonegg.so并且格式為共享庫的插件,你可以使用以下GCC指令行調用該插件(假設你正在編譯一個經典的“Hello,World!” C代碼):
雖然DragonEgg理論上支援GCC 4.5及更高版本,但強烈建議使用GCC 4.6。DragonEgg沒有在其他GCC版本中進行過廣泛測試和維護。
2.2.2 使用DragonEgg和LLVM工具了解編譯流程
如果你希望看到前端的運作情況,請使用-S -fplugin-arg-dragonegg-emit-ir标志,該标志将産生以LLVM IR代碼表示的人工可讀檔案:
一旦編譯器将程式轉換為IR則停止編譯,并将記憶體中的表示内容寫入磁盤的能力是LLVM的一個獨有特征。大多數其他編譯器無法做到這一點。在欣賞LLVM IR如何表示源程式之後,你可以手動使用多個LLVM工具繼續完成編譯過程。以下指令調用一個特殊的彙程式設計式,将LLVM從文本形式轉換為二進制形式,仍儲存在磁盤上:
如果你願意,可以用一個特殊的IR反彙編器(llvm-dis)把它翻譯回可讀的形式。以下工具将在顯示成功完成代碼轉換的相關統計資訊的同時,進行獨立于編譯目标的優化:
-stats标志是可選的。之後,你可以使用LLVM後端工具将其轉換為目标機器的彙編語言:
再強調一下,-stats标志是可選的。由于hello.s是一個彙編檔案,是以既可以使用GNU binutils彙編器,也可以使用LLVM彙編器。在下面的指令中,我們将使用LLVM彙編器:
因為LLVM連結器項目lld目前正在開發中,還沒有內建到核心LLVM項目中,是以LLVM預設使用你的系統連結器。是以,如果你沒有lld,可以使用正常的編譯器驅動程式來完成編譯,這會激活你的系統連結器:
請記住,出于性能方面的原因,除了目标檔案之外,真正的LLVM編譯器驅動程式在任何階段都不會将程式表示内容寫入磁盤,因為它仍然缺少內建的連結器。它會使用記憶體中的表示内容并協調幾個LLVM元件進行編譯。
2.2.3 了解LLVM測試套件
LLVM測試套件包括一套用于測試LLVM編譯器的官方基準程式。該測試套件對于LLVM開發人員非常有用,它通過編譯和運作這些程式來驗證優化和編譯器的改進。如果正在使用LLVM的非穩定版本,或者更改了LLVM源代碼并懷疑某些功能不能正常工作,那麼可以自行運作該測試套件。但請記住,在LLVM主源代碼樹中存在更簡單的LLVM回歸測試和單元測試,可以使用make check-all輕松運作它們。測試套件不同于傳統的回歸測試和單元測試,因為它包含了整個基準程式。
必須将LLVM測試套件放在LLVM源代碼樹中,以允許LLVM建構系統識别它。可以從
http://llvm.org/releases/3.4/test-suite-3.4.src.tar.gz找到版本3.4的資源。
要擷取源代碼,請使用以下指令:
如果你喜歡通過SVN下載下傳,以獲得最新但可能不穩定的版本,請使用以下指令:
如果你喜歡使用GIT,請使用以下指令:
需要重新生成LLVM的建構檔案才能使用測試套件。在此特例中,不能使用CMake。必須使用經典的配置腳本來建構測試套件。讀者可以參考第1章中介紹的配置步驟。
測試套件有一套Makefile檔案,用于測試和檢查基準。也可以提供一個自定義的Makefile來評估自定義程式。請将自定義Makefile檔案放在測試套件的源代碼目錄中,并使用命名模闆llvm/projects/test-suite/TEST..Makefile命名該檔案,
其中,必須将記号替換為所需的任何名稱,比如llvm/projects/test-suite/TEST.example.Makefile。
在配置期間,将在基準測試程式将要運作的LLVM對象目錄中建立測試套件的目錄。要運作并測試示例Makefile檔案,請進入第1章中的對象目錄路徑,然後執行以下指令:
2.2.4 使用LLDB
LLDB(低級調試器)項目是一個用LLVM基礎架構建構的調試器,它作為在Mac OS X上的Xcode 5調試器而被積極開發出來。從2011年開始開發到寫本書時為止,LLDB還沒有在Xcode範圍之外釋出一個穩定的版本。可以從
http://llvm.org/releases/3.4/lldb-3.4.src.tar.gz擷取LLDB資源。像許多依賴于LLVM的項目一樣,可以通過将其內建到LLVM建構系統中來輕松建構它。要做到這一點,隻需将其源代碼放在LLVM tools檔案夾中,如下例所示:
也可以使用其SVN存儲庫來獲得最新版本:
如果你願意,還可以使用GIT鏡像來擷取它:
對于GNU/Linux系統,LLDB仍然處于實驗階段。
在建構之前,請注意LLDB有一些軟體先決條件:Swig、libedit(僅适用于Linux)和Python。例如,在Ubuntu系統上,可以使用以下指令來解決這些依賴關系:
請記住,與本章介紹的其他項目一樣,需要重新生成LLVM建構檔案以允許進行LLDB編譯。請按照第1章中所述的步驟從源代碼建構LLVM。
要對最近的lldb安裝進行簡單測試,隻需使用-v标志運作,即可列印其版本:
使用LLDB執行調試會話
為了示範如何使用LLDB,我們将啟動一個調試會話來分析Clang二進制檔案。你可以看到Clang二進制檔案包含許多的C++符号。如果你使用預設選項編譯LLVM/Clang項目,将得到帶調試符号的Clang二進制檔案。如果在運作配置腳本生成LLVM Makefile時省略--enable-optimized标志,或者在運作CMake檔案時使用-DCMAKE_BUILD_TYPE="Debug"(這是預設建構類型),都會發生這種情況。
如果你熟悉GDB,可能有興趣參考
http://lldb.llvm.org/lldb-gdb.html中的表格,該表格列出了常用GDB指令及相應的對等LLDB指令。
與GDB一樣,我們通過傳遞将要調試的可執行檔案的路徑作為指令行參數來啟動LLDB:
為了開始調試,我們将指令行參數提供給Clang二進制檔案。我們将使用-v參數,它将列印Clang版本:
在LLDB到達我們的斷點之後,就可以用next指令單步執行每一行C++代碼。與GDB一樣,LLDB也可以接受任何指令縮寫,前提是這個縮寫不會帶來歧義,例如用n代替next:
要檢視LLDB如何列印C++對象,請在聲明argv或ArgAllocator對象之後單步執行到達該行并列印它:
你覺得滿意後,用q指令退出調試器:
2.2.5 libc++标準庫介紹
libc++庫是LLVM項目重寫的C++标準庫,它支援最新的C++标準(包括C++ 11和C++ 1y),并且在MIT許可證和UIUC許可證下獲得雙重許可。libc++庫是Compiler-RT的重要夥伴,是用Clang++建構最終C++可執行檔案時所使用的運作時庫的一部分,必要時也包含libclc(OpenCL運作時庫)。它不同于Compiler-RT,因為并非一定要建構它。Clang并不局限于libc++,在沒有libc++的情況下,它可以将你的程式與GNU libstdc++連結。如果這兩個庫都存在,你可以使用-stdlib選項指定Clang++使用哪個庫。libc++庫支援x86和x86_64處理器,而且它被設計為用于Mac OS X和GNU/Linux系統的GNU libstdc++的替代品。
據libc++開發人員稱,繼續開發GNU libstdc++的主要障礙之一是需要重寫代碼來支援更新的C++标準,并且主線libstdc++開發切換到GPLv3許可證之後,以至于一些依賴于LLVM項目的公司無法使用。請注意,LLVM項目在商業産品中經常使用與GPL許可不相容的方式。面對這些挑戰,LLVM社群決定主要為Mac OS X開發新的C++标準庫,同時支援Linux。
在你的蘋果電腦中擷取libc++的最簡單方法是安裝Xcode 4.2或更高版本。
如果你打算自己為GNU/Linux機器建構庫,請記住,C++标準庫由庫本身和一個低級函數層組成,這個函數層實作了用于處理異常和運作時類型資訊(RTTI)的若幹功能。這種關注點的分離使得C++标準庫更容易移植到其他系統。在建構标準庫時,它也提供了不同的選項。你可以建構與libsupc++(這個較低層的GNU實作)或者libc++ abi(LLVM團隊的實作)連結的libc++。不過,libc++ abi目前隻支援Mac OS X系統。
要在GNU/Linux機器上用libsupc++建構libc++,首先需要下載下傳源代碼包:
在撰寫本書之前,仍然無法像在其他項目中那樣,依靠LLVM建構系統來建立庫檔案。是以,請注意,這次我們沒有将libc++源代碼放入LLVM源代碼樹中。
另外,也可以使用SVN版本庫中的實驗性版本:
還可以使用GIT鏡像:
隻要你有基于LLVM的工作編譯器,就需要生成隻使用基于LLVM的新編譯器的libc++建構檔案。在這個例子中,我們假定我們的路徑中有一個LLVM 3.4工作編譯器。
要使用libsupc++,首先需要知道它的頭檔案在你的系統中安裝在什麼位置。由于它是GNU/Linux的正常GCC編譯器的一部分,是以可以使用以下指令來找到它:
通常,前兩個路徑即libsupc++頭檔案的位置。要确認這一點,請查找libsupc++頭檔案(如bits/exception_ptr.h)是否存在:
之後,生成libc++建構檔案,以便使用基于LLVM的編譯器編譯它。要執行此操作,請分别改寫用于定義系統C和C++編譯器的shell CC和CXX環境變量,以使用你想嵌入libc++的LLVM編譯器。要使用CMake與libsupc++一起建構libc++,需要定義CMake參數LIBCXX_CXX_ABI(該參數定義要使用的低級庫)和參數LIBCXX_LIBSUPCXX_INCLUDE_PATHS(這是一個用分号分隔的路徑清單,路徑指向你剛發現的包含libsupc++ include檔案的檔案夾):
在這個階段,確定../libcxx是到達libc++源檔案夾的正确路徑。運作make指令來建構項目。使用sudo作為安裝指令,因為我們将在/usr中安裝庫,以便以後可以使用clang++來查找庫:
調用clang++編譯C++項目時,可以通過使用-stdlib=libc++标志來實驗新庫和最新的C++标準。
要驗證你的新庫,請使用以下指令編譯一個簡單的C++應用程式:
可以使用readelf指令執行一個簡單的實驗來分析hello二進制檔案,并确認它确實與新的libc++庫連結:
前面的代碼省略了後面的條目。我們看到正好在第一個ELF動态段條目中有一個特定的請求來加載我們剛剛編譯的libc++.so.1共享庫,由此可以确認我們的C++二進制檔案現在使用新的LLVM的C++标準庫。你可以在官方項目網站
http://libcxx.llvm.org上找到更多資訊。
2.3 總結
LLVM由幾個子項目組成,其中一些對主編譯器驅動程式來說不是必需的,但卻是有用的工具和庫。在本章中,我們展示了如何建構和安裝這些元件。後續章節将更加詳細地探讨這些工具的細節。我們建議讀者以後在需要擷取建構和安裝操作說明時再重讀本章。
在下一章中,我們将向你介紹LLVM核心庫的設計和工具。