LLVM是一個*水平的編譯器框架。它包含有豐富軟件庫,可以為編譯器的初學者提供良好的學習體驗,并大大降低編譯器開發(fā)的學習門檻。
本書的前半部分將向您介紹怎么樣去配置、構建、和安裝LLVM的不同軟件庫、工具和外部項目。接下來,本書的后半部分將向您介紹LLVM的各種設計細節(jié),并逐步地講解LLVM的各個編譯步驟:前段、中間表示(IR)、后端、即時編譯(JIT)引擎、跨平臺編譯和插件接口。本書包含有大量翔實的示例和代碼片段,以幫助讀者平穩(wěn)順利的掌握LLVM的編譯器開發(fā)環(huán)境。
LLVM是一個非常具有啟發(fā)意義的軟件項目,它起始于Chris Lattner個人對編譯器的熱情。LLVM最初版本發(fā)行后出現(xiàn)的一系列事件以及后來被廣泛采用的經歷也遵循了一種其他開源項目常見的成功發(fā)展模式:這些項目通常是人們對某個問題的強烈好奇心的產物,并非始于某個公司。例如,第一個Linux內核的誕生源于一名芬蘭學生對操作系統(tǒng)領域的興趣,因而產生了強烈動機去理解和實踐一個真正的操作系統(tǒng)應該如何工作。
對于Linux或LLVM,許多程序員的貢獻使它們迅速成長為一流軟件,在質量上可以與現(xiàn)有的任何其他競爭對手相媲美。因此,把任何一個大項目的成功歸功于特定個人是不公平的。無可否認的是,在開源社區(qū)中,一個學生的軟件項目想要飛躍成為復雜且健壯的軟件需要一個關鍵因素:吸引那些愿意在該項目上花費時間的貢獻者和程序員。
這樣的因素天然存在于充滿教育氣息的校園氛圍之中。教育的重要任務是教會學生理解任務的工作原理,因此對學生而言,他們可以在解開錯綜復雜的機制并最終掌握它們的過程中享受到勝利的喜悅。伊利諾伊大學厄巴納–香檳分校(UIUC)的LLVM項目正是在這種環(huán)境下發(fā)展起來的,它既被用作研究原型,也被用作Lattner的碩士導師Vikram Adve講授編譯器課程的教學框架。學生們?yōu)樽畛醯腷ug排查做出了貢獻,這也為LLVM最終成為一個設計良好且易于學習的項目奠定了發(fā)展方向。
軟件理論和實踐之間的顯著差異使許多計算機科學專業(yè)的學生感到困惑。計算理論中一個簡潔明了的概念可能涉及多層級的實現(xiàn)細節(jié),這些細節(jié)使得現(xiàn)實中的軟件項目變得過于復雜而無法讓人們掌握,特別是其所有微妙之處。巧妙的抽象設計是幫助人類大腦掌握項目所有層面的關鍵:從高層級的視圖(抽象意義下的程序實現(xiàn)和工作方式)到最低層級的細節(jié)。
理論與實踐之間的差異在編譯器這一軟件中尤為顯著。對學習編譯器工作原理有極大熱情的學生,在理解編譯器的實際實現(xiàn)時常常面臨艱巨的挑戰(zhàn)。盡管學校已經教授了編譯器的相關理論,但在LLVM項目之前,如果充滿好奇心的學生要學習實現(xiàn)真正的編譯器,GCC項目是少數(shù)開源選項之一。
然而從最純粹的意義上說,軟件項目反映的是其創(chuàng)建者的觀點。這些觀點通過跨多個組件對模塊和數(shù)據(jù)表示進行抽象來實現(xiàn)。但對于同一主題,程序員可能有不同的看法。因此,對于GCC這樣已有近30年歷史的老舊軟件庫而言,其中集合了不同時代的程序員的不同觀點,這使得該軟件越來越難以被新程序員和好學者理解。
LLVM項目不僅吸引了經驗豐富的編譯器程序員,還吸引了許多年輕且具有好奇心的從事科研的學生,他們從中看到一片更干凈、更簡單的黑客土壤,它代表了一個具有很大潛力的編譯器。這一點可以從選擇LLVM作為研究原型的科學論文的龐大數(shù)量得到驗證。學生們做出如此選擇的原因很簡單:在學術界,學生通常負責項目的具體實現(xiàn),因此對他們來說,掌握實驗框架代碼庫對于研究是至關重要的。由于LLVM使用C++語言(而不是GCC中使用的C)、模塊化(而不是GCC的單一龐大結構)以及更容易映射到現(xiàn)代編譯器理論的概念,因此,很多研究人員發(fā)現(xiàn)修改LLVM代碼以實現(xiàn)他們的科研想法是很容易的,并且有很多這方面成功的例子。LLVM在學術界的成功可以說是理論與實踐之間縮小差距的結果。
除了作為科研工作的實驗框架之外,與GCC的GPL許可證相比,LLVM項目還有更加自由的許可證,因而引起了產業(yè)界的興趣。對于一個從學術界發(fā)展起來的項目,編寫其代碼的研究人員通常會擔心寫好的代碼在用于單獨的某個實驗后遭遇被丟棄的命運。為了克服這種局限性,在UIUC的碩士項目中,Chris Lattner決定根據(jù)伊利諾伊大學/NCSA開源許可協(xié)議對該項目進行許可,該許可只要求保留版權聲明就允許包括商用目的在內的使用。Chris的目標是使LLVM被最大限度地采用,最終結果超出預期。2012年,LLVM榮獲ACM軟件系統(tǒng)獎,這是對為科研做出杰出貢獻的軟件的高度認可。
許多商業(yè)公司基于不同的需求使用LLVM項目,也為該項目做出不同的貢獻,擴展了基于LLVM的編譯器可以使用的語言范圍以及能夠為其生成代碼的機器范圍。最終,LLVM項目具備了前所未有的成熟的庫和工具,進入了新的階段:從學術軟件的實驗狀態(tài),進入被商業(yè)產品使用的健壯框架狀態(tài)。因此,項目的名稱也從低級虛擬機(Low Level Virtual Machine)更改為縮寫LLVM。
停用低級虛擬機的名稱,轉而使用LLVM,這一決定反映了該項目在不同時期的目標。起初,LLVM是一個碩士科研項目,目標是成為一個可以用于研究程序終身優(yōu)化的框架。相關工作成果發(fā)表在2003年MICRO(微體系結構國際研討會)的一篇名為《LLVA: A Low-level Virtual Instruction Set Architecture》的論文以及2004年CGO(代碼生成和優(yōu)化國際研討會)的一篇名為《LLVM: A Compilation Framework for Lifelong Program Analysis & Transformation》的論文中。前者描述了LLVM的指令集,而后者對整個框架進行了描述。
在學術環(huán)境之外,LLVM被廣泛用作一個設計良好的編譯器,它具有將中間表示寫入磁盤等有用的特性。在商業(yè)系統(tǒng)中,它從未真正像Java虛擬機(JVM)一樣被使用,因此繼續(xù)使用低級虛擬機名稱毫無意義。另一方面,其他一些奇怪的名字仍然作為LLVM的歷史遺產而存在。在磁盤文件中存儲的LLVM中間表示程序稱為LLVM位碼。位碼的名稱類似于Java的字節(jié)碼,但前者反映了LLVM中間表示所需的空間,與Java字節(jié)碼的含義不同。
我們編寫此書有雙重目的。首先,由于LLVM項目發(fā)展速度很快,我們希望將其循序漸進地呈現(xiàn)給你,使本書的內容盡可能簡單易懂,同時讓你享受使用功能強大的編譯器庫的樂趣。 其次,我們希望喚起你開源黑客的精神去探索超出本書的概念,永遠不要停止擴充知識的腳步。
祝你閱讀愉快!
本書包含的內容
第1章介紹如何在Linux、Windows或Mac上安裝Clang / LLVM軟件包,包括有關在Visual Studio和Xcode上構建LLVM的討論。本章還將介紹LLVM不同發(fā)行版的風格,以便于你根據(jù)自身需要選擇最合適的發(fā)行版本:預構建的二進制文件、軟件分發(fā)包或源代碼。
第2章介紹包含于單獨的軟件包或倉庫中的外部LLVM項目,例如額外的Clang工具、DragonEgg GCC插件、LLVM調試器(LLDB)和LLVM測試套件。
第3章解釋LLVM項目中不同工具的組織形式,并通過一個實例介紹如何使用它們將源代碼編譯成匯編語言。本章還將介紹編譯器驅動程序的工作原理,以及如何編寫你的第一個LLVM工具。
第4章介紹LLVM編譯器前端,即Clang項目。本章將一步一步地完整呈現(xiàn)前端涉及的所有步驟,同時還將解釋如何編寫調用前端不同功能的小程序。本章最后介紹如何使用Clang庫編寫一個小型編譯器驅動程序。
第5章解釋LLVM設計中的一個關鍵部分,即其中間表示(IR)。本章將解釋它的重要特點、語法、結構以及如何編寫生成LLVM IR的工具。
第6章介紹LLVM的編譯器后端,它負責將LLVM IR轉換為機器代碼。本章將逐步介紹后端涉及的所有步驟,并介紹編寫自己的LLVM后端所需的知識。本章最后展示如何創(chuàng)建一個后端編譯流程。
第7章解釋LLVM即時編譯基礎架構,它允許按需生成和執(zhí)行機器代碼。對于僅在運行時才知道源程序代碼的應用程序來說,此技術至關重要,例如Internet瀏覽器中的JavaScript解釋器。 本章將指導你使用正確的庫來創(chuàng)建自己的JIT編譯器。
第8章介紹如何使用Clang / LLVM在其他平臺(如基于ARM的平臺)下編譯程序。由于程序的最終運行平臺和編譯平臺是不同的,其中的關鍵步驟在于配置正確的編譯環(huán)境。
第9章介紹一個功能強大的工具,該工具甚至無須運行程序,直接通過分析代碼,即可查找大型源代碼庫中的錯誤。本章還將介紹如何使用你自己的錯誤檢查程序擴展Clang靜態(tài)分析器。
第10章介紹LibTooling框架和一系列基于此庫構建的Clang工具,這些工具可以幫助你方便地重構源代碼或者進行簡單的分析。本章最后將展示如何使用該框架編寫自己的C++源代碼重構工具。
在撰寫本書時,LLVM 3.5尚未發(fā)布。雖然本書側重于LLVM 3.4版本,但我們計劃發(fā)布附錄將書中的示例更新為LLVM 3.5,這樣你就可以使用最新版本的LLVM來練習本書的內容。該附錄將通過https://www.packtpub.com/sites/default/files/downloads/6924OS_Appendix.pdf提供。
閱讀本書需要的前提
要開始探索LLVM世界,可以使用UNIX系統(tǒng)、Mac OS X系統(tǒng)或Windows系統(tǒng),只要它們配備現(xiàn)代C++編譯器即可。LLVM源代碼對所用的C++編譯器要求很高,因此我們建議讀者總是使用最新的C++版本。這意味著在Linux上至少需要GCC 4.8.1,在Max OS X上至少需要Xcode 5.1,在Windows上需要Visual Studio 2012。
盡管我們會解釋如何使用Visual Studio在Windows上構建LLVM,但該平臺并不是本書的重點,因為某些LLVM功能在該平臺上無法使用。例如,LLVM在Windows上缺少可加載模塊支持,但是我們要介紹的內容包括如何編寫作為共享庫構建的LLVM插件。在這種情況下,支持該內容的唯一方法是使用Linux或Mac OS X。
如果讀者不想自己構建LLVM,可以使用預構建的二進制包,但是這也限制了讀者能夠使用的平臺范圍。
本書目標讀者
本書面向有興趣了解LLVM框架的編程愛好者、計算機科學專業(yè)學生和編譯器工程師。你需要有C++背景知識,盡管不是強制性的,但至少應該了解一些編譯器理論。無論你是新手還是編譯專家,本書都提供了LLVM的實用介紹,并避免了過于復雜的場景。如果你對此技術感興趣或有需求,那么本書絕對適合你。
下載示例代碼
本書的示例代碼可以從http://www.packtpub.com通過個人賬號下載,也可以訪問華章圖書官網http://www.hzbook.com,通過注冊并登錄個人賬號下載。
布魯諾·卡多索·洛佩斯(Bruno Cardoso Lopes)在巴西坎皮納斯大學獲得計算機科學博士學位。自2007年以來,他一直是LLVM的貢獻者,從頭開始實現(xiàn)MIPS后端,并且已經維護了幾年。另外,他還編寫了x86 AVX支持方案,并改進了ARM匯編器。他的研究興趣包括代碼壓縮技術和對ISA進行位寬壓縮。之前他還開發(fā)了Linux和FreeBSD操作系統(tǒng)的驅動程序。
拉斐爾·奧勒(Rafael Auler)是巴西坎皮納斯大學的博士生,并擁有該大學計算機科學碩士學位和計算機工程學士學位。在作為碩士生期間,他編寫了一個可以根據(jù)體系結構描述文件自動生成LLVM后端的概念驗證工具。目前,他的博士研究課題包括動態(tài)二進制翻譯、即時編譯器和計算機體系結構。 Rafael還是微軟研究院2013年研究生研究獎學金獲得者。
出版者的話
譯者序
前言
關于作者
關于審稿人
第1章 構建和安裝LLVM 1
1.1 了解LLVM版本 1
1.2 獲取預構建包 2
1.2.1 獲取官方預構建二進制文件 2
1.2.2 使用軟件包管理器 3
1.3 從源代碼構建 4
1.3.1 系統(tǒng)要求 4
1.3.2 獲取源代碼 4
1.3.3 構建和安裝LLVM 5
1.3.4 Windows和Microsoft Visual Studio 10
1.3.5 Mac OS X和Xcode 12
1.4 總結 14
第2章 外部項目 15
2.1 Clang外部項目介紹 15
2.1.1 構建和安裝Clang外部工具 16
2.1.2 理解Compiler-RT 17
2.1.3 實驗Compiler-RT 17
2.2 使用DragonEgg插件 18
2.2.1 構建DragonEgg 19
2.2.2 使用DragonEgg和LLVM工具了解編譯流程 19
2.2.3 理解LLVM測試套件 20
2.2.4 使用LLDB 21
2.2.5 libc++標準庫介紹 23
2.3 總結 25
第3章 工具和設計 26
3.1 LLVM的基本設計原理及其歷史 26
3.2 理解目前的LLVM 27
3.3 與編譯器驅動程序交互 29
3.4 使用獨立工具 30
3.5 深入LLVM內部設計 33
3.5.1 了解LLVM的基本庫 33
3.5.2 介紹LLVM的C++慣例 34
3.5.3 演示可插拔的流程接口 37
3.6 編寫你的第一個LLVM項目 38
3.6.1 編寫Makefile 38
3.6.2 編寫代碼 40
3.7 關于LLVM源代碼的一般建議 41
3.7.1 將代碼理解為文檔 42
3.7.2 請求社區(qū)的幫助 42
3.7.3 應對更新:使用SVN日志作為文檔 42
3.7.4 結束語 44
3.8 總結 44
第4章 前端 45
4.1 Clang簡介 45
4.1.1 前端操作 46
4.1.2 庫 47
4.1.3 理解Clang診斷 49
4.2 Clang前端階段介紹 52
4.2.1 詞法分析 52
4.2.2 語法分析 58
4.2.3 語義分析 63
4.2.4 生成LLVM IR代碼 65
4.3 完整的例子 65
4.4 總結 68
第5章 LLVM中間表示 69
5.1 概述 69
5.2 操作IR格式的基本工具示例 71
5.3 LLVM IR語法介紹 71
5.4 編寫自定義的LLVM IR生成器 76
5.4.1 構建和運行IR生成器 79
5.4.2 使用C++后端編寫代碼來生成IR構造 80
5.5 在IR層執(zhí)行優(yōu)化 80
5.5.1 編譯時優(yōu)化和鏈接時優(yōu)化 80
5.5.2 發(fā)現(xiàn)最佳編譯器流程 82
5.5.3 流程間的依賴關系 83
5.5.4 了解流程API 85
5.5.5 自定義流程 85
5.6 總結 89
第6章 后端 90
6.1 概述 90
6.2 后端代碼結構介紹 92
6.3 后端庫介紹 93
6.4 如何使用TableGen實現(xiàn)LLVM后端 94
6.4.1 TableGen語言 95
6.4.2 代碼生成器.td文件介紹 96
6.5 指令選擇階段介紹 100
6.5.1 SelectionDAG類 100
6.5.2 降級 102
6.5.3 DAG合并以及合法化 103
6.5.4 DAG到DAG指令選擇 104
6.5.5 指令選擇過程可視化 107
6.5.6 快速指令選擇 107
6.6 調度器 107
6.6.1 指令執(zhí)行進程表 108
6.6.2 競爭檢測 109
6.6.3 調度單元 109
6.7 機器指令 109
6.8 寄存器分配 110
6.8.1 寄存器合并器 111
6.8.2 虛擬寄存器重寫 114
6.8.3 編譯目標的信息 115
6.9 前序代碼和結束代碼 116
6.10 機器代碼框架介紹 116
6.10.1 MC指令 116
6.10.2 代碼輸出 117
6.11 自定義機器流程 119
6.12 總結 121
第7章 即時編譯器 122
7.1 LLVM JIT引擎的基礎知識介紹 122
7.1.1 介紹執(zhí)行引擎 123
7.1.2 內存管理 124
7.2 llvm::JIT框架介紹 124
7.2.1 將二進制大對象寫入內存 125
7.2.2 使用JITMemoryManager 125
7.2.3 目標代碼輸出器 125
7.2.4 目標信息 127
7.2.5 學習如何使用JIT類 127
7.3 llvm::MCJIT框架介紹 131
7.3.1 MCJIT引擎 131
7.3.2 MCJIT中模塊編譯過程 132
7.3.3 使用MCJIT引擎 135
7.4 使用LLVM JIT編譯工具 137
7.4.1 使用lli工具 137
7.4.2 使用llvm-rtdyld工具 138
7.5 其他資源 139
7.6 總結 139
第8章 跨平臺編譯 140
8.1 GCC和LLVM對比 140
8.2 目標三元組介紹 141
8.3 準備自己的工具鏈 142
8.3.1 標準C/C++庫 143
8.3.2 運行時庫 143
8.3.3 匯編器和鏈接器 144
8.3.4 Clang前端 144
8.4 用Clang命令行參數(shù)進行交叉編譯 145
8.4.1 針對目標的驅動程序選項 145
8.4.2 依賴包 145
8.4.3 交叉編譯 146
8.4.4 更改系統(tǒng)根目錄 148
8.5 生成Clang交叉編譯器 149
8.5.1 配置選項 149
8.5.2 構建和安裝基于Clang的交叉編譯器 149
8.5.3 其他構建方法 150
8.6 測試 151
8.6.1 開發(fā)板 151
8.6.2 模擬器 151
8.7 其他資源 152
8.8 總結 152
第9章 Clang靜態(tài)分析器 153
9.1 靜態(tài)分析器的作用 153
9.1.1 傳統(tǒng)警告信息和Clang靜態(tài)分析器比較 153
9.1.2 符號執(zhí)行引擎的高效性 156
9.2 測試靜態(tài)分析器 158
9.2.1 使用驅動程序與使用編譯器 158
9.2.2 了解可用的檢查器 158
9.2.3 在Xcode IDE中使用靜態(tài)分析器 160
9.2.4 生成HTML格式的圖形
9.2.5 處理大型項目 161
9.3 使用自定義的檢查器擴展靜態(tài)分析器 164
9.3.1 熟悉項目架構 164
9.3.2 自定義檢查器