iOS 編譯詳解 LLVM Clang

前言

語言類型

咱們有不少維度能夠將計算機語言進行分類,其中以編譯/執行方式爲維度,能夠將計算機語言分爲:前端

  • 編譯型語言git

    • C++ Objective C Swift Kotlin
    • 先經過編譯器生成機器碼,機器碼能夠直接在 CPU 上執行
    • 👍 執行效率較高
    • 👎 調試周期長
  • 直譯式語言(腳本語言)github

    • JavaScript Python
    • 不須要通過編譯,在執行時經過一箇中間的解釋器將代碼解釋爲 CPU 能夠執行的代碼
    • 👍 編寫調試方便
    • 👎 執行效率低

編譯型語言和直譯式語言的編譯過程以下objective-c

從上圖咱們能夠知道,編譯型語言須要在運行以前就將代碼所有編譯好,最終運行的文件是編譯後的可執行文件。咱們將編譯型語言所使用的編譯方式稱爲 AOT (Ahead of time) 預先編譯。編程

而直譯式語言則是在運行的過程當中,一邊編譯一邊執行,最終運行的文件其實就是一開始寫的源代碼。咱們將直譯式語言所使用的編譯方式稱爲 JIT (Just in time)即時編譯。swift

iOS 編譯工具

iOS 在 Xcode 5 版本前使用的是 GCC 編譯器,在 Xcode 5 中將 GCC 完全拋棄,替換爲了 LLVM 。LLVM 包含了編譯器前端、優化器和編譯器後端三大模塊。其中 Swift 除了在編譯器前端和 Objective-C 稍有不一樣,其餘模塊都差很少。後端

Objective-C 採用 Clang 做爲編譯器前端 數組

Swift 採用 Swift 做爲編譯器前端 xcode

LLVM

做者 Chris Lattner

Chris Lattner 在2000年開發了一個叫做 Low Level Virtual Machine 的編譯器開發工具套件,後來涉及範圍愈來愈大,能夠用於常規編譯器,JIT 編譯器,彙編器,調試器,靜態分析工具等一系列跟編程語言相關的工做,因而就把簡稱 LLVM 這個簡稱做爲了正式的名字。Chris Lattner 後來又開發了 Clang ,使得 LLVM 直接挑戰 GCC 的地位。安全

2005年加入蘋果,將蘋果使用的 GCC 全面轉爲 LLVM。

2010年開始主導開發 Swift 語言。

2017年離開了 Apple 入職特斯拉負責自動駕駛軟件的開發,並於同年下半年入職 Google 加入深度學習與人工智能研發團隊。

LLVM 簡介

LLVM 是一個開源的,模塊化和可重用的編譯器和工具鏈技術的集合,或者說是一個編譯器套件。

可使用 LLVM 來編譯 Kotlin,Ruby,Python,Haskell,Java,D,PHP,Pure,Lua 和許多其餘語言

LLVM 核心庫還提供一個優化器,對流行的 CPU 作代碼生成支持。

LLVM 同時支持 AOT 預先編譯和 JIT 即時編譯

2012年,LLVM 得到美國計算機學會 ACM 的軟件系統大獎,和 UNIX,WWW,TCP/IP,Tex,JAVA 等齊名。

LLVM IR

LLVM IR (Intermediate Representation)直譯過來是「中間描述」,它是整個編譯過程當中生成的區別於源碼和機器碼的一種中間代碼。IR 提供了獨立於任何特定機器架構的源語,所以它是 LLVM 優化和進行代碼生成的關鍵,也是 LLVM 有別於其餘編譯器的最大特色。LLVM 的核心功能都是圍繞的 IR 創建的,它是 LLVM 編譯過程當中前端的輸出,後端的輸入。

在這一點上 IR 和 JVM 的 Java bytecode 很像,二者都是用於表述計算的模型,但二者所處的抽象層次不一樣。Java bytecode 更高層(更抽象),包含了大量類 Java 的面嚮對象語言的操做。LLVM IR 則更底層(更接近機器)。IR 的存在乎味着它能夠做爲多種語言的後端,這樣 LLVM 就可以提供和語言無關的優化,同時還可以方便的針對多種 CPU 代碼生成。

爲何須要 IR

編譯器的架構分爲前端、優化器和後端。傳統編譯器(如 CGG )的前端和後端沒有徹底分離,耦合在了一塊兒,於是若是要支持一門新的語言或硬件平臺,須要作大量的工做。而 LLVM 和傳統編譯器最大的不一樣點在於,前端輸入的任何語言,在通過編譯器前端處理後,生成的中間碼都是 IR 格式的。

傳統的靜態編譯器

LLVM 編譯器

這樣作的優勢是若是須要支持一種新的編程語言,那麼咱們只須要實現一種新的前端。若是咱們須要支持一種新的硬件設備,那咱們只須要實現一個新的後端。而優化階段由於是針對了統一的 LLVM IR ,因此它是一個通用的階段,不管是支持新的編程語言,仍是支持新的硬件設備,這裏都不須要對優化階段作修改。因此從這裏能夠看出 LLVM IR 的做用。

LLVM IR 的三種格式:
  • 內存中的編譯中間語言
  • 硬盤上存儲的可讀中間格式(以 .ll 結尾)
  • 硬盤上存儲的二進制中間語言(以 .bc 結尾)

這三種中間格式是徹底等價的。

Bitcode

iOS 開發的小夥伴可能對 IR 不是很瞭解,但我相信你必定據說過 Bitcode 。Bitcode 說白了其實就是咱們前面提到的 LLVM IR 三種格式中的第三種,即存儲在磁盤上的二進制文件(以 .bc 結尾)。

之因此要把 Bitcode 拿出來單獨說,是由於 Apple 單獨對 Bitcode 進行了額外的優化。從 Xcode 7 開始,Apple 支持在提交 App 編譯產物的同時提交 App 的 Bitcode (非強制),而且以後對提交了 Bitcode 的 App 都單獨進行了雲端編譯打包。也就是說,即使在提交時已經將本地編譯好的 ipa 提交到 App Store,Apple 最終仍是會使用 Bitcode 在雲端再次打包,而且最終用戶下載到手機上的版本也是由 Apple 在雲端編譯出來的版本,而非開發人員在本地編譯的版本。

這裏有一篇文章Xcode 7 Bitcode的工做流程及安全性評估,揭示了360團隊如何經過一個小 trick 來驗證 Apple 審覈人員安裝的 App 是直接由本地編譯出來的版本仍是雲端經過 Bitcode 編譯出來的版本,並由此發現了一個可能繞過審覈的漏洞。

爲何須要 Bitcode

Apple 之因此這麼作,一是由於 Apple 能夠在雲端編譯過程當中作一些額外的針對性優化工做,而這些額外的優化是本地環境所沒法實現的。二是 Apple 能夠爲安裝 App 的目標設備進行二進制優化,減小安裝包的下載大小。

好比咱們在本地編譯生成的 ipa 是同時包含了多個CPU架構的(armv7 ,arm64 ),對於 iPhone X 而言 armv7 的架構文件就是無用文件。而 Apple 能夠在雲端爲不一樣的設備編譯出對應 CPU 架構的 ipa ,這樣 iPhone X 下載到的 App 就只包含了所需的 arm64 架構文件。

更爲黑科技的是,因爲 Bitcode 是無關設備架構的,它能夠被轉化爲任何被支持的 CPU 架構,包括如今還沒被髮明的 CPU 架構。之後若是蘋果新出了一款新手機而且 CPU 也是全新設計的,在蘋果後臺服務器同樣能夠從這個 App 的 Bitcode 開始編譯轉化爲新 CPU 上的可執行程序,可供新手機用戶下載運行這個 App ,而無需開發人員從新在本地編譯打包上傳。

Clang & Swift

Clang 編譯器

Clang 是 LLVM 的子項目,是 C、C++ 和 Objective-C 編譯器,目標是替代傳統編譯器 GCC 。Clang 在整個 Objective-C 編譯過程當中扮演了編譯器前端的角色,同時也參與到了 Swift 編譯過程當中的 Objective-C API 映射階段。

Clang 的主要功能是輸出代碼對應的抽象語法樹( AST ),針對用戶發生的編譯錯誤準確地給出建議,並將代碼編譯成 LLVM IR。

Clang 的特色是編譯速度快,模塊化,代碼簡單易懂,診斷信息可讀性強,佔用內存小以及容易擴展和重用等。

咱們以 Xcode 爲例,Clang 編譯 Objective-C 代碼的速度是 Xcode 5 版本前使用的 GCC 的3倍,其生成的 AST 所耗用掉的內存僅僅是 GCC 的五分之一左右。

Clang 的主要工做:

  • 預處理: 好比把宏嵌入到對應的位置,頭文件的導入,去除註釋( clang -E main.m )
  • 詞法分析: 這裏會把代碼切成一個個 Token,好比大小括號,等於號還有字符串等
  • 語法分析: 驗證語法是否正確
  • 生成 AST : 將全部節點組成抽象語法樹 AST
  • 靜態分析:分析代碼是否存在問題,給出錯誤信息和修復方案
  • 生成 LLVM IR: CodeGen 會負責將語法樹自頂向下遍歷逐步翻譯成 LLVM IR

Swift 編譯器

和 Clang 同樣,Swift 編譯器主要負責對 Swift 源代碼進行靜態分析和糾錯,並轉換爲 LLVM IR 。他是 Swift 編譯的前端模塊。不過和 Clang 不一樣,Swift 編譯器會多出 SIL optimizer ,它會先將 Swift 文件轉換成中間代碼 SIL ,而後再根據 SIL 生成 IR 。是否是以爲很複雜,Swift 編譯器會在編譯其間生成兩種不一樣的中間代碼,這是爲何呢?下面會有詳細的解釋。

Swift 編譯器的主要工做:

  • 解析:解析器負責生成沒有任何語義或類型信息的抽象語法樹( AST ),並針對輸入源的語法問題發出警告或錯誤
  • 詞法分析:獲取解析的 AST 並將其轉換爲格式良好,徹底類型檢查的AST形式,爲源代碼中的詞法問題發出警告或錯誤
  • Clang 導入器:導入 Clang 模塊並將它們導出的 C 或 Objective-C API 映射到相應的 Swift API
  • SIL 生成:將通過類型檢查的 AST 降級爲 SIL
  • SIL 規範化:執行額外的數據流診斷(例如使用未初始化的變量)
  • SIL 優化:爲程序執行額外的高級 Swift 特定優化,包括自動引用計數優化,虛擬化和通用專業化
  • LLVM IR 生成:將 SIL 降級到 LLVM IR

爲何要增長 SIL 層

Swift 中間語言( SWIFT Integration Layer )是一種高級的,特定於 Swift 的中間語言,適用於進一步分析和優化 Swift 代碼。SIL 屬於 High-Level IR,其相對於LLVM IR 的抽象層級更高,並且是特定於 Swift 語言的。

因爲源碼和 LLVM IR 之間存在着很是大的抽象鴻溝,IR 不適用對源碼進行分析和檢查。所以 Clang 使用了 Analysis 經過 CFG (控制流圖)來對代碼進行分析和檢查。可是 CFG 自己不夠精準,且不在主流程上(會和 IR 生成過程並行執行),所以 CFG 和 IR 生成中會出現部分重複分析,作無用功。

而在 Swift 的編譯過程當中,SIL 會在生成 LLVM IR 以前作好全部的分析和規範化,並在 IRGen 的幫助降低級到 LLVM IR ,避免了部分重複任務,也使得整個編譯流程更加統一。

並且由於 Swift 在編譯時就完成了方法綁定直接經過地址調用屬於強類型語言,方法調用再也不是像 Objective-C 那樣的消息轉發,這樣編譯就能夠得到更多的信息用在後面的後端優化上。所以咱們能夠在 SIL 上對 Swift 作針對性的優化,而這些優化是 LLVM IR 所沒法實現的。

這些優化包括:
  • 臨界拆分:不支持任意的基礎 block 參數經過終端進行臨界拆分
  • 泛型優化:分析泛型函數的特定調用,並生成新的特定版本的函數.而後將泛型的特定用法所有重寫爲對應的特定函數的直接調用
  • witness和虛函數表的去虛擬化優化:經過給定類型去查找關聯的類的虛函數表或者類型的 witness 表,並將虛函數調用替換爲調用函數映射
  • 內聯優化:對於transparent函數進行內聯
  • 內存提高:將 alloc_box 結構優化爲 alloc_stack
  • 引用計數優化
  • 高級領域特定優化:對基礎的 Swift 類型容器(相似 ArrayString )實現了高級優化

經過分析和檢查的安全 SIL 會被 IRGen 轉換成 LLVM IR,並進一步接受 LLVM 的優化。

##動手實操

  1. 首先,咱們寫一個簡單的程序,只有一個入口函數和簡單的邏輯。
#import <Foundation/Foundation.h>
#define DEFINEEight 8

void test(int a, int b) {
    int c = a + b - DEFINEEight;
}
複製代碼
  1. 寫好代碼後,經過如下命令,LLVM 會預處理你的代碼,好比把宏嵌入到對應的位置,頭文件的導入,去除註釋等。
clang -E main.m
複製代碼

獲得的就是這樣的代碼

# 1 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/Foundation.framework/Headers/FoundationLegacySwiftCompatibility.h" 1 3
# 185 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" 2 3
# 2 "main.m" 2


void test(int a, int b) {
    int c = a + b - 8;
}
複製代碼
  1. 預處理完成後就會進行詞法分析,這裏會把代碼切成一個個 Token ,每個 Token 都表明了一個特徵元素。

    Token 的分類
    • 關鍵字:語法中的關鍵字,if else while for 等。

    • 標識符:變量名

    • 字面量:值,數字,字符串

    • 特殊符號:加減乘除等符號

    經過如下代碼能夠獲得詞法分析後的代碼。

    clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
    複製代碼

    獲得的詞法分析代碼

    void 'void'      [StartOfLine]  Loc=<main.m:4:1>
    identifier 'test'        [LeadingSpace] Loc=<main.m:4:6>
    l_paren '('             Loc=<main.m:4:10>
    int 'int'               Loc=<main.m:4:11>
    identifier 'a'   [LeadingSpace] Loc=<main.m:4:15>
    comma ','               Loc=<main.m:4:16>
    int 'int'        [LeadingSpace] Loc=<main.m:4:18>
    identifier 'b'   [LeadingSpace] Loc=<main.m:4:22>
    r_paren ')'             Loc=<main.m:4:23>
    l_brace '{'      [LeadingSpace] Loc=<main.m:4:25>
    int 'int'        [StartOfLine] [LeadingSpace]   Loc=<main.m:5:5>
    identifier 'c'   [LeadingSpace] Loc=<main.m:5:9>
    equal '='        [LeadingSpace] Loc=<main.m:5:11>
    identifier 'a'   [LeadingSpace] Loc=<main.m:5:13>
    plus '+'         [LeadingSpace] Loc=<main.m:5:15>
    identifier 'b'   [LeadingSpace] Loc=<main.m:5:17>
    minus '-'        [LeadingSpace] Loc=<main.m:5:19>
    numeric_constant '8'     [LeadingSpace] Loc=<main.m:5:21 <Spelling=main.m:2:21>>
    semi ';'                Loc=<main.m:5:32>
    r_brace '}'      [StartOfLine]  Loc=<main.m:6:1>
    eof ''          Loc=<main.m:6:2>
    複製代碼
  2. 而後是語法分析,驗證語法是否正確。確認無誤後將全部節點組成抽象語法樹 AST 。

  3. clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
    複製代碼

    獲得的 AST

    |-FunctionDecl 0x7f9a2108a0c0 <line:4:1, line:6:1> line:4:6 test 'void (int, int)'
    | |-ParmVarDecl 0x7f9a21089f48 <col:11, col:15> col:15 used a 'int'
    | |-ParmVarDecl 0x7f9a21089fc0 <col:18, col:22> col:22 used b 'int'
    | `-CompoundStmt 0x7f9a2108a348 <col:25, line:6:1>
    |   `-DeclStmt 0x7f9a2108a330 <line:5:5, col:32>
    |     `-VarDecl 0x7f9a2108a1e0 <col:5, line:2:21> line:5:9 c 'int' cinit
    |       `-BinaryOperator 0x7f9a2108a308 <col:13, line:2:21> 'int' '-'
    |         |-BinaryOperator 0x7f9a2108a2c0 <line:5:13, col:17> 'int' '+'
    |         | |-ImplicitCastExpr 0x7f9a2108a290 <col:13> 'int' <LValueToRValue>
    |         | | `-DeclRefExpr 0x7f9a2108a240 <col:13> 'int' lvalue ParmVar 0x7f9a21089f48 'a' 'int'
    |         | `-ImplicitCastExpr 0x7f9a2108a2a8 <col:17> 'int' <LValueToRValue>
    |         |   `-DeclRefExpr 0x7f9a2108a268 <col:17> 'int' lvalue ParmVar 0x7f9a21089fc0 'b' 'int'
    |         `-IntegerLiteral 0x7f9a2108a2e8 <line:2:21> 'int' 8
    `-<undeserialized declarations>
    複製代碼

    爲了方便查看,咱們將 AST 以樹狀圖的形式表示

    節點的分類:

    TranslationUnitDecl:根節點,表示一個源文件

    Decl:聲明

    Expr:表達式

    Literal:字面量,是特殊的 Expr

    Stmt:語句

  4. 拿到 AST 後 Clang 靜態分析器( Clang static analyzer )會對代碼進行靜態分析。

    Clang static analyzer 的架構包含了一個 Analyzer core 核心分析引擎和用於檢查具體代碼的 checkers ,全部的 checkers 都是基於 analyzer core 提供的基礎功能來實現具體的代碼檢查的。

    AST 生成後 Clang static analyzer 會使用 checkers 對代碼進行檢查,好比是否使用了未聲明的變量等等。你也能夠編寫新的 checkers 來添加自定義檢查。這種方式可以方便用戶擴展對代碼檢查規則或者對 bug 類型進行擴展,可是這種架構也有不足,每執行完一條語句後,分析引擎會遍歷全部 checker 中的回調函數,因此 checker 越多,速度越慢。

    Clang 的靜態分析器不只可以將出現問題的代碼位置暴露出來,還可以提供多個修復代碼的方法。

  5. 完成這些步驟後就能夠開始 IR 中間代碼的生成了,CodeGen 會負責將 AST 自上向下遍歷逐步翻譯成 LLVM IR。

    clang -S -fobjc-arc -emit-llvm main.m -o main.ll
    複製代碼

    生成的代碼以下(此處僅截取了 test 方法)

    ; Function Attrs: noinline nounwind optnone ssp uwtable
    define void @test(i32, i32) #0 {
      %3 = alloca i32, align 4
      %4 = alloca i32, align 4
      %5 = alloca i32, align 4
      store i32 %0, i32* %3, align 4
      store i32 %1, i32* %4, align 4
      %6 = load i32, i32* %3, align 4
      %7 = load i32, i32* %4, align 4
      %8 = add nsw i32 %6, %7
      %9 = sub nsw i32 %8, 8
      store i32 %9, i32* %5, align 4
      ret void
    }
    複製代碼

    是否是看的頭大了。其實 IR 也不是很難,稍微瞭解一下 IR 的語法就可以讀懂其中的邏輯了。

    LLVM IR 語法:

    ; 註釋

    @ 全局

    % 局部

    alloca 分配內存空間

    i32 32bit,即4個字節

    align 內存對齊

    Store 寫入內存

    load 讀取內存

    icmp 兩個整數值比較,返回布爾值

    br 選擇分支,根據 cond 來轉向 label,不根據條件跳轉的話相似 goto

    indirectbr 根據條件間接跳轉到一個 label,而這個 label 通常是在一個數組裏,因此跳轉目標是可變的,由運行時決定的

    label 代碼標籤

    若是有學習過機器碼的同窗有沒有發現,IR代碼其實已經很像機器碼了。

  6. 這裏 LLVM 會去作些優化工做,在 Xcode 的編譯設置裏也能夠設置優化級別 -01 / -03 / -0s ,還能夠寫些本身的 Pass

    Pass 是 LLVM 優化工做的一個節點,一個節點作些事,一塊兒加起來就構成了 LLVM 完整的優化和轉化。

    咱們能夠經過在上面一段命令中加入 -O3 / -O2 優化參數來控制優化登記。

    clang -O3 -S -fobjc-arc -emit-llvm main.m -o main.ll
    複製代碼

    下面經過一個小例子,來展現 LLVM 對 IR 所作的具體優化。咱們先寫一個方法,包含一個循環語句。

    int main() {
        int i = DEFINEEight;
        while (i < 10) {
            i++;
            printf("%d",i);
        }
        return 0;
    }
    複製代碼

    使用 -O3 參數生成 IR

    ; Function Attrs: nounwind ssp uwtable
    define void @demo() local_unnamed_addr #0 {
      %1 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i64 0, i64 0), i32 1), !clang.arc.no_objc_arc_exceptions !9
      %2 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i64 0, i64 0), i32 2), !clang.arc.no_objc_arc_exceptions !9
      %3 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i64 0, i64 0), i32 3), !clang.arc.no_objc_arc_exceptions !9
      %4 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i64 0, i64 0), i32 4), !clang.arc.no_objc_arc_exceptions !9
      %5 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i64 0, i64 0), i32 5), !clang.arc.no_objc_arc_exceptions !9
      %6 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i64 0, i64 0), i32 6), !clang.arc.no_objc_arc_exceptions !9
      %7 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i64 0, i64 0), i32 7), !clang.arc.no_objc_arc_exceptions !9
      %8 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i64 0, i64 0), i32 8), !clang.arc.no_objc_arc_exceptions !9
      %9 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i64 0, i64 0), i32 9), !clang.arc.no_objc_arc_exceptions !9
      %10 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i64 0, i64 0), i32 10), !clang.arc.no_objc_arc_exceptions !9
      ret void
    }
    複製代碼

    此時咱們將循環條件 (i < 10) 修改成 (i < 100)

    ; Function Attrs: nounwind ssp uwtable
    define void @demo() local_unnamed_addr #0 {
      br label %1
    
    ; <label>:1:                                      ; preds = %1, %0
      %2 = phi i32 [ 0, %0 ], [ %3, %1 ]
      %3 = add nuw nsw i32 %2, 1
      %4 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i64 0, i64 0), i32 %3), !clang.arc.no_objc_arc_exceptions !9
      %5 = icmp eq i32 %3, 100
      br i1 %5, label %6, label %1
    
    ; <label>:6:                                      ; preds = %1
      ret void
    }
    複製代碼

    能夠發現當循環次數較低時,生成的 IR 會直接將全部循環的邏輯都寫出來,將執行效率最大化。而當循環次數過大時,則會退而使用更爲複雜的邏輯去實現。 除了上面的這種簡單優化,LLVM 還提供了其餘優化 Pass:

    • 各類類,方法,成員變量等的結構體的生成,並將其放到對應的 Mach-O 的 section 中
    • Non-Fragile ABI 合成 OBJC_IVAR_$_ 偏移值常量
    • ObjCMessageExpr 翻譯成相應版本的 objc_msgSendsuper 翻譯成 objc_msgSendSuper
    • strongweakcopyatomic 合成@property 自動實現 settergetter
    • @synthesize 的處理
    • 生成 block_layout 數據結構
    • _block__weak
    • _block _invoke
    • ARC 處理,插入 objc_storeStrongobjc_storeWeak 等 ARC 代碼
    • ObjCAutoreleasePoolStmt 轉 objc_autorealeasePoolPush / Pop,自動添加 [super dealloc],給每一個 ivar 的類合成 .cxx_destructor 方法自動釋放類的成員變量。
  7. 若是開啓了 Bitcode , 蘋果會作進一步的優化

  8. clang -emit-llvm -c main.m -o main.bc
    複製代碼
  9. 生成彙編

    clang -S -fobjc-arc main.m -o main.s
    複製代碼
  10. 生成目標文件

    clang -fmodules -c main.m -o main.o
    複製代碼
  11. 生成可執行文件

    clang main.o -o main
    複製代碼
  12. 執行

    ./main
    複製代碼

總結

LLVM 編譯過程

  • 預處理
  • 詞法分析
  • 語法分析
  • 生成 AST
  • 靜態分析
  • 生成 LLVM IR
  • 編譯器優化
  • Bitcode (可選)
  • 生成彙編
  • 生成目標文件
  • 生成可執行文件

LLVM 分工

編譯器前端

在 iOS 編譯中,Clang 就是整個編譯器的前端。它包含了詞法分析器、語法分析器、靜態分析器、IR 生成器等一系列組件。這些組件共同協做,爲 LLVM 提供了預處理,語法分析,語義分析,靜態分析、錯誤處理、生成 IR 等各類各樣的功能。

編譯器優化

IR 是編譯器前端的輸出,也是編譯器後端的輸入,在整個 LLVM 編譯器中擔任承上啓下的角色。能夠說,LLVM 的核心功能都是圍繞着 IR 而構建的。LLVM 的優化器經過各類各樣的 Pass 來直接優化 IR,使得代碼優化過程和語言、平臺無關,大大提高了開發效率。

編譯器後端

在 iOS 編譯中,編譯器後端其實就是 LLVM 本身提供的一套後端。它包含了 機器碼生成器、連接器等工具,會對 IR 進行機器無關的代碼優化,生成機器語言。

編譯器後端生成的產物

LLVM 機器碼生成器會針對不一樣的架構,生成不一樣的機器碼。

實際應用

深刻了解 LLVM 和 Clang ,以及他們提供的開發工具,咱們可以實現不少有意思的功能。好比經過 Libclang、libTooling ,咱們能夠實現語法樹分析、語言轉化(例如將 Objective-C 轉換爲 Swift )。咱們還能夠開發本身的 Clang 插件,對咱們的代碼作個性化的檢查。咱們還能夠經過寫 Pass 實現自定義的代碼優化、代碼混淆。咱們甚至能夠開發本身的新語言,你須要作的僅僅是寫一個編譯器前端,將你的代碼轉換成 LLVM IR 。

除此以外,瞭解了編譯內部的實現過程和細節,也一樣有助於咱們在解決問題時找到新的思路。OCEval 是 iOS 的一個動態執行熱修復的第三方開源庫,和 js 的 eval 函數相似,這個庫能夠將字符串形式的 Objective-C 代碼轉換成實際的運行代碼並執行,其中很是重要的一個實現細節就是實現了一套簡單的詞法分析和語法分析,將字符串轉成了 AST 並最終得到執行所須要的方法名、參數等。


引用

深刻剖析 iOS 編譯 Clang LLVM By 戴銘

iOS編譯過程的原理和應用 By 🐺面對疾風吧

深刻淺出 iOS 編譯 By 🐺面對疾風吧

Swift的高級中間語言:SIL By sea_biscute

LLVM

Clang

Swift

相關文章
相關標籤/搜索