iOS LLVM編譯流程

1. LLVM概念

1.1 編譯器

LLVM與編譯器息息相關(guān)缓呛,究竟什么是編譯器呢蜡娶?帶著疑問往下看吧坟奥。
編譯器就是將一種語言(通常為高級語言)翻譯為另一種語言(通常為低級語言的程序免都。一個現(xiàn)代編譯器的主要工作流程:源代碼(source code)預(yù)處理器(preprocessor)編譯器(compiler)目標(biāo)代碼(object code)鏈接器(Linker)可執(zhí)行程序(executables)

源代碼一般為高級語言(High-level language)驯妄, 如C食侮、C++号涯、JavaObjective-C等或匯編語言疙描,而目標(biāo)則是機(jī)器語言的目標(biāo)代碼(Object copy)诚隙,有時也稱作機(jī)器代碼(Machine code)。

  • 解釋型語言
    引入一個Python代碼起胰,見下圖:

    創(chuàng)建Python案例

    創(chuàng)建一個FirstDemo.py文件久又,里面只有一行代碼,print('Hello world for first time')效五。通過解釋器指令python地消,解釋這段代碼:
    解析Python案例

    通過上面的流程可以發(fā)現(xiàn)解釋型語言的運(yùn)行流程:
    解釋型語言編譯流程

    解釋型語言特點(diǎn):邊解釋,邊執(zhí)行畏妖,運(yùn)行速度慢脉执,部分改動無需整體重新編譯,不可脫離解釋器環(huán)境運(yùn)行戒劫。

  • 編譯型語言
    引入一個C代碼半夷,見下圖:

    創(chuàng)建C語言案例

    創(chuàng)建一個firstDemoForC.c文件,里面添加了一個main函數(shù)迅细。首先通過clang去讀取這個代碼:
    讀取C代碼

    讀取之后發(fā)現(xiàn)代碼并沒有立刻執(zhí)行巫橄,而是生成了一個a.out文件。這個文件就是可執(zhí)行文件茵典。通過./a.out執(zhí)行這段代碼:
    a.out執(zhí)行

    通過上面的流程可以發(fā)現(xiàn)編譯型語言的運(yùn)行流程:
    編譯型語言運(yùn)行流程

    編譯型語言特點(diǎn):先整體編譯湘换,再執(zhí)行,運(yùn)行速度快统阿,任意改動需重新編譯彩倚,可脫離編譯環(huán)境運(yùn)行。

注意:編譯型語言編譯是先將代碼編譯成cpu可讀的懂的二進(jìn)制才能執(zhí)行

1.2 LLVM概述

LLVM構(gòu)架編譯器(compiler)的框架系統(tǒng)扶平,以C++編寫而成帆离,用于優(yōu)化以任意程序語言編寫的程序的編譯時間(compile-time)、鏈接時間(link-time)蜻直、運(yùn)行時間(run-time)以及空閑時間(idle-time)盯质,對開發(fā)者保持開放袁串,并兼容已有腳本概而。

目前LLVM已經(jīng)被Apple呼巷、MicrosoftGoogle赎瑰、Facebook等各大公司采用王悍。

1.2.1 傳統(tǒng)編譯器的設(shè)計(jì)
傳統(tǒng)編譯器的設(shè)計(jì)
  • 編譯器前端(Frontend)
    編譯器前端的任務(wù)是解析源代碼。它會進(jìn)行:詞法分析餐曼、語法分析压储、語義分析,檢查源代碼是否存在錯誤源譬,然后構(gòu)建抽象語法樹集惋,LLVM的前端會生成中間代碼IR

  • 優(yōu)化器(Optimizer)
    優(yōu)化器負(fù)責(zé)進(jìn)行各種優(yōu)化踩娘。改善運(yùn)行時間刮刑,例如消除冗余計(jì)算等。

  • 后端(Backend)
    也可以叫代碼生成器(CodeGenerator)养渴,將代碼映射到目標(biāo)指令集雷绢。生成機(jī)器語言,并且進(jìn)行機(jī)器相關(guān)的代碼優(yōu)化理卑。

補(bǔ)充:隨著高級語言越來越多翘紊,終端類型種類的增加,所使用的的CPU架構(gòu)等也不盡相同藐唠,所以為了適配多種環(huán)境帆疟,不得不設(shè)計(jì)不同的編譯器,而這些編譯器前端后端往往是捆綁在一起的宇立。

1.2.2 LLVM的設(shè)計(jì)思路

LLVM的設(shè)計(jì)之初踪宠,即將編譯器前端(Frontend)和后端(Backend)進(jìn)行了分離。將前端和后端針對不同的架構(gòu)泄伪,按照獨(dú)立的項(xiàng)目進(jìn)行研發(fā)殴蓬,而它們均采用通用的代碼形式IR

前后端分離獨(dú)立適配架構(gòu)

當(dāng)編譯器決定支持多種語言或多種硬件架構(gòu)時蟋滴,LLVM最重要的地方就體現(xiàn)出來了染厅,使用通用的代碼表示形式(IR),它是用來在編譯器中表示代碼的形式津函。所以LLVM可以為任何編程語言獨(dú)立編寫前端肖粮,并且可以為任意硬件架構(gòu)獨(dú)立編寫后端。

1.2.3 iOS編譯架構(gòu)

Objective C/C/C++使用的編譯器前端是Clang尔苦,SwiftSwift涩馆,后端都是LLVM行施。

iOS編譯架構(gòu)

1.3 Clang

ClangLLVM項(xiàng)目中的一個子項(xiàng)目。它是基于LLVM架構(gòu)的輕量級編譯器魂那,誕生之初是為了替代GCC蛾号,提供更快的編譯速度。它是負(fù)責(zé)編譯C涯雅、C++鲜结、Objective-C語言的編譯器,它屬于整個LLVM架構(gòu)中活逆,編譯器前端精刷。對于開發(fā)者來說,研究Clang可以給我們帶來很多好處蔗候。

2. 編譯流程詳解

通過命令可以打印源碼的編譯階段怒允。引入下面一個案例,main.m中添加代碼:

int main(int argc, const char * argv[]) {
    return 0;
}

通過指令clang -ccc-print-phases main.m锈遥,查看編譯流程:

通過指令查看編譯流程

流程說明:
0.輸入文件:找到源文件
1.預(yù)處理階段:這個過程處理包括宏的替換纫事,頭文件的導(dǎo)入
2.編譯階段:進(jìn)行詞法分析語法分析迷殿、檢測語法是否正確儿礼,最終生成IR
3.后端:這里LLVM會通過一個一個的Pass節(jié)點(diǎn))去優(yōu)化,每個Pass做一些事情庆寺,最終生成匯編代碼
4.生成目標(biāo)文件
5.鏈接:鏈接需要的動態(tài)庫靜態(tài)庫蚊夫,生成可執(zhí)行文件
6.通過不同的架構(gòu),生成對應(yīng)的可行文件

2.1 預(yù)處理

執(zhí)行如下指令:clang -E main.m懦尝,對源代碼進(jìn)行預(yù)處理知纷。見下面流程:

執(zhí)行指令進(jìn)行預(yù)處理

在預(yù)處理之后,輸出mainE.m文件陵霉,查看mainE.m文件:
mainE.m文件查看

打開mainE.m源文件會發(fā)現(xiàn)琅轧,其進(jìn)行了宏的替換,如上面案例中宏c直接替換成了30踊挠;進(jìn)行頭文件的導(dǎo)入乍桂。

2.2 編譯階段

2.2.1 詞法分析

預(yù)處理完成后就會進(jìn)行詞法分析,這里會把代碼切成一個個Token效床,比如大小括號睹酌,等于號以及字符串等。詞法分析指令為:

clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m

參考下面的案例:

詞法分析案例

通過指令的輸出可以看到剩檀,語法分析會將源碼進(jìn)行切割并檢測憋沿。比如分號逗號沪猴,int等辐啄。

2.2.2 語法分析

詞法分析完成之后就是語法分析采章,它的任務(wù)是驗(yàn)證語法是否正確。在詞法分析的基礎(chǔ)上將單詞序列組合成各類語法短語壶辜,如:程序悯舟,語句表達(dá)式等等士复,然后將所有節(jié)點(diǎn)組成抽象語法樹Abstract Syntax Tree图谷,AST)翩活。語法分析程序判斷源程序在結(jié)構(gòu)上是否正確阱洪。語法分析指令:

clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

參考下面的案例:

語法分析案例

通過上面的輸出可以發(fā)現(xiàn),其是一個樹結(jié)構(gòu)菠镇,比如下面的FunctionDecl冗荸,表示一個方法,在源碼的第五行利耍,名稱為main蚌本,返回值為int,傳入兩個參數(shù)一個是int隘梨,一個是const char **程癌。見下圖:

注意:一旦生成抽象語法樹,如果源碼中存在語法錯誤轴猎,就會報(bào)錯嵌莉,而上面的預(yù)處理和詞法分析不會報(bào)錯。

如在源碼中設(shè)置一個語法錯誤捻脖,通過語法分析指令進(jìn)行進(jìn)行編譯锐峭,就會報(bào)錯,見下面案例:


語法錯誤案例
2.2.3 生成中間代碼IR(intermediate representation)

完成以上步驟后可婶,就開始生成中間代碼IR了沿癞,代碼生成器(Code Generation)會將語法樹自頂向下遍歷逐步翻譯成LLVM IR。通過下面指令可以生成.ll的文本文件矛渴,查看IR代碼椎扬。

clang -S -fobjc-arc -emit-llvm main.m

通過上面的指令獲取main.ll文件,其結(jié)構(gòu)見下圖:

IR代碼查看

  • IR的基本語法

    • @ 全局標(biāo)識
    • % 局部標(biāo)識
    • alloca 開辟空間
    • align 內(nèi)存對齊
    • i32 32bit具温,4個字節(jié)
    • store 寫入內(nèi)存
    • load 讀取數(shù)據(jù)
    • call 調(diào)用函數(shù)
    • ret返回
  • IR優(yōu)化
    上面生成的IR代碼是沒有經(jīng)過優(yōu)化的蚕涤,其實(shí)我們在平時閱讀代碼時,經(jīng)常會看下面的一些定義:

#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
  • fastpath:可以理解為快速流程桂躏,對更有可能執(zhí)行的流程進(jìn)行優(yōu)化钻趋,提高運(yùn)行速度;
    slowpath:基本流程剂习,不被優(yōu)化的蛮位。

XCode中也有相應(yīng)的優(yōu)化設(shè)置入口:

優(yōu)化入口

LLVM的優(yōu)化級別分別是-O0 -O1 -O2 -O3 -Os(第一個是大寫英文字母O)较沪。可以通過下面的指令獲取優(yōu)化后的IR代碼失仁,也就是.ll文件:

clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll

通過以上指令生成優(yōu)化后IR代碼如下:

生成優(yōu)化后IR代碼

好明顯尸曼,相較于優(yōu)化前,代碼精簡了很多萄焦。

這里需要注意的是控轿,通常debug模式下,優(yōu)化模式選擇None -O0拂封,也就是不優(yōu)化茬射,避免一些保留代碼被屏蔽,從而影響調(diào)試冒签。而release模式設(shè)置為Fastest,Smallest -Os在抛。

  • bitCode
    Xcode7以后開啟bitCode蘋果會做進(jìn)一步的優(yōu)化,生成.bc的中間代碼萧恕。我們通過優(yōu)化后的IR代碼生成.bc代碼刚梭。對應(yīng)指令為:
clang -emit-llvm -c main.ll -o main.bc

2.3 后端生成匯編代碼

我們通過最終的.bc或者.ll代碼生成匯編代碼

    clang -S -fobjc-arc main.bc -o main.s
    clang -S -fobjc-arc main.ll -o main.s

生成匯編代碼也可以進(jìn)行優(yōu)化:

    clang -Os -S -fobjc-arc main.m -o main.s

采用相同的案例,分別三種方式生成匯編代碼票唆,可以看到其優(yōu)化效果朴读。在進(jìn)行IR優(yōu)化后生成的.ll文件,依然可以進(jìn)行優(yōu)化生成回應(yīng)的匯編代碼走趋。在不同的節(jié)點(diǎn)上都可以進(jìn)行優(yōu)化衅金。見下圖:

匯編代碼的生成

2.4 生成目標(biāo)文件(匯編器)

目標(biāo)文件的生成,是匯編器以匯編代碼作為輸入吆视,將匯編代碼轉(zhuǎn)換為機(jī)器代碼典挑,最后輸出目標(biāo)文件object file)。指令為:

clang -fmodules -c main.s -o main.o

生成目標(biāo)文件啦吧,見下圖:

生成目標(biāo)文件

其中main.o文件即為目標(biāo)文件您觉,但是此時生成的目標(biāo)文件是不可執(zhí)行的。通過nm命令授滓,查看下main.o中的符號:

    $xcrun nm -nm main.o
            (undefined) external _printf
    0000000000000000 (__TEXT,__text) external _test
    000000000000000a (__TEXT,__text) external _main
  • _printf是一個undefifined external
  • undefifined表示在當(dāng)前文件暫時找不到符號_printf
  • external表示這個符號是外部可以訪問的

此時就需要鏈接琳水,鏈接器把編譯生成的.o文件和(.dylib .a)文件鏈接生成一個mach-o文件。

    clang main.o -o main

生成對應(yīng)的可執(zhí)行文件般堆,見下圖:


生成可執(zhí)行文件

查看鏈接之后的符號:

$xcrun nm -nm main
        (undefined) external _printf (from libSystem)
        (undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
000000100000f6d (__TEXT,__text) external _test
000000100000f77 (__TEXT,__text) external _main

可以發(fā)現(xiàn)此時的外部函數(shù)有2個在孝,_printfdyld_stub_binder,它們都來自libSystem庫淮摔。dyld_stub_binder這個函數(shù)的作用是進(jìn)行運(yùn)行時綁定流程私沮。

鏈接是在編譯時,用來確定外部函數(shù)來自哪個動態(tài)庫和橙;綁定是在運(yùn)行時仔燕,將對應(yīng)方法的實(shí)現(xiàn)地址與符號進(jìn)行綁定造垛。

可執(zhí)行文件運(yùn)行結(jié)果:


可執(zhí)行文件運(yùn)行結(jié)果
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市晰搀,隨后出現(xiàn)的幾起案子五辽,更是在濱河造成了極大的恐慌,老刑警劉巖外恕,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杆逗,死亡現(xiàn)場離奇詭異,居然都是意外死亡鳞疲,警方通過查閱死者的電腦和手機(jī)罪郊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來建丧,“玉大人排龄,你說我怎么就攤上這事◆嶂欤” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵尺铣,是天一觀的道長拴曲。 經(jīng)常有香客問我,道長凛忿,這世上最難降的妖魔是什么澈灼? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮店溢,結(jié)果婚禮上叁熔,老公的妹妹穿的比我還像新娘。我一直安慰自己床牧,他們只是感情好荣回,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著戈咳,像睡著了一般心软。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上著蛙,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天删铃,我揣著相機(jī)與錄音,去河邊找鬼踏堡。 笑死猎唁,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的顷蟆。 我是一名探鬼主播诫隅,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼缎患,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了阎肝?” 一聲冷哼從身側(cè)響起挤渔,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎风题,沒想到半個月后判导,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沛硅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年眼刃,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摇肌。...
    茶點(diǎn)故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡擂红,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出围小,到底是詐尸還是另有隱情昵骤,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布肯适,位于F島的核電站变秦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏框舔。R本人自食惡果不足惜蹦玫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望刘绣。 院中可真熱鬧樱溉,春花似錦、人聲如沸纬凤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽移斩。三九已至肚医,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間向瓷,已是汗流浹背肠套。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留猖任,地道東北人你稚。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親刁赖。 傳聞我的和親對象是個殘疾皇子搁痛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評論 2 355

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