本文將簡單介紹 Clang LLVM 相關的知識,然后介紹一下代碼是如何一步步的編譯運行的,以及可以利用 clang 能做些什么。
簡介
編譯器就是語言翻譯器,把高級語言翻譯成計算機能夠執行的機器語言。
語言翻譯主要工作流程:
源代碼 (source code) → 預處理器 (preprocessor) → 編譯器 (compiler) → 目標代碼 (object code) → 鏈接器 (linker) → 可執行程序 (executables)
LLVM (Low Level Virtual Machine) 是一個開源的編譯器架構。Clang 是 LLVM 的一個編譯器前端,它目前支持 C, C++, Objective-C 等編程語言。
Clang 對源程序進行預處理、詞法分析、語法分析,并將分析結果轉換為 Abstract Syntax Tree ( 抽象語法樹 ) ,最后使用 LLVM 作為編譯器后端代碼的生成器。
Clang 的開發目標是提供一個可以替代 GCC 的前端編譯器。Apple 對 Objective-C 新增很多特性,想做的很多功能,比如更好的IDE支持,GCC 不能很好的支持,于是,蘋果開發了 Clang 與 LLVM 來完全取代GCC。Clang作為編譯器前端,LLVM作為編譯器后端。
與 GCC 相比,Clang 是一個重新設計的編譯器前端,具有一系列優點,例如模塊化,代碼簡單易懂,占用內存小以及容易擴展和重用等。由于 Clang 在設計上的優異性,使得 Clang 非常適合用于設計源代碼級別的分析和轉化工具。Clang 也已經被應用到一些重要的開發領域,如 Static Analysis 是一個基于 Clang 的靜態代碼分析工具。
用 Clang 編譯 OC 程序
當用 Xcode 創建了項目,然后點擊 run 運行的時候,可以在 Xcode 中看到編譯的信息:
看一下編譯 main.m 文件的信息,相當于執行了一長串的命令,其中命令的參數就是你在 Build Settings 里面設置的一些選項,拼接成了這一串命令,主要的就是調用 Clang 編譯的命令:
clang -x objective-c -fobjc-arc ... main.m -o main.o
clang 命令:
在命令行中 clang 相當于一個黑盒的驅動,里面封裝了編譯管線、前段命令、LLVM 命令、Toolchain 命令等。
clang 編譯的過程
下面通過編譯 main.m 文件,來看一下編譯的過程。main.m 中是很簡單的打印代碼:
#import <Foundation/Foundation.h>
int main() {
@autoreleasepool {
NSLog(@"Hello world!");
}
return 0;
}
命令行輸入命令:
clang -fobjc-arc -framework Foundation main.m -o main
-fobjc-arc 表示編譯器需要支持 ARC 特性
-framework Foundation 表示引用Foundation框架
上面的命令會生成可執行文件 main,然后命令行輸入執行文件 main:
./main
看到運行結果:
Hello world!
實質上,上述編譯過程是分為四個階段進行的,即預處理(Preprocess)、編譯(Compilation)、匯編 (Assembly)和連接(Linking)。
1、預處理(Preprocess)
這個步驟會進行,import 頭文件的處理,宏定義的展開,#開頭的預處理指令等,的處理。
預處理的命令:
clang -E main.m 或者 clang -E main.m -o test.i
查看文件可看到頭部,十幾萬行的預處理
-fmodules 參數可以把那些庫打包導入,import Foundation,這樣每次編譯都不用展開那么多東西
clang -E -fmodules main.m
2、詞法分析(Lexical Analysis)
詞法分析,將預處理過的代碼轉化成一個個的 Token,對應的命令為:
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
處理后的結果為:
可看到代碼,拆分成了一個個的 Token
3、語法分析(Semantic Analysis)
語法分析,在 clang 中由 Parser 和 Sema 兩個模塊配合完成,來驗證語法是否正確。
提示代碼哪里寫錯了,少了冒號、括號等一些提示。
根據當前語言的語法,生成語義節點,并將所有節點組合成抽象語法樹(AST: Abstract Syntax Tree),對應的命令為:
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
處理后,生成的語法樹:
FunctionDecl,表示函數名 main
CompoundStmt,表示大括號 {}
ObjCAutoreleasePoolStmt,表示 @autoreleasepool
靜態分析:
編譯器把源碼生成了抽象語法樹,編譯器可以對這棵樹做分析處理,以找出代碼中的錯誤,比如類型檢查:即檢查程序中是否有類型錯誤。例如:如果代碼中給某個對象發送了一個消息,編譯器會檢查這個對象是否實現了這個消息(函數、方法)。此外,clang 對整個程序還做了其它更高級的一些分析,以確保程序沒有錯誤。
還有一種類型檢查:動態分析
動態的在運行時做檢查,靜態的在編譯時做檢查。編寫代碼時可以向任意對象發送任何消息,在運行時,才會檢查對象是否能夠響應這些消息。
4、IR 代碼生成 (CodeGen)
clang 完成代碼的標記,解析和分析后,接著就會生成 LLVM 代碼。將上一步的語法樹從頂至下遍歷,翻譯成 LLVM IR。
LLVM IR 是編譯前端的輸出,也是 LLVM 后端的輸入,是前后端橋接語言。
與 OC Runtime 橋接:
Class、Meta Class、Protocol、Category 內存結構生成,并存放在指定 section 中(如 Class:_DATA, _objc_classrefs)
Method、Ivar、Property 內存結構生成
組成 method_list、ivar_list、property_list 并填入 Class
Non-Fragile ABI:為每個 Ivar 合成 OBJC_IVAR_$_ 偏移值常量
存取 Ivar 的語句(_ivar = 123; int a = _ivar;)
轉寫成 base + OBJC_IVAR_$_的形式將語法樹中的 ObjCMessageExpr 翻譯成相應版本的 obj_msgSend,對 super 關鍵字的調用翻譯成 objc_msgSendSuper
根據修飾符 strong、weak、copy、atomic 合成 @property 自動實現的 setter、getter
處理 @synthesize
生成 block_layout 的數據結構
變量的 capture(__block __weak)
生成 _block_invoke 函數
ARC 分析對象引用關系,將 obj_storeStrong/objc_storeWeak 等 ARC 代碼插入
將 ObjcCAutoreleasePoolStmt 轉譯成 objc_autoreleasePoolPush/Pop
實現自動調用 [super dealloc]
為每個擁有 ivar 的 Class 合成 .cxx_destructor 方法來自動釋放類的成員變量,代替 MRC 時代的 self.xxx = nil
可以使用下面的命令,查看生成 IR 代碼:
clang -S -fobjc-arc -emit-llvm main.m -o main.ll
-S 編譯到匯編層面
-fobjc-arc 開啟ARC
-emit-llvm 生成中間的LLVM語言
執行命令后可得到文件 main.ll:
define i32 @main() #0 {
%1 = alloca i32, align 4
store i32 0, i32* %1, align 4
%2 = call i8* @llvm.objc.autoreleasePoolPush() #1
notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*))
call void @llvm.objc.autoreleasePoolPop(i8* %2)
ret i32 0
}
這里 LLVM 會去做些優化工作,在 Xcode 的編譯設置里也可以設置優化級別 -O1,-O3,-Os :
clang -O3 -S -fobjc-arc -emit-llvm main.m -o main.ll
下面是優化后的中間LLVM代碼
define i32 @main() local_unnamed_addr #0 {
%1 = tail call i8* @llvm.objc.autoreleasePoolPush() #1
notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*)), !clang.arc.no_objc_arc_exceptions !9
tail call void @llvm.objc.autoreleasePoolPop(i8* %1) #1
ret i32 0
}
生成字節碼 LLVM Bitcode
clang -emit-llvm -c main.m -o main.bc
將上面生成的 IR 代碼生成二進制碼格式
5、生成 Target 相關匯編(Assemble)
生成對應機器相關的匯編語言:
clang -S -fobjc-arc main.m -o main.s
生成 Target 相關 Mach-O 文件
clang -fmodules -c main.m -o main.o
Mach-O(Mach object)文件,是一種用于記錄可執行文件、對象代碼、共享庫、動態加載代碼和內存轉儲的文件格式,是macOS/iOS上程序以及庫的標準格式。
6、生成可執行文件
鏈接生成可執行文件 main。
將程序的目標文件與所需的所有附加的目標文件(靜態連接庫和動態連接庫)連接起來,最終生成可執行文件。
clang main.o -o main
執行就可以看到程序運行
./main
7、編譯過程小結
- 預處理
- 符號化 (Tokenization)
- 宏定義的展開
- #include 的展開
- 語法和語義分析
- 將符號化后的內容轉化為一棵解析樹 (parse tree)
- 解析樹做語義分析
- 輸出一棵抽象語法樹(Abstract Syntax Tree* (AST))
- 生成代碼和優化
- 將 AST 轉換為更低級的中間碼 (LLVM IR)
- 對生成的中間碼做優化
- 生成特定目標代碼
- 輸出匯編代碼
- 匯編器
- 將匯編代碼轉換為目標對象文件。
- 鏈接器
- 將多個目標對象文件合并為一個可執行文件 (或者一個動態庫)
clang 的應用
上面介紹了,用 clang 編譯的過程,從預處理到詞法分析、語法分析,再到生成匯編語言。
那么我們能用 clang 做些什么?
libclang
libclang 是一個 C 的類庫,提供了簡潔的 API,可以對 C 和 clang 做橋接,并可以用它對所有的源碼做分析處理,如獲取 Tokens、遍歷語法樹、代碼補全、獲取診斷信息等。
libclang api 穩定,不受 clang 源碼更新影響,但是只能訪問上層語法樹,不能獲取全部信息。
推薦使用 ClangKit 它是基于 clang 提供的功能,用 Objective-C 進行封裝的一個庫。
clang 還提供了一個直接使用 LibTooling 的 C++ 類庫。它能夠發揮 clang 的強大功能,可以對源碼做任意類型的分析,甚至重寫程序,對語法樹有完全的控制權。如果你想要給 clang 添加一些自定義的分析、創建自己的重構器 (refactorer)、或者需要基于現有代碼做出大量修改,甚至想要基于工程生成相關圖形或者文檔,那么可以使用 LibTooling。
將 OC 代碼轉換為 C/C++
當需要查看 OC 代碼底層實現時,可以利用 clang 將 OC 代碼轉換為 C/C++ 代碼。
下面命令可以進行轉換:
clang -rewrite-objc main.m
//-fobjc-arc 表示ARC環境。-fobjc-runtime 表示當前運行時環境。
clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.15 main.m
執行后,可在當前文件夾下生成 main.cpp 文件。
當 .m 文件包含系統頭文件時,會報錯找不到頭文件,可以指定SDK解決:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
或者
xcrun -sdk iphonesimulator13.2 clang -rewrite-objc main.m
//其中的 sdk iphonesimulator13.2 可以通過命令 xcodebuild -showsdks 來查看
或者使用下面命令,指定了 SDK 和架構,轉換后的代碼會少一些:
xcrun -sdk iphoneos clang -arch arm64 -w -rewrite-objc main.m
References
https://llvm.org/
http://clang.llvm.org/docs/index.html
https://objccn.io/issue-6-2/
https://objccn.io/issue-6-3/
https://www.cnblogs.com/wfwenchao/p/5543595.html
https://www.ibm.com/developerworks/cn/opensource/os-cn-clang/index.html