iOS LLVM編譯流程

LLVM

LLVM是架構編譯器(compiler)的框架系統,由C++編寫完成,用于優化已任意程序編寫的程序的編譯時間(compile-time)、鏈接時間(link-time)、運行時間(run-time)以及空閑時間(idle-time),對開發者保持開放,并兼容已有腳本

傳統編譯器設計

  • 編譯器前端(Frontend):負責解析源代碼,它會進行詞法分析、語法分析、語義分析和檢查源代碼是否存在錯誤,然后構建抽象語法樹(Abstract Syntax Tree,AST)LLVM會生成中間代碼(intermediate representation IR)

  • 優化器(Optimizer):負責進行各種優化,改善代碼的運行時間,例如消除冗余計算等

  • 后端(Backend)/代碼生成器(CodeGenerator):將代碼映射到目標指令集。生成機器語言,并進行機器相關的代碼優化

傳統編譯器設計

iOS的編譯器架構

OC/C/C++使用的編譯器前端是Clang,swift前端是Swift,后端都是LLVMClang會生成中間代碼(intermediate representation IR)

iOS編譯器架構

LLVM設計

使用通用的代碼表示形式(IR),用來在編譯器中表示代碼的形式,所有LLVM可以為任何編程語言獨立編寫前端,并且可以為任意硬件架構獨立編寫后端

LLVM

Clang簡介

Clang是LLVM項目中的子項目,基于LLVM架構的輕量級編譯器,誕生之初是為了替代GCC,提高更快的編譯速度。它是負責編譯C、C++、Object-C語言的編譯器,在整個LLVM架構中屬于編譯器前端

編譯流程

  • 單獨新建一個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;[圖片上傳中...(image.png-1984f6-1624280414052-0)]

}
  • 通過命令clang -ccc-print-phases main.m
    LLVM編譯流程
//輸入文件:找到源文件
+- 0: input, "main.m", objective-c
//預處理階段:處理包括宏的替換,頭文件的導入
+- 1: preprocessor, {0}, objective-c-cpp-output
//編譯階段:進行詞法、語法分析,檢測語法是否正確,最終生成IR
+- 2: compiler, {1}, ir
//后端:LLVM會通過一個一個的Pass去優化,每個Pass做些事情,最終生成匯編代碼
+- 3: backend, {2}, assembler
//生成目標文件
+- 4: assembler, {3}, object
//鏈接:鏈接需要的動態庫和靜態庫,生成可執行文件
+- 5: linker, {4}, image
//通過不同的架構,生成對應的可執行文件
6: bind-arch, "x86_64", {5}, image

1、預處理階段

預處理階段是進行宏的替換和頭文件的導入,我們可以通過下面命令來查看導入和替換情況

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

//生成對應的文件查看替換后的源碼
clang -E main.m >> main2.m
  • typedef給數據類型取別名時,在預處理階段不會被替換
  • define的取的別名在預處理階段會被替換,所有我們可以用這方法來進行關鍵代碼混淆,例如將關鍵類和方法用系統類似的名稱取別名

2、編譯階段

編譯階段主要是進行:詞法、語法分析和代碼錯誤檢查,然后生成中間代碼IR

(1)詞法分析

預處理完成后就會進行詞法分析,會把代碼切成一個個Token,比如大小括號,等于號還有字符串等

  • 通過下面命令查看詞法分析后結果
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
(2)語法分析

驗證語法是否正確,在詞法分析的基礎上將單詞序列組合成各類語法短語,如程序、語句、表達式等等,然后將所有節點組成抽象語法樹(Abstract Syntax Tree,AST)。語法分析程序判斷源程序在結構上是否正確

  • 我們可以通過下面命令查看語法分析的結果
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
語法分析
  • 如果導入頭文件找不到,可以指定SDK
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -fmodules -fsyntax-only -Xclang -ast-dump main.m

下面是幾個關鍵字的含義

  • -FunctionDecl 函數
  • -ParmVarDecl 參數
  • -CallExpr 調用一個函數
  • -BinaryOperator 運算符
(3)生成中間代碼IR

代碼生成器(Code Generaltion)會將語法樹自頂向下遍歷逐步翻譯成LLVM IR,OC代碼會在這一步進行runtime的橋接:property合成,ARC處理等

  • 我們可以通過下面命令生成.ll的文本文件,來查看IR代碼
clang -S -fobjc-arc -emit-llvm main.m

IR基本語法
@ 全局標識
% 局部標識
alloca 開辟空間
align 內存對齊
i32 32bit,4個字節
store 寫入內存
load 讀取數據
call 調用函數
ret 返回

IR代碼
  • IR文件在OC中是可以進行優化的,一般設置是在target - Build Setting - Optimization Level(優化器等級)中設置。LLVM的優化級別分別是-O0 -O1 -O2 -O3 -Os(第一個是大寫英文字母O),下面是帶優化的生成中間代碼IR的命令
clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll
優化后的IR代碼
  • xcode7以后開啟bitcode,蘋果會做進一步優化,生成.bc的中間代碼,我們通過優化后的IR代碼生成.bc代碼
clang -emit-llvm -c main.ll -o main.bc
.bc代碼

3、后端

LLVM在后端主要是會通過一個個的Pass去優化,每個Pass做一些事情,最終生成匯編代碼

  • 我們通過最終的.bc或者.ll代碼生成匯編代碼
clang -S -fobjc-arc main.bc -o main.s 
clang -S -fobjc-arc main.ll -o main.s
  • 生成的匯編代碼也可以進行優化
clang -Os -S -fobjc-arc main.m -o main.s
匯編代碼main.s

4、生成目標文件

是匯編器以匯編代碼作為插入,將匯編代碼轉換為機器代碼,最后輸出目標文件(object file)

clang -fmodules -c main.s -o main.o
  • 通過nm命令,查看main.o中的符號
xcrun nm -nm main.o
main.o中的符號
  • external表示這個符號是可以外部訪問
  • _printf函數是一個是undefined 、external
  • undefined表示在當前文件暫時找不到符號_printf

5、鏈接

鏈接主要是鏈接需要的動態庫和靜態庫

  • 靜態庫和可執行文件合并
  • 動態庫是獨立鏈接

鏈接器會吧編譯生成的.o文件(.dylib.a)文件,生成一個mach-o文件

clang main.o -o main

查看鏈接后的符號

xcrun nm -nm main

如下所示,其中undefined表示會在運行時進行動態綁定

mach-o文件符號

6、綁定

綁定主要是通過不同的架構,生成對應的mach-o格式可執行文件

總結

LLVM編譯流程
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容