iOS 編譯詳解 LLVM Clang
我們有很多維度可以將計算機語言進行分類,其中以編譯/執行方式為維度,可以將計算機語言分為:
-
編譯型語言
- C++ Objective C Swift Kotlin
- 先通過編譯器生成機器碼,機器碼可以直接在 CPU 上執行
- :+1: 執行效率較高
- :-1: 調試周期長
-
直譯式語言(指令碼語言)
- JavaScript Python
- 不需要經過編譯,在執行時通過一箇中間的直譯器將程式碼解釋為 CPU 可以執行的程式碼
- :+1: 編寫除錯方便
- :-1: 執行效率低
編譯型語言和直譯式語言的編譯過程如下

iOS 編譯工具
iOS 在 Xcode 5 版本前使用的是 GCC 編譯器,在 Xcode 5 中將 GCC 徹底拋棄,替換為了 LLVM 。LLVM 包含了編譯器前端、優化器和編譯器後端三大模組。其中 Swift 除了在編譯器前端和 Objective-C 稍有不同,其他模組都差不多。
Objective-C 採用 Clang 作為編譯器前端

Swift 採用 Swift 作為編譯器前端

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 是 LLVM 優化和進行程式碼生成的關鍵,因為 IR 提供了獨立於任何特定機器架構的源語。
在這一點上 IR 和 JVM 的 Java bytecode
很像,兩者都是用於表述計算的模型,但兩者所處的抽象層次不同。Java bytecode 更高層(更抽象),包含了大量類 Java 的面嚮物件語言的操作。LLVM IR 則更底層(更接近機器)。IR 的存在意味著它可以作為多種語言的後端,這樣就能夠提供語言無關的優化同時還能夠方便的針對多種 CPU 的程式碼生成。
編譯器的架構分為前端、優化器和後端。傳統編譯器(如 CGG )的前端和後端沒有完全分離,耦合在了一起,因而如果要支援一門新的語言或硬體平臺,需要做大量的工作。而 LLVM 和傳統編譯器最大的不同點在於,前端輸入的任何語言,在經過編譯器前端處理後,生成的中間碼都是 IR 格式的。
傳統的靜態編譯器

LLVM 編譯器

這樣做的優點是如果需要支援一種新的程式語言,那麼我們只需要實現一種新的前端。如果我們需要支援一種新的硬體裝置,那我們只需要實現一個新的後端。而優化階段因為是針對了統一的 LLVM IR ,所以它是一個通用的階段,不論是支援新的程式語言,還是支援新的硬體裝置,這裡都不需要對優化階段做修改。所以從這裡可以看出 LLVM IR 的作用。
LLVM IR 主要有三種格式:一種是在記憶體中的編譯中間語言;一種是硬碟上儲存的二進位制中間語言(以 .bc
結尾),最後一種是可讀的中間格式(以 .ll
結尾)。這三種中間格式是完全等價的。
Bitcode
Bitcode 是 LLVM 編譯器的中間程式碼的一種編碼,本質上就是我們前面提到的 IR 三種格式中的其中一種,是儲存在磁碟上的二進位制檔案。
之所以要把 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 編譯出來的版本,並由此發現了一個可能繞過稽核的漏洞。
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 的主要工作:
CodeGen
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 型別容器(類似
Array
或String
)實現了高階優化
通過分析和檢查的安全 SIL 會被 IRGen 轉換成 LLVM IR,並進一步接受 LLVM 的優化。
##動手實操
- 首先,我們寫一個簡單的程式,只有一個入口函式和簡單的邏輯。
#import <Foundation/Foundation.h> #define DEFINEEight 8 void test(int a, int b) { int c = a + b - DEFINEEight; } 複製程式碼
- 寫好程式碼後,通過以下命令,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; } 複製程式碼
-
預處理完成後就會進行詞法分析,這裡會把程式碼切成一個個 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> 複製程式碼
-
-
然後是語法分析,驗證語法是否正確。確認無誤後將所有節點組成抽象語法樹 AST 。
-
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:語句
-
拿到 AST 後 Clang 靜態分析器( Clang static analyzer )會對程式碼進行靜態分析。
Clang static analyzer 的架構包含了一個 Analyzer core 核心分析引擎和用於檢查具體程式碼的 checkers ,所有的 checkers 都是基於 analyzer core 提供的基礎功能來實現具體的程式碼檢查的。
AST 生成後 Clang static analyzer 會使用 checkers 對程式碼進行檢查,比如是否使用了未宣告的變數等等。你也可以編寫新的 checkers 來新增自定義檢查。這種方式能夠方便使用者擴充套件對程式碼檢查規則或者對 bug 型別進行擴充套件,但是這種架構也有不足,每執行完一條語句後,分析引擎會遍歷所有 checker 中的回撥函式,所以 checker 越多,速度越慢。
Clang 的靜態分析器不僅能夠將出現問題的程式碼位置暴露出來,還能夠提供多個修復程式碼的方法。
-
完成這些步驟後就可以開始 IR 中間程式碼的生成了,CodeGen 會負責將 AST 自上向下遍歷逐步翻譯成 LLVM IR。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程式碼其實已經很像機器碼了。
-
這裡 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_msgSend
,super
翻譯成objc_msgSendSuper
-
strong
,weak
,copy
,atomic
合成@property
自動實現setter
和getter
-
@synthesize
的處理 - 生成
block_layout
資料結構 -
_block
和__weak
-
_block
_invoke
- ARC 處理,插入
objc_storeStrong
和objc_storeWeak
等 ARC 程式碼 - ObjCAutoreleasePoolStmt 轉 objc_autorealeasePoolPush / Pop,自動新增
[super dealloc]
,給每個ivar
的類合成.cxx_destructor
方法自動釋放類的成員變數。
-
如果開啟了 Bitcode , 蘋果會做進一步的優化
-
clang -emit-llvm -c main.m -o main.bc 複製程式碼
-
生成彙編
clang -S -fobjc-arc main.m -o main.s 複製程式碼
-
生成目標檔案
clang -fmodules -c main.m -o main.o 複製程式碼
-
生成可執行檔案
clang main.o -o main 複製程式碼
-
執行
./main 複製程式碼
總結
LLVM 編譯過程
- 預處理
- 詞法分析
- 語法分析
- 生成 AST
- 靜態分析
- 生成 LLVM IR
- 編譯器優化
- Bitcode (可選)
- 生成彙編
- 生成目標檔案
- 生成可執行檔案
LLVM 分工
編譯器前端的任務是進行:預處理,語法分析,語義分析,生成中間程式碼( IR )。在這個過程中,會進行型別檢查,如果發現錯誤或者警告會標註出來在哪一行。
編譯器後端會進行機器無關的程式碼優化,生成機器語言,並且進行機器相關的程式碼優化。iOS 的編譯過程,後端的處理如下
LLVM 機器碼生成器會針對不同的架構,比如 arm64 等生成不同的機器碼。
編譯器前端的工作

編譯器後端

編譯器後端生成的產物

實際應用
深入瞭解 LLVM 和 Clang ,以及他們提供的開發工具,我們能夠實現很多有意思的功能。比如通過 Libclang、libTooling ,我們可以實現語法樹分析、語言轉化(例如將 Objective-C 轉換為 Swift )。我們還可以開發自己的 Clang 外掛,對我們的程式碼做個性化的檢查。我們還可以通過寫 Pass 實現自定義的程式碼優化、程式碼混淆。我們甚至可以開發自己的新語言,你需要做的僅僅是寫一個編譯器前端,將你的程式碼轉換成 LLVM IR 。
除此之外,瞭解了編譯內部的實現過程和細節,也同樣有助於我們在解決問題時找到新的思路。 OCEval 是 iOS 的一個動態執行熱修復的第三方開源庫,和 js 的 eval
函式類似,這個庫可以將字串形式的 Objective-C 程式碼轉換成實際的執行程式碼並執行,其中非常重要的一個實現細節就是實現了一套簡單的詞法分析和語法分析,將字串轉成了 AST 並最終獲得執行所需要的方法名、引數等。