LLVM概述
LLVM是架構(gòu)編譯器(compiler)的框架系統(tǒng)
,以C++
編寫而成徐绑,用于優(yōu)化以任意程序語言編寫的程序的編譯時間(compile-time)
、鏈接時間(link-time)
、運行時間(run-time)
以及空閑時間(idle-time)
,對開發(fā)者保持開放劲厌,并兼容已有腳本。
LLVM
計劃啟動于2000
年听隐,最初由美國UIUC
大學(xué)的Chris Latter
博士支持開展补鼻。
2006
年Chris Latter
加盟Apple Inc
。并致力于LLVM在Apple
開發(fā)體系中的應(yīng)用雅任。Apple
也是LLVM
計劃的主要資助者风范。
目前LLVM
已經(jīng)被蘋果iOS開發(fā)工具
、Xilinx Vivado
沪么、Facebook
硼婿、Google等
大公司采用。
傳統(tǒng)編譯器
- 新建一個
hello.c
文件
#include <stdio.h>
int main (int a, char* argv[]){
printf("hello \n");
return 0;
}
-
clang hello.c
桌面上會生成一個a.out
文件
通過
file
指令查看a.out
file a.out
a.out: Mach-O 64-bit executable x86_64
可以看到a.out
是一個Mach-O
格式的64位可執(zhí)行文件成玫,架構(gòu)是x86_64
模擬器可讀加酵。所以Mac
電腦可以讀拳喻。
-
./a.out
哭当,執(zhí)行這個文件
就會打印一個hello
編譯器就是將代碼,轉(zhuǎn)換成CPU
能看懂的二進(jìn)制文件冗澈,這里的.out
文件也是二進(jìn)制文件
傳統(tǒng)編譯器設(shè)計
編譯器前端(Frontend)
編譯器前端的任務(wù)是解析源代碼
钦勘。他會進(jìn)行:詞法分析
、語法分析
亚亲、語義分析
彻采,檢查源代碼是否存在錯誤,然后構(gòu)建抽象語法樹
(Abstract Syntax Tree捌归,AST
)肛响,LLVM的前端還會生成中間代碼
(intermediate representation,IR
)惜索。
優(yōu)化器(Optimizer)
優(yōu)化器負(fù)責(zé)進(jìn)行各種優(yōu)化特笋。改善代碼的運行時間,例如消除冗余計算等巾兆。
后端(Backend) / 代碼生成器(CodeGenerator)
將代碼映射到目標(biāo)指令集猎物。生成機器語言虎囚,并且進(jìn)行機器相關(guān)的代碼優(yōu)化。
iOS編譯器架構(gòu)
Objective-C/C/C++
使用的編譯器前端是clang
蔫磨,Swift
是swift
淘讥,后端都是LLVM
。
LLVM的設(shè)計
當(dāng)編譯器決定支持多種源語言或多種硬件架構(gòu)時堤如,LLVM
最重要的地方就來了蒲列。其他的編譯器如GCC
,它非常成功搀罢,但由于它是作為整體應(yīng)用程序設(shè)計的嫉嘀,因此他們的用途受到了很大的限制。
LLVM
最重要的方面是魄揉,使用通用的代碼表示形式(IR
)剪侮,它是用來在編譯器中表示代碼的形式。所以LLVM
可以為任何編程語言獨立編寫前端洛退,并且可以為任意硬件架構(gòu)獨立編寫后端瓣俯。
clang
clang
是LLVM
項目中的一個子項目。它是基于LLVM
架構(gòu)的輕量級編譯器兵怯,誕生之初是為了替代GCC
彩匕,提供更快的編譯速度。它是負(fù)責(zé)編譯C
媒区、C++
驼仪、Objective-C
語言的編譯器,它屬于整個LLVM
架構(gòu)中的袜漩,編譯器前端绪爸。對于開發(fā)者來說,研究clang
可以給我們帶來很多好處宙攻。
編譯流程
通過命令可以打印源碼的編譯階段
clang -ccc-print-phases main.m
0: input, "main.m", objective-c
1: preprocessor, {0}, objective-c-cpp-output 2: compiler, {1}, ir
3: backend, {2}, assembler
4: assembler, {3}, object
5: linker, {4}, image
6: bind-arch, "x86_64", {5}, image
總共有這7個階段奠货。
0:輸入文件
:找到源文件
1:預(yù)處理階段
:這個過程處理包括宏的替換,頭文件的導(dǎo)入座掘。
2:編譯階段
:進(jìn)行詞法分析递惋、語法分析、檢測語法是否正確溢陪,最終生成IR
3:后端
:這里L(fēng)LVM會通過一個一個的Pass去優(yōu)化萍虽,每個Pass做一些事情,最終生成匯編代碼形真。
4:生成目標(biāo)文件
杉编。
5:鏈接
:鏈接需要的動靜態(tài)庫,生成可執(zhí)行文件。
6:通過不同的架構(gòu)
王财,生成對應(yīng)的可執(zhí)行文件卵迂。
預(yù)處理階段
執(zhí)行如下命令
clang -E main.m
clang -E main.m >> xxxx.m
執(zhí)行完畢可以看到頭文件的導(dǎo)入和宏的替換。
typedef int LY_INT_32 ; typedef不會被替換
#define isVip wee68990 可以用來做混淆
編譯階段
-
詞法分析
預(yù)處理完成后就會進(jìn)行詞法分析绒净。這里會把代碼切成一個個Token
见咒,比如大小括號,等于號還有字符串等挂疆。-
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
一個符號一個符號分析
-
-
語法分析
詞法分析完成之后就是語法分析改览,它的任務(wù)是驗證語法是否正確
。在詞法分析的基礎(chǔ)上將單詞序列組合成各類語法短語
缤言。如“程序”宝当,“語句”,“表達(dá)式等等”胆萧,然后將所有結(jié)點組成抽象語法樹(Abstract Syntax Tree, AST
)庆揩。語法分析程序判斷程序在結(jié)構(gòu)上是否正確。-
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
如果導(dǎo)入頭文件找不到跌穗,那么可以指定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
-
-
生成中間代碼IR(intermediate representation)
完成以上步驟后就開始生成中間代碼IR
了订晌,代碼生成器(Code Generation
)會將語法樹自頂向下遍歷逐步翻譯成LLVM IR
。通過下面的命令可以生成.ll
的文本文件蚌吸,查看IR
代碼锈拨。-
clang -S -fobjc-arc -emit-llvm main.m
Objective-C
代碼在這一步會進(jìn)行runtime的橋接
:property合成
,ARC處理
等羹唠。
-
IR的基本語法
:
@ 全局標(biāo)識
% 局部標(biāo)識
alloca 開辟空間
align 內(nèi)存對齊
i32 32個bit奕枢,4字節(jié)
store 寫入內(nèi)存
load 讀取數(shù)據(jù)
call 調(diào)用函數(shù)
ret 返回
IR的優(yōu)化
LLVM
的優(yōu)先級別分別是 -O0 -O1 -O2 -O3 -Os
(第一個是大寫字母O)
-clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll
也可以在Xcode
中可以設(shè)置
bitCode
Xcode7
之后開啟bitCode
,蘋果會做進(jìn)一步的優(yōu)化佩微。生成.bc
的中間代碼缝彬。
我們通過優(yōu)化后的IR代碼生成.bc
代碼
clang -emit-llvm -c main.ll -o main.bc
-
生成匯編代碼
我們通過最終的.bc
或者.ll
代碼生成匯編代碼clang -S -fobjc-arc main.bc -o main.s
clang -S -fobjc-arc main.ll -o main.s
生成匯編代碼也可以進(jìn)行優(yōu)化
clang -Os -S -fobjc-arc main.m -o main.s
-
生成目標(biāo)文件(匯編器)
目標(biāo)文件的生成,是以匯編器以匯編代碼作為輸入喊衫,將匯編代碼轉(zhuǎn)換為機器代碼跌造,最后輸出目標(biāo)文件(object file)。clang -fmodules -c main.s -o main.o
通過nm
命令族购,查看下main.o
中的符號
$xcrun nm -nm main.o
(undefined) external _printf
0000000000000000 (__TEXT,__text) external _test
000000000000000a (__TEXT,__text) external _main
_printf
是一個undefined external
undefined
表示在當(dāng)前文件暫時找不到_printf
external
表示這個符號是外部可以訪問的。
-
生成可執(zhí)行文件(鏈接)
鏈接器把編譯產(chǎn)生的.o
文件和(.dylib .a
)文件生成一個mach-O
文件clang main.o -o main
查看鏈接之后的符號
$xcrun nm -nm main
(undefined) external _printf (from libSystem)
(undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
000000100000f6d (__TEXT,__text) external _test
000000100000f77 (__TEXT,__text) external _main