二十五、LLVM

  1. 什么是編譯器
  2. LLVM概述
  3. LLVM案例體驗
  4. LLVM源碼 & 編譯流程

1 什么是編譯器娩嚼?

1.1 Python案例

  • 創(chuàng)建python文件夾蘑险,新建helloDemo.py文件,內(nèi)容如下:
print("hello")

  • 調(diào)用python helloDemo.py執(zhí)行文件岳悟,打印出python

    image

1.2 C 案例

  • vim創(chuàng)建helloDemo.c文件:
#include <stdio.h>
int main(int a, char * argv[]) {
        printf("hello \n");
        return 0;
}

  • clang helloDemo.c編譯佃迄,生成a.out文件泼差。file a.out查看文件:

    image

發(fā)現(xiàn).out文件是:64位的Mach-O可執(zhí)行文件,當(dāng)前clang出來的是x86_64架構(gòu)呵俏, mac電腦可讀堆缘。 所以可以./a.out直接執(zhí)行:

image

Q:解釋型語言與編譯型語言

  • python解釋型語言,一邊翻譯一邊執(zhí)行普碎。和js一樣吼肥,機器可直接執(zhí)行。
  • C語言是編譯型語言麻车,不能直接執(zhí)行缀皱,需要編譯器將其轉(zhuǎn)換機器識別語言

編譯型語言編譯后輸出的是指令(0动猬、1組合)啤斗,cpu可直接執(zhí)行指令
解釋性語言:生成的是數(shù)據(jù),不是0枣察、1組合争占,機器也能直接識別

編譯器的作用燃逻,就是將高級語言轉(zhuǎn)化為機器能夠識別語言(可執(zhí)行文件)序目。

Q:匯編有指令嗎?

  • 早期科學(xué)家伯襟,使用0猿涨、1編碼。 比如 00001111 對應(yīng) call姆怪, 00000111 對應(yīng)bl叛赚。有了對應(yīng)關(guān)系后。 再手敲0和1就有點難受了稽揭。于是寫個中間解釋器俺附,我們只用輸入callbl這樣的標(biāo)記指令溪掀,經(jīng)過解釋器事镣,變成0和1的組合,再交給機器去執(zhí)行揪胃。 這就是匯編的由來璃哟。
  • 而基于匯編往上,再映射封裝相關(guān)對應(yīng)關(guān)系喊递。就跨時代性c語言随闪,再往上層封裝,就出現(xiàn)了高級語言oc骚勘、swift等語言铐伴。所以匯編執(zhí)行快,因為它是直接轉(zhuǎn)換機器語言的。
  • 匯編指令集盛杰,是針對同一操作系統(tǒng)而言挽荡,它支持跨平臺機器指令cpu的在識別即供。早期的計算機廠家非常定拟,雖然都用01組合,但相同組合背后卻是相應(yīng)不同指令逗嫡。所以匯編無法跨平臺青自,不同操作系統(tǒng)下,匯編指令不同的驱证。

2. LLVM概述

  • LLVM架構(gòu)編譯器compiler)的框架系統(tǒng)延窜,以c++編寫而成,用于優(yōu)化任意程序語言編寫的程序的編譯時間compile-time)抹锄、鏈接時間(link-time)逆瑞、運行時間run-time)以及空閑時間idle-time),對開發(fā)者保持開放伙单,并兼任已有腳本获高。
  • 2006年Chris Lattner加盟Apple Inc.并致力于LLVMApple開發(fā)體系中的應(yīng)用。Apple也是LLVM計劃主要資助者吻育。
    目前LLVM已經(jīng)被蘋果iOS開發(fā)工具念秧、Xilinx VivadoFacebook布疼、Google等各大公司采用摊趾。

2.1 傳統(tǒng)編譯器的設(shè)計

image
- 編譯器前端(Frontend):

編譯器的前端任務(wù)解析源代碼。 會進行詞法分析游两、語法分析砾层、語義分析。檢查源代碼是否存在錯誤贱案,然后構(gòu)建抽象語法樹(Abstract Syntax Tree AST)肛炮,LLVM前端還會生成中間代碼(intermediate representation, IR)

- 優(yōu)化器(Optimizer)

優(yōu)化器負責(zé)各種優(yōu)化改善代碼的運行時間轰坊,如消除冗余計算

- 后端(Backkend)/ 代碼生成器(CodeGenerator)

將代碼映射目標(biāo)指令集铸董,生成機器語言,并進行機器相關(guān)代碼優(yōu)化 (目標(biāo)指不同操作系統(tǒng)

iOS的編譯器架構(gòu):

Objective C / C / C++ 使用的編譯器前端Clang肴沫,Swiftswift粟害,后端都是LLVM

image

2.2 LLVM的設(shè)計

  • GCC是一個非常成功編譯器颤芬,但由于它作為整體應(yīng)用程序設(shè)計的悲幅,用途受到了限制套鹅。

  • LLVM最重要的地方:支持多種語言多種硬件架構(gòu)。使用通用代碼表示形式:IR(用來在編譯器中表示代碼的形式)

  • LLVM可以為任何編程語言獨立編寫前端汰具,也可以為任何硬件架構(gòu)獨立編寫后端.

  • 所以LLVM不是一個簡單的編譯器卓鹿,而是架構(gòu)編譯器,可以兼容所有前端后端留荔。

image

2.3 Clang

ClangLLVM項目的一個子項目吟孙。基于LLVM架構(gòu)輕量級編輯器聚蝶,誕生之初就是為了替代GCC杰妓,提供更快編譯速度。 他是負責(zé)編譯C碘勉、C++巷挥、Objecte-C語言的編譯器,它屬于整個LLVM架構(gòu)中的編譯器前端验靡。

  • 對于開發(fā)者而言倍宾,研究Clang可以給我們帶來很多好處

3. LLVM案例體驗

  • 新建一個Mac OS命令行工程:

    image
  • 沒有改動代碼

    image

3.1 編譯流程

  • cd到main.m的文件夾胜嗓。使用下面命令查看main.m的編譯步驟:
clang -ccc-print-phases main.m

image

編譯流程分為以下7步

  • 0: input, "main.m", objective-c
    輸入文件:找到源文件
  • 1: preprocessor, {0}, objective-c-cpp-output
    預(yù)處理:宏的展開高职,頭文件的導(dǎo)入
  • 2: compiler, {1}, ir
    編譯:詞法、語法兼蕊、語義分析初厚,最終生成IR
  • 3: backend, {2}, assembler ()
    匯編: LLVM通過一個個的Pass去優(yōu)化件蚕,每個Pass做一些事孙技,最后生成匯編代碼
  • 4: assembler, {3}, object
    目標(biāo)文件
  • 5: linker, {4}, image
    鏈接: 鏈接需要的動態(tài)庫和靜態(tài)庫,生成可執(zhí)行文件
  • 6: bind-arch, "x86_64", {5}, image
    架構(gòu)可執(zhí)行文件:通過不同架構(gòu)排作,生成對應(yīng)的可執(zhí)行文件

optimizer優(yōu)化沒有作為一個獨立階段牵啦,在編譯階段內(nèi)部完成

3.2 預(yù)處理階段

  • main.m中準備測試代碼:
#import <stdio.h>
#define C 30

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        int b = 20;
        printf("%d", a + b + C);
    }
    return 0;
}

  • clang預(yù)編譯輸出main2.m文件:
clang -E main.m >> main2.m

  • 打開main2.m,有575行妄痪。其中大部分是stdio庫的代碼:

    image
  • 我們發(fā)現(xiàn)測試代碼中的宏C哈雏,在預(yù)編譯階段完成了替換,變成了30

預(yù)編譯階段: 1. 導(dǎo)入頭文件 2.替換宏

  • 修改測試代碼衫生,給int類型取個別名HT_INT_64裳瘪,再次預(yù)編譯處理
#define C 30

typedef int HT_INT_64;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        HT_INT_64 a = 10;
        HT_INT_64 b = 20;
        printf("%d", a + b + C);
    }
    return 0;
}

  • 發(fā)現(xiàn)typedef不會被替換

    image

安全拓展:

  1. 使用define重要方法名稱進行替換。比如#define Pay XXXTest這樣開發(fā)者使用宏P(guān)ay開發(fā)舒服罪针,但是被hank時彭羹,實際代碼是XXXTest,不容易被察覺泪酱。
    #define真實內(nèi)容派殷,不應(yīng)該寫成亂碼还最,會讓人有此地?zé)o銀三百兩的感覺,最好弄成系統(tǒng)類似名稱或其他不經(jīng)意名稱毡惜。這樣才容易忽視拓轻,安全級別更高 ??)
    typedef沒有這個偷梁換柱的效果。define只影響預(yù)處理期经伙。

3.3 編譯階段

3.3.1 詞法分析
  • 編譯main.m文件:
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m

-詞法分析扶叉,就是根據(jù)空格括號這些將代碼拆分成一個個Token。標(biāo)注了位置第幾行第幾個字符開始的帕膜。

image
3.3.2 語法分析
  • 語法分析是驗證語法是否正確辜梳。
    在詞法分析的基礎(chǔ)上,將單詞序列組合成各類語法短語泳叠,如“程序”作瞄,“語句”,“表達式”等危纫,然后將所有節(jié)點組成抽象語法樹(Abstract Syntax Tree宗挥,AST)。 語法分析程序判斷源程序結(jié)構(gòu)上是否正確种蝶。
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

  • 作用域契耿、類型運算方式十分清晰螃征。( 語法樹一次只能處理一次計算搪桂。兩次運算,就得多分一層級盯滚。)

    image
  • 語法分析踢械,就是在生成語法樹完成檢測的。

  • 頭文件找不到時魄藕,可以指定SDK:
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.2.sdk(自己SDK路徑) -fmodules -fsyntax-only -Xclang -ast-dump main.m

3.4 生成中間代碼IR(Intermediate representation)

3.4.1 生成中間代碼
  • 完成以上步驟后内列,就開始生成中間代碼IR,代碼生成器(Code Generation)會將語法樹自頂向下遍歷逐步翻譯成LLVMIR背率。

  • 便于理解话瞧,我們簡化代碼:

#import <stdio.h>

int test(int a, int b) {
    return a + b + 3;
}

int main(int argc, const char * argv[]) {
    int a = test(1,2);
    printf("%d",a);
    return 0;
}

通過下面命令生成.ll文本文件,查看IR代碼:

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

  • IR基本語法
    @ 全局標(biāo)識
    % 局部標(biāo)識
    alloca 開辟空間
    align 內(nèi)存對齊
    i32 32個bit寝姿,4個字節(jié)
    store 寫入內(nèi)存
    load 讀取數(shù)據(jù)
    call 調(diào)用數(shù)據(jù)
    ret 返回
  • 使用VSCodeSublime Text可以打開代碼:(可以指定文件語言交排,讓代碼高亮色
image
  • Q:圖中為何多創(chuàng)建那么多局部變量?(如test函數(shù)內(nèi)的a5饵筑、a6)
  • 因為在上一階段(編譯階段)埃篓,我們將代碼編譯成了語法樹結(jié)構(gòu)。而此時翻翩,我們只是沿語法樹進行讀取都许。 語法樹每一個層級稻薇,都需要一個臨時變量承接。再返回上一層級處理胶征。
  • 所以會產(chǎn)生那么多局部變量塞椎。
3.4.2 IR優(yōu)化
  • 我們可以在XcodeBuild Settings中搜索Optimization,可以看到優(yōu)化級別。
    (Debug模式默認None [O0]無優(yōu)化睛低,Release模式默認Fastest,Smallest [Os]最快最小)
image
  • LLVM的優(yōu)化級別分為 -O0案狠、 -O1-O2钱雷、 -O3骂铁、-Os(第一個字母是Optimization的O)。

  • 分別選擇O0Os兩個優(yōu)化等級進行中間代碼的生成比較:

clang -S -fobjc-arc -emit-llvm main.m -o mainO0.ll      //  O0  無優(yōu)化
clang -Os -S -fobjc-arc -emit-llvm main.m -o mainOs.ll  //  Os 最快最小

image
image
  • 優(yōu)化后的代碼罩抗,舒服多了拉庵。之前那些冗余臨時局部變量,也都被優(yōu)化套蒂,代碼量減少很多钞支。
3.4.3 bitCode再優(yōu)化
  • Xcode7之后開啟bitCode蘋果會再進一步優(yōu)化操刀,生成.bc中間代碼烁挟。

優(yōu)化體現(xiàn):上傳APPstore的包,針對不同型號手機做了區(qū)分骨坑,不同型號手機下載時撼嗓,大小不同

clang -emit-llvm -c main.ll -o main.bc

3.5 生成匯編代碼

  • 完成中間代碼的生成后欢唾,可以將代碼轉(zhuǎn)變匯編代碼了且警。

  • 此刻我們有4種不同程度的代碼(源代碼->無優(yōu)化IR代碼->Os優(yōu)化IR代碼 -> bitcode優(yōu)化代碼):

    image
  • 分別對4種程度的代碼輸出匯編文件:

clang -S -fobjc-arc main.m -o main.s
clang -S -fobjc-arc main.ll -o mainO0.s
clang -S -fobjc-arc mainOs.ll -o mainOs.s
clang -S -fobjc-arc main.bc -o mainbc.s

image

可以看到在生成匯編代碼時,只有選擇優(yōu)化等級匈辱,才能減少匯編代碼量振湾。

【拓展】在生成中間代碼前后杀迹,都可以進行優(yōu)化亡脸。

  • [嘗試一] 將main.m直接選擇Os級別優(yōu)化生成.s匯編文件
clang -Os -S -fobjc-arc main.m -o mainOs.s

  • [嘗試二] 將main.m生成無優(yōu)化main.s,再main.s選擇Os級別優(yōu)化生成.s匯編文件
clang -S -fobjc-arc -emit-llvm main.m -o mainO0.ll
clang -Os -S -fobjc-arc mainO0.ll -o mainOoOs.s

  • [嘗試三] 將main.m選擇Os級別優(yōu)化生成main.s树酪,再main.s選擇無優(yōu)化級別生成.s匯編文件
clang -Os -S -fobjc-arc -emit-llvm main.m -o mainOs.ll
clang -S -fobjc-arc mainOs.ll -o mainOsOo.s

  • [嘗試四] 將main.m選擇Os級別優(yōu)化生成main.s浅碾,再main.s選擇Os級別優(yōu)化生成.s匯編文件
clang -Os -S -fobjc-arc -emit-llvm main.m -o mainOs.ll
clang -Os -S -fobjc-arc mainOs.ll -o mainOsOs.s

  • 內(nèi)容比較:
image

3.6 生成目標(biāo)文件(機器代碼)

  • 生成匯編文件后,匯編器匯編代碼作為輸入续语,將匯編代碼轉(zhuǎn)換機器代碼垂谢,輸出目標(biāo)文件(object file)
clang -fmodules -c main.s -o main.o

  • file對比一下main.s匯編代碼和main.o機器代碼:
file main3.m
file main.o

image
  • xcrun執(zhí)行nm命令查看main.o文件中的符號:
xcrun nm -nm main.o

image
  • 此時只是把當(dāng)前文件編譯為了機器碼外部符號(如printf)無法識別疮茄。

undefined: 表示當(dāng)前文件暫時找不到符號滥朱。
external:表示這個符號外部可以訪問的根暑。(實現(xiàn)不在我這,在外部某個地方

所以當(dāng)前雖轉(zhuǎn)換成了機器代碼徙邻。但是只是目標(biāo)文件排嫌,并不能直接執(zhí)行,需要所有資源鏈接起來缰犁,才可以執(zhí)行淳地。

3.7 生成可執(zhí)行文件(鏈接)

  • 通過鏈接器把編譯產(chǎn)生的.o文件和.dylib.a文件鏈接關(guān)聯(lián)起來帅容,生成真正的mach-o可執(zhí)行文件
clang main.o -o main // 將目標(biāo)文件轉(zhuǎn)成可執(zhí)行文件
file main            // 查看文件
xcrun nm -nm main    // 查看main的符號

image
  • 對比main.o目標(biāo)文件颇象,此時生成的main文件:
  1. object文件變成了executable可執(zhí)行文件
  2. 雖然都有undefined,但是可執(zhí)行文件中指定了該符號來源庫并徘。機器在運行時遣钳,會從相應(yīng)的庫中取讀取符號(printf)

至此桑孩,我們已完整分析:源代碼可執(zhí)行文件整個流程

image

4. LLVM源碼 & 編譯流程

?? ?? ?? 【注意】

  1. LLVM源碼2.29G境肾,編譯后文件30G,請確保電腦硬盤空間足夠挟伙;
  2. 編譯時路幸,電腦溫度會飆升90多度荐开,請用空調(diào)伺候著,可能黑屏简肴;
  3. 編譯時間長達1個多小時晃听,請合理安排時間。

如果以上3點砰识,你確定能接受能扒,那我們就開始吧。

4.1 LLVM下載

拓展:

  • github上的官方源碼:https://github.com/llvm/llvm-project(國內(nèi)網(wǎng)絡(luò)限制)
  • 需要注意的是初斑,官方源碼不能直接編譯,需要下載clang膨处、compiler-rt见秤、libcxxlibcxxabi這4個庫真椿。
  • 建議使用上面Gitee源鹃答。

4.2 LLVM編譯

  • 30G90°C突硝,1hour之旅测摔,begin~
  • 最新的LLVM只支持cmake編譯,需要使用Homebrew安裝cmake:
4.2.1 安裝cmake
  • 查看brew列表,檢查是否安裝過cmake锋八,如果有浙于,就跳過此步驟
brew list

  • 如果沒有,就使用brew安裝:
brew install cmake

如果報權(quán)限錯誤挟纱,可sudo chown -Rwhoami:admin /usr/local/share放開權(quán)限

image
4.2.2 編譯llvm
  • cmake編譯成Xcode項目
cd llvm-project
mkdir build
cd build
cmake -G Xcode ../llvm   
// 或者: cmake -G Xcode CMAKE_BUILD_TYPE="Release" ../llvm
// 或者: cmake -G Xcode CMAKE_BUILD_TYPE="debug" ../llvm 

  • 成功之后路媚,可以看到生成的Xcode文件:

    image
  • 打開LLVM.xcodeproj,選擇自動創(chuàng)建Schemes樊销。

    image
  • 自動創(chuàng)建完成后整慎,選擇ALL_BUILD進行編譯(耗時0.5-1小時,CPU滿負荷運轉(zhuǎn))

    image
  • 編譯完成。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末围苫,一起剝皮案震驚了整個濱河市裤园,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌剂府,老刑警劉巖拧揽,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異腺占,居然都是意外死亡淤袜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門衰伯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铡羡,“玉大人,你說我怎么就攤上這事意鲸》持埽” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵怎顾,是天一觀的道長读慎。 經(jīng)常有香客問我,道長槐雾,這世上最難降的妖魔是什么夭委? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮募强,結(jié)果婚禮上株灸,老公的妹妹穿的比我還像新娘。我一直安慰自己钻注,他們只是感情好蚂且,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著幅恋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泵肄。 梳的紋絲不亂的頭發(fā)上捆交,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天淑翼,我揣著相機與錄音,去河邊找鬼品追。 笑死玄括,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肉瓦。 我是一名探鬼主播遭京,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼泞莉!你這毒婦竟也來了哪雕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鲫趁,失蹤者是張志新(化名)和其女友劉穎斯嚎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挨厚,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡堡僻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了疫剃。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钉疫。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖巢价,靈堂內(nèi)的尸體忽然破棺而出陌选,到底是詐尸還是另有隱情,我是刑警寧澤蹄溉,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布咨油,位于F島的核電站,受9級特大地震影響柒爵,放射性物質(zhì)發(fā)生泄漏役电。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一棉胀、第九天 我趴在偏房一處隱蔽的房頂上張望法瑟。 院中可真熱鬧,春花似錦唁奢、人聲如沸霎挟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽酥夭。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間熬北,已是汗流浹背疙描。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留讶隐,地道東北人起胰。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像巫延,于是被迫代替她去往敵國和親效五。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

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