# iOS的編譯、鏈接工具 — Clang/LLVM
## LLVM的誕生
## LLVM及其子項(xiàng)目
### Clang:Clang做了哪些事?Clang提供了哪些功能?
## Clang-LLVM架構(gòu)
## 應(yīng)用
# 從源碼到可執(zhí)行文件 — iOS應(yīng)用編譯归斤、靜態(tài)鏈接過(guò)程
## Clang常用命令與參數(shù)
## 1. 預(yù)處理(Preprocess)
## 2. 詞法分析 (Lexical Analysis)
## 3. 語(yǔ)法分析 (Semantic Analysis)
## 3.1 靜態(tài)分析(Static Analyzer)
## 4. IR代碼生成 (CodeGen)
## 5. 生成字節(jié)碼 (LLVM Bitcode)
## 6. 生成相關(guān)匯編
## 7. 生成目標(biāo)文件
## 8. 生成可執(zhí)行文件
# 小結(jié):iOS從編碼到打包
# 參考鏈接
# iOS的編譯痊夭、鏈接工具 — Clang/LLVM
- The LLVM Project is a collection of modular and reusable compiler and toolchain technologies(LLVM項(xiàng)目是一系列分模塊刁岸、可重用的編譯工具鏈). Despite its name, LLVM has little to do with traditional virtual machines. The name "LLVM" itself is not an acronym; it is the full name of the project.
- Clang is an "LLVM native" C/C++/Objective-C compiler.
## LLVM的誕生
2000年,伊利諾伊大學(xué)厄巴納-香檳分校(University of Illinois at Urbana-Champaign 簡(jiǎn)稱UIUC)這所享有世界聲望的一流公立研究型大學(xué)的克里斯·拉特納(Chris Lattner她我,twitter為 clattner_llvm) 開(kāi)發(fā)了一個(gè)叫作 Low Level Virtual Machine 的編譯器開(kāi)發(fā)工具套件虹曙,后來(lái)涉及范圍越來(lái)越大,可以用于常規(guī)編譯器番舆,JIT編譯器酝碳,匯編器,調(diào)試器恨狈,靜態(tài)分析工具等一系列跟編程語(yǔ)言相關(guān)的工作疏哗,于是就把簡(jiǎn)稱 LLVM 這個(gè)簡(jiǎn)稱作為了正式的名字。
2005年禾怠,由于GCC 對(duì)于 Objective-C 的支持比較差返奉,效率和性能都沒(méi)有辦法達(dá)到蘋(píng)果公司的要求,而且它還難以推動(dòng) GCC 團(tuán)隊(duì)吗氏。于是芽偏,蘋(píng)果公司決定自己來(lái)掌握編譯相關(guān)的工具鏈,于是將Chris Lattner招入麾下弦讽,發(fā)起了 Clang 軟件項(xiàng)目污尉。
- Clang 作為 LLVM 編譯器工具集的前端(front-end),目的是輸出代碼對(duì)應(yīng)的抽象語(yǔ)法樹(shù)(Abstract Syntax Tree, AST)往产,并將代碼編譯成LLVM Bitcode被碗。接著在后端(back-end)使用LLVM編譯成平臺(tái)相關(guān)的機(jī)器語(yǔ)言。Clang支持C仿村、C++锐朴、Objective C。
- 測(cè)試證明Clang編譯Objective-C代碼時(shí)速度為GCC的3倍奠宜,還能針對(duì)用戶發(fā)生的編譯錯(cuò)誤準(zhǔn)確地給出建議包颁。
- 此后瞻想,蘋(píng)果使用的 GCC 全面替換成了 LLVM。
2010年娩嚼,Chris Lattner開(kāi)始主導(dǎo)開(kāi)發(fā) Swift 語(yǔ)言蘑险。這也使得 Swift 這門(mén)集各種高級(jí)語(yǔ)言特性的語(yǔ)言,能夠在非常高的起點(diǎn)上岳悟,出現(xiàn)在開(kāi)發(fā)者面前佃迄。
2012年,LLVM 獲得美國(guó)計(jì)算機(jī)學(xué)會(huì) ACM 的軟件系統(tǒng)大獎(jiǎng)贵少,和 UNIX呵俏,WWW,TCP/IP滔灶,Tex普碎,JAVA 等齊名。
## LLVM及其子項(xiàng)目
llvm特點(diǎn):
- 模塊化
- 統(tǒng)一的中間代碼IR录平,而前端麻车、后端可以不一樣。而GCC的前端斗这、后端耦合在了一起动猬,所以支持一門(mén)新語(yǔ)言或者新的平臺(tái),非常困難表箭。
- 功能強(qiáng)大的Pass系統(tǒng)赁咙,根據(jù)依賴性自動(dòng)對(duì)Pass(包括分析、轉(zhuǎn)換和代碼生成Pass)進(jìn)行排序免钻,管道化以提高效率彼水。
llvm有廣義和狹義兩種定義:
- 在廣義中,llvm特指一整個(gè)編譯器框架伯襟,是一個(gè)模塊化和可重用的編譯器和工具鏈技術(shù)的集合猿涨,由前端、優(yōu)化器姆怪、后端組成叛赚,clang只是用于c/c++的一種前端,llvm針對(duì)不同的語(yǔ)言可以設(shè)計(jì)不同的前端稽揭,同樣的針對(duì)不同的平臺(tái)架構(gòu)(amd俺附,arm,misp)溪掀,也會(huì)有不同后端設(shè)計(jì)
- 在狹義中 事镣,特指llvm后端,指優(yōu)化器(pass)對(duì)IR進(jìn)行一系列優(yōu)化直到目標(biāo)代碼生成的過(guò)程
簡(jiǎn)單羅列LLVM幾個(gè)主要的子項(xiàng)目揪胃,詳見(jiàn)官網(wǎng):
LLVM Core libraries:LLVM核心庫(kù)提供了一個(gè)獨(dú)立于源和目標(biāo)架構(gòu)的現(xiàn)代優(yōu)化器optimizer璃哟,以及對(duì)許多流行cpu(以及一些不太常見(jiàn)的cpu)的代碼生成(code generation)支持氛琢。這些庫(kù)是圍繞一種被稱為L(zhǎng)LVM中間表示(“LLVM IR”)的良好指定的代碼表示構(gòu)建的。
Clang:一個(gè) C/C++/Objective-C 編譯器随闪,提供高效快速的編譯效率阳似,比 GCC 快3倍,其中的 clang static analyzer 主要是進(jìn)行語(yǔ)法分析铐伴,語(yǔ)義分析和生成中間代碼撮奏,當(dāng)然這個(gè)過(guò)程會(huì)對(duì)代碼進(jìn)行檢查,出錯(cuò)的和需要警告的會(huì)標(biāo)注出來(lái)当宴。(見(jiàn)下文詳述)
lld: 是LLVM開(kāi)發(fā)一個(gè)內(nèi)置的畜吊,平臺(tái)獨(dú)立的鏈接器,去除對(duì)所有第三方鏈接器的依賴户矢。在2017年5月玲献,lld已經(jīng)支持ELF、PE/COFF逗嫡、和Mach-O青自。在lld支持不完全的情況下株依,用戶可以使用其他項(xiàng)目驱证,如 GNU ld 鏈接器。
lld支持鏈接時(shí)優(yōu)化恋腕。當(dāng)LLVM鏈接時(shí)優(yōu)化被啟用時(shí)抹锄,LLVM可以輸出bitcode而不是本機(jī)代碼,而本機(jī)代碼生成由鏈接器優(yōu)化處理荠藤。LLDB:基于 LLVM 和 Clang提供的庫(kù)構(gòu)建的一個(gè)優(yōu)秀的本地調(diào)試器伙单,使用了 Clang ASTs、表達(dá)式解析器哈肖、LLVM JIT吻育、LLVM 反匯編器等。
### Clang
從Clang的源碼目錄中可以大致看出Clang提供的功能:
Clang提供了哪些功能淤井?
Clang 為一些需要分析代碼語(yǔ)法布疼、語(yǔ)義信息的工具提供了基礎(chǔ)設(shè)施。分別是:
LibClang币狠。LibClang提供了一個(gè)穩(wěn)定的高級(jí) C 接口游两,Xcode 使用的就是 LibClang。LibClang 可以訪問(wèn) Clang 的上層高級(jí)抽象的能力漩绵,比如獲取所有 Token贱案、遍歷語(yǔ)法樹(shù)、代碼補(bǔ)全等止吐。由于 API 很穩(wěn)定宝踪,Clang 版本更新對(duì)其 影響不大侨糟。但是,LibClang 并不能完全訪問(wèn)到 Clang AST 信息瘩燥。
Clang Plugins粟害。可以在 AST 上做些操作颤芬,這些操作能夠集成到編譯中悲幅,成為編譯的一部分。插件是在運(yùn) 行時(shí)由編譯器加載的動(dòng)態(tài)庫(kù)站蝠,方便集成到構(gòu)建系統(tǒng)中汰具。
使用 Clang Plugins 一般都是希望能夠完全控制 Clang AST,同時(shí)能夠集成在編譯流程中菱魔,可以影響編譯的過(guò)程留荔,進(jìn)行中斷或者提示。
應(yīng)用:實(shí)現(xiàn)命名規(guī)范澜倦、代碼規(guī)范等一些擴(kuò)展功能LibTooling聚蝶。是一個(gè) C++ 接口,所寫(xiě)的工具不依賴于構(gòu)建系統(tǒng)藻治,可以作為一個(gè)命令單獨(dú)使用碘勉。與 Clang Plugins 相比,LibTooling 無(wú)法影響編譯過(guò)程桩卵;與 LibClang 相比验靡,LibTooling 的接口沒(méi)有那么穩(wěn)定。
應(yīng)用:做代碼轉(zhuǎn)換雏节,比如把 OC 轉(zhuǎn) JavaScript 或 Swift胜嗓;代碼檢查。
Clang的優(yōu)點(diǎn)
Clang 是 C钩乍、C++辞州、Objective-C 的編譯前端,而 Swift 有自己的編譯前端 (也就是 Swift 前端多出的 SIL optimizer)寥粹。Clang 有哪些優(yōu)勢(shì)变过?
- 對(duì)于使用者來(lái)說(shuō),Clang 編譯的速度非撑抛鳎快牵啦,對(duì)內(nèi)存的使用率非常低,并且兼容 GCC妄痪。
- 對(duì)于代碼診斷來(lái)說(shuō)哈雏, Clang 也非常強(qiáng)大,Xcode 也是用的 Clang。使用 Clang 編譯前端裳瘪,可以精確地顯示出問(wèn)題所在的行和具體位置秘案,并且可以確切地說(shuō)明出現(xiàn)這個(gè)問(wèn)題的原因这溅,并指出錯(cuò)誤的類型是什么普办,使得我們可以快速掌握問(wèn)題的細(xì)節(jié)岛啸。這樣的話,我們不用看源碼派殷,僅通過(guò) Clang 突出標(biāo)注的問(wèn)題范圍也能夠了解到問(wèn)題的情況还最。
- Clang 對(duì) typedef 的保留和展開(kāi)也處理得非常好。typedef 可以縮寫(xiě)很長(zhǎng)的類型毡惜,保留 typedef 對(duì)于粗粒度診斷分析很有幫助拓轻。但有時(shí)候,我們還需要了解細(xì)節(jié)经伙,對(duì) typedef 進(jìn)行展開(kāi)即可扶叉。
- Fix-it 提示也是 Clang 提供的一種快捷修復(fù)源碼問(wèn)題的方式。在宏的處理上帕膜,很多宏都是深度嵌套的枣氧, Clang 會(huì)自動(dòng)打印實(shí)例化信息和嵌套范圍信息來(lái)幫助你進(jìn)行宏的診斷和分析。
- Clang 的架構(gòu)是模塊化的垮刹。除了代碼靜態(tài)分析外达吞,利用其輸出的接口還可以開(kāi)發(fā)用于代碼轉(zhuǎn)義、代碼生成危纫、代碼重構(gòu)的工具宗挥,方便與 IDE 進(jìn)行集成。
Clang 是基于 C++ 開(kāi)發(fā)的种蝶,如果你想要了解 Clang 的話,需要有一定的 C++ 基礎(chǔ)瞒大。但是螃征,Clang 源碼本身質(zhì)量非常高,有很多值得學(xué)習(xí)的地方透敌,比如說(shuō)目錄清晰盯滚、功能解耦做得很好、分類清晰方便組合和復(fù)用酗电、代碼風(fēng)格統(tǒng)一而且規(guī)范魄藕、注釋量大便于閱讀等。
## Clang-LLVM架構(gòu)
Clang-LLVM架構(gòu)撵术,即用Clang作為前端的LLVM(編譯工具集)背率。
LLVM架構(gòu)的主要組成部分:
前端:前端用來(lái)獲取源代碼然后將它轉(zhuǎn)變?yōu)槟撤N中間表示,我們可以選擇不同的編譯器來(lái)作為L(zhǎng)LVM的前端寝姿,如gcc交排,clang(Clang-LLVM)。
LLVM支持三種表達(dá)形式:人類可讀的匯編(.ll
后綴饵筑,是LLVM IR文件埃篓,其有自己的語(yǔ)法)、在C++中對(duì)象形式根资、序列化后的bitcode形式(.bc
后綴)架专。Pass(v.通過(guò)/傳遞/變化 n.經(jīng)過(guò)/通行證/道路/流程/階段) :是 LLVM 優(yōu)化(optimize)工作的一個(gè)節(jié)點(diǎn),一個(gè)節(jié)點(diǎn)做些事玄帕,一起加起來(lái)就構(gòu)成了 LLVM 完整的優(yōu)化和轉(zhuǎn)化胶征。
Pass用來(lái)將程序的中間表示之間相互變換。一般情況下桨仿,Pass可以用來(lái)優(yōu)化代碼睛低,這部分通常是我們關(guān)注的部分。我們可以自己編寫(xiě)Pass服傍,做一些代碼混淆優(yōu)化等操作钱雷。后端:后端用來(lái)生成實(shí)際的機(jī)器碼。至3.4版本的LLVM已經(jīng)支持多種后端指令集吹零,比如主流的x86罩抗、x86-64、z/Architecture灿椅、ARM和PowerPC等
雖然如今大多數(shù)編譯器都采用的是這種架構(gòu)套蒂,但是LLVM不同的就是對(duì)于不同的語(yǔ)言它都提供了同一種中間表示。傳統(tǒng)的編譯器的架構(gòu)如下:
LLVM的架構(gòu)如下:
當(dāng)編譯器需要支持多種源代碼和目標(biāo)架構(gòu)時(shí)茫蛹,基于LLVM的架構(gòu)操刀,設(shè)計(jì)一門(mén)新的語(yǔ)言只需要去實(shí)現(xiàn)一個(gè)新的前端就行了,支持新的后端架構(gòu)也只需要實(shí)現(xiàn)一個(gè)新的后端婴洼,其它部分完成可以復(fù)用骨坑,不用重新設(shè)計(jì)。在基于LLVM進(jìn)行代碼混淆時(shí)柬采,只需要關(guān)注中間層代碼(IR)表示欢唾。
## 應(yīng)用:
- iOS 開(kāi)發(fā)中 Objective-C 是 Clang / LLVM 來(lái)編譯的。
- swift 是 Swift / LLVM粉捻,其中 Swift 前端會(huì)多出 SIL optimizer礁遣,它會(huì)把 .swift 生成的中間代碼 .sil 屬于 High-Level IR, 因?yàn)?swift 在編譯時(shí)就完成了方法綁定直接通過(guò)地址調(diào)用屬于強(qiáng)類型語(yǔ)言肩刃,方法調(diào)用不再是像OC那樣的消息發(fā)送祟霍,這樣編譯就可以獲得更多的信息用在后面的后端優(yōu)化上杏头。
- Gallium3D 中使用 LLVM 進(jìn)行 JIT 優(yōu)化
- Xorg 中的 pixman 也有考慮使用 LLVM 優(yōu)化執(zhí)行速度
- LLVM-Lua 用LLVM 來(lái)編譯 lua 代碼
- gpuocelot 使用 LLVM 可以讓 CUDA 程序無(wú)需重新編譯就能夠在多種 CPU 機(jī)器上跑。
下面浅碾,通過(guò)具體的代碼大州、命令,來(lái)看一下iOS中源代碼詳細(xì)的編譯垂谢、鏈接過(guò)程
# 從源碼到可執(zhí)行文件 — iOS應(yīng)用編譯厦画、靜態(tài)鏈接過(guò)程
我們?cè)陂_(kāi)發(fā)的時(shí)候的時(shí)候,如果想要生成一個(gè)可執(zhí)行文件或應(yīng)用滥朱,我們點(diǎn)擊run就完事了根暑,那么在點(diǎn)擊run之后編譯器背后又做了哪些事情呢?
我們先來(lái)一個(gè)例子:
#include <stdio.h>
#define DEFINEEight 8
int main(){
int eight = DEFINEEight;
int six = 6;
int rank = eight + six;
printf("%d\n",rank);
return 0;
}
上面這個(gè)文件徙邻,我們可以通過(guò)命令行直接編譯排嫌,然后鏈接:
xcrun -sdk iphoneos clang -arch armv7 -F Foundation -fobjc-arc -c main.m -o main.o
xcrun -sdk iphoneos clang main.o -arch armv7 -fobjc-arc -framework Foundation -o main
然后將該可執(zhí)行文件copy到手機(jī)目錄 /usr/bin 下面:
xx-iPhone:/usr/bin root# ./main
14
下面深入剖析其中的過(guò)程。
## Clang常用命令與參數(shù)
// 查看編譯的步驟
clang -ccc-print-phases main.m
// Rewrite Objective-C source to C++缰犁,將OC源代碼重寫(xiě)為C++(僅供參考淳地,與真正的運(yùn)行時(shí)代碼還是有細(xì)微差別的)
// 如果想了解真正的代碼,可以使用-emit-llvm參數(shù)查看.ll中間代碼
clang -rewrite-objc main.m
// 查看操作內(nèi)部命令
clang -### main.m -o main
// 直接生成可執(zhí)行文件
clang main.m // 默認(rèn)生成的文件名為a.out
/*
參數(shù):
-cc1:Clang編譯器前端具有幾個(gè)額外的Clang特定功能帅容,這些功能不通過(guò)GCC兼容性驅(qū)動(dòng)程序接口公開(kāi)颇象。 -cc1參數(shù)表示將使用編譯器前端,而不是驅(qū)動(dòng)程序并徘。 clang -cc1功能實(shí)現(xiàn)了核心編譯器功能遣钳。
-E:只進(jìn)行預(yù)編譯處理(preprocessor)
-S:只進(jìn)行預(yù)編譯、編譯工作
-c:只進(jìn)行預(yù)處理麦乞、編譯蕴茴、匯編工作
-fmodules:允許modules的語(yǔ)言特性。
在使用#include姐直、#import時(shí)倦淀,會(huì)看到預(yù)處理時(shí)已經(jīng)把宏替換了,并且導(dǎo)入了頭文件简肴。但是這樣的話會(huì)引入很多不會(huì)去改變的系統(tǒng)庫(kù)比如Foundation晃听。
所以有了pch預(yù)處理文件,可以在這里去引入一些通用的頭文件砰识。
后來(lái)Xcode新建的項(xiàng)目里面去掉了pch文件,引入了moduels的概念佣渴,把一些通用的庫(kù)打成modules的形式辫狼,然后導(dǎo)入。現(xiàn)在Xcode中默認(rèn)是打開(kāi)的辛润,即編譯源碼時(shí)會(huì)加上-fmodules參數(shù)膨处。也是因?yàn)閙odules機(jī)制的出現(xiàn),pch不再默認(rèn)自動(dòng)創(chuàng)建。
使用了該參數(shù)真椿,在導(dǎo)入庫(kù)的地方鹃答,只需要 @import Foundation; 就行
可以看到使用了@import之后,clang -fmodules xx 生成的文件中突硝,不再有上萬(wàn)行的系統(tǒng)庫(kù)的代碼引入测摔,精簡(jiǎn)了很多。
-fsyntax-only:防止編譯器生成代碼,只是語(yǔ)法級(jí)別的說(shuō)明和修改
-Xclang <arg>:向clang編譯器傳遞參數(shù)
-dump-tokens:運(yùn)行預(yù)處理器,拆分內(nèi)部代碼段為各種token
-ast-dump:構(gòu)建抽象語(yǔ)法樹(shù)AST,然后對(duì)其進(jìn)行拆解和調(diào)試
-fobjc-arc:為OC對(duì)象生成retain和release的調(diào)用
-emit-llvm:使用LLVM描述匯編和對(duì)象文件
-o <file>:輸出到目標(biāo)文件
*/
查看更多的`clang`使用方法可以在終端輸入`clang --hep`查看,也可以點(diǎn)擊下面的鏈接:https://link.jianshu.com/?t=https://gist.github.com/masuidrive/5231110
## 1. 預(yù)處理(Preprocess)
預(yù)編譯過(guò)程主要處理源代碼文件中的以"#"開(kāi)頭的預(yù)編譯指令解恰,不檢查語(yǔ)法錯(cuò)誤锋八。規(guī)則如下:
- 將所有的 “#define” 刪除,并且展開(kāi)所有的宏定義护盈。
- 處理所有條件預(yù)編譯指令挟纱,比如 “#if”、“#ifdef”腐宋、“#elif”紊服、“#else”、“#endif”胸竞。
- 處理 “#include” 預(yù)編譯指令欺嗤,將被包含的文件內(nèi)容插入到(全部復(fù)制到)該預(yù)編譯指令的位置。注意撤师,這個(gè)過(guò)程是遞歸進(jìn)行的剂府,也就是說(shuō)被包含的文件可能還包含其他文件。#include 可以導(dǎo)入任何(合法/不合法)文件剃盾,都能展開(kāi)腺占。
- 刪除所有的注釋“//”和“/* */”,會(huì)變成空行痒谴。
- 保留所有的 #pragma 編譯器指令衰伯,因?yàn)榫幾g器須要使用它們。
- 添加行號(hào)和文件名標(biāo)識(shí)积蔚,比如# 2 "main.m" 2意鲸,以便于編譯時(shí)編譯器產(chǎn)生調(diào)試用的行號(hào)信息及用于編譯時(shí)產(chǎn)生編譯錯(cuò)誤或警告時(shí)能夠顯示行號(hào)。
格式是“# 行號(hào) 文件名 標(biāo)志
”尽爆,參數(shù)解釋如下:- 行號(hào)與文件名:表示從它后一行開(kāi)始的內(nèi)容來(lái)源于哪一個(gè)文件的哪一行
- 標(biāo)志:可以是1,2,3,4四個(gè)數(shù)字怎顾,每個(gè)數(shù)字的含義如下:
1:表示新文件的開(kāi)始
2:表示從一個(gè)被包含的文件中返回
3:表示后面的內(nèi)容來(lái)自系統(tǒng)頭文件
4:表示后面的內(nèi)容應(yīng)當(dāng)被當(dāng)做一個(gè)隱式的extern 'C'塊
經(jīng)過(guò)預(yù)編譯后的.i 文件
不包含任何宏定義,因?yàn)樗械暮暌呀?jīng)被展開(kāi)漱贱,并且包含的文件也已經(jīng)被插入到 .i 文件中槐雾。所以當(dāng)我們無(wú)法判斷宏定義是否正確或頭文件包含是否正確時(shí),可以查看預(yù)編譯后的文件來(lái)確定問(wèn)題幅狮。
可以通過(guò)執(zhí)行以下命令募强,-E
表示只進(jìn)行預(yù)編譯:
clang -E main.m 或者 clang -E -fmodules main.m(后者需要源碼中改為@import)
執(zhí)行完這個(gè)命令之后株灸,我們會(huì)發(fā)現(xiàn)導(dǎo)入了很多的頭文件內(nèi)容。
......
# 408 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/stdio.h" 2 3 4
# 2 "main.m" 2
int main(){
int eight = 8;
int six = 6;
int rank = eight + six;
printf("%d\n",rank);
return 0;
}
可以看到上面的預(yù)處理已經(jīng)把宏替換了擎值,并且導(dǎo)入了頭文件慌烧。
## 2. 詞法分析 (Lexical Analysis)
預(yù)處理之后,就是編譯鸠儿。編譯過(guò)程就是把預(yù)處理完的文件進(jìn)行一系列詞法分析屹蚊、語(yǔ)法分析、語(yǔ)義分析及優(yōu)化后生產(chǎn)相應(yīng)的匯編代碼文件捆交,這個(gè)過(guò)程往往是我們所說(shuō)的整個(gè)程序構(gòu)建的核心部分淑翼,也是最復(fù)雜的部分之一。
首先品追,Clang 會(huì)對(duì)代碼進(jìn)行詞法分析玄括,將代碼切分成 Token。你可以在這個(gè)鏈接
中肉瓦,看到 Clang 定義的所有 Token 類型遭京。我們可以把這些 Token 類型,分為下面這 4 類泞莉。
- 關(guān)鍵字:語(yǔ)法中的關(guān)鍵字哪雕,比如 if、else鲫趁、while斯嚎、for 等;
- 標(biāo)識(shí)符:變量名;
- 字面量:值、數(shù)字挨厚、字符串;
- 特殊符號(hào):加減乘除堡僻、左右括號(hào)等符號(hào)。
clang -fsyntax-only -Xclang -dump-tokens main.m
每一個(gè)標(biāo)記都包含了對(duì)應(yīng)的源碼內(nèi)容和其在源碼中的位置疫剃。注意這里的位置是宏展開(kāi)之前的位置钉疫,這樣一來(lái),如果編譯過(guò)程中遇到什么問(wèn)題巢价,clang 能夠在源碼中指出出錯(cuò)的具體位置牲阁。
int 'int' [StartOfLine] Loc=<main.m:4:1>
identifier 'main' [LeadingSpace] Loc=<main.m:4:5>
l_paren '(' Loc=<main.m:4:9>
r_paren ')' Loc=<main.m:4:10>
l_brace '{' Loc=<main.m:4:11>
int 'int' [StartOfLine] [LeadingSpace] Loc=<main.m:5:5>
identifier 'eight' [LeadingSpace] Loc=<main.m:5:9>
equal '=' [LeadingSpace] Loc=<main.m:5:15>
numeric_constant '8' [LeadingSpace] Loc=<main.m:5:17 <Spelling=main.m:2:21>>
semi ';' Loc=<main.m:5:28>
int 'int' [StartOfLine] [LeadingSpace] Loc=<main.m:6:5>
identifier 'six' [LeadingSpace] Loc=<main.m:6:9>
equal '=' [LeadingSpace] Loc=<main.m:6:13>
numeric_constant '6' [LeadingSpace] Loc=<main.m:6:15>
semi ';' Loc=<main.m:6:16>
int 'int' [StartOfLine] [LeadingSpace] Loc=<main.m:7:5>
identifier 'rank' [LeadingSpace] Loc=<main.m:7:9>
equal '=' [LeadingSpace] Loc=<main.m:7:14>
identifier 'eight' [LeadingSpace] Loc=<main.m:7:16>
plus '+' [LeadingSpace] Loc=<main.m:7:22>
identifier 'six' [LeadingSpace] Loc=<main.m:7:24>
semi ';' Loc=<main.m:7:27>
identifier 'printf' [StartOfLine] [LeadingSpace] Loc=<main.m:8:5>
l_paren '(' Loc=<main.m:8:11>
string_literal '"%d\n"' Loc=<main.m:8:12>
comma ',' Loc=<main.m:8:18>
identifier 'rank' Loc=<main.m:8:19>
r_paren ')' Loc=<main.m:8:23>
semi ';' Loc=<main.m:8:24>
return 'return' [StartOfLine] [LeadingSpace] Loc=<main.m:9:5>
numeric_constant '0' [LeadingSpace] Loc=<main.m:9:12>
semi ';' Loc=<main.m:9:13>
r_brace '}' [StartOfLine] Loc=<main.m:10:1>
eof '' Loc=<main.m:10:2>
## 3. 語(yǔ)法、語(yǔ)義分析
這個(gè)階段有兩個(gè)模塊Parser(語(yǔ)法syntax分析器)壤躲、Sema(語(yǔ)義分析Semantic)配合完成:
- Parser:遍歷每個(gè)Token做詞句分析城菊,根據(jù)當(dāng)前語(yǔ)言的語(yǔ)法,驗(yàn)證語(yǔ)法是否正確碉克,最后生成一個(gè) 節(jié)點(diǎn)(Nodes)并記錄相關(guān)的信息役电。
- Semantic:在Lex 跟 syntax Analysis之后, 已經(jīng)確保 詞 句已經(jīng)是正確的形式,semantic 接著做return values, size boundaries, uninitialized variables 等檢查棉胀,如果發(fā)現(xiàn)語(yǔ)義上有錯(cuò)誤給出提示法瑟;如果沒(méi)有錯(cuò)誤就會(huì)將 Token 按照語(yǔ)法組合成語(yǔ)義,生成 Clang 語(yǔ)義節(jié)點(diǎn)(Nodes)唁奢,然后將這些節(jié)點(diǎn)按照層級(jí)關(guān)系構(gòu)成抽象語(yǔ)法樹(shù)(AST)霎挟。
AST可以說(shuō)是Clang的核心,大部分的優(yōu)化, 判斷都在AST處理(例如尋找Class, 替換代碼...等)麻掸。此步驟會(huì)將 Clang Attr 轉(zhuǎn)換成 AST 上的 AttributeList酥夭,能在clang插件上透過(guò) Decl::getAttr<T>
獲取
Clang Attributes:是 Clang 提供的一種源碼注解,方便開(kāi)發(fā)者向編譯器表達(dá)某種要求脊奋,參與控制如 Static Analyzer熬北、Name Mangling、Code Generation 等過(guò)程, 一般以
__attribute__(xxx)
的形式出現(xiàn)在代碼中, Ex:NS_CLASS_AVAILABLE_IOS(9_0)
結(jié)構(gòu)跟其他Compiler的AST相同诚隙。與其他編譯器不同的是 Clang的AST是由C++構(gòu)成類似Class讶隐、Variable的層級(jí)表示,其他的則是以匯編語(yǔ)言編寫(xiě)久又。這代表著AST也能有對(duì)應(yīng)的api巫延,這讓AST操作, 獲取信息都比較容易,甚至還夾帶著地址跟代碼位置地消。
AST Context: 存儲(chǔ)所有AST相關(guān)資訊, 且提供ASTMatcher等遍歷方法
在 Clang的定義中炉峰,節(jié)點(diǎn)主要分成:Type(類型),Decl(聲明)脉执,Stmt(陳述)疼阔,其他的都是這三種的派生。Type具體到某個(gè)語(yǔ)言的類型時(shí)便可以派生出 PointerType(指針類型)半夷、ObjCObjectType(objc對(duì)象類型)婆廊、BuiltinType(內(nèi)置基礎(chǔ)數(shù)據(jù)類型)這些表示。通過(guò)這三者的聯(lián)結(jié)玻熙、重復(fù)或選擇(alternative)就能構(gòu)成一門(mén)編程語(yǔ)言否彩。舉個(gè)例子,下圖的一段代碼:詳細(xì)可以看了解 Clang AST
FunctionDecl嗦随、ParmVarDecl 都是基于 Decl派生的類列荔,CompoundStmt、ReturnStmt枚尼、DeclStmt都是基于 Stmt派生的類贴浙。)
從上圖中可以看到:
- 一個(gè)FunctionDecl(函數(shù)的實(shí)現(xiàn))由一個(gè) ParmVarDecl聯(lián)結(jié) CompoundStmt組成。
- 函數(shù)的 CompoundStmt 由 DeclStmt和 ReturnStmt聯(lián)結(jié)組成署恍。
- 還可以發(fā)現(xiàn)這段代碼的ParmVarDecl由 BuiltinType 和一個(gè)標(biāo)識(shí)符字面量聯(lián)結(jié)組成崎溃。
很明顯一門(mén)編程語(yǔ)言中還有很多其他形態(tài),我們都可以用這種方式描述出來(lái)盯质。所以說(shuō)從抽象的角度看袁串,擁有無(wú)限種形態(tài)的編程語(yǔ)言便可以用有限的形式來(lái)表示概而。
clang -fsyntax-only -Xclang -ast-dump main.m
......
`-FunctionDecl 0x7fcbb9947b20 <main.m:4:1, line:10:1> line:4:5 main 'int ()'
`-CompoundStmt 0x7fcbb9947fc8 <col:11, line:10:1>
|-DeclStmt 0x7fcbb9947c50 <line:5:5, col:28>
| `-VarDecl 0x7fcbb9947bd0 <col:5, line:2:21> line:5:9 used eight 'int' cinit
| `-IntegerLiteral 0x7fcbb9947c30 <line:2:21> 'int' 8
|-DeclStmt 0x7fcbb9947d00 <line:6:5, col:16>
| `-VarDecl 0x7fcbb9947c80 <col:5, col:15> col:9 used six 'int' cinit
| `-IntegerLiteral 0x7fcbb9947ce0 <col:15> 'int' 6
|-DeclStmt 0x7fcbb9947e20 <line:7:5, col:27>
| `-VarDecl 0x7fcbb9947d30 <col:5, col:24> col:9 used rank 'int' cinit
| `-BinaryOperator 0x7fcbb9947e00 <col:16, col:24> 'int' '+'
| |-ImplicitCastExpr 0x7fcbb9947dd0 <col:16> 'int' <LValueToRValue>
| | `-DeclRefExpr 0x7fcbb9947d90 <col:16> 'int' lvalue Var 0x7fcbb9947bd0 'eight' 'int'
| `-ImplicitCastExpr 0x7fcbb9947de8 <col:24> 'int' <LValueToRValue>
| `-DeclRefExpr 0x7fcbb9947db0 <col:24> 'int' lvalue Var 0x7fcbb9947c80 'six' 'int'
|-CallExpr 0x7fcbb9947f20 <line:8:5, col:23> 'int'
| |-ImplicitCastExpr 0x7fcbb9947f08 <col:5> 'int (*)(const char *, ...)' <FunctionToPointerDecay>
| | `-DeclRefExpr 0x7fcbb9947e38 <col:5> 'int (const char *, ...)' Function 0x7fcbb9932e70 'printf' 'int (const char *, ...)'
| |-ImplicitCastExpr 0x7fcbb9947f68 <col:12> 'const char *' <NoOp>
| | `-ImplicitCastExpr 0x7fcbb9947f50 <col:12> 'char *' <ArrayToPointerDecay>
| | `-StringLiteral 0x7fcbb9947e98 <col:12> 'char [4]' lvalue "%d\n"
| `-ImplicitCastExpr 0x7fcbb9947f80 <col:19> 'int' <LValueToRValue>
| `-DeclRefExpr 0x7fcbb9947eb8 <col:19> 'int' lvalue Var 0x7fcbb9947d30 'rank' 'int'
`-ReturnStmt 0x7fcbb9947fb8 <line:9:5, col:12>
`-IntegerLiteral 0x7fcbb9947f98 <col:12> 'int' 0
在抽象語(yǔ)法樹(shù)中的每個(gè)節(jié)點(diǎn)都標(biāo)注了其對(duì)應(yīng)源碼中的位置,如果產(chǎn)生了什么問(wèn)題囱修,clang 可以定位到問(wèn)題所在處的源碼位置赎瑰。
語(yǔ)法樹(shù)直觀圖:
## 3.1 靜態(tài)分析 (Static Analyzer)
一旦編譯器把源碼生成了抽象語(yǔ)法樹(shù),編譯器可以對(duì)這棵樹(shù)做分析處理破镰,以找出代碼中的錯(cuò)誤餐曼,比如類型檢查:即檢查程序中是否有類型錯(cuò)誤。例如:如果代碼中給某個(gè)對(duì)象發(fā)送了一個(gè)消息鲜漩,編譯器會(huì)檢查這個(gè)對(duì)象是否實(shí)現(xiàn)了這個(gè)消息(函數(shù)源譬、方法)。此外孕似,clang 對(duì)整個(gè)程序還做了其它更高級(jí)的一些分析踩娘,以確保程序沒(méi)有錯(cuò)誤。
OVERVIEW: Clang Static Analyzer Checkers List
USAGE: -analyzer-checker <CHECKER or PACKAGE,...>
CHECKERS:
alpha.clone.CloneChecker Reports similar pieces of code.
alpha.core.BoolAssignment Warn about assigning non-{0,1} values to Boolean variables
alpha.core.CallAndMessageUnInitRefArg Check for logical errors for function calls and Objective-C message expressions (e.g., uninitialized arguments, null function pointers, and pointer to undefined variables)
alpha.core.CastSize Check when casting a malloc'ed type T, whether the size is a multiple of the size of T
...
scan-build 是用于靜態(tài)分析代碼的工具鳞青,它包含在 clang 的源碼包中霸饲。使用scan-build可以從命令行運(yùn)行分析器,比如:
roten@localhost scan-build % ./scan-build --use-analyzer=xcode xcodebuild -project Demo123.xcodeproj // 需要設(shè)置 --use-analyzer指定 clang 的路徑
scan-build: Using '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang' for static analysis
Build settings from command line:
CLANG_ANALYZER_EXEC = /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang
CLANG_ANALYZER_OTHER_FLAGS =
CLANG_ANALYZER_OUTPUT = plist-html
CLANG_ANALYZER_OUTPUT_DIR = /var/folders/1r/n7kwlmgn74l3pvvht646f6fm0000gp/T/scan-build-2020-09-01-140523-22105-1
RUN_CLANG_STATIC_ANALYZER = YES
note: Using new build system
note: Planning build
note: Constructing build description
Build system information
....
** BUILD SUCCEEDED **
scan-build: Removing directory '/var/folders/1r/n7kwlmgn74l3pvvht646f6fm0000gp/T/scan-build-2020-09-01-140523-22105-1' because it contains no reports.
scan-build: No bugs found.
關(guān)于靜態(tài)分析更多可以查看 :Clang 靜態(tài)分析器
clang 完成代碼的標(biāo)記臂拓,解析和分析后厚脉,接著就會(huì)生成 LLVM 代碼。
## 4. IR代碼生成 (CodeGen)
CodeGen負(fù)責(zé)將語(yǔ)法樹(shù)從頂至下遍歷胶惰,翻譯成LLVM IR傻工,LLVM IR是Frontend的輸出,也是LLVM Backerend的輸入孵滞,橋接前后端中捆。
clang -S -fobjc-arc -emit-llvm main.m -o main.ll
; ModuleID = 'main.m'
source_filename = "main.m"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.15.0"
@.str = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1
; Function Attrs: noinline optnone ssp uwtable
define i32 @main() #0 {
%1 = alloca i32, align 4
%2 = alloca i32, align 4
%3 = alloca i32, align 4
%4 = alloca i32, align 4
store i32 0, i32* %1, align 4
store i32 8, i32* %2, align 4
store i32 6, i32* %3, align 4
%5 = load i32, i32* %2, align 4
%6 = load i32, i32* %3, align 4
%7 = add nsw i32 %5, %6
store i32 %7, i32* %4, align 4
%8 = load i32, i32* %4, align 4
%9 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i32 0, i32 0), i32 %8)
ret i32 0
}
declare i32 @printf(i8*, ...) #1
attributes #0 = { noinline optnone ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "darwin-stkchk-strong-link" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "probe-stack"="___chkstk_darwin" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "darwin-stkchk-strong-link" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "probe-stack"="___chkstk_darwin" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7}
!llvm.ident = !{!8}
!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 10, i32 15]}
!1 = !{i32 1, !"Objective-C Version", i32 2}
!2 = !{i32 1, !"Objective-C Image Info Version", i32 0}
!3 = !{i32 1, !"Objective-C Image Info Section", !"__DATA,__objc_imageinfo,regular,no_dead_strip"}
!4 = !{i32 4, !"Objective-C Garbage Collection", i32 0}
!5 = !{i32 1, !"Objective-C Class Properties", i32 64}
!6 = !{i32 1, !"wchar_size", i32 4}
!7 = !{i32 7, !"PIC Level", i32 2}
!8 = !{!"Apple clang version 11.0.0 (clang-1100.0.33.12)"}
## 4.1 中間代碼優(yōu)化 (Optimize)
可以在中間代碼層次去做一些優(yōu)化工作,我們?cè)赬code的編譯設(shè)置里面也可以設(shè)置優(yōu)化級(jí)別-O1
,-O3
,-Os
對(duì)應(yīng)著不同的入?yún)⒎蝗模斜热珙愃扑来a清理泄伪,內(nèi)聯(lián)化,表達(dá)式重組匿级,循環(huán)變量移動(dòng)這樣的 Pass蟋滴。Pass就是LLVM系統(tǒng)轉(zhuǎn)化和優(yōu)化的工作的一個(gè)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)做一些工作痘绎,這些工作加起來(lái)就構(gòu)成了LLVM整個(gè)系統(tǒng)的優(yōu)化和轉(zhuǎn)化津函。
我們還可以去寫(xiě)一些自己的Pass,官方有比較完整的 Pass 教程: Writing an LLVM Pass — LLVM 5 documentation孤页。
## 5. 生成字節(jié)碼 (LLVM Bitcode)
我們?cè)赬code7中默認(rèn)生成bitcode就是這種的中間形式存在尔苦,開(kāi)啟了bitcode,那么蘋(píng)果后臺(tái)拿到的就是這種中間代碼,蘋(píng)果可以對(duì)bitcode做一個(gè)進(jìn)一步的優(yōu)化允坚,如果有新的后端架構(gòu)魂那,仍然可以用這份bitcode去生成。
Bitcode是編譯后的程序的中間表現(xiàn)屋讶,包含Bitcode并上傳到App Store Connect的Apps會(huì)在App Store上編譯和鏈接冰寻。包含Bitcode可以在不提交新版本App的情況下,允許Apple在將來(lái)的時(shí)候再次優(yōu)化你的App 二進(jìn)制文件皿渗。
對(duì)于iOS Apps,Enable bitcode 默認(rèn)為YES轻腺,是可選的(可以改為NO)乐疆。對(duì)于WatchOS和tvOS,bitcode是強(qiáng)制的贬养。如果你的App支持bitcode挤土,App Bundle(項(xiàng)目中所有的target)中的所有的Apps和frameworks都需要包含Bitcode。
clang -emit-llvm -c main.m -o main.bc
## 6. 生成相關(guān)匯編
clang -S -fobjc-arc main.m -o main.s
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 15 sdk_version 10, 15
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq $32, %rsp
movl $0, -4(%rbp)
movl $8, -8(%rbp)
movl $6, -12(%rbp)
movl -8(%rbp), %eax
addl -12(%rbp), %eax
movl %eax, -16(%rbp)
movl -16(%rbp), %esi
leaq L_.str(%rip), %rdi
movb $0, %al
callq _printf
xorl %esi, %esi
movl %eax, -20(%rbp) ## 4-byte Spill
movl %esi, %eax
addq $32, %rsp
popq %rbp
retq
.cfi_endproc
## -- End function
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "%d\n"
.section __DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
.long 0
.long 64
.subsections_via_symbols
## 7. 生成目標(biāo)文件
編譯階段完成误算,接下來(lái)就是匯編階段仰美。匯編器是將匯編代碼轉(zhuǎn)變成機(jī)器可以執(zhí)行的指令,每一個(gè)匯編語(yǔ)句幾乎都對(duì)應(yīng)一條機(jī)器指令儿礼。所以匯編器的匯編過(guò)程相對(duì)于編譯器來(lái)講比較簡(jiǎn)單咖杂,它沒(méi)有復(fù)雜的語(yǔ)法,也沒(méi)有語(yǔ)義蚊夫,也不需要做指令優(yōu)化诉字,只是根據(jù)匯編指令和機(jī)器指令的對(duì)照表一一翻譯就可以了。
這些文件以 .o 結(jié)尾知纷。如果用 Xcode 構(gòu)建應(yīng)用程序壤圃,可以在工程的 derived data 目錄中,Objects-normal 文件夾下找到這些文件琅轧。
clang -fmodules -c main.m -o main.o
## 8. 生成可執(zhí)行文件
clang main.o -o main // 生成可執(zhí)行文件
./main //執(zhí)行 可執(zhí)行文件 代碼
打印結(jié)果:14
## 記錄一個(gè)Clang命令報(bào)錯(cuò):
/usr/local/include/stdint.h:59:11: error: #include nested too deeply
# include <stdint.h>
^
/usr/local/include/stdint.h:82:11: error: #include nested too deeply
# include <inttypes.h>
^
...
解決方案:
1伍绳、可能是xcode-select 沒(méi)裝,于是執(zhí)行xcode-select --install 進(jìn)行工具安裝乍桂。
2冲杀、如果問(wèn)題還在。brew doctor一下就行了
mkdir /tmp/includes
brew doctor 2>&1 | grep "/usr/local/include" | awk '{$1=$1;print}' | xargs -I _ mv _ /tmp/includes
參考鏈接:https://github.com/SOHU-Co/kafka-node/issues/881
# 小結(jié):iOS從編碼到打包
- 首先我們編寫(xiě)完成代碼之后模蜡,會(huì)通過(guò)LLVM編譯器預(yù)處理我們的代碼漠趁,比如將宏放在指定的位置
- 預(yù)處理結(jié)束之后,LLVM會(huì)對(duì)代碼進(jìn)行詞法分析和語(yǔ)法分析忍疾,生成AST闯传。AST是抽象語(yǔ)法樹(shù),主要用來(lái)進(jìn)行快速遍歷卤妒,實(shí)現(xiàn)靜態(tài)代碼檢查的功能甥绿。
- AST會(huì)生成IR字币,IR是一種更加接近機(jī)器碼的語(yǔ)言,通過(guò)IR可以生成不同平臺(tái)的機(jī)器碼共缕。對(duì)于iOS平臺(tái)洗出,IR生成的可執(zhí)行文件就是Mach-O.
- 然后通過(guò)鏈接器將符號(hào)和地址綁定在一起,并且將項(xiàng)目中的多個(gè)Mach-O文件(目標(biāo)文件)合并成一個(gè)Mach-O文件(可執(zhí)行文件)图谷。(關(guān)于Mach-O翩活、鏈接下一節(jié)講)
- 將可執(zhí)行文件與資源文件、storyboard便贵、xib等打包菠镇,最后通過(guò)簽名等操作生成.app文件,然后對(duì).app文件進(jìn)行壓縮就生成了我們可以安裝的ipa包承璃。
- 當(dāng)然利耍,ipa包的安裝途徑有兩種:
- 通過(guò)開(kāi)發(fā)者賬號(hào)上傳到App Store,然后在App Store上下載安裝盔粹。
- 通過(guò)PP助手隘梨、iFunBox、Xcode等工具來(lái)安裝
# 參考鏈接
-
關(guān)于LLVM舷嗡,這些東西你必須知道! 本篇文章大部分來(lái)自此文章轴猎。按照自己的理解記憶方式刪減、添加了一些知識(shí)咬崔。原文中還補(bǔ)充有:
- Clang的三大基礎(chǔ)設(shè)施(libclang税稼、LibTooling、ClangPlugin)的應(yīng)用垮斯、代碼示例
- 動(dòng)手寫(xiě)Pass的代碼示例
- 深入剖析 iOS 編譯 Clang / LLVM — 戴銘
- 《程序員的自我修養(yǎng)》
- (Xcode) 編譯器小白筆記 - LLVM前端Clang