引言
維基百科:
編譯語言(英語:Compiled language)是一種以編譯器來實現(xiàn)的編程語言这弧。它不像解釋型語言一樣,由解釋器將代碼一句一句運行报破,而是以編譯器绷落,先將代碼編譯為機(jī)器碼红碑,再加以運行。理論上撑螺,任何編程語言都可以是編譯式吏砂,或直譯式的。它們之間的區(qū)別拳魁,僅與程序的應(yīng)用有關(guān)惶桐。
前言
iOS開發(fā)使用Object-C和Swift編譯語言姚糊,兩者都需要通過編譯器(Clang + LLVM)把代碼編譯器生成機(jī)器碼,機(jī)器碼可以直接在CPU上執(zhí)行授舟。接下來詳細(xì)介紹一下這兩種語言的優(yōu)缺點救恨,可以更深入的理解為什么移動端開發(fā)會采用編譯語言。
編譯語言/直譯式語言優(yōu)缺點比較:
編譯/直譯式 | 優(yōu)點 | 缺點 |
---|---|---|
編譯語言 | 運行速度快(被預(yù)先編譯成機(jī)器碼释树,可以直接運行) | 開發(fā)肠槽、調(diào)試比較長(程序開發(fā)速度,以及除錯時間時間較長) |
直譯式語言 | 開發(fā)奢啥、調(diào)試比較短(程序開發(fā)速度秸仙,以及除錯時間時間較長。應(yīng)用程序不能脫離其解釋器桩盲,但這種方式比較靈活寂纪,可以動態(tài)地調(diào)整、修改應(yīng)用程序) | 運行速度慢(源代碼一邊由相應(yīng)語言的解釋器翻譯”成目標(biāo)代碼(機(jī)器語言),一邊執(zhí)行) |
編譯語言: 像C++捞蛋、Objective-C(Swift)孝冒、C、Java等都是編譯語言拟杉,必須通過編譯器生成機(jī)器碼迈倍,機(jī)器碼可以直接在CPU上執(zhí)行,所以你執(zhí)行效率較高捣域。而每次運行都需要編譯器生成機(jī)器碼再運行啼染,故編寫、調(diào)試焕梅、排錯比較繁瑣迹鹅。
直譯式語言:像JavaScript、Python贞言、PHP等都是直譯式語言斜棚,不需要經(jīng)過編譯的過程,而是在執(zhí)行的時候通過一個中間的解釋器將代碼解釋為CPU可以執(zhí)行的代碼该窗。所以弟蚀,較編譯語言來說,直譯式語言效率低一些酗失,但是編寫比較靈活义钉。
總之,由于移動端(iOS规肴、Android)設(shè)備性能限制的情況下捶闸,采用編譯語言進(jìn)行開發(fā)是一種比較好的方式
一、iOS編譯器
在講解編譯過程之前拖刃,需要了解蘋果公司采用的編譯器删壮,編譯器采用哪種方式進(jìn)行編譯,才能更深入的理解編譯底層的整個過程兑牡。
1.Xcode編譯器發(fā)展史
Xcode3 以前: GCC央碟;
Xcode3: 增加LLVM,GCC(前端) + LLVM(后端)均函;
Xcode4.2: 出現(xiàn)Clang - LLVM 3.0成為默認(rèn)編譯器亿虽;
Xcode4.6: LLVM 升級到4.2版本;
Xcode5: GCC被廢棄边酒,新的編譯器是LLVM 5.0经柴,從GCC過渡到Clang-LLVM的時代正式完成
-
1.1.為什么蘋果的Xcode會使用Clang+LLVM取代GCC狸窘?
- GCC最初是作為CNU(GNU是“GNU is Not Unix”)操作系統(tǒng)的編譯器編寫的墩朦,是一套由 GNU 開發(fā)的編程語言編譯器,不屬于蘋果維護(hù)也不能完全控制開發(fā)進(jìn)程翻擒,Apple為Objective-C增加許多新特性氓涣,但是GCC開發(fā)者對這些支持卻不友好牛哺;Apple需要做模塊化,GCC開發(fā)者卻拖著遲遲不實現(xiàn)劳吠。
-
1.2.GCC被取代的歷史必然
- 隨著Apple對其IDE(也就是Xcode)性能的要求越來越高,蘋果公司需要找到一個可以控制的編譯器引润。而在在科技的歷史長河中,LLVM項目于2000年在伊利諾伊大學(xué)厄巴納 - 香檳分校開始痒玩,由Vikram Adve和Chris Lattner領(lǐng)導(dǎo)淳附。 LLVM最初是作為研究基礎(chǔ)設(shè)施開發(fā)的,用于研究靜態(tài)和動態(tài)編程語言的動態(tài)編譯技術(shù)蠢古。 LLVM是根據(jù)伊利諾伊大學(xué)/ NCSA開源許可證發(fā)布的奴曙,是一個許可的免費軟件許可證。
-
1.3.GCC被取代
- 2005年草讶,Apple Inc.聘請了Lattner并組建了一個團(tuán)隊洽糟,致力于LLVM系統(tǒng),以便在Apple的開發(fā)系統(tǒng)中實現(xiàn)各種用途堕战。 LLVM是Apple最新的macOS和iOS開發(fā)工具中不可或缺的一部分坤溃。
2.為什么選擇LLVM編譯器
2.1編譯器
2.1.1經(jīng)典編譯器設(shè)計簡介
傳統(tǒng)靜態(tài)編譯器(如大多數(shù)C編譯器)最流行的設(shè)計是三階段設(shè)計,其主要組件是前端嘱丢,優(yōu)化器和后端薪介。 前端解析源代碼,檢查它是否有錯誤越驻,并構(gòu)建一個特定于語言的抽象語法樹(AST)來表示輸入代碼昭灵。 AST可選地轉(zhuǎn)換為新的表示以進(jìn)行優(yōu)化,優(yōu)化器和后端在代碼上運行伐谈。
2.1.2GCC編譯器設(shè)計簡介(同樣采用三相設(shè)計)
GCC編譯器的三相設(shè)計中最成功的實現(xiàn)是對Java烂完、 .Net語言的編譯解析。
2.1.3 LLVM編譯器設(shè)計簡介(實現(xiàn)三相設(shè)計)
在基于LLVM的編譯器中诵棵,前端負(fù)責(zé)解析抠蚣,驗證和診斷輸入代碼中的錯誤,然后將解析的代碼轉(zhuǎn)換為LLVM IR(通常情況履澳。但是也有例外嘶窄,通過構(gòu)建AST然后將AST轉(zhuǎn)換為LLVM IR)。該IR可選地通過一系列改進(jìn)代碼的分析和優(yōu)化過程提供距贷,然后被發(fā)送到代碼生成器以生成本機(jī)機(jī)器代碼柄冲,如圖下圖所示。
為什么要使用三相設(shè)計忠蝗?優(yōu)勢在哪现横?
首先解決了一個很大的問題:假如有N種語言(C、OC、C++戒祠、Swift...)的前端骇两,同時也有M個架構(gòu)(模擬器、arm64姜盈、x86...)的Target低千,是否就需要 N × M 個編譯器?
三相架構(gòu)的價值就體現(xiàn)出來了馏颂,通過共享優(yōu)化器的中轉(zhuǎn)示血,很好的解決了這個問題。
假如你需要增加一種語言救拉,只需要增加一種前端矾芙;假如你需要增加一種處理器架構(gòu),也只需要增加一種后端近上,而其他的地方都不需要改動剔宪。這復(fù)用思想很牛逼吧。
2.2 LLVM編譯器的組成
LLVM項目是模塊化和可重用的編譯器和工具鏈技術(shù)的集合壹无。LLVM主要的子項目有一下幾個:
1.LLVM核心庫:
LLVM提供一個獨立的鏈接代碼優(yōu)化器為許多流行CPU(以及一些不太常見的CPU)的代碼生成支持葱绒。這些庫是圍繞一個指定良好的代碼表示構(gòu)建的,稱為LLVM中間表示(“LLVM IR”)斗锭。LLVM還可以充當(dāng)JIT編譯器 - 它支持x86 / x86_64和PPC / PPC64程序集生成地淀,并具有針對編譯速度的快速代碼優(yōu)化。岖是。
2.LLVM IR 生成器Clang:
Clang是一個“LLVM原生”C / C ++ / Objective-C編譯器帮毁,旨在提供驚人的快速編譯(例如,在調(diào)試配置中編譯Objective-C代碼時比GCC快3倍)豺撑,非常有用的錯誤和警告消息以及提供構(gòu)建優(yōu)秀源代碼工具的平臺烈疚。
3.LLDB項目:
LLDB項目以LLVM和Clang提供的庫為基礎(chǔ),提供了一個出色的本機(jī)調(diào)試器聪轿。它使用Clang AST和表達(dá)式解析器爷肝,LLVM JIT,LLVM反匯編程序等陆错,以便提供“正常工作”的體驗灯抛。在加載符號時,它也比GDB快速且內(nèi)存效率更高音瓷。
4.libc ++和libc++:
libc ++和libc++ ABI項目提供了C ++標(biāo)準(zhǔn)庫的標(biāo)準(zhǔn)符合性和高性能實現(xiàn)对嚼,包括對C ++ 11的完全支持。
5.lld項目:
lld項目旨在成為clang / llvm的內(nèi)置鏈接器绳慎。目前纵竖,clang必須調(diào)用系統(tǒng)鏈接器來生成可執(zhí)行文件漠烧。
總之,LLVM是Apple主導(dǎo)的開源框架磨确,并提供一套使用于Apple平臺的LLVM編譯器沽甥,同時提供優(yōu)秀的性能声邦,所以Apple采用LLVM的方式進(jìn)行編譯
3. Clang + LLVM 的編譯簡單過程
3.1.Clang + LLVM 編譯過程
LLVM采用三相設(shè)計乏奥,前端Clang負(fù)責(zé)解析,驗證和診斷輸入代碼中的錯誤亥曹,然后將解析的代碼轉(zhuǎn)換為LLVM IR邓了,后端LLVM編譯把IR通過一系列改進(jìn)代碼的分析和優(yōu)化過程提供,然后被發(fā)送到代碼生成器以生成本機(jī)機(jī)器代碼媳瞪。
簡單的流程如下圖:
3.2 Clang 編譯前端
編譯器前端的任務(wù)是進(jìn)行:語法分析骗炉,語義分析,生成中間代碼(intermediate representation )蛇受。在這個過程中句葵,會進(jìn)行類型檢查,如果發(fā)現(xiàn)錯誤或者警告會標(biāo)注出來在哪一行兢仰。
3.3 LLVM 編譯后端
編譯器后端會進(jìn)行機(jī)器無關(guān)的代碼優(yōu)化乍丈,生成機(jī)器語言,并且進(jìn)行機(jī)器相關(guān)的代碼優(yōu)化把将。iOS的編譯過程轻专,后端的處理如下:
LVVM優(yōu)化器會進(jìn)行BitCode的生成,鏈接期優(yōu)化等等察蹲。
LLVM機(jī)器碼生成器會針對不同的架構(gòu)请垛,比如arm64等生成不同的機(jī)器碼。
二洽议、LLVM 編譯詳細(xì)過程原理
1.簡述LLVM的使用場景
通過上方的講解宗收,可能對LLVM有一個簡單的了解,但是感覺LLVM是底層編譯器的內(nèi)容亚兄,和開發(fā)沒有太大的關(guān)系镜雨,在使用過程中基本上用不到,感覺遙不可及儿捧,其實LLVM在項目的使用過程中一直都在使用荚坞,只是沒有發(fā)現(xiàn)。
Clang是LLVM的一個前端菲盾,在Xcode編譯iOS項目的時候颓影,都是使用的LLVM,其實在編寫代碼以及調(diào)試的時候都在接觸LLVM提供的功能懒鉴,例如:代碼的亮度(Clang)诡挂、實時代碼檢查(Clang)碎浇、代碼提示(Clang)、debug斷點調(diào)試(LLDB)璃俗。
2.項目編譯過程簡介
下面來簡單的講講整個 iOS 項目的編譯過程,其中可能會有一些疑問奴璃,先保留著,后面會詳細(xì)解釋
我們的項目是一個 target城豁,一個編譯目標(biāo)苟穆,它擁有自己的文件和編譯規(guī)則,在我們的項目中可以存在多個子項目唱星,這在編譯的時候就導(dǎo)致了使用了 Cocoapods 或者擁有多個 target 的項目會先編譯依賴庫雳旅。這些庫都和我們的項目編譯流程一致。Cocoapods 的原理解釋將在文章后面一部分進(jìn)行解釋间聊。
iOS 項目的編譯過程
1.寫入輔助文件:將項目的文件結(jié)構(gòu)對應(yīng)表攒盈、將要執(zhí)行的腳本、項目依賴庫的文件結(jié)構(gòu)對應(yīng)表寫成文件哎榴,方便后面使用型豁;并且創(chuàng)建一個 .app 包,后面編譯后的文件都會被放入包中尚蝌;
2.運行預(yù)設(shè)腳本:Cocoapods 會預(yù)設(shè)一些腳本迎变,當(dāng)然你也可以自己預(yù)設(shè)一些腳本來運行。這些腳本都在 Build Phases 中可以看到驼壶;
3.編譯文件:針對每一個文件進(jìn)行編譯氏豌,生成可執(zhí)行文件 Mach-O,這過程 LLVM 的完整流程热凹,前端泵喘、優(yōu)化器、后端般妙;
4.鏈接文件:將項目中的多個可執(zhí)行文件合并成一個文件纪铺;
5.拷貝資源文件:將項目中的資源文件拷貝到目標(biāo)包;
6.編譯 storyboard 文件:storyboard 文件也是會被編譯的碟渺;
7.鏈接 storyboard 文件:將編譯后的 storyboard 文件鏈接成一個文件鲜锚;
8.編譯 Asset 文件:我們的圖片如果使用 Assets.xcassets 來管理圖片,那么這些圖片將會被編譯成機(jī)器碼苫拍,除了 icon 和 launchImage芜繁;
9.運行 Cocoapods 腳本:將在編譯項目之前已經(jīng)編譯好的依賴庫和相關(guān)資源拷貝到包中。
10.生成 .app 包
11.將 Swift 標(biāo)準(zhǔn)庫拷貝到包中
12.對包進(jìn)行簽名
13.完成打包
簡單的Demo的Build過程圖和App 內(nèi)部的結(jié)構(gòu)在上述流程中:2 - 9 步驟的數(shù)量和順序并不固定绒极,這個過程可以在 Build Phases 中指定骏令。Phases:階段、步驟
垄提。這個 Tab 的意思就是編譯步驟榔袋。其實不僅我們的整個編譯步驟和順序可以被設(shè)定周拐,包括編譯過程中的編譯規(guī)則(Build Rules)和具體步驟的參數(shù)(Build Settings),在對應(yīng)的 Tab 都可以看到凰兑。關(guān)于整個編譯流程的日志和設(shè)定妥粟,可以查看這篇文章:Build 過程,跟著它的步驟來查看自己的項目將有助于你理解整個編譯流程吏够。后面也會詳細(xì)講解這些內(nèi)容勾给。
3.文件編譯過程
3.1. 預(yù)處理
- 預(yù)處理顧名思義是預(yù)先處理,那預(yù)處理都做了哪些事情呢?內(nèi)容如下稿饰。
- (1) import 頭文件替換
代碼中會有很多 #import 宏锦秒,預(yù)處理的第一步就是將 import 引入的文件代碼放入對應(yīng)文件露泊。 - (2) macro 宏展開
帶參數(shù)宏和不帶參數(shù)宏 - (3)處理其他的預(yù)編譯指令(其實預(yù)編譯過程也是出了預(yù)編譯指令的過程)
- (1) import 頭文件替換
條件編譯語句也是在預(yù)處理階段完成喉镰,并且條件編譯只允許編譯源程序中滿足條件的程序段,使生成的目標(biāo)程序較短惭笑,從而減少了內(nèi)存的開銷并提高了程序的效率,如以下代碼就只會保留一個return語句:
#if DEBUG
return YES;
#else
return NO;
#endif
-
(4)總之
簡單來說侣姆,“#”這個符號是編譯器預(yù)處理的標(biāo)志
// Clang 方法: 在終端執(zhí)行一下
$clang -E main.m
// main.m
#import <Foundation/Foundation.h>
#define aa 10
int main(){
NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;
NSLog(@"------%@--%d--",[obj1 class],aa);
}
// 編譯后的代碼
int main(){
NSObject *obj = [[NSObject alloc] init];
id __attribute__((objc_ownership(weak))) obj1 = obj;
NSLog(@"------%@--%d--",[obj1 class],10);
}
3.2. Lexical Analysis - 詞法分析(輸出token流)
使用 clang 命令 clang -Xclang -dump-tokens main.m
轉(zhuǎn)化后的代碼如下(去掉了#import <Foundation/Foundation.h>的內(nèi)容):
- 詞法分析,只需要將源代碼以字符文本的形式轉(zhuǎn)化成Token流的形式沉噩,不涉及交驗語義捺宗,不需要遞歸,是線性的川蒙。
什么是token流呢蚜厉?可以這么理解:就是有"類型",有"值"的一些小單元畜眨。
詞法分析其實是編譯器開始工作真正意義上的第一個步驟昼牛,其所做的工作主要為將輸入的代碼轉(zhuǎn)換為一系列符合特定語言的詞法單元,這些詞法單元類型包括了關(guān)鍵字康聂,操作符贰健,變量等等。
可以通過下發(fā)被編譯過的代碼對應(yīng)main.m文件恬汁,把所有的內(nèi)容都一一對應(yīng)起來伶椿。
// 編譯前
int main(){
NSObject *obj = [[NSObject alloc] init];
id __attribute__((objc_ownership(weak))) obj1 = obj;
NSLog(@"------%@--%d--",[obj1 class],10);
}
//編譯后
int 'int' [StartOfLine] Loc=<main3.m:11:1>
identifier 'main' [LeadingSpace] Loc=<main3.m:11:5>
l_paren '(' Loc=<main3.m:11:9>
r_paren ')' Loc=<main3.m:11:10>
l_brace '{' Loc=<main3.m:11:11>
identifier 'NSObject' [StartOfLine] [LeadingSpace] Loc=<main3.m:13:5>
star '*' [LeadingSpace] Loc=<main3.m:13:14>
identifier 'obj' Loc=<main3.m:13:15>
equal '=' [LeadingSpace] Loc=<main3.m:13:19>
l_square '[' [LeadingSpace] Loc=<main3.m:13:21>
l_square '[' Loc=<main3.m:13:22>
identifier 'NSObject' Loc=<main3.m:13:23>
identifier 'alloc' [LeadingSpace] Loc=<main3.m:13:32>
r_square ']' Loc=<main3.m:13:37>
identifier 'init' [LeadingSpace] Loc=<main3.m:13:39>
r_square ']' Loc=<main3.m:13:43>
semi ';' Loc=<main3.m:13:44>
identifier 'id' [StartOfLine] [LeadingSpace] Loc=<main3.m:14:5>
__attribute '__attribute__' [LeadingSpace] Loc=<main3.m:14:8 <Spelling=<built-in>:309:16>>
l_paren '(' Loc=<main3.m:14:8 <Spelling=<built-in>:309:29>>
l_paren '(' Loc=<main3.m:14:8 <Spelling=<built-in>:309:30>>
identifier 'objc_ownership' Loc=<main3.m:14:8 <Spelling=<built-in>:309:31>>
l_paren '(' Loc=<main3.m:14:8 <Spelling=<built-in>:309:45>>
identifier 'weak' Loc=<main3.m:14:8 <Spelling=<built-in>:309:46>>
r_paren ')' Loc=<main3.m:14:8 <Spelling=<built-in>:309:50>>
r_paren ')' Loc=<main3.m:14:8 <Spelling=<built-in>:309:51>>
r_paren ')' Loc=<main3.m:14:8 <Spelling=<built-in>:309:52>>
identifier 'obj1' [LeadingSpace] Loc=<main3.m:14:15>
equal '=' [LeadingSpace] Loc=<main3.m:14:20>
identifier 'obj' [LeadingSpace] Loc=<main3.m:14:22>
semi ';' Loc=<main3.m:14:25>
identifier 'NSLog' [StartOfLine] [LeadingSpace] Loc=<main3.m:15:5>
l_paren '(' Loc=<main3.m:15:10>
at '@' Loc=<main3.m:15:11>
string_literal '"------%@--%d--"' Loc=<main3.m:15:12>
comma ',' Loc=<main3.m:15:28>
l_square '[' Loc=<main3.m:15:29>
identifier 'obj1' Loc=<main3.m:15:30>
identifier 'class' [LeadingSpace] Loc=<main3.m:15:35>
r_square ']' Loc=<main3.m:15:40>
comma ',' Loc=<main3.m:15:41>
numeric_constant '10' Loc=<main3.m:15:42 <Spelling=main3.m:10:12>>
r_paren ')' Loc=<main3.m:15:44>
semi ';' Loc=<main3.m:15:45>
r_brace '}' [StartOfLine] Loc=<main3.m:17:1>
eof '' Loc=<main3.m:17:2>
這里,每一個符號都會標(biāo)記出來其位置氓侧,這個位置是宏展開之前的位置脊另,這樣后面如果發(fā)現(xiàn)報錯,就可以正確的提示錯誤位置了约巷。
3.3.Semantic Analysis - 語法分析(輸出(AST)抽象語法樹)
對代碼進(jìn)行標(biāo)記并不是Clang最終的目的偎痛,而是一個Clang的一個過程,其實標(biāo)記代碼為了讓代碼更便于轉(zhuǎn)化成機(jī)器語言载庭,標(biāo)記代碼轉(zhuǎn)化成抽象語法樹(abstract syntax tree – AST)是一個必經(jīng)之路看彼。
3.3.1 AST
使用 clang 命令 clang -Xclang -ast-dump -fsyntax-only main.m
廊佩,轉(zhuǎn)化后的樹如下
-FunctionDecl 0x7fbbea20f538 <main3.m:11:1, line:17:1> line:11:5 main 'int ()'
`-CompoundStmt 0x7fbbea20fa40 <col:11, line:17:1>
|-DeclStmt 0x7fbbea20f6b8 <line:13:5, col:44>
| `-VarDecl 0x7fbbea20f5e8 <col:5, col:43> col:15 used obj 'NSObject *' cinit
| `-ObjCMessageExpr 0x7fbbea20f688 <col:21, col:43> 'NSObject *' selector=init
| `-ObjCMessageExpr 0x7fbbea20f658 <col:22, col:37> 'NSObject *' selector=alloc class='NSObject'
|-DeclStmt 0x7fbbea20f830 <line:14:5, col:25>
| `-VarDecl 0x7fbbea20f778 <col:5, col:22> col:15 used obj1 '__weak id':'__weak id' cinit
| `-ImplicitCastExpr 0x7fbbea20f818 <col:22> 'id':'id' <BitCast>
| `-ImplicitCastExpr 0x7fbbea20f800 <col:22> 'NSObject *' <LValueToRValue>
| `-DeclRefExpr 0x7fbbea20f7d8 <col:22> 'NSObject *' lvalue Var 0x7fbbea20f5e8 'obj' 'NSObject *'
`-ExprWithCleanups 0x7fbbea20fa28 <line:15:5, col:44> 'void'
`-CallExpr 0x7fbbea20f9d0 <col:5, col:44> 'void'
|-ImplicitCastExpr 0x7fbbea20f9b8 <col:5> 'void (*)(id, ...)' <FunctionToPointerDecay>
| `-DeclRefExpr 0x7fbbea20f848 <col:5> 'void (id, ...)' Function 0x7fbbe94469e0 'NSLog' 'void (id, ...)'
|-ImplicitCastExpr 0x7fbbea20fa10 <col:11, col:12> 'id':'id' <BitCast>
| `-ObjCStringLiteral 0x7fbbea20f8a8 <col:11, col:12> 'NSString *'
| `-StringLiteral 0x7fbbea20f870 <col:12> 'char [15]' lvalue "------%@--%d--"
|-ObjCMessageExpr 0x7fbbea20f908 <col:29, col:40> 'Class':'Class' selector=class
| `-ImplicitCastExpr 0x7fbbea20f8f0 <col:30> 'id':'id' <LValueToRValue>
| `-DeclRefExpr 0x7fbbea20f8c8 <col:30> '__weak id':'__weak id' lvalue Var 0x7fbbea20f778 'obj1' '__weak id':'__weak id'
`-IntegerLiteral 0x7fbbea20f938 <line:10:12> 'int' 10
這個main方法的抽象樹可以看出來樹頂是FunctionDecl
:方法聲明(Function Declaration)。
這里因為截取了部分代碼靖榕,其實并不是整個樹的樹頂标锄。真正的樹頂描述應(yīng)該是:TranslationUnitDecl。
詳細(xì)的AST語法不多介紹茁计,關(guān)于 AST 的詳細(xì)解釋可以查看:Introduction to the Clang AST料皇。
3.3.2 靜態(tài)分析
- 通過語法樹進(jìn)行代碼靜態(tài)分析,找出非語法性錯誤
- 模擬代碼執(zhí)行路徑星压,分析出control-flow graph(CFG) 【MRC時代會分析出引用計數(shù)的錯誤】
- 預(yù)置了常用Checker(檢查器)
3.4. CodeGen - (Intermediate Representation践剂,簡稱IR)IR中間代碼生成
當(dāng)通過Clang語法解析,代碼沒有出現(xiàn)報錯娜膘,Clang前端就將進(jìn)入最后一步:生成LLVM IR中間代碼逊脯,并將生成的LLVM IR代碼遞交給優(yōu)化器。
使用命令 clang -S -emit-llvm main3.m -o main.ll
生成LLVM 中間代碼LLVM IR
// mian3.m代碼
#import <Foundation/Foundation.h>
#define aa 10
int main(){
NSObject *obj = [[NSObject alloc] init];
NSLog(@"------%@--%d--",[obj class],aa);
}
; ModuleID = 'main3.m'
source_filename = "main3.m"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.13.0"
%0 = type opaque
%struct._class_t = type { %struct._class_t*, %struct._class_t*, %struct._objc_cache*, i8* (i8*, i8*)**, %struct._class_ro_t* }
%struct._objc_cache = type opaque
%struct._class_ro_t = type { i32, i32, i32, i8*, i8*, %struct.__method_list_t*, %struct._objc_protocol_list*, %struct._ivar_list_t*, i8*, %struct._prop_list_t* }
%struct.__method_list_t = type { i32, i32, [0 x %struct._objc_method] }
%struct._objc_method = type { i8*, i8*, i8* }
%struct._objc_protocol_list = type { i64, [0 x %struct._protocol_t*] }
%struct._protocol_t = type { i8*, i8*, %struct._objc_protocol_list*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct._prop_list_t*, i32, i32, i8**, i8*, %struct._prop_list_t* }
%struct._ivar_list_t = type { i32, i32, [0 x %struct._ivar_t] }
%struct._ivar_t = type { i64*, i8*, i8*, i32, i32 }
%struct._prop_list_t = type { i32, i32, [0 x %struct._prop_t] }
%struct._prop_t = type { i8*, i8* }
%struct.__NSConstantString_tag = type { i32*, i32, i8*, i64 }
@"OBJC_CLASS_$_NSObject" = external global %struct._class_t
@"OBJC_CLASSLIST_REFERENCES_$_" = private global %struct._class_t* @"OBJC_CLASS_$_NSObject", section "__DATA,__objc_classrefs,regular,no_dead_strip", align 8
@OBJC_METH_VAR_NAME_ = private unnamed_addr constant [6 x i8] c"alloc\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@OBJC_SELECTOR_REFERENCES_ = private externally_initialized global i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_, i32 0, i32 0), section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
@OBJC_METH_VAR_NAME_.1 = private unnamed_addr constant [5 x i8] c"init\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@OBJC_SELECTOR_REFERENCES_.2 = private externally_initialized global i8* getelementptr inbounds ([5 x i8], [5 x i8]* @OBJC_METH_VAR_NAME_.1, i32 0, i32 0), section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
@__CFConstantStringClassReference = external global [0 x i32]
@.str = private unnamed_addr constant [15 x i8] c"------%@--%d--\00", section "__TEXT,__cstring,cstring_literals", align 1
@_unnamed_cfstring_ = private global %struct.__NSConstantString_tag { i32* getelementptr inbounds ([0 x i32], [0 x i32]* @__CFConstantStringClassReference, i32 0, i32 0), i32 1992, i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str, i32 0, i32 0), i64 14 }, section "__DATA,__cfstring", align 8
@OBJC_METH_VAR_NAME_.3 = private unnamed_addr constant [6 x i8] c"class\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@OBJC_SELECTOR_REFERENCES_.4 = private externally_initialized global i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_.3, i32 0, i32 0), section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
@llvm.compiler.used = appending global [7 x i8*] [i8* bitcast (%struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_" to i8*), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_, i32 0, i32 0), i8* bitcast (i8** @OBJC_SELECTOR_REFERENCES_ to i8*), i8* getelementptr inbounds ([5 x i8], [5 x i8]* @OBJC_METH_VAR_NAME_.1, i32 0, i32 0), i8* bitcast (i8** @OBJC_SELECTOR_REFERENCES_.2 to i8*), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_.3, i32 0, i32 0), i8* bitcast (i8** @OBJC_SELECTOR_REFERENCES_.4 to i8*)], section "llvm.metadata"
; Function Attrs: noinline optnone ssp uwtable
define i32 @main() #0 {
%1 = alloca %0*, align 8
%2 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_", align 8
%3 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_, align 8, !invariant.load !8
%4 = bitcast %struct._class_t* %2 to i8*
%5 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %4, i8* %3)
%6 = bitcast i8* %5 to %0*
%7 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_.2, align 8, !invariant.load !8
%8 = bitcast %0* %6 to i8*
%9 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %8, i8* %7)
%10 = bitcast i8* %9 to %0*
store %0* %10, %0** %1, align 8
%11 = load %0*, %0** %1, align 8
%12 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_.4, align 8, !invariant.load !8
%13 = bitcast %0* %11 to i8*
%14 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %13, i8* %12)
notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*), i8* %14, i32 10)
ret i32 0
}
; Function Attrs: nonlazybind
declare i8* @objc_msgSend(i8*, i8*, ...) #1
declare void @NSLog(i8*, ...) #2
attributes #0 = { noinline optnone ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "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-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { nonlazybind }
attributes #2 = { "correctly-rounded-divide-sqrt-fp-math"="false" "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" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+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}
!llvm.ident = !{!7}
!0 = !{i32 1, !"Objective-C Version", i32 2}
!1 = !{i32 1, !"Objective-C Image Info Version", i32 0}
!2 = !{i32 1, !"Objective-C Image Info Section", !"__DATA,__objc_imageinfo,regular,no_dead_strip"}
!3 = !{i32 4, !"Objective-C Garbage Collection", i32 0}
!4 = !{i32 1, !"Objective-C Class Properties", i32 64}
!5 = !{i32 1, !"wchar_size", i32 4}
!6 = !{i32 7, !"PIC Level", i32 2}
!7 = !{!"Apple LLVM version 9.1.0 (clang-902.0.39.2)"}
!8 = !{}
在這里簡單介紹一些 LLVM IR 的指令:
%:局部變量
@:全局變量
alloca:分配內(nèi)存堆棧
i32:32 位的整數(shù)
i32**:一個指向 32 位 int 值的指針的指針
align 4:向 4 個字節(jié)對齊竣贪,即便數(shù)據(jù)沒有占用 4 個字節(jié)军洼,也要為其分配四個字節(jié)
call:調(diào)用
LLVM IR 是Frontend的輸出,也是LLVM Backend的輸入演怎,前后端的橋接語言, 更具生成的文件解析匕争,其實生成的LLVM IR對Runtime進(jìn)行橋接的一個文件
1.Class/Meta Class/Protocol/Category內(nèi)存結(jié)構(gòu)生成,并存放在指定section中(如Class:_DATA,_objc_classrefs)
2.Method/lvar/Property內(nèi)存結(jié)構(gòu)生成
3.組成method_list/ivar_list/property_list并填入Class
4.Non-Fragile ABI:為每個Ivar合成OBJC_IVAR_$_ 偏移值常量
5.存取Ivar的語句(ivar = 123; int a = ivar;)轉(zhuǎn)寫成base + OBJC_IVAR$_的形式
6.將語法樹中的ObjcMessageExpr翻譯成相應(yīng)版本的objc_msgSend爷耀,7.對super關(guān)鍵字的調(diào)用翻譯成objc_msgSendSuper
8.根據(jù)修飾符strong/weak/copy/atomic合成@property 自動實現(xiàn)的 setter/getter
9.處理@synthesize
10.生成block_layout的數(shù)據(jù)結(jié)構(gòu)
11.變量的capture(__block/__weak)
12.生成_block_invoke函數(shù)
13.ARC:分析對象引用關(guān)系甘桑,將objc_storeStrong/objc_storeWeak等ARC代碼插入
14.將ObjCAutoreleasePoolStmt轉(zhuǎn)譯成objc_autoreleasePoolPush/Pop
15.實現(xiàn)自動調(diào)用[super dealloc]
16為每個擁有ivar的Class合成.cxx_destructor方法來自動釋放類的成員變量,代替MRC時代的“self.xxx = nil”
3.5. Optimize - 優(yōu)化IR
使用命令 clang -O3 -S -emit-llvm main3.m -o main3.ll
; ModuleID = 'main3.m'
source_filename = "main3.m"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.13.0"
%struct._class_t = type { %struct._class_t*, %struct._class_t*, %struct._objc_cache*, i8* (i8*, i8*)**, %struct._class_ro_t* }
%struct._objc_cache = type opaque
%struct._class_ro_t = type { i32, i32, i32, i8*, i8*, %struct.__method_list_t*, %struct._objc_protocol_list*, %struct._ivar_list_t*, i8*, %struct._prop_list_t* }
%struct.__method_list_t = type { i32, i32, [0 x %struct._objc_method] }
%struct._objc_method = type { i8*, i8*, i8* }
%struct._objc_protocol_list = type { i64, [0 x %struct._protocol_t*] }
%struct._protocol_t = type { i8*, i8*, %struct._objc_protocol_list*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct._prop_list_t*, i32, i32, i8**, i8*, %struct._prop_list_t* }
%struct._ivar_list_t = type { i32, i32, [0 x %struct._ivar_t] }
%struct._ivar_t = type { i64*, i8*, i8*, i32, i32 }
%struct._prop_list_t = type { i32, i32, [0 x %struct._prop_t] }
%struct._prop_t = type { i8*, i8* }
%struct.__NSConstantString_tag = type { i32*, i32, i8*, i64 }
@"OBJC_CLASS_$_NSObject" = external global %struct._class_t
@"OBJC_CLASSLIST_REFERENCES_$_" = private global %struct._class_t* @"OBJC_CLASS_$_NSObject", section "__DATA,__objc_classrefs,regular,no_dead_strip", align 8
@OBJC_METH_VAR_NAME_ = private unnamed_addr constant [6 x i8] c"alloc\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@OBJC_SELECTOR_REFERENCES_ = private externally_initialized global i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_, i64 0, i64 0), section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
@OBJC_METH_VAR_NAME_.1 = private unnamed_addr constant [5 x i8] c"init\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@OBJC_SELECTOR_REFERENCES_.2 = private externally_initialized global i8* getelementptr inbounds ([5 x i8], [5 x i8]* @OBJC_METH_VAR_NAME_.1, i64 0, i64 0), section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
@__CFConstantStringClassReference = external global [0 x i32]
@.str = private unnamed_addr constant [15 x i8] c"------%@--%d--\00", section "__TEXT,__cstring,cstring_literals", align 1
@_unnamed_cfstring_ = private global %struct.__NSConstantString_tag { i32* getelementptr inbounds ([0 x i32], [0 x i32]* @__CFConstantStringClassReference, i32 0, i32 0), i32 1992, i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str, i32 0, i32 0), i64 14 }, section "__DATA,__cfstring", align 8
@OBJC_METH_VAR_NAME_.3 = private unnamed_addr constant [6 x i8] c"class\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@OBJC_SELECTOR_REFERENCES_.4 = private externally_initialized global i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_.3, i64 0, i64 0), section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
@llvm.compiler.used = appending global [7 x i8*] [i8* bitcast (%struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_" to i8*), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_, i32 0, i32 0), i8* getelementptr inbounds ([5 x i8], [5 x i8]* @OBJC_METH_VAR_NAME_.1, i32 0, i32 0), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_.3, i32 0, i32 0), i8* bitcast (i8** @OBJC_SELECTOR_REFERENCES_ to i8*), i8* bitcast (i8** @OBJC_SELECTOR_REFERENCES_.2 to i8*), i8* bitcast (i8** @OBJC_SELECTOR_REFERENCES_.4 to i8*)], section "llvm.metadata"
; Function Attrs: ssp uwtable
define i32 @main() local_unnamed_addr #0 {
%1 = load i8*, i8** bitcast (%struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_" to i8**), align 8
%2 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_, align 8, !invariant.load !8
%3 = tail call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %1, i8* %2)
%4 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_.2, align 8, !invariant.load !8
%5 = tail call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %3, i8* %4)
%6 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_.4, align 8, !invariant.load !8
%7 = tail call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %5, i8* %6)
notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*), i8* %7, i32 10)
ret i32 0
}
; Function Attrs: nonlazybind
declare i8* @objc_msgSend(i8*, i8*, ...) local_unnamed_addr #1
declare void @NSLog(i8*, ...) local_unnamed_addr #2
attributes #0 = { ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "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-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { nonlazybind }
attributes #2 = { "correctly-rounded-divide-sqrt-fp-math"="false" "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" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+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}
!llvm.ident = !{!7}
!0 = !{i32 1, !"Objective-C Version", i32 2}
!1 = !{i32 1, !"Objective-C Image Info Version", i32 0}
!2 = !{i32 1, !"Objective-C Image Info Section", !"__DATA,__objc_imageinfo,regular,no_dead_strip"}
!3 = !{i32 4, !"Objective-C Garbage Collection", i32 0}
!4 = !{i32 1, !"Objective-C Class Properties", i32 64}
!5 = !{i32 1, !"wchar_size", i32 4}
!6 = !{i32 7, !"PIC Level", i32 2}
!7 = !{!"Apple LLVM version 9.1.0 (clang-902.0.39.2)"}
!8 = !{}
這一步驟的優(yōu)化是非常重要的歹叮,很多直接轉(zhuǎn)換來的代碼是不合適且消耗內(nèi)存的跑杭,因為是直接轉(zhuǎn)換,所以必然會有這樣的問題盗胀,而優(yōu)化放在這一步的好處在于前端不需要考慮任何優(yōu)化過程艘蹋,減少了前端的開發(fā)工作。
3.6. 生成Target相關(guān)匯編
使用命令 clang -S -o - main3.m | open -f
可以查看生成的匯編代碼:
.section __TEXT,__text,regular,pure_instructions
.macosx_version_min 10, 13
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## BB#0:
pushq %rbp
Lcfi0:
.cfi_def_cfa_offset 16
Lcfi1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Lcfi2:
.cfi_def_cfa_register %rbp
subq $16, %rsp
movq L_OBJC_CLASSLIST_REFERENCES_$_(%rip), %rax
movq L_OBJC_SELECTOR_REFERENCES_(%rip), %rsi
movq %rax, %rdi
callq _objc_msgSend
movq L_OBJC_SELECTOR_REFERENCES_.2(%rip), %rsi
movq %rax, %rdi
callq _objc_msgSend
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
movq L_OBJC_SELECTOR_REFERENCES_.4(%rip), %rsi
movq %rax, %rdi
callq _objc_msgSend
leaq L__unnamed_cfstring_(%rip), %rsi
movl $10, %edx
movq %rsi, %rdi
movq %rax, %rsi
movb $0, %al
callq _NSLog
xorl %eax, %eax
addq $16, %rsp
popq %rbp
retq
.cfi_endproc
## -- End function
.section __DATA,__objc_classrefs,regular,no_dead_strip
.p2align 3 ## @"OBJC_CLASSLIST_REFERENCES_$_"
L_OBJC_CLASSLIST_REFERENCES_$_:
.quad _OBJC_CLASS_$_NSObject
.section __TEXT,__objc_methname,cstring_literals
L_OBJC_METH_VAR_NAME_: ## @OBJC_METH_VAR_NAME_
.asciz "alloc"
.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
.p2align 3 ## @OBJC_SELECTOR_REFERENCES_
L_OBJC_SELECTOR_REFERENCES_:
.quad L_OBJC_METH_VAR_NAME_
.section __TEXT,__objc_methname,cstring_literals
L_OBJC_METH_VAR_NAME_.1: ## @OBJC_METH_VAR_NAME_.1
.asciz "init"
.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
.p2align 3 ## @OBJC_SELECTOR_REFERENCES_.2
L_OBJC_SELECTOR_REFERENCES_.2:
.quad L_OBJC_METH_VAR_NAME_.1
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "------%@--%d--"
.section __DATA,__cfstring
.p2align 3 ## @_unnamed_cfstring_
L__unnamed_cfstring_:
.quad ___CFConstantStringClassReference
.long 1992 ## 0x7c8
.space 4
.quad L_.str
.quad 14 ## 0xe
.section __TEXT,__objc_methname,cstring_literals
L_OBJC_METH_VAR_NAME_.3: ## @OBJC_METH_VAR_NAME_.3
.asciz "class"
.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
.p2align 3 ## @OBJC_SELECTOR_REFERENCES_.4
L_OBJC_SELECTOR_REFERENCES_.4:
.quad L_OBJC_METH_VAR_NAME_.3
.section __DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
.long 0
.long 64
.subsections_via_symbols
注意代碼中的 .section 指令票灰,它指定了接下來會執(zhí)行的代碼段女阀。
在這篇文章中,詳細(xì)解釋了這些匯編指令或代碼到底是如何工作的:Mach-O 可執(zhí)行文件屑迂。
3.7. Link生成Executable
在最后浸策,LLVM 將會把這些匯編代碼輸出成二進(jìn)制的可執(zhí)行文件,使用命令 clang main3.m -o main.out 即可查看
Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x1000 (vmaddr 0x100000000 fileoff 0)
Section __text: 0x34 (addr 0x100000f50 offset 3920)
Section __stubs: 0x6 (addr 0x100000f84 offset 3972)
Section __stub_helper: 0x1a (addr 0x100000f8c offset 3980)
Section __cstring: 0xe (addr 0x100000fa6 offset 4006)
Section __unwind_info: 0x48 (addr 0x100000fb4 offset 4020)
total 0xaa
Segment __DATA: 0x1000 (vmaddr 0x100001000 fileoff 4096)
Section __nl_symbol_ptr: 0x10 (addr 0x100001000 offset 4096)
Section __la_symbol_ptr: 0x8 (addr 0x100001010 offset 4112)
total 0x18
Segment __LINKEDIT: 0x1000 (vmaddr 0x100002000 fileoff 8192)
total 0x100003000
上面的代碼中惹盼,每個 segment 的意義也不一樣:
-
__PAGEZERO
segment 它的大小為 4GB庸汗。這 4GB 并不是文件的真實大小,但是規(guī)定了進(jìn)程地址空間的前 4GB 被映射為 不可執(zhí)行手报、不可寫和不可讀蚯舱。 -
__TEXT
segment 包含了被執(zhí)行的代碼改化。它被以只讀和可執(zhí)行的方式映射。進(jìn)程被允許執(zhí)行這些代碼枉昏,但是不能修改陈肛。 -
__DATA
segment 以可讀寫和不可執(zhí)行的方式映射。它包含了將會被更改的數(shù)據(jù)兄裂。 -
__LINKEDIT
segment 指出了 link edit 表(包含符號和字符串的動態(tài)鏈接器表)的地址句旱,里面包含了加載程序的元數(shù)據(jù),例如函數(shù)的名稱和地址晰奖。
關(guān)于 section 中的內(nèi)容的研究可以查看:
Mach-O 可執(zhí)行文件
Mach-O 可執(zhí)行文件2
關(guān)于更詳細(xì)的編譯過程可以查看:
深入剖析 iOS 編譯 Clang LLVM
4. Swift的編譯過程
在 Swift 編譯器結(jié)構(gòu) 的官方文檔中描述了 Swift 編譯器是如何工作的谈撒,分為如下步驟:
- 解析:解析器是一個簡單的遞歸下降解析器(在 lib / Parse 中實現(xiàn)),帶有集成的手動編碼詞法分析器匾南。解析器負(fù)責(zé)生成沒有任何語義或類型信息的抽象語法樹(AST)啃匿,并針對輸入源的語法問題發(fā)出警告或錯誤。
- 語意分析:語義分析(在 lib / Sema 中實現(xiàn))負(fù)責(zé)解析 AST 并將其轉(zhuǎn)換為格式良好的完全檢查形式的 AST午衰,并在源代碼中發(fā)出語義問題的警告或錯誤立宜。語義分析包括類型推斷冒萄,如果成功臊岸,則所得到的代碼是類型檢查安全的 AST 。
- Clang導(dǎo)入器:Clang導(dǎo)入器(在 lib / ClangImporter 中實現(xiàn))導(dǎo)入Clang模塊尊流,并將它們導(dǎo)出的 C 或 Objective-C API 映射到相應(yīng)的 Swift API中帅戒。結(jié)果導(dǎo)入的 AST 可以通過語義分析來引用。
- SIL生成:Swift中間語言(Swift Intermediate Language崖技,簡稱SIL)是一種高級的逻住,Swift特有的中間語言,適用于 Swift 代碼的進(jìn)一步分析和優(yōu)化迎献。SIL 生成階段(在 lib / SILGen 中實現(xiàn))將類型檢查的 AST 降低到所謂的 “原始” SIL瞎访。SIL的設(shè)計描述在 docs/ SIL.rst 中可以看到。
- SIL優(yōu)化:在SIL優(yōu)化(在 lib/Analysis吁恍,lib/ ARC扒秸,lib/LoopTransforms,和 lib/Transforms 中實現(xiàn))執(zhí)行額外的高級別冀瓦,Swift 特有的優(yōu)化的程序伴奥,包括(例如)自動引用計數(shù)優(yōu)化,虛擬化和通用專業(yè)化翼闽。
- LLVM IR生成:IR生成(在 lib/IRGen 中實現(xiàn))將 SIL 降到 LLVM IR拾徙,此時LLVM可以繼續(xù)對其進(jìn)行優(yōu)化并生成機(jī)器碼。
相關(guān)內(nèi)容不詳細(xì)講解感局,可參考:https://blog.csdn.net/aas319/article/details/78606342
三尼啡、相關(guān)衍生的內(nèi)容
1.Xcode 編譯設(shè)置
1.1 Build Settings
這里是編譯設(shè)置暂衡,針對編譯流程中的各個過程進(jìn)行參數(shù)和工具的配置:
Architectures:編譯目標(biāo) CPU 架構(gòu),這里比較常見的是
Build Active Architectures Only
(只編譯為當(dāng)前架構(gòu)崖瞭,是指你在 scheme 中選定的設(shè)備的 CPU 架構(gòu))古徒,debug
設(shè)置為YES
,Release
設(shè)置為NO
读恃。Assets:
Assets.xcassets
資源組的配置隧膘。Build Locations:查看 Build 日志可以看到在編譯過程中的目標(biāo)文件夾。
-
Build Options:這里是一些編譯的選項設(shè)定寺惫,包含:
- 是否總是嵌入 Swift 標(biāo)準(zhǔn)庫疹吃,這個在靜態(tài)庫和動態(tài)庫的第一篇文章中有講,iOS 系統(tǒng)目前是不包含 Swift 標(biāo)準(zhǔn)庫的西雀,都是被打包在項目中萨驶。
- c/c++/objective-c 編譯器:Apple LLVM 9.0
- 是否打開 Bitcode
- …
Deployment:iOS 部署設(shè)置。說白了就是安裝到手機(jī)的設(shè)置艇肴。
Headers:頭文件腔呜?具體作用不詳,知道的可以說一下再悼。
Kernel Module:內(nèi)核模塊核畴,作用不詳解恰。
Linking:鏈接設(shè)置念颈,鏈接路徑、鏈接標(biāo)記抬吟、Mach-O 文件類型莺奸。
Packaging:打包設(shè)置丑孩,info.plist 的路徑設(shè)置、Bundle ID 灭贷、App 顯示名稱的設(shè)置温学。
Search Paths:庫的搜索路徑、頭文件的搜索路徑甚疟。
Signing:簽名設(shè)置仗岖,開發(fā)、生產(chǎn)的簽名設(shè)置古拴,這些都和你在開發(fā)者網(wǎng)站配置的證書相關(guān)箩帚。
Testing:測試設(shè)置,作用不詳黄痪。
Text-Based API:基于文本的 API紧帕,字面翻譯,作用不詳。
Versioning:版本管理是嗜。
Apple LLVM 9.0 系列:LLVM 的配置愈案,包含路徑、編譯器每一步的設(shè)置鹅搪、語言設(shè)置站绪。在這里
Apple LLVM 9.0 - Warnings
可以選擇在編譯的時候?qū)⒛男┣闆r認(rèn)定為錯誤(Error)和警告(Warning),可以開啟困難模式丽柿,任何一個小的警告都會被認(rèn)定為錯誤恢准。Asset Catalog Compiler - Options:Asset 文件的編譯設(shè)置。
Interface Builder Storyboard Compiler - Options:Storyboard 的編譯設(shè)置甫题。
以及一些靜態(tài)分析和 Swift 編譯器的設(shè)定馁筐。
1.2 Build Phases
編譯階段,編譯的時候?qū)⒏鶕?jù)順序來進(jìn)行編譯坠非。這里固定的有:
- Compile Sources:編譯源文件敏沉。
- Link Binary With Libraries:相關(guān)的鏈接庫。
- Copy Bundle Resources:要拷貝的資源文件炎码,有時候如果一個資源文件在開發(fā)過程中發(fā)現(xiàn)找不到盟迟,可以在這里找一下,看看是不是加進(jìn)來了潦闲。
如果使用了 Cocoapods攒菠,那么將會被添加:
- [CP] Check Pods Manifest.lock:檢查 Podfile.lock 和 Manifest.lock 文件的一致性,這個會再后面的 Cocoapods 原理中詳細(xì)解釋矫钓。
- [CP] Embed Pods Frameworks:將所有 cocoapods 打的 framework 拷貝到包中要尔。
- [CP] Copy Pods Resources:將所有 cocoapods 的資源文件拷貝到包中。
1.3 Build Rules
編譯規(guī)則新娜,這里設(shè)定了不同文件的處理方式,例如:
- Copy Plist File:在編譯打包的時候既绩,將 info.plist 文件拷貝概龄。
- Compress PNG File:在編譯打包的時候,將 PNG 文件壓縮饲握。
- Swift Compiler:Swift 文件的編譯方式私杜,使用 Swift 編譯器。
- ….
2. Cocoapods 原理
使用了 Cocoapods 后救欧,我們的編譯流程會多出來一些衰粹,雖然每個 target 的編譯流程都是一致的,但是 Cocoapods 是如何將這些庫導(dǎo)入我們的項目笆怠、原項目和其他庫之間的依賴又是如何實現(xiàn)的仍然是一個需要了解的知識點铝耻。下面這幾篇文章從不同角度解釋了 Cocoapods 是如何工作的:
這些文章大部分都是講述了 Objective-C 語言下 Cocoapods 是如何使用靜態(tài)庫動態(tài)庫并添加依賴的。特別說明一下 Swift 的實現(xiàn):
Cocoapods 對于 Swift 和 Objective-C 的操作區(qū)別其實并不是很大瓢捉,Swift 是必須使用動態(tài)庫的频丘,因此在 podfile 中我們必須添加代碼 use_frameworks!
來指明 Cocoapods 所有管理的庫都將被編譯成 framework 動態(tài)庫。這些庫的依賴過程和 Objective-C 一致泡态,我們的主項目依賴于 Pods-ProjectName.framework
target搂漠,而這個 pods 的 target 則依賴于其他我們使用的庫。
需要說明一個概念某弦,就是 Swift 的命名空間和 Module桐汤。
- Module:在 Swift 中,項目里的每個 target靶壮、framework 都是一個 Module惊科。
- 命名空間:每個 Module 都擁有獨立的命名空間。
因此亮钦,我們在使用 Swift 庫的時候馆截,例如 Alamofire
,在某個文件中 import Alamofire
蜂莉,其實就是在引入這個 Module蜡娶,在代碼提示中可以看到,這樣其中的代碼才可以被我們使用映穗。
在同一個 Module 內(nèi)我們的 Swift 文件是不需要被引用的窖张,可以直接使用其中 public
、internal
描述的類屬性和方法蚁滋。這也說明一個問題宿接,在同一個 Module 中,命名空間也是同一個辕录,重復(fù)的類都會在編譯的時候報錯睦霎。
總之
LLVM 的編譯過程是相當(dāng)復(fù)雜的,中間牽扯到的技術(shù)和語言要比我們做一個簡單的移動開發(fā)要復(fù)雜的多走诞,本文也只是非常淺顯的描述了相關(guān)的編譯過程副女,其主要目的是為了在 iOS 開發(fā)中更加得心應(yīng)手。
研究編譯原理蚣旱、深究編譯過程以及其相關(guān)內(nèi)容碑幅,在開發(fā)過程中是非常重要的一個環(huán)節(jié),任何開發(fā)者都應(yīng)該做深入研究塞绿。如果你看不懂相關(guān)代碼沟涨,也不要覺得困難,很多代碼的命名就能很清楚的表明它到底是干什么的异吻,英語差的話翻譯一下就好了裹赴。只是相對于英語好,底層代碼也略懂的情況來說,學(xué)習(xí)成本比較高篮昧,不過這不應(yīng)該是打敗你的理由赋荆。
原理是我們構(gòu)建整個代碼世界的基礎(chǔ),不懂原理(無論哪方面)都只能讓我們做一個壘磚的懊昨,而不是整個工程的控制者窄潭。