什么是編譯器
編譯器
就是將我們寫的程序,比如用C, Jave, C++, 匯編
等語言寫的程序救拉,經(jīng)過編譯器的轉(zhuǎn)換
,把這些語言轉(zhuǎn)換成計算機
或者微型處理器
能夠識別的機器代碼
,它是由0和1
組成的序列损合,說白了,就是相當于英語翻譯成中文的工具一樣娘纷。
編譯型語言和解釋型語言的區(qū)別嫁审?
-
編譯型語言
需通過編譯器
(compiler)將源代碼
編譯成機器碼
,之后才能執(zhí)行的語言赖晶。一般需經(jīng)過編譯
(compile)律适、鏈接
(linker)這兩個步驟。編譯是把源代碼編譯成機器碼
遏插,鏈接是把各個模塊的機器碼和依賴庫串連起來生成可執(zhí)行文件
擦耀。
代表語言:C、C++涩堤、Pascal、Object-C以及最近很火的蘋果新語言swift - 解釋性語言的程序
不需要編譯
分瘾,相比編譯型語言省了道工序胎围,解釋性語言在運行程序的時候才逐行翻譯
。
代表語言:JavaScript德召、Python白魂、Erlang、PHP上岗、Perl福荸、Ruby
LLVM概述
LLVM是架構(gòu)編譯器
的框架系統(tǒng),以C++
編寫而成肴掷,用于優(yōu)化任意程序語言編寫的程序的編譯時間
(compile-time)敬锐、鏈接時間
(link-time)、運行時間
(run-time)以及空閑時間
(idle-time)呆瞻。對開發(fā)者保持開放台夺,并兼容已有腳本
LLVM
計劃啟動于2000年,最初由美國UIUC大學的Chris Lattner博士主持開展痴脾。2006年Chris Lattner加盟Apple Inc
颤介,并致力于LLVM在Apple開放體系中的應用。
Apple
也是LLVM計劃的主要自助者。
目前LLVM已經(jīng)被蘋果iOS開發(fā)工具
滚朵、Xilinx Vivado
冤灾、Facebook
、Google
等各大公司采用
傳統(tǒng)編譯器設計
源碼Source Code
+ 前端 Frontend
+ 優(yōu)化器 Optimizer
+ 后端 Backend
(代碼生成器 CodeGenerator)+ 機器碼 Machine Code
iOS的編譯器架構(gòu)
OC
辕近、C
韵吨、C++
使用的編譯器前端是Clang
,Swift是swift亏推,后端都是LLVM
学赛,如下圖所示
模塊職責
- 編譯器前端(Frontend)
編譯器前端的任務是解析源代碼
。它會進行:詞法分析
吞杭、語法分析
盏浇、語義分析
,檢查源代碼是否存在錯誤
芽狗,然后構(gòu)建抽象語法樹
(Abstract Syntax Tree, AST)绢掰;
LLVM
的前端還會生成中間代碼
(intermediate representation, IR)。 - 優(yōu)化器(Optimizer)
優(yōu)化器負責進行各種優(yōu)化童擎。改善代碼的運行時間
滴劲,例如:消除冗余計算等。 - 后端(Backend)/代碼生成器(Code Generator)
將代碼映射到目標指令集
顾复。生成機器代碼
班挖,并進行機器代碼的相關優(yōu)化。
LLVM的設計
LLVM設計的最重要方面是芯砸,使用通用的代碼表示形式
(IR)萧芙,它是用來在編譯器中表示代碼的形式,所有LLVM可以為任何編程語言
獨立編寫前端假丧,并且可以為任意硬件架構(gòu)獨立編寫后端
LLVM的設計是前后端分離的
双揪,無論前端還是后端發(fā)生變化,都不會影響另一個
Clang
clang是LLVM
項目中的一個子項目
包帚,它是基于LLVM架構(gòu)圖
的輕量級編譯器
渔期,誕生之初是為了替代GCC
,提供更快的編譯速度
渴邦,它是負責C
疯趟、C++
、OC語言
的編譯器谋梭,屬于整個LLVM架構(gòu)中的編譯器前端
迅办,對于開發(fā)者來說,研究Clang可以給我們帶來很多好處章蚣。
編譯流程上:詞法&語法&IR
創(chuàng)建main.m文件站欺,編寫代碼如下
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
return 0;
}
通過命令clang -ccc-print-phases main.m
姨夹,打印源碼的編譯階段
//輸出以下內(nèi)容:
+- 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
- 0:輸入文件,
找到源文件
- 1:預處理階段矾策,這個過程包括
宏的替換
磷账,頭文件的導入
- 2:編譯階段,進行
詞法分析
贾虽、語法分析
逃糟、檢測語法
是否正確,最終生成IR
- 3:后端蓬豁,LLVM會通過一個一個的Pass去優(yōu)化绰咽,每個Pass做一些事情,最終
生成匯編代碼
- 4:
生成.o目標文件
- 5:鏈接地粪,鏈接需要的
動態(tài)庫
和靜態(tài)庫
取募,生成可執(zhí)行文件
- 6:通過不同的架構(gòu),生成對應的
可執(zhí)行文件
預處理階段
將宏和導入的頭文件
進行替換蟆技,main.m文件修改代碼如下
#import <stdio.h>
#define C 30
int main(int argc, const char * argv[]) {
int a = 10;
int b = 20;
printf("%d",a + b + C);
return 0;
}
通過命令玩敏,打印預處理階段
//在終端直接查看替換結(jié)果
clang -E main.m
//生成對應的文件查看替換后的源碼,-E做預處理生成預處理后的文件
clang -E main.m >> main1.m
//輸出以下內(nèi)容:
# 1 "main.m"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 378 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "main.m" 2
......
int main(int argc, const char * argv[]) {
int a = 10;
int b = 20;
printf("%d",a + b + 30);
return 0;
}
展開宏和stdio頭文件
质礼,main函數(shù)中原本+ C
變?yōu)?code>+ 30
使用define和typedef的區(qū)別:
-
define
:宏定義旺聚,在預處理階段會被替換
可用來做代碼混淆,將App中核心代碼眶蕉,用系統(tǒng)相似的名稱進行取別名砰粹,然后在預處理階段就被替換,以此達到代碼混淆的目的 -
typedef
:對數(shù)據(jù)類型取別名造挽,在預處理階段不會被替換掉伸眶。typedef
不是預處理指令
編譯階段
編譯階段可劃分為三個部分:詞法分析
、語法分析
刽宪、生成IR中間代碼
。
- 詞法分析
預處理完成后界酒,就會進行詞法分析
圣拄,這里會把代碼切成一個個Token,例如:大小括號
毁欣,等于號
庇谆,還有字符串
等
// 查看詞法分析結(jié)果命令
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
// 如果頭文件找不到,指定sdk
clang -isysroot (自己SDK路徑) -fmodules -fsyntax-only -Xclang -dump-tokens main.m
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -fmodules -fsyntax-only -Xclang -dump-tokens main.m
- 語法分析
詞法分析完成后就是語法分析
凭疮,它的任務是驗證語法是否正確
饭耳,在詞法分析的基礎上將單詞序列組合成各類語法短語
,如程序
执解、語句
寞肖、表達式
等等,然后將所有節(jié)點組成抽象語法樹
(Abstract Syntax Tree , AST),語法分析程序判斷程序在結(jié)構(gòu)上是否正確
// 查看語法分析結(jié)果命令
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
// 如果導入頭文件找不到新蟆,可以指定SDK
clang -isysroot (自己SDK路徑) -fmodules -fsyntax-only -Xclang -ast-dump main.m
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -fmodules -fsyntax-only -Xclang -ast-dump main.m
重點關鍵字的介紹:FunctionDecl:函數(shù)
觅赊、ParmVarDecl:參數(shù)
、CallExpr:函數(shù)調(diào)用
琼稻、BinaryOperator:運算符
- 生成IR中間代碼
完成以上步驟后吮螺,就會開始生成IR中間代碼
,代碼生成器
(Code Generator)會將語法樹自頂向下遍歷帕翻,逐步翻譯成LLVM IR
main.m文件修改代碼如下
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代碼
鸠补。OC代碼在這一步會進行runtime橋接
,:property合成
嘀掸、ARC處理
等
clang -S -fobjc-arc -emit-llvm main.m
查看IR中間代碼
//以下是IR基本語法
@ 全局標識
% 局部標識
alloca 開辟空間
align 內(nèi)存對齊
i32 32bit紫岩,4個字節(jié)
store 寫入內(nèi)存
load 讀取數(shù)據(jù)
call 調(diào)用函數(shù)
ret 返回
- IR的優(yōu)化
Xcode中找到Target
→Build Setting
→Optimization Level
,可以對當前項目設置優(yōu)化等級
在LLVM中横殴,優(yōu)化級別分別是-O0
被因、-O1
、-O2
衫仑、-O3
梨与、-Os
(第一個是大寫英文字母O)。
通過以下命令文狱,可設置優(yōu)化等級粥鞋,并生成IR代碼:
clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll
查看優(yōu)化后的IR代碼
xcode7以后開啟bitcode,蘋果會做進一步優(yōu)化瞄崇,生成.bc的中間代碼呻粹,我們通過優(yōu)化后的IR代碼生成.bc代碼
clang -emit-llvm -c main.ll -o main.bc
小結(jié):編譯階段流程
輸入代碼
-> 展開預處理
-> 詞法分析(token)
-> 語法分析(AST語法樹)
-> IR代碼
-> IR代碼優(yōu)化
編譯流程下:生成可執(zhí)行文件
什么是Bitcode?
Bitcode
是被編譯程序的一種中間形式的代碼苏研。包含Bitcode
并上傳到App Store Connect
的App等浊,會在App Store
上編譯和鏈接。包含Bitcode可以在不提交新版本App的情況下摹蘑,允許Apple在將來的時候再次優(yōu)化你的App二進制文件筹燕。
在Xcode中,默認開啟Bitcode
設置衅鹿。如果你的App支持Bitcode
撒踪,App使用到的其他二進制形式也要支持Bitcode
,否則就會報錯大渤。
解決Bitcode報錯只有兩種方案:
-
將不支持Bitcode的SDK移除掉
制妄,或等待第三方更新 - 將使用
Bitcode
的選項設置為NO
生成匯編代碼
- 通過最終的
.ll
或.bc
代碼,生成匯編代碼
// 命令如下
clang -S -fobjc-arc main.ll -o main.s
clang -S -fobjc-arc main.bc -o main.s
查看匯編代碼
- 生成匯編代碼也可以進行優(yōu)化
clang -Os -S -fobjc-arc main.m -o main.s
生成目標文件(匯編器)
目標文件的生成泵三,是匯編器
以匯編代碼
作為插入耕捞,將匯編代碼
轉(zhuǎn)換為機器代碼
衔掸,最后輸出目標文件
(object file)
// 命令
clang -fmodules -c main.s -o main.o
通過nm命令,查看main.o中的符號
xcrun nm -nm main.o
//輸出以下內(nèi)容:
(undefined) external _printf
0000000000000000 (__TEXT,__text) external _main
-
_printf
函數(shù)被標記為undefined
砸脊、external
-
undefined
表示在當前文件暫時找不到符號_printf
-
external
表示這個符號是外部可以訪問的
生成可執(zhí)行文件(鏈接)
鏈接
主要是鏈接需要的動態(tài)庫
和靜態(tài)庫
具篇,生成可執(zhí)行文件
,其中
-
靜態(tài)庫
會和可執(zhí)行文件
合并 動態(tài)庫是獨立的
鏈接器
把編譯生成的.o文件
和 .dyld
.a文件
鏈接凌埂,生成一個mach-o
可執(zhí)行文件
clang main.o -o main
查看鏈接后可執(zhí)行文件的符號
xcrun nm -nm main
//輸出以下內(nèi)容:
(undefined) external _printf (from libSystem)
(undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
0000000100003f77 (__TEXT,__text) external _main
0000000100008008 (__DATA,__data) non-external __dyld_private
- 鏈接后
_printf符號
可以找到所屬的動態(tài)庫
驱显,但依然被標記為undefined
。因為libSystem
屬于系統(tǒng)動態(tài)庫瞳抓,在運行時進行動態(tài)綁定
埃疫。 - 鏈接后還多了
dyld_stub_binder
符號,它在運行時用于符號的重綁定
:
- 以
printf
函數(shù)為例孩哑,printf
函數(shù)存在于libSystem
系統(tǒng)庫中栓霜,它存在于懶加載符號表
中。它的函數(shù)地址在運行時横蜒,首次對printf
函數(shù)進行調(diào)用胳蛮,才會通過dyld_stub_binder
進行重綁定。 -
dyld_stub_binder
函數(shù)地址的綁定時機:當dyld
加載主程序
時丛晌,符號被dyld直接綁定
仅炊。
總結(jié)
創(chuàng)建Clang插件
由于國內(nèi)的網(wǎng)絡限制,我們需要借助鏡像下載LLVM
的源碼
https://mirror.tuna.tsinghua.edu.cn/help/llvm/
- 下載llvm項目
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/llvm.git
- 在
LLVM
的tools
目錄下下載Clang
cd llvm/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang.git
- 在
LLVM
的projects
目錄下下載compiler-rt
澎蛛、libcxx
抚垄、libcxxabi
cd ../projects
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/compiler-rt.g
it
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxx.git
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxxabi.git
- 在
Clang
的tools
下安裝extra
工具
cd ../tools/clang/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang-tools-extra.git
LLVM編譯
由于最新的LLVM
只支持cmake
來編譯,所以需要安裝cmake
安裝cmake
- 查看brew是否安裝cmake谋逻,如果已經(jīng)安裝呆馁,則跳過下面步驟
brew list
- 通過brew安裝cmake
brew install cmake
通過Xcode編譯LLVM
- cmake編譯成Xcode項目:
mkdir build_xcode
cd build_xcode
cmake -G Xcode ../llvm
- 使用Xcode編譯Clang
- 選擇自動創(chuàng)建
Schemes
- 編譯
(CMD + B)
,選擇ALL_BUILD Secheme
進行編譯,預計1+小時
通過ninja編譯LLVM
- 使用
ninja
進行編譯則還需要安裝ninja
,使用$ brew install ninja
命令即可安裝ninja
- 在
LLVM
源碼根目錄下新建一個build_ninja
目錄沃呢,最終會在build_ninja
目錄下生成build.ninja
- 在
LLVM
源碼根目錄下新建llvm_release
目錄,最終編譯文件會在llvm_release
文件夾路徑下
$ cd llvm_build
//注意DCMAKE_INSTALL_PREFIX后面不能有空格
$ cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX= 安裝路徑(本機為/ Users/xxx/xxx/LLVM/llvm_release)
- 依次執(zhí)行編譯纺腊,安裝指令
$ ninja
$ ninja install
創(chuàng)建Clang插件
- 在
/llvm/tools/clang/tools
目錄下,新建插件HKPlugin
- 修改
/llvm/tools/clang/tools
目錄下的CMakeLists.txt
文件
- 新增
add_clang_subdirectory(HKPlugin)
- 在
HKPlugin
目錄下送巡,新建HKPlugi.cpp
和CMakeLists.txt
文件
- 打開CMakeLists.txt文件寫入以下內(nèi)容
add_llvm_library( HKPlugin MODULE BUILDTREE_ONLY
HKPlugin.cpp
)
- 接下來利用
cmake
重新生成Xcode
項目,在build_xcode
目錄下執(zhí)行以下命令
cmake -G Xcode ../llvm
- 最后在
LLVM
的Xcode
項目中盒卸,Loadable modules
目錄下找到自定義Plugin目錄
骗爆,就可以在里面編寫插件代碼了
課后總結(jié)
LLVM概述
- LLVM編譯器(重點):
-
編譯型語言
&&解釋型語言
- 編譯器
前端
:讀取代碼,詞法分析蔽介,語法分析摘投,生成AST(LLVM:IR)
優(yōu)化器
:根據(jù)一個又一個Pass進行優(yōu)化
后端
:生成匯編代碼煮寡,根據(jù)不同的架構(gòu)生成不同的可執(zhí)行文件 - llvm的好處是啥?
前后端分離
犀呼,擴展性非常強幸撕。
- LLVM編譯流程(重點):
- Clang插件
編譯LLVM工程
創(chuàng)建插件