iOS啟動優化 —— LLVM編譯流程 & Clang插件開發


1. LLVM

1.1 LLVM概述

LLVM是架構編譯器的框架系統,以C++編寫而成,用於優化任意程序語言編寫的程序的編譯時間(compile-time)鏈接時間(link-time)運行時間(run-time)以及空閑時間(idle-time)。對開發者保持開放,並兼容已有腳本。目前LLVM已經被蘋果IOS開發工具,Xilinx Vivado, Facebook,Google等各大公司采用。

1.2 傳統編譯器設計

源碼 Source Code + 前端 Frontend + 優化器 Optimizer + 后端 Backend(代碼生成器 CodeGenerator)+ 機器碼 Machine Code,如下圖所示

  • 前端Frontend:負責解析源代碼,它會進行:詞法分析語法分析語義分析,檢查源代碼是否存在錯誤。然后構建針對語言的抽象語法樹AST:Abstract Syntax Tree。LLVM 的前端還會生成中間代碼(intermediate representation,簡稱IR)

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

  • 后端 Backend(代碼生成器 Code Generator):將代碼映射到目標指令集,生成機器代碼,並且進行機器代碼相關的代碼優化;

1.3 ios的編譯器架構

OC、C、C++使用的編譯器前端是ClangSwift是swift,后端都是LLVM,如下圖所示

1.4 LLVM的設計

LLVM設計的最重要方面是,使用通用的代碼表示形式(IR),它是用來在編譯器中表示代碼的形式,所有LLVM可以為任何編程語言獨立編寫前端,並且可以為任意硬件架構獨立編寫后端,做到了前后端分離如下所示

傳統的編譯器,前端,優化器和后端是連在一起的,是一個項目。但是在llvm中,前端和后端分開了,兩者中間有一個通用的中間層,也就是IR。前端解析源代碼,然后詞法分析語法分析語義分析AST等工作完成之后,生成IR輸出給優化器,優化器負責優化IR代碼,然后后端接受IR代碼后根據需要適配的設備生成X86、ARM64等。所以,當出現一個新設備,只需要研發一個新設備的后端。出現一個高級語言,就研發高級語言的前端。這樣就能支持所有的語言和設備。

1.5 Clang

clangLLVM項目中的一個子項目,它是基於LLVM架構圖的輕量級編譯器,誕生之初是為了替代GCC,提供更快的編譯速度,它是負責C、C++、OC語言的編譯器,屬於整個LLVM架構中的 編譯器前端,對於開發者來說,研究Clang可以給我們帶來很多好處

2. 編譯流程

可以通過以下命令打印源碼的編譯階段:

clang -ccc-print-phases main.m

這里新建一個后通過命令打印源碼的編譯階段:

  • 0 - 輸入文件:找到源文件
  • 1 - 預處理階段:這個過程處理包括宏的替換,頭文件的導入
  • 2 - 編譯階段:進行詞法分析、語法分析、檢測語法是否正確,最終生成IR
  • 3 - 后端:這里LLVM會通過一個一個的pass去優化,每個pass做一些事情,最終生成匯編代碼
  • 4 - 匯編代碼生成目標文件
  • 5 - 鏈接:鏈接需要的動態庫和靜態庫,生成可執行文件
  • 6 - 綁定:通過不同的架構,生成對應架構的可執行文件

在main.m中輸入一些代碼。

然后通過 指令clang -E main.m >> main1.m生成預處理之后的文件。

開頭是一些宏展開和.h文件的展開。

然后最后看到main函數,這里成C沒有了,變成了30.

所以我們得出:

  • typedef不是預處理指令,也就是說:typedef可以給數據類型取別名,但是在預處理階段不會被替換掉
  • define則在預處理階段會被替換,所以經常被是用來進行代碼混淆,目的是為了app安全

3. 編譯階段

編譯階段會進行詞法分析語法分析檢測語法是否正確,最終生成IR。

3.1 詞法分析

預處理完成后就會進行詞法分析,這里會把代碼切成一個個Token,比如大小括號等於號還有字符串等。 通過下列指令來查看詞法分析

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

詞法分析結果:

3.2 語法分析

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

通過下列指令來查看語法分析

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

得到下面的結果(這里面的地址是虛擬地址,還沒開辟內存,可以看作是文件的偏移地址):

  • FunctionDecl :函數方法聲明。
  • ParmVarDecl: 參數聲明。
  • CompoundStmt:復合語句。
  • CallExpr:函數調用。
  • BinaryOperator: 運算符。
  • ImplicitCastExpr:函數指針。
  • DeclRefExpr:函數類型。

3.3 生成中間代碼IR

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

簡化一下代碼:

然后通過下列指令來生成 .ll 的文本文件,查看IR代碼。

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

生成IR代碼如下(這一步會進行語法檢查):

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

上面的IR代碼是沒有經過優化的,所以會比較長。 LLVM的優化級別分別是: -O0 , -O1 , -O2 , -O3 , -Os 。 可以在xcode里面 target -> Build Settings -> optimization Level 設置優化等級。

輸入下列指令來生成優化后的IR代碼

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

下面是優化后的IR代碼,可以明顯看出來代碼少了很多。優化等級並不是越高越好的,一般情況下,debug模式下是不進行優化的,而在release模式下是-Os 優化等級

3.4 bitCode

xcode7以后開啟bitcode,蘋果會做進一步優化,生成.bc的中間代碼,我們通過優化后的IR代碼生成.bc代碼。Bitcode的目的是根據不同的CPU架構,蘋果能夠在APPStore直接下載不同的架構的包

輸入下列指令來生成bc代碼。

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

4. 生成匯編代碼

到了這一步,這里就到了backend。這里LLVM會通過一個一個的pass去優化,每個pass做一些事情,最終生成匯編代碼。

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

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

這里分別通過main.llmain.bcmain.m來生成匯編之后進行對比。

main.bc生成的匯編代碼:

main.ll生成的匯編代碼:

main.m生成的匯編代碼:

這里發現通過main.bc 和 main.ll 生成的匯編代碼都是54行,說明並沒有額外進行代碼優化。main.m是沒有經過優化的源碼,轉化為匯編后則多了幾行代碼。那么這里的代碼是否還能進行優化呢?試一下。 輸入以下代碼

clang -Os -S -fobjc-arc main.bc -o main3.s

這是指令運行后得到的代碼,發現比之前的又少了幾行,這就說明:當選定了優化等級了之后,在不同的節點上,還能進行優化。

5. 生成目標文件(匯編器)

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

通過以下指令來生產.o文件

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

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

$xcrun nm -nm main.o

指令執行后發現輸出下面的結果:

  • _printf函數是一個是undefinedexternal
  • undefined表示在當前文件暫時找不到符號_printf
  • external表示這個符號是外部可以訪問

這里為什么undefined了呢?因為這里調用了外部的方法,這個時候就需要鏈接了。

6. 生成可執行文件(鏈接)

鏈接主要是鏈接需要的動態庫和靜態庫,生成可執行文件。

連接器把編譯生成的.o文件和 .dyld .a文件鏈接,生成一個mach-o文件

clang main.o -o main

查看鏈接之后的符號

$xcrun nm -nm main

指令執行后得到下面的結果:

這里看到有兩個undefined,一個是_printf,一個是dyld_stub_binder,但是后面都有(from libSystem)。這里的dyld_stub_binder也是一個外部函數,在dyld里面,當mach-o 進入到內存之后,外部符號就會和binder進行綁定。這個過程是dyld強制綁定的,這里就是去綁定_printf。 鏈接就是要知道內部的符號是在外面的哪個庫里面。綁定就是將外面的函數的地址和內部的符號進行綁定。鏈接編譯期綁定執行期。所以只要鏈接就一定有一個外部函數也就是dyld_stub_binder。

7. clang 插件

7.1 LLVM下載

	由於國內網絡限制,需要借助鏡像下載llvm的源碼鏈接: [link](https://mirror.tuna.tsinghua.edu.cn/help/llvm/).
復制代碼

下載LLVM項目

git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/llvm.git

在LLVM的tool目錄下下載Clang

cd llvm/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang.git

在LLVM的projects目錄下下載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

在Clang的tools下安裝extra工具

cd ../tools/clang/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang-tools-extra.git

7.2 LLVM 編譯

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

查看brew是否安裝cmake,如果已經安裝,則跳過下面步驟

brew list

通過brew安裝cmake

brew install cmake

7.3 編譯LLVM

通過xcode編譯LLVM

  • cmake編譯成Xcode項目
mkdir build_xcode
cd build_xcode
cmake -G Xcode ../llvm
  • 使用xcode編譯Clang

選擇手動創建schemes

編譯(CMD + B),選擇ALL_BUILD Secheme進行編譯,預計1+小時。

通過ninja編譯LLVM

使用ninja進行編譯則還需要安裝ninja,使用以下命令安裝ninja

brew install 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)

依次執行編譯,安裝指令

ninja
ninja install

7.4 創建插件

在/llvm/tools/clang/tools下新建插件LSPlugin

在/llvm/tools/clang/tools目錄下的CMakeLists.txt文件,新增add_clang_subdirectory(LSPlugin)。

在LSPlugin目錄下新建 LSPlugin.cpp 和CMakeLists.txt,並在CMakeLists.txt中加上以下代碼

add_llvm_library( HKPlugin MODULE BUILDTREE_ONLY
  LSPlugin.cpp
)

接下來利用cmake重新生成Xcode項目,在build_xcode目錄下執行以下命令

cmake -G Xcode ../llvm

最后可以在LLVM的xcode項目中可以看到Loadable modules目錄下由自定義的LSPlugin目錄了,然后可以在里面編寫插件代碼了。

面試基礎

iOS面試基礎知識 (一)

iOS面試基礎知識 (二)

iOS面試基礎知識 (三)

iOS面試基礎知識 (四)

iOS面試基礎知識 (五)

知識詳解

iOS面試要點之GCD面試要點

iOS面試要點之多線程面試要點

iOS面試要點之block面試要點

iOS面試要點之Runtime面試要點

iOS面試要點之RunLoop面試要點

iOS面試要點之內存管理面試要點

iOS面試要點之MVC、MVVM面試要點

iOS面試要點之網絡性能優化要點

iOS面試要點之網絡編程面試要點

iOS面試要點之KVC&KVO面試要點

iOS面試要點之數據存儲面試要點

iOS面試要點之混編技術面試要點

iOS面試要點之設計模式面試要點

iOS面試要點之UI面試要點


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM