- 什么是編譯器
- LLVM概述
- LLVM案例體驗
- LLVM源碼 & 編譯流程
1 什么是編譯器娩嚼?
1.1 Python案例
- 創(chuàng)建
python
文件夾蘑险,新建helloDemo.py
文件,內(nèi)容如下:
print("hello")
-
調(diào)用
python helloDemo.py
執(zhí)行文件岳悟,打印出python
image
1.2 C 案例
-
vim
創(chuàng)建helloDemo.c
文件:
#include <stdio.h>
int main(int a, char * argv[]) {
printf("hello \n");
return 0;
}
-
clang helloDemo.c
編譯佃迄,生成a.out
文件泼差。file a.out
查看文件:image
發(fā)現(xiàn).out
文件是:64位的Mach-O
可執(zhí)行文件,當(dāng)前clang
出來的是x86_64
架構(gòu)呵俏, mac
電腦可讀堆缘。 所以可以./a.out
直接執(zhí)行:
Q:
解釋型
語言與編譯型
語言
python
是解釋型語言
,一邊翻譯
一邊執(zhí)行
普碎。和js
一樣吼肥,機器可直接執(zhí)行。C
語言是編譯型語言
麻车,不能直接執(zhí)行缀皱,需要編譯器
將其轉(zhuǎn)換
成機器識別語言
。
編譯型語言
:編譯后
輸出的是指令
(0动猬、1組合)啤斗,cpu可直接執(zhí)行指令
解釋性語言
:生成的是數(shù)據(jù)
,不是0枣察、1組合
争占,機器也能直接識別
編譯器
的作用燃逻,就是將高級語言
轉(zhuǎn)化為機器
能夠識別
的語言
(可執(zhí)行文件
)序目。
Q:匯編有指令嗎?
- 早期科學(xué)家伯襟,使用
0猿涨、1編碼
。 比如00001111
對應(yīng)call
姆怪,00000111
對應(yīng)bl
叛赚。有了對應(yīng)關(guān)系
后。 再手敲
0和1就有點難受
了稽揭。于是寫個中間解釋器
俺附,我們只用輸入call
、bl
這樣的標(biāo)記指令
溪掀,經(jīng)過解釋器
事镣,變成0和1的組合,再交給機器去執(zhí)行揪胃。 這就是匯編的由來
璃哟。
- 而基于匯編往上,再
映射
和封裝
相關(guān)對應(yīng)關(guān)系
喊递。就跨時代性
的c
語言随闪,再往上
層封裝,就出現(xiàn)了高級語言oc
骚勘、swift
等語言铐伴。所以匯編執(zhí)行快
,因為它是直接轉(zhuǎn)換
為機器語言
的。
- 但
匯編
的指令集
盛杰,是針對同一操作系統(tǒng)
而言挽荡,它不
支持跨平臺
。機器指令
是cpu
的在識別
即供。早期的計算機廠家
非常多
定拟,雖然都用0
和1
的組合
,但相同組合背后卻是相應(yīng)不同
的指令
逗嫡。所以匯編無法跨平臺
青自,不同操作系統(tǒng)
下,匯編指令
是不同
的驱证。
2. LLVM概述
-
LLVM
是架構(gòu)編譯器
(compiler
)的框架系統(tǒng)
延窜,以c++
編寫而成,用于優(yōu)化
以任意程序語言
編寫的程序的編譯時間
(compile-time
)抹锄、鏈接時間
(link-time
)逆瑞、運行時間
(run-time
)以及空閑時間
(idle-time
),對開發(fā)者保持開放伙单,并兼任已有腳本获高。 - 2006年
Chris Lattner
加盟Apple Inc.
并致力于LLVM
在Apple開發(fā)體系
中的應(yīng)用。Apple
也是LLVM計劃
的主要資助者
吻育。
目前LLVM
已經(jīng)被蘋果iOS開發(fā)工具
念秧、Xilinx Vivado
、Facebook
布疼、Google
等各大公司采用摊趾。
2.1 傳統(tǒng)編譯器的設(shè)計
- 編譯器前端(Frontend):
編譯器的前端任務(wù)
是解析源代碼
。 會進行詞法分析
游两、語法分析
砾层、語義分析
。檢查源代碼
是否存在錯誤
贱案,然后構(gòu)建抽象語法樹
(Abstract Syntax Tree AST)肛炮,LLVM前端
還會生成中間代碼
(intermediate representation, IR)
- 優(yōu)化器(Optimizer)
優(yōu)化器負責(zé)各種優(yōu)化
。改善
代碼的運行時間
轰坊,如消除冗余計算
等
- 后端(Backkend)/ 代碼生成器(CodeGenerator)
將代碼映射
到目標(biāo)指令集
铸董,生成機器語言
,并進行機器相關(guān)
的代碼優(yōu)化
(目標(biāo)指不同操作系統(tǒng)
)
iOS的編譯器架構(gòu):
Objective C
/C
/C++
使用的編譯器前端
是Clang
肴沫,Swift
是swift
粟害,后端都是LLVM
。image
2.2 LLVM的設(shè)計
GCC
是一個非常成功
的編譯器
颤芬,但由于它作為整體應(yīng)用程序
設(shè)計的悲幅,用途
受到了限制
套鹅。LLVM
最重要的地方:支持多種語言
或多種硬件架構(gòu)
。使用通用代碼
表示形式:IR
(用來在編譯器中表示代碼的形式)LLVM
可以為任何編程語言
獨立編寫前端
汰具,也可以為任何硬件架構(gòu)
獨立編寫后端
.所以LLVM
不是
一個簡單的編譯器
卓鹿,而是架構(gòu)編譯器
,可以兼容
所有前端
和后端
留荔。
2.3 Clang
Clang
是LLVM項目
的一個子項目
吟孙。基于LLVM架構(gòu)
的輕量級編輯器
聚蝶,誕生之初
就是為了替代GCC
杰妓,提供更快
的編譯速度
。 他是負責(zé)編譯C
碘勉、C++
巷挥、Objecte-C
語言的編譯器
,它屬于
整個LLVM架構(gòu)
中的編譯器前端
验靡。
- 對于開發(fā)者而言倍宾,
研究Clang
可以給我們帶來很多好處
。
3. LLVM案例體驗
-
新建一個
Mac OS
的命令行
工程:image -
沒有改動代碼
image
3.1 編譯流程
- cd到
main.m
的文件夾胜嗓。使用下面命令查看main.m
的編譯步驟:
clang -ccc-print-phases main.m
編譯流程
分為以下7步
:
-
0: input, "main.m", objective-c
:
輸入文件:找到源文件 -
1: preprocessor, {0}, objective-c-cpp-output
:
預(yù)處理:宏的展開高职,頭文件的導(dǎo)入 -
2: compiler, {1}, ir
:
編譯:詞法、語法兼蕊、語義分析初厚,最終生成IR -
3: backend, {2}, assembler ()
:
匯編: LLVM通過一個個的Pass去優(yōu)化件蚕,每個Pass做一些事孙技,最后生成匯編代碼 -
4: assembler, {3}, object
:
目標(biāo)文件 -
5: linker, {4}, image
:
鏈接: 鏈接需要的動態(tài)庫和靜態(tài)庫,生成可執(zhí)行文件 -
6: bind-arch, "x86_64", {5}, image
:
架構(gòu)可執(zhí)行文件:通過不同架構(gòu)排作,生成對應(yīng)的可執(zhí)行文件
optimizer優(yōu)化
并沒有
作為一個獨立階段
牵啦,在編譯階段
內(nèi)部完成
的
3.2 預(yù)處理階段
-
main.m
中準備測試代碼
:
#import <stdio.h>
#define C 30
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
int b = 20;
printf("%d", a + b + C);
}
return 0;
}
-
clang
預(yù)編譯輸出main2.m
文件:
clang -E main.m >> main2.m
-
打開
main2.m
,有575行
妄痪。其中大部分是stdio
庫的代碼:image 我們發(fā)現(xiàn)測試代碼中的
宏C
哈雏,在預(yù)編譯階段
完成了替換
,變成了30
預(yù)編譯階段: 1.
導(dǎo)入頭文件
2.替換宏
- 修改測試代碼衫生,給
int類型
取個別名HT_INT_64
裳瘪,再次預(yù)編譯處理
:
#define C 30
typedef int HT_INT_64;
int main(int argc, const char * argv[]) {
@autoreleasepool {
HT_INT_64 a = 10;
HT_INT_64 b = 20;
printf("%d", a + b + C);
}
return 0;
}
-
發(fā)現(xiàn)
typedef
不會被替換image
安全拓展:
- 使用
define
將重要方法
名稱進行替換
。比如#define Pay XXXTest
這樣開發(fā)者使用宏P(guān)ay
開發(fā)舒服罪针,但是被hank
時彭羹,實際代碼是XXXTest
,不容易被察覺泪酱。
(#define
的真實內(nèi)容
派殷,不應(yīng)該
寫成亂碼
还最,會讓人有此地?zé)o銀三百兩
的感覺,最好
弄成系統(tǒng)類似名稱
或其他不經(jīng)意
的名稱
毡惜。這樣才容易
被忽視
拓轻,安全級別
才更高
??)
typedef
沒有這個偷梁換柱的效果。define
只影響預(yù)處理期经伙。
3.3 編譯階段
3.3.1 詞法分析
- 編譯
main.m
文件:
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
-詞法分析
扶叉,就是根據(jù)空格
和括號
這些將代碼拆分
成一個個Token
。標(biāo)注了位置
是第幾行
的第幾個字符
開始的帕膜。
3.3.2 語法分析
-
語法分析
是驗證語法
是否正確
辜梳。
在詞法分析的基礎(chǔ)上,將單詞
序列組合
成各類語法短語
泳叠,如“程序”作瞄,“語句”,“表達式”等危纫,然后將所有節(jié)點組成抽象語法樹
(Abstract Syntax Tree宗挥,AST)。語法分析程序
判斷源程序
在結(jié)構(gòu)
上是否正確
种蝶。
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
-
作用域
契耿、類型
、運算方式
都十分清晰
螃征。( 語法樹一次只能處理一次計算搪桂。兩次運算,就得多分一層級盯滚。)image 語法分析
踢械,就是在生成語法樹
時完成檢測
的。
- 頭文件找不到時魄藕,可以指定SDK:
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.2.sdk(自己SDK路徑) -fmodules -fsyntax-only -Xclang -ast-dump main.m
3.4 生成中間代碼IR(Intermediate representation)
3.4.1 生成中間代碼
完成以上步驟后内列,就開始生成
中間代碼IR
,代碼生成器(Code Generation)會將語法樹自頂向下
遍歷逐步翻譯成LLVM
的IR
背率。便于理解话瞧,我們簡化代碼:
#import <stdio.h>
int test(int a, int b) {
return a + b + 3;
}
int main(int argc, const char * argv[]) {
int a = test(1,2);
printf("%d",a);
return 0;
}
通過下面命令生成.ll
文本文件,查看IR代碼:
clang -S -fobjc-arc -emit-llvm main.m
- IR基本語法
@
全局標(biāo)識
%
局部標(biāo)識
alloca
開辟空間
align
內(nèi)存對齊
i32
32個bit寝姿,4個字節(jié)
store
寫入內(nèi)存
load
讀取數(shù)據(jù)
call
調(diào)用數(shù)據(jù)
ret
返回
- 使用
VSCode
或Sublime Text
可以打開代碼:(可以指定文件
的語言
交排,讓代碼
有高亮色
)
- Q:圖中為何
多創(chuàng)建
那么多局部變量
?(如test函數(shù)內(nèi)的a5饵筑、a6)- 因為在上一階段(
編譯階段
)埃篓,我們將代碼
編譯成了語法樹結(jié)構(gòu)
。而此時翻翩,我們只是沿
著語法樹
進行讀取
都许。 語法樹每一個層級
稻薇,都需要
一個臨時變量
來承接
。再返回上一層級處理
胶征。- 所以會
產(chǎn)生
那么多局部變量
塞椎。
3.4.2 IR優(yōu)化
- 我們可以在
Xcode
的Build Settings
中搜索Optimization
,可以看到優(yōu)化級別。
(Debug模式
默認None [O0]
無優(yōu)化睛低,Release模式
默認Fastest,Smallest [Os]
最快最小)
LLVM的優(yōu)化級別分為
-O0
案狠、-O1
、-O2
钱雷、-O3
骂铁、-Os
(第一個字母是Optimization的O)。分別選擇
O0
和Os
兩個優(yōu)化等級進行中間代碼的生成比較:
clang -S -fobjc-arc -emit-llvm main.m -o mainO0.ll // O0 無優(yōu)化
clang -Os -S -fobjc-arc -emit-llvm main.m -o mainOs.ll // Os 最快最小
-
優(yōu)化后
的代碼罩抗,舒服
多了拉庵。之前那些冗余
的臨時局部變量
,也都被優(yōu)化
套蒂,代碼量減少
很多钞支。
3.4.3 bitCode再優(yōu)化
-
Xcode7之后
,開啟bitCode
蘋果會再進一步優(yōu)化
操刀,生成.bc
的中間代碼
烁挟。
優(yōu)化體現(xiàn)
:上傳APPstore的包
,針對不同型號手機
做了區(qū)分
骨坑,不同型號手機下載
時撼嗓,包
的大小不同
。
clang -emit-llvm -c main.ll -o main.bc
3.5 生成匯編代碼
完成
中間代碼
的生成后欢唾,可以將代碼轉(zhuǎn)變
為匯編代碼
了且警。-
此刻我們有
4種
不同程度的代碼(源代碼
->無優(yōu)化IR代碼
->Os優(yōu)化IR代碼
->bitcode優(yōu)化代碼
):image 分別對
4種程度
的代碼輸出匯編
文件:
clang -S -fobjc-arc main.m -o main.s
clang -S -fobjc-arc main.ll -o mainO0.s
clang -S -fobjc-arc mainOs.ll -o mainOs.s
clang -S -fobjc-arc main.bc -o mainbc.s
可以看到在生成匯編代碼
時,只有選擇
了優(yōu)化等級
匈辱,才能減少
匯編代碼量
振湾。
【拓展】在
生成中間代碼
的前后
杀迹,都可以
進行優(yōu)化
亡脸。
- [嘗試一] 將
main.m
直接選擇Os級別
優(yōu)化生成.s
匯編文件clang -Os -S -fobjc-arc main.m -o mainOs.s
- [嘗試二] 將
main.m
生成無優(yōu)化
的main.s
,再main.s
選擇Os級別
優(yōu)化生成.s
匯編文件clang -S -fobjc-arc -emit-llvm main.m -o mainO0.ll clang -Os -S -fobjc-arc mainO0.ll -o mainOoOs.s
- [嘗試三] 將
main.m
選擇Os級別
優(yōu)化生成main.s
树酪,再main.s
選擇無優(yōu)化
級別生成.s
匯編文件clang -Os -S -fobjc-arc -emit-llvm main.m -o mainOs.ll clang -S -fobjc-arc mainOs.ll -o mainOsOo.s
- [嘗試四] 將
main.m
選擇Os級別
優(yōu)化生成main.s
浅碾,再main.s
選擇Os級別
優(yōu)化生成.s
匯編文件clang -Os -S -fobjc-arc -emit-llvm main.m -o mainOs.ll clang -Os -S -fobjc-arc mainOs.ll -o mainOsOs.s
- 內(nèi)容比較:
image
3.6 生成目標(biāo)文件(機器代碼)
-
生成匯編文件
后,匯編器
以匯編代碼
作為輸入
续语,將匯編代碼轉(zhuǎn)換
為機器代碼
垂谢,輸出
目標(biāo)文件(object file
)
clang -fmodules -c main.s -o main.o
file
對比一下main.s
匯編代碼和main.o
機器代碼:file main3.m file main.o
image
-
xcrun
執(zhí)行nm
命令查看main.o
文件中的符號
:
xcrun nm -nm main.o
- 此時只是把
當(dāng)前文件
編譯為了機器碼
,外部符號
(如printf
)無法識別疮茄。
undefined:
表示當(dāng)前文件
暫時找不到符號
滥朱。
external:
表示這個符號
是外部可以訪問
的根暑。(實現(xiàn)
不在我這,在外部
的某個地方
)
所以當(dāng)前雖轉(zhuǎn)換
成了機器代碼
徙邻。但是只是目標(biāo)文件
排嫌,并不能
直接執(zhí)行
,需要將
所有資源鏈接
起來缰犁,才可以執(zhí)行
淳地。
3.7 生成可執(zhí)行文件(鏈接)
- 通過
鏈接器
把編譯產(chǎn)生的.o
文件和.dylib
、.a
文件鏈接關(guān)聯(lián)
起來帅容,生成真正的mach-o可執(zhí)行文件
clang main.o -o main // 將目標(biāo)文件轉(zhuǎn)成可執(zhí)行文件
file main // 查看文件
xcrun nm -nm main // 查看main的符號
- 對比
main.o
目標(biāo)文件颇象,此時生成的main
文件:
- 從
object
文件變成了executable
可執(zhí)行文件- 雖然都有
undefined
,但是可執(zhí)行文件
中指定了該符號
的來源庫
并徘。機器在運行時
遣钳,會從相應(yīng)的庫
中取讀取
該符號
(printf
)
至此桑孩,我們已完整分析:源代碼
到可執(zhí)行文件
的整個流程
:
4. LLVM源碼 & 編譯流程
?? ?? ?? 【注意】
LLVM
源碼2.29G
境肾,編譯后
文件30G
,請確保
電腦硬盤空間足夠
挟伙;編譯時
路幸,電腦溫度會飆升90多度
荐开,請用空調(diào)伺候
著,可能
會黑屏
简肴;編譯時間
長達1個多小時
晃听,請合理安排時間。
如果以上3點
砰识,你確定能接受
能扒,那我們就開始
吧。
4.1 LLVM下載
-
Gitee
上有已配置
好關(guān)聯(lián)庫
的源碼
辫狼,可直接下載: https://gitee.com/mirrors/LLVM
拓展:
- github上的官方源碼:https://github.com/llvm/llvm-project(國內(nèi)網(wǎng)絡(luò)限制)
- 需要注意的是初斑,官方源碼不能直接編譯,需要下載
clang
膨处、compiler-rt
见秤、libcxx
、libcxxabi
這4個庫真椿。- 建議使用上面Gitee源鹃答。
4.2 LLVM編譯
-
30G
,90°C
突硝,1hour
之旅测摔,begin~
- 最新的LLVM只支持
cmake
編譯,需要使用Homebrew
安裝cmake
:
4.2.1 安裝cmake
- 查看
brew列表
,檢查是否安裝過cmake
锋八,如果有浙于,就跳過此步驟
brew list
- 如果沒有,就使用
brew安裝
:
brew install cmake
如果報權(quán)限錯誤挟纱,可
sudo chown -R
whoami:admin /usr/local/share
放開權(quán)限image
4.2.2 編譯llvm
-
cmake
編譯成Xcode
項目
cd llvm-project
mkdir build
cd build
cmake -G Xcode ../llvm
// 或者: cmake -G Xcode CMAKE_BUILD_TYPE="Release" ../llvm
// 或者: cmake -G Xcode CMAKE_BUILD_TYPE="debug" ../llvm
-
成功之后路媚,可以看到生成的
Xcode
文件:image -
打開
LLVM.xcodeproj
,選擇自動創(chuàng)建Schemes
樊销。image -
自動創(chuàng)建完成
后整慎,選擇ALL_BUILD
進行編譯(耗時0.5-1小時,CPU滿負荷運轉(zhuǎn))image 編譯完成。