淺談iOS編譯過程

引言

維基百科:
編譯語言(英語: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ō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)化器和后端在代碼上運行伐谈。


1111.png
2.1.2GCC編譯器設(shè)計簡介(同樣采用三相設(shè)計)

GCC編譯器的三相設(shè)計中最成功的實現(xiàn)是對Java烂完、 .Net語言的編譯解析。


222.png
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ī)器代碼柄冲,如圖下圖所示。


3333.png

為什么要使用三相設(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í)行文件漠烧。

其他的就不再詳細(xì)介紹了,詳情可以參考(LLVMClang)

總之,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ī)器代碼媳瞪。

簡單的流程如下圖:

444.png
3.2 Clang 編譯前端

編譯器前端的任務(wù)是進(jìn)行:語法分析骗炉,語義分析,生成中間代碼(intermediate representation )蛇受。在這個過程中句葵,會進(jìn)行類型檢查,如果發(fā)現(xiàn)錯誤或者警告會標(biāo)注出來在哪一行兢仰。

555.png
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)化等等察蹲。

666.png

LLVM機(jī)器碼生成器會針對不同的架構(gòu)请垛,比如arm64等生成不同的機(jī)器碼。

777.png

二洽议、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)
888.jpeg
999.jpeg

在上述流程中: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ù)編譯指令的過程)

條件編譯語句也是在預(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

PARSING MACH-O FILES

關(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è)置為 YESRelease 設(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 文件是不需要被引用的窖张,可以直接使用其中 publicinternal 描述的類屬性和方法蚁滋。這也說明一個問題宿接,在同一個 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ǔ),不懂原理(無論哪方面)都只能讓我們做一個壘磚的懊昨,而不是整個工程的控制者窄潭。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市酵颁,隨后出現(xiàn)的幾起案子嫉你,更是在濱河造成了極大的恐慌,老刑警劉巖躏惋,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幽污,死亡現(xiàn)場離奇詭異,居然都是意外死亡簿姨,警方通過查閱死者的電腦和手機(jī)距误,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扁位,“玉大人准潭,你說我怎么就攤上這事∮虺穑” “怎么了刑然?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長暇务。 經(jīng)常有香客問我泼掠,道長,這世上最難降的妖魔是什么垦细? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任择镇,我火速辦了婚禮,結(jié)果婚禮上蝠检,老公的妹妹穿的比我還像新娘沐鼠。我一直安慰自己,他們只是感情好叹谁,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著乘盖,像睡著了一般焰檩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上订框,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天析苫,我揣著相機(jī)與錄音,去河邊找鬼。 笑死衩侥,一個胖子當(dāng)著我的面吹牛国旷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播茫死,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼跪但,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了峦萎?” 一聲冷哼從身側(cè)響起屡久,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎爱榔,沒想到半個月后被环,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡详幽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年筛欢,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唇聘。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡版姑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出雳灾,到底是詐尸還是另有隱情漠酿,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布谎亩,位于F島的核電站炒嘲,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏匈庭。R本人自食惡果不足惜夫凸,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望阱持。 院中可真熱鬧夭拌,春花似錦、人聲如沸衷咽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽镶骗。三九已至桶现,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鼎姊,已是汗流浹背骡和。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工相赁, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人慰于。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓钮科,卻偏偏與公主長得像,于是被迫代替她去往敵國和親婆赠。 傳聞我的和親對象是個殘疾皇子绵脯,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容