LLVM

什么是編譯器

編譯器就是將我們寫的程序,比如用C, Jave, C++, 匯編等語言寫的程序救拉,經(jīng)過編譯器的轉(zhuǎn)換,把這些語言轉(zhuǎn)換成計算機或者微型處理器能夠識別的機器代碼,它是由0和1組成的序列损合,說白了,就是相當于英語翻譯成中文的工具一樣娘纷。

編譯型語言和解釋型語言的區(qū)別嫁审?

  • 編譯型語言
    需通過編譯器(compiler)將源代碼編譯成機器碼,之后才能執(zhí)行的語言赖晶。一般需經(jīng)過編譯(compile)律适、鏈接(linker)這兩個步驟。編譯是把源代碼編譯成機器碼遏插,鏈接是把各個模塊的機器碼和依賴庫串連起來生成可執(zhí)行文件擦耀。
    代表語言:C、C++涩堤、Pascal、Object-C以及最近很火的蘋果新語言swift
  • 解釋性語言的程序不需要編譯分瘾,相比編譯型語言省了道工序胎围,解釋性語言在運行程序的時候才逐行翻譯
    代表語言:JavaScript德召、Python白魂、Erlang、PHP上岗、Perl福荸、Ruby

LLVM概述

LLVM是架構(gòu)編譯器的框架系統(tǒng),以C++編寫而成肴掷,用于優(yōu)化任意程序語言編寫的程序的編譯時間(compile-time)敬锐、鏈接時間(link-time)、運行時間(run-time)以及空閑時間(idle-time)呆瞻。對開發(fā)者保持開放台夺,并兼容已有腳本

LLVM計劃啟動于2000年,最初由美國UIUC大學的Chris Lattner博士主持開展痴脾。2006年Chris Lattner加盟Apple Inc颤介,并致力于LLVM在Apple開放體系中的應用。
Apple也是LLVM計劃的主要自助者。
目前LLVM已經(jīng)被蘋果iOS開發(fā)工具滚朵、Xilinx Vivado冤灾、FacebookGoogle等各大公司采用

傳統(tǒng)編譯器設計

源碼Source Code + 前端 Frontend + 優(yōu)化器 Optimizer + 后端 Backend(代碼生成器 CodeGenerator)+ 機器碼 Machine Code

傳統(tǒng)編譯器設計
iOS的編譯器架構(gòu)

OC辕近、C韵吨、C++使用的編譯器前端是Clang,Swift是swift亏推,后端都是LLVM学赛,如下圖所示

ios的編譯器架構(gòu)

模塊職責

  • 編譯器前端(Frontend)
    編譯器前端的任務是解析源代碼。它會進行:詞法分析吞杭、語法分析盏浇、語義分析檢查源代碼是否存在錯誤芽狗,然后構(gòu)建抽象語法樹(Abstract Syntax Tree, AST)绢掰;
    LLVM的前端還會生成中間代碼(intermediate representation, IR)。
  • 優(yōu)化器(Optimizer)
    優(yōu)化器負責進行各種優(yōu)化童擎。改善代碼的運行時間滴劲,例如:消除冗余計算等。
  • 后端(Backend)/代碼生成器(Code Generator)
    將代碼映射到目標指令集顾复。生成機器代碼班挖,并進行機器代碼的相關優(yōu)化。
LLVM的設計

LLVM設計的最重要方面是芯砸,使用通用的代碼表示形式(IR)萧芙,它是用來在編譯器中表示代碼的形式,所有LLVM可以為任何編程語言獨立編寫前端假丧,并且可以為任意硬件架構(gòu)獨立編寫后端

LLVM的設計

LLVM的設計是前后端分離的双揪,無論前端還是后端發(fā)生變化,都不會影響另一個

Clang

clang是LLVM項目中的一個子項目包帚,它是基于LLVM架構(gòu)圖輕量級編譯器渔期,誕生之初是為了替代GCC,提供更快的編譯速度渴邦,它是負責C疯趟、C++OC語言的編譯器谋梭,屬于整個LLVM架構(gòu)中的編譯器前端迅办,對于開發(fā)者來說,研究Clang可以給我們帶來很多好處章蚣。

編譯流程上:詞法&語法&IR

創(chuàng)建main.m文件站欺,編寫代碼如下

#import <Foundation/Foundation.h> 
int main(int argc, const char * argv[]) { 
    return 0;
}

通過命令clang -ccc-print-phases main.m姨夹,打印源碼的編譯階段

//輸出以下內(nèi)容: 
                +- 0: input, "main.m", objective-c 
             +- 1: preprocessor, {0}, objective-c-cpp-output 
         +- 2: compiler, {1}, ir 
      +- 3: backend, {2}, assembler 
   +- 4: assembler, {3}, object 
+- 5: linker, {4}, image 
6: bind-arch, "x86_64", {5}, image
  • 0:輸入文件,找到源文件
  • 1:預處理階段矾策,這個過程包括宏的替換磷账,頭文件的導入
  • 2:編譯階段,進行詞法分析贾虽、語法分析逃糟、檢測語法是否正確,最終生成IR
  • 3:后端蓬豁,LLVM會通過一個一個的Pass去優(yōu)化绰咽,每個Pass做一些事情,最終生成匯編代碼
  • 4:生成.o目標文件
  • 5:鏈接地粪,鏈接需要的動態(tài)庫靜態(tài)庫取募,生成可執(zhí)行文件
  • 6:通過不同的架構(gòu),生成對應的可執(zhí)行文件
預處理階段

宏和導入的頭文件進行替換蟆技,main.m文件修改代碼如下

#import <stdio.h> 
#define C 30 
int main(int argc, const char * argv[]) {
    int a = 10; 
    int b = 20;
    printf("%d",a + b + C); 
    return 0; 
}

通過命令玩敏,打印預處理階段

//在終端直接查看替換結(jié)果
clang -E main.m

//生成對應的文件查看替換后的源碼,-E做預處理生成預處理后的文件 
clang -E main.m >> main1.m

//輸出以下內(nèi)容:
# 1 "main.m" 
# 1 "<built-in>" 1 
# 1 "<built-in>" 3 
# 378 "<built-in>" 3 
# 1 "<command line>" 1 
# 1 "<built-in>" 2 
# 1 "main.m" 2
......
int main(int argc, const char * argv[]) {
    int a = 10; 
    int b = 20; 
    printf("%d",a + b + 30);
    return 0; 
}

展開宏和stdio頭文件质礼,main函數(shù)中原本+ C變?yōu)?code>+ 30

使用define和typedef的區(qū)別:
  • define:宏定義旺聚,在預處理階段會被替換
    可用來做代碼混淆,將App中核心代碼眶蕉,用系統(tǒng)相似的名稱進行取別名砰粹,然后在預處理階段就被替換,以此達到代碼混淆的目的
  • typedef:對數(shù)據(jù)類型取別名造挽,在預處理階段不會被替換掉伸眶。typedef不是預處理指令
編譯階段

編譯階段可劃分為三個部分:詞法分析語法分析刽宪、生成IR中間代碼

  • 詞法分析
    預處理完成后界酒,就會進行詞法分析圣拄,這里會把代碼切成一個個Token,例如:大小括號毁欣,等于號庇谆,還有字符串
// 查看詞法分析結(jié)果命令
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
// 如果頭文件找不到,指定sdk
clang -isysroot (自己SDK路徑) -fmodules -fsyntax-only -Xclang -dump-tokens main.m
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -fmodules -fsyntax-only -Xclang -dump-tokens main.m
詞法分析之后的結(jié)果
  • 語法分析
    詞法分析完成后就是語法分析凭疮,它的任務是驗證語法是否正確饭耳,在詞法分析的基礎上將單詞序列組合成各類語法短語,如程序执解、語句寞肖、表達式等等,然后將所有節(jié)點組成抽象語法樹(Abstract Syntax Tree , AST),語法分析程序判斷程序在結(jié)構(gòu)上是否正確
// 查看語法分析結(jié)果命令
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
// 如果導入頭文件找不到新蟆,可以指定SDK
clang -isysroot (自己SDK路徑) -fmodules -fsyntax-only -Xclang -ast-dump main.m
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -fmodules -fsyntax-only -Xclang -ast-dump main.m
語法分析之后的結(jié)果1

重點關鍵字的介紹:FunctionDecl:函數(shù)觅赊、ParmVarDecl:參數(shù)CallExpr:函數(shù)調(diào)用琼稻、BinaryOperator:運算符

  • 生成IR中間代碼
    完成以上步驟后吮螺,就會開始生成IR中間代碼代碼生成器(Code Generator)會將語法樹自頂向下遍歷帕翻,逐步翻譯成LLVM IR

main.m文件修改代碼如下

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代碼鸠补。OC代碼在這一步會進行runtime橋接:property合成嘀掸、ARC處理

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

查看IR中間代碼

image.png
//以下是IR基本語法
@ 全局標識
% 局部標識
alloca 開辟空間
align 內(nèi)存對齊
i32 32bit紫岩,4個字節(jié)
store 寫入內(nèi)存
load 讀取數(shù)據(jù)
call 調(diào)用函數(shù)
ret 返回
  • IR的優(yōu)化
    Xcode中找到TargetBuild SettingOptimization Level,可以對當前項目設置優(yōu)化等級

在LLVM中横殴,優(yōu)化級別分別是-O0被因、-O1-O2衫仑、-O3梨与、-Os(第一個是大寫英文字母O)。

通過以下命令文狱,可設置優(yōu)化等級粥鞋,并生成IR代碼:
clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll

查看優(yōu)化后的IR代碼

image.png

xcode7以后開啟bitcode,蘋果會做進一步優(yōu)化瞄崇,生成.bc的中間代碼呻粹,我們通過優(yōu)化后的IR代碼生成.bc代碼

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

小結(jié):編譯階段流程
輸入代碼 -> 展開預處理 -> 詞法分析(token) -> 語法分析(AST語法樹) -> IR代碼 -> IR代碼優(yōu)化

編譯流程下:生成可執(zhí)行文件

什么是Bitcode?

Bitcode是被編譯程序的一種中間形式的代碼苏研。包含Bitcode并上傳到App Store Connect的App等浊,會在App Store上編譯和鏈接。包含Bitcode可以在不提交新版本App的情況下摹蘑,允許Apple在將來的時候再次優(yōu)化你的App二進制文件筹燕。
在Xcode中,默認開啟Bitcode設置衅鹿。如果你的App支持Bitcode撒踪,App使用到的其他二進制形式也要支持Bitcode,否則就會報錯大渤。

解決Bitcode報錯只有兩種方案:

  • 將不支持Bitcode的SDK移除掉制妄,或等待第三方更新
  • 將使用Bitcode的選項設置為NO
生成匯編代碼
  • 通過最終的.ll.bc代碼,生成匯編代碼
// 命令如下
clang -S -fobjc-arc main.ll -o main.s 
clang -S -fobjc-arc main.bc -o main.s

查看匯編代碼

image.png
  • 生成匯編代碼也可以進行優(yōu)化
clang -Os -S -fobjc-arc main.m -o main.s
生成目標文件(匯編器)

目標文件的生成泵三,是匯編器匯編代碼作為插入耕捞,將匯編代碼轉(zhuǎn)換為機器代碼衔掸,最后輸出目標文件(object file)

// 命令
clang -fmodules -c main.s -o main.o

通過nm命令,查看main.o中的符號

xcrun nm -nm main.o 
 
//輸出以下內(nèi)容: 
             (undefined) external _printf 
0000000000000000 (__TEXT,__text) external _main
  • _printf函數(shù)被標記為undefined 砸脊、external
  • undefined表示在當前文件暫時找不到符號_printf
  • external表示這個符號是外部可以訪問的
生成可執(zhí)行文件(鏈接)

鏈接主要是鏈接需要的動態(tài)庫靜態(tài)庫具篇,生成可執(zhí)行文件,其中

  • 靜態(tài)庫會和可執(zhí)行文件合并
  • 動態(tài)庫是獨立的

鏈接器把編譯生成的.o文件.dyld .a文件鏈接凌埂,生成一個mach-o可執(zhí)行文件

clang main.o -o main

查看鏈接后可執(zhí)行文件的符號

xcrun nm -nm main 

//輸出以下內(nèi)容:
             (undefined) external _printf (from libSystem)                
             (undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header 
0000000100003f77 (__TEXT,__text) external _main 
0000000100008008 (__DATA,__data) non-external __dyld_private
  • 鏈接后_printf符號可以找到所屬的動態(tài)庫驱显,但依然被標記為undefined。因為libSystem屬于系統(tǒng)動態(tài)庫瞳抓,在運行時進行動態(tài)綁定埃疫。
  • 鏈接后還多了dyld_stub_binder符號,它在運行時用于符號的重綁定
  1. printf函數(shù)為例孩哑,printf函數(shù)存在于libSystem系統(tǒng)庫中栓霜,它存在于懶加載符號表中。它的函數(shù)地址在運行時横蜒,首次對printf函數(shù)進行調(diào)用胳蛮,才會通過dyld_stub_binder進行重綁定。
  2. dyld_stub_binder函數(shù)地址的綁定時機:當dyld加載主程序時丛晌,符號被dyld直接綁定仅炊。
總結(jié)
LLVM的編譯流程

創(chuàng)建Clang插件

由于國內(nèi)的網(wǎng)絡限制,我們需要借助鏡像下載LLVM的源碼
https://mirror.tuna.tsinghua.edu.cn/help/llvm/

  • 下載llvm項目
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/llvm.git
  • LLVMtools目錄下下載Clang
cd llvm/tools 
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang.git
  • LLVMprojects目錄下下載compiler-rt澎蛛、libcxx抚垄、libcxxabi
cd ../projects 
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/compiler-rt.g 
it
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxx.git 
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxxabi.git
  • Clangtools下安裝extra工具
cd ../tools/clang/tools 
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang-tools-extra.git
LLVM編譯

由于最新的LLVM只支持cmake來編譯,所以需要安裝cmake

安裝cmake

  • 查看brew是否安裝cmake谋逻,如果已經(jīng)安裝呆馁,則跳過下面步驟
brew list
  • 通過brew安裝cmake
brew install cmake
通過Xcode編譯LLVM
  • cmake編譯成Xcode項目:
mkdir build_xcode 
cd build_xcode 
cmake -G Xcode ../llvm
  • 使用Xcode編譯Clang
  1. 選擇自動創(chuàng)建Schemes
image.png
  1. 編譯(CMD + B),選擇ALL_BUILD Secheme進行編譯,預計1+小時
image.png
通過ninja編譯LLVM
  • 使用ninja進行編譯則還需要安裝ninja,使用$ brew install ninja命令即可安裝ninja
  • LLVM源碼根目錄下新建一個build_ninja目錄沃呢,最終會在build_ninja目錄下生成build.ninja
  • LLVM源碼根目錄下新建llvm_release目錄,最終編譯文件會在llvm_release文件夾路徑下
$ cd llvm_build
//注意DCMAKE_INSTALL_PREFIX后面不能有空格
$ cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX= 安裝路徑(本機為/ Users/xxx/xxx/LLVM/llvm_release)
  • 依次執(zhí)行編譯纺腊,安裝指令
$ ninja
$ ninja install
創(chuàng)建Clang插件
  • /llvm/tools/clang/tools目錄下,新建插件HKPlugin
image.png
  • 修改/llvm/tools/clang/tools目錄下的CMakeLists.txt文件
image.png
  • 新增add_clang_subdirectory(HKPlugin)
image.png
  • HKPlugin目錄下送巡,新建HKPlugi.cppCMakeLists.txt文件
image.png
  • 打開CMakeLists.txt文件寫入以下內(nèi)容
add_llvm_library( HKPlugin MODULE BUILDTREE_ONLY 
    HKPlugin.cpp 
)
  • 接下來利用cmake重新生成Xcode項目,在build_xcode目錄下執(zhí)行以下命令
cmake -G Xcode ../llvm
  • 最后在LLVMXcode項目中盒卸,Loadable modules目錄下找到自定義Plugin目錄骗爆,就可以在里面編寫插件代碼了
image.png

課后總結(jié)

LLVM概述

  • LLVM編譯器(重點):
  1. 編譯型語言 && 解釋型語言
  2. 編譯器
    前端:讀取代碼,詞法分析蔽介,語法分析摘投,生成AST(LLVM:IR)
    優(yōu)化器:根據(jù)一個又一個Pass進行優(yōu)化
    后端:生成匯編代碼煮寡,根據(jù)不同的架構(gòu)生成不同的可執(zhí)行文件
  3. llvm的好處是啥?
    前后端分離犀呼,擴展性非常強幸撕。
  • LLVM編譯流程(重點):
  • Clang插件
  1. 編譯LLVM工程
  2. 創(chuàng)建插件
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市外臂,隨后出現(xiàn)的幾起案子坐儿,更是在濱河造成了極大的恐慌,老刑警劉巖宋光,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件貌矿,死亡現(xiàn)場離奇詭異,居然都是意外死亡罪佳,警方通過查閱死者的電腦和手機逛漫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赘艳,“玉大人酌毡,你說我怎么就攤上這事±俟埽” “怎么了枷踏?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長娇掏。 經(jīng)常有香客問我呕寝,道長,這世上最難降的妖魔是什么婴梧? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任下梢,我火速辦了婚禮,結(jié)果婚禮上塞蹭,老公的妹妹穿的比我還像新娘孽江。我一直安慰自己,他們只是感情好番电,可當我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布岗屏。 她就那樣靜靜地躺著,像睡著了一般漱办。 火紅的嫁衣襯著肌膚如雪这刷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天娩井,我揣著相機與錄音暇屋,去河邊找鬼。 笑死洞辣,一個胖子當著我的面吹牛咐刨,可吹牛的內(nèi)容都是我干的昙衅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼定鸟,長吁一口氣:“原來是場噩夢啊……” “哼而涉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起联予,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤啼县,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后躯泰,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谭羔,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年麦向,在試婚紗的時候發(fā)現(xiàn)自己被綠了瘟裸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡诵竭,死狀恐怖话告,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情卵慰,我是刑警寧澤沙郭,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站裳朋,受9級特大地震影響病线,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鲤嫡,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一送挑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧暖眼,春花似錦惕耕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至栋豫,卻和暖如春挤安,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背丧鸯。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工蛤铜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓昂羡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親摔踱。 傳聞我的和親對象是個殘疾皇子虐先,可洞房花燭夜當晚...
    茶點故事閱讀 44,665評論 2 354