什么是LLVM
LLVM項(xiàng)目是模塊化、可重用的編譯器以及工具鏈技術(shù)的集合。
美國(guó)計(jì)算機(jī)協(xié)會(huì) (ACM) 將其2012 年軟件系統(tǒng)獎(jiǎng)項(xiàng)頒給了LLVM,之前曾經(jīng)獲得此獎(jiǎng)項(xiàng)的軟件和技術(shù)包括:Java、Apache、 Mosaic、the World Wide Web、Smalltalk、UNIX、Eclipse等等
創(chuàng)始人:Chris Lattner,亦是Swift之父
備注:有些文章把LLVM當(dāng)做Low Level Virtual Machine(低級(jí)虛擬機(jī))的縮寫(xiě)簡(jiǎn)稱,官方描述如下
The name "LLVM" itself is not an acronym; it is the full name of the project. “LLVM”這個(gè)名稱本身不是首字母縮略詞; 它是項(xiàng)目的全名。
傳統(tǒng)的編譯器架構(gòu)

傳統(tǒng)編譯器架構(gòu)
Frontend:前端
詞法分析、語(yǔ)法分析、語(yǔ)義分析、生成中間代碼
Optimizer:優(yōu)化器
中間代碼優(yōu)化
Backend:后端
生成機(jī)器碼
LLVM架構(gòu)

LLVM架構(gòu)
不同的前端后端使用統(tǒng)一的中間代碼LLVM Intermediate Representation (LLVM IR)
如果需要支持一種新的編程語(yǔ)言,那么只需要實(shí)現(xiàn)一個(gè)新的前端
如果需要支持一種新的硬件設(shè)備,那么只需要實(shí)現(xiàn)一個(gè)新的后端
優(yōu)化階段是一個(gè)通用的階段,它針對(duì)的是統(tǒng)一的LLVM IR,不論是支持新的編程語(yǔ)言,還是支持新的硬件設(shè)備,都不需要對(duì)優(yōu)化階段做修改
相比之下,GCC的前端和后端沒(méi)分得太開(kāi),前端后端耦合在了一起。所以GCC為了支持一門(mén)新的語(yǔ)言,或者為了支持一個(gè)新的目標(biāo)平臺(tái),就 變得特別困難
LLVM現(xiàn)在被作為實(shí)現(xiàn)各種靜態(tài)和運(yùn)行時(shí)編譯語(yǔ)言的通用基礎(chǔ)結(jié)構(gòu)(GCC家族、Java、.NET、Python、Ruby、Scheme、Haskell、D等)
什么是Clang
LLVM項(xiàng)目的一個(gè)子項(xiàng)目,基于LLVM架構(gòu)的C/C++/Objective-C編譯器前端。
相比于GCC,Clang具有如下優(yōu)點(diǎn)
編譯速度快:在某些平臺(tái)上,Clang的編譯速度顯著的快過(guò)GCC(Debug模式下編譯OC速度比GGC快3倍)
占用內(nèi)存小:Clang生成的AST所占用的內(nèi)存是GCC的五分之一左右
模塊化設(shè)計(jì):Clang采用基于庫(kù)的模塊化設(shè)計(jì),易于 IDE 集成及其他用途的重用
診斷信息可讀性強(qiáng):在編譯過(guò)程中,Clang 創(chuàng)建并保留了大量詳細(xì)的元數(shù)據(jù) (metadata),有利于調(diào)試和錯(cuò)誤報(bào)告
設(shè)計(jì)清晰簡(jiǎn)單,容易理解,易于擴(kuò)展增強(qiáng)
Clang與LLVM關(guān)系

Clang與LLVM
LLVM整體架構(gòu),前端用的是clang,廣義的LLVM是指整個(gè)LLVM架構(gòu),一般狹義的LLVM指的是LLVM后端(包含代碼優(yōu)化和目標(biāo)代碼生成)。
源代碼(c/c++)經(jīng)過(guò)clang--> 中間代碼(經(jīng)過(guò)一系列的優(yōu)化,優(yōu)化用的是Pass) --> 機(jī)器碼
OC源文件的編譯過(guò)程
這里用Xcode創(chuàng)建一個(gè)Test項(xiàng)目,然后cd到main.m的上一路徑。
命令行查看編譯的過(guò)程:$ clang -ccc-print-phases main.m
$ clang -ccc-print-phases main.m 0: input,"main.m", objective-c1: preprocessor, {0}, objective-c-cpp-output2: compiler, {1}, ir3: backend, {2}, assembler4: assembler, {3}, object5: linker, {4}, image6:bind-arch,"x86_64", {5}, image
0.找到main.m文件
1.預(yù)處理器,處理include、import、宏定義
2.編譯器編譯,編譯成ir中間代碼
3.后端,生成目標(biāo)代碼
4.匯編
5.鏈接其他動(dòng)態(tài)庫(kù)靜態(tài)庫(kù)
6.編譯成適合某個(gè)架構(gòu)的代碼
查看preprocessor(預(yù)處理)的結(jié)果:$ clang -E main.m
這個(gè)命令敲出,終端就會(huì)打印許多信息,大致如下:
# 1"main.m"# 1"<built-in>"1# 1"<built-in>"3# 353"<built-in>"3# 1"<command line>"1# 1"<built-in>"2# 1"main.m"2...intmain(intargc,constchar* argv[]) {@autoreleasepool{NSLog(@"Hello, World!");}return0;}
詞法分析
詞法分析,生成Token: $ clang -fmodules -E -Xclang -dump-tokens main.m
將代碼分成一個(gè)個(gè)小單元(token)
舉例如下:
voidtest(inta,intb){intc = a + b -3;? }
void'void'[StartOfLine]? Loc=identifier'test'[LeadingSpace] Loc=l_paren'('Loc=int'int'Loc=identifier'a'[LeadingSpace] Loc=comma','Loc=int'int'[LeadingSpace] Loc=identifier'b'[LeadingSpace] Loc=r_paren')'Loc=l_brace'{'Loc=int'int'[StartOfLine] [LeadingSpace]? Loc=identifier'c'[LeadingSpace] Loc=equal'='[LeadingSpace] Loc=identifier'a'[LeadingSpace] Loc=plus'+'[LeadingSpace] Loc=identifier'b'[LeadingSpace] Loc=minus'-'[LeadingSpace] Loc=numeric_constant'3'[LeadingSpace] Loc=semi';'Loc=r_brace'}'[StartOfLine]? Loc=eof''Loc=
可以看出,詞法分析的時(shí)候,將上面的代碼拆分一個(gè)個(gè)token,后面數(shù)字表示某一行的第幾個(gè)字符,例如第一個(gè)void,表示第18行第一個(gè)字符。
語(yǔ)法樹(shù)-AST
語(yǔ)法分析,生成語(yǔ)法樹(shù)(AST,Abstract Syntax Tree): $ clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
通過(guò)語(yǔ)法樹(shù),我們能知道這個(gè)代碼是做什么的。
還是剛剛的test函數(shù)
生成語(yǔ)法樹(shù)如下:
|-FunctionDecl 0x7fa1439f5630 line:18:6test'void (int, int)'| |-ParmVarDecl 0x7fa1439f54b0 col:15 used a'int'| |-ParmVarDecl 0x7fa1439f5528 col:22 used b'int'| `-CompoundStmt 0x7fa142167c88 |? `-DeclStmt 0x7fa142167c70 |? ? `-VarDecl 0x7fa1439f5708 col:9 c'int'cinit|? ? ? `-BinaryOperator 0x7fa142167c48 'int''-'|? ? ? ? |-BinaryOperator 0x7fa142167c00 'int''+'|?| |-ImplicitCastExpr 0x7fa1439f57b8 'int'|? ? ? ? | | `-DeclRefExpr 0x7fa1439f5768 'int'lvalue ParmVar 0x7fa1439f54b0'a''int'|?| `-ImplicitCastExpr 0x7fa1439f57d0 'int'|? ? ? ? |? `-DeclRefExpr 0x7fa1439f5790 'int'lvalue ParmVar 0x7fa1439f5528'b''int'|? ? ? ? `-IntegerLiteral 0x7fa142167c28 'int'3`-
在終端敲出的時(shí)候,終端很直觀的幫我們用顏色區(qū)分。我們可以用圖形顯示如下:

test函數(shù)的語(yǔ)法樹(shù)
LLVM IR
LLVM IR有3種表示形式(本質(zhì)是等價(jià)的)
text:便于閱讀的文本格式,類似于匯編語(yǔ)言,拓展名.ll, $ clang -S -emit-llvm main.m
memory:內(nèi)存格式
bitcode:二進(jìn)制格式,拓展名.bc, $ clang -c -emit-llvm main.m
我們以text形式編譯查看:
; Function Attrs: noinline nounwind optnone ssp uwtabledefinevoid@test(i32, i32)#2 {%3= alloca i32, align4%4= alloca i32, align4%5= alloca i32, align4store i32 %0, i32* %3, align4store i32 %1, i32* %4, align4%6= load i32, i32* %3, align4%7= load i32, i32* %4, align4%8= add nsw i32 %6, %7%9= sub nsw i32 %8,3store i32 %9, i32* %5, align4retvoid}
IR基本語(yǔ)法
注釋以分號(hào) ; 開(kāi)頭
全局標(biāo)識(shí)符以@開(kāi)頭,局部標(biāo)識(shí)符以%開(kāi)頭
alloca,在當(dāng)前函數(shù)棧幀中分配內(nèi)存
i32,32bit,4個(gè)字節(jié)的意思
align,內(nèi)存對(duì)齊
store,寫(xiě)入數(shù)據(jù)
load,讀取數(shù)據(jù)
官方語(yǔ)法參考https://llvm.org/docs/LangRef.html
應(yīng)用與實(shí)踐
我們的開(kāi)發(fā)都是基于源碼開(kāi)發(fā),所以我們首先要進(jìn)行源碼下載和編譯。
源碼下載
下載LLVM$ git clonehttps://git.llvm.org/git/llvm.git/下載clang$ cd llvm/tools$ git clonehttps://git.llvm.org/git/clang.git/備注:clang是llvm的子項(xiàng)目,但是它們的源碼是分開(kāi)的,我們需要將clang放在llvm/tools目錄下。
源碼編譯
這里我們?cè)诮K端敲出的clang是xcode默認(rèn)內(nèi)置clang編譯器,我們自己要進(jìn)行LLVM開(kāi)發(fā)的話,需要編譯屬于我們自己的clang編譯器
首先安裝cmake和ninja(先安裝brew,https://brew.sh/)$ brew install cmake$ brew install ninjaninja如果安裝失敗,可以直接從github獲取release版放入【/usr/local/bin】中https://github.com/ninja-build/ninja/releases在LLVM源碼同級(jí)目錄下新建一個(gè)【llvm_build】目錄(最終會(huì)在【llvm_build】目錄下生成【build.ninja】$ cd llvm_build$ cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=LLVM的安裝路徑備注:生成build.ninja,就表示編譯成功,-DCMAKE_INSTALL_PREFIX 表示編譯好的東西放在指定的路徑,-D表示參數(shù)。更多cmake相關(guān)選項(xiàng),可以參考: https://llvm.org/docs/CMake.html
接下來(lái)依次執(zhí)行編譯、安裝指令
$ ninja編譯完畢后, 【llvm_build】目錄大概21.05G(這個(gè)真的是好大?。? ninja install
然后到這里我們的編譯就完成了。
另一種方式是通過(guò)Xcode編譯,生成Xcode項(xiàng)目再進(jìn)行編譯,但是速度很慢(可能需要1個(gè)多小時(shí))。
方法如下:
在llvm同級(jí)目錄下新建一個(gè)【llvm_xcode】目錄
$ cd llvm_xcode
$ cmake -G Xcode ../llvm
應(yīng)用與實(shí)踐的參考
libclang、libTooling
官方參考:https://clang.llvm.org/docs/Tooling.html
應(yīng)用:語(yǔ)法樹(shù)分析、語(yǔ)言轉(zhuǎn)換等
Clang插件開(kāi)發(fā)
官方參考
1、https://clang.llvm.org/docs/ClangPlugins.html
2、https://clang.llvm.org/docs/ExternalClangExamples.html
3、https://clang.llvm.org/docs/RAVFrontendAction.html
應(yīng)用:代碼檢查(命名規(guī)范、代碼規(guī)范)等
Pass開(kāi)發(fā)
官方參考:https://llvm.org/docs/WritingAnLLVMPass.html
應(yīng)用:代碼優(yōu)化、代碼混淆等
開(kāi)發(fā)新的編程語(yǔ)言
1、https://llvm-tutorial-cn.readthedocs.io/en/latest/index.html
2、https://kaleidoscope-llvm-tutorial-zh-cn.readthedocs.io/zh_CN/latest/