一:編譯器
編譯器是什么已不用多說,一句話從代碼到機器碼就是編譯器的工作.
左邊輸入源碼,右邊輸出機器碼
Frontend表示前端,主要負責詞法分析、語法分析、語義分析、生成中間代碼.這時就會進行各種檢查,會報錯或者警告.
Optimizer表示優化器,負責中間代碼的優化,去除冗余代碼,優化結構
Backend表示后端,生成機器碼,并且進行鏈接,也就是將不同的二進制文件合并成一個可執行文件.
1.LLVM
Xcode5之后完全使用LLVM作為編譯器.
LLVM也是上面說的那種Frontend -> Optimizer ->Backend架構.
不過LLVM路子很野,可以有很多個接口,也就是前端(Frontend),每一種前端對應一種或多種語言,這些前端最終都會生成相同的中間代碼,叫做LLVM IR;
優化器的從始至終只處理LLVM IR.
新增一個前端不需要對LLVM的優化器進行調整,只需要新增一個前端; 增加一個新的平臺只需要增加一個后端即可.
相比較而言,GCC就支持一個新的前端或者后端就要麻煩的多,原本的GCC家族(C,C++,OC),以及Java、.NET、Python、Ruby等都可以使用LLVM編譯.
LLVM IR有3種表示形式,
存在內存中.
存在硬盤中的.ll代碼文件,可以閱讀.
存在硬盤中的二進制文件,擴展名是.bc,也就是bitcode.
2.Clang
Clang就是一個LLVM前端,負責將C,C++,OC翻譯成LLVM IR.
Clang的工作內容:
預處理, 去掉注釋,頭文件的引用關系,把宏定義對應到各個位置
靜態分析,給出錯誤信息,警告信息和修復方案
詞法分析,這里會把代碼切成一個個 Token,括號,符號,關鍵字等等都被切割出來
語法分析,驗證語法是否正確,將所有節點組成抽象語法樹AST
生成 LLVM IR, CodeGen會負責將語法樹自頂向下遍歷逐步翻譯成 LLVM IR
3.Swift
同樣LLVM中還需要一個前端負責對 Swift 源代碼進行靜態分析和糾錯,并轉換為 LLVM IR,這個前端也叫swift.
不過swift比clang的過程要復雜一些,多了一個生成SIL的過程.
Swift的工作內容:
導入Clang模塊并將它們導出的OC API 映射到相應的 Swift API
解析生成AST
生成SIL,將經過類型檢查的 AST 降級為 SIL
優化SIL,為程序執行額外的高級 Swift 特定優化,包括自動引用計數優化,虛擬化和通用專業化
將 SIL 降級到 LLVM IR
二.編譯流程
引用戴銘老師的例子走一遍,原文
1.編譯一個main.m
裝了Xcode就自帶LLVM,可以直接試一試.
創建一個項目,覆蓋main.m的代碼
#import <Foundation/Foundation.h>
#define DEFINEEight 8
#pragma 這是標記
//這是注釋
int main(){
@autoreleasepool {
int eight = DEFINEEight;
int six = 6;
NSString* site = [[NSString alloc] initWithUTF8String:"starming"];
int rank = eight + six;
NSLog(@"%@ rank %d", site, rank);
}
return 0;
}
運行:
clang -ccc-print-phases main.m
輸出:
+- 0: input, "main.m", objective-c
+- 1: preprocessor, {0}, objective-c-cpp-output
+- 2: compiler, {1}, ir
+- 3: backend, {2}, assembler
+- 4: assembler, {3}, object
+- 5: linker, {4}, image
6: bind-arch, "arm64", {5}, image
第0步引入文件
第1步預編譯,輸出c++文件
第2步編譯為LLVM IR文件
第3步輸出匯編文件
第4步輸出二進制文件
第5步鏈接各二進制文件
第6步根據架構輸出對應可執行文件
執行:
clang -E main.m
輸出了非常多的東西,因為導入了foundation,先看最后幾行
# 1 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Headers/FoundationLegacySwiftCompatibility.h" 1 3
# 187 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" 2 3
# 22 "main.m" 2
#pragma 這是標記
int main(){
@autoreleasepool {
int eight = 8;
int six = 6;
NSString* site = [[NSString alloc] initWithUTF8String:"starming"];
int rank = eight + six;
NSLog(@"%@ rank %d", site, rank);
}
return 0;
}
除了foundation,還可以看到DEFINEEight被替換成了8,注釋沒了,但是#pragma還在
所以預編譯就做了這些事:導入文件,去除注釋,替換宏定義.
這一步會生成main.cpp文件,在main.m的同一路徑.幾萬行代碼,在最后可以找到替換為C++的main函數
int main(){
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int eight = 8;
int six = 6;
NSString* site = ((NSString * _Nullable (*)(id, SEL, const char * _Nonnull))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("initWithUTF8String:"), (const char *)"starming");
int rank = eight + six;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_681pp9bd3c31j1_m0jqnt4h00000gn_T_main_3eda9c_mi_0, site, rank);
}
return 0;
}
接下來是詞法分析
執行:
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
輸出
annot_module_include '#import <Foundation/Foundation.h>
#define DEFINEEight 8
#pragma 這是標記
//這是注釋
int main(){
@autoreleasepool {
int eight = DEFINEEight;
int six = 6;
NSString* site = [[NSString alloc] initWithUTF8String:"starming"];
int rank = eight + six;
NSLog(@"%@ rank %d", site, rank);
}
return 0;
}
@???W?\?V?A?`' Loc=<main.m:21:1>
int 'int' [StartOfLine] Loc=<main.m:26:1>
identifier 'main' [LeadingSpace] Loc=<main.m:26:5>
l_paren '(' Loc=<main.m:26:9>
r_paren ')' Loc=<main.m:26:10>
l_brace '{' Loc=<main.m:26:11>
at '@' [StartOfLine] [LeadingSpace] Loc=<main.m:27:5>
identifier 'autoreleasepool' Loc=<main.m:27:6>
l_brace '{' [LeadingSpace] Loc=<main.m:27:22>
int 'int' [StartOfLine] [LeadingSpace] Loc=<main.m:28:9>
identifier 'eight' [LeadingSpace] Loc=<main.m:28:13>
equal '=' [LeadingSpace] Loc=<main.m:28:19>
numeric_constant '8' [LeadingSpace] Loc=<main.m:28:21 <Spelling=main.m:22:21>>
semi ';' Loc=<main.m:28:32>
int 'int' [StartOfLine] [LeadingSpace] Loc=<main.m:29:9>
identifier 'six' [LeadingSpace] Loc=<main.m:29:13>
equal '=' [LeadingSpace] Loc=<main.m:29:17>
numeric_constant '6' [LeadingSpace] Loc=<main.m:29:19>
semi ';' Loc=<main.m:29:20>
identifier 'NSString' [StartOfLine] [LeadingSpace] Loc=<main.m:30:9>
star '*' Loc=<main.m:30:17>
identifier 'site' [LeadingSpace] Loc=<main.m:30:19>
equal '=' [LeadingSpace] Loc=<main.m:30:24>
l_square '[' [LeadingSpace] Loc=<main.m:30:26>
l_square '[' Loc=<main.m:30:27>
identifier 'NSString' Loc=<main.m:30:28>
identifier 'alloc' [LeadingSpace] Loc=<main.m:30:37>
r_square ']' Loc=<main.m:30:42>
identifier 'initWithUTF8String' [LeadingSpace] Loc=<main.m:30:44>
colon ':' Loc=<main.m:30:62>
string_literal '"starming"' Loc=<main.m:30:63>
r_square ']' Loc=<main.m:30:73>
semi ';' Loc=<main.m:30:74>
int 'int' [StartOfLine] [LeadingSpace] Loc=<main.m:31:9>
identifier 'rank' [LeadingSpace] Loc=<main.m:31:13>
equal '=' [LeadingSpace] Loc=<main.m:31:18>
identifier 'eight' [LeadingSpace] Loc=<main.m:31:20>
plus '+' [LeadingSpace] Loc=<main.m:31:26>
identifier 'six' [LeadingSpace] Loc=<main.m:31:28>
semi ';' Loc=<main.m:31:31>
identifier 'NSLog' [StartOfLine] [LeadingSpace] Loc=<main.m:32:9>
l_paren '(' Loc=<main.m:32:14>
at '@' Loc=<main.m:32:15>
string_literal '"%@ rank %d"' Loc=<main.m:32:16>
comma ',' Loc=<main.m:32:28>
identifier 'site' [LeadingSpace] Loc=<main.m:32:30>
comma ',' Loc=<main.m:32:34>
identifier 'rank' [LeadingSpace] Loc=<main.m:32:36>
r_paren ')' Loc=<main.m:32:40>
semi ';' Loc=<main.m:32:41>
r_brace '}' [StartOfLine] [LeadingSpace] Loc=<main.m:33:5>
return 'return' [StartOfLine] [LeadingSpace] Loc=<main.m:34:5>
numeric_constant '0' [LeadingSpace] Loc=<main.m:34:12>
semi ';' Loc=<main.m:34:13>
r_brace '}' [StartOfLine] Loc=<main.m:35:1>
eof '' Loc=<main.m:35:2>
可以看出詞法分析只需要處理main.m中的代碼,把所有的字符串,符號,括號都拆分開,拆出來的每一個部分,叫做token.
在接下來是語法分析
執行:
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
輸出
-FunctionDecl 0x15ab12390 <line:26:1, line:35:1> line:26:5 main 'int ()'
`-CompoundStmt 0x15b026d48 <col:11, line:35:1>
|-ObjCAutoreleasePoolStmt 0x15b026d00 <line:27:5, line:33:5>
| `-CompoundStmt 0x15b026cc8 <line:27:22, line:33:5>
| |-DeclStmt 0x15ab12530 <line:28:9, col:32>
| | `-VarDecl 0x15ab124a8 <col:9, line:22:21> line:28:13 used eight 'int' cinit
| | `-IntegerLiteral 0x15ab12510 <line:22:21> 'int' 8
| |-DeclStmt 0x15ab4e6f8 <line:29:9, col:20>
| | `-VarDecl 0x15ab12560 <col:9, col:19> col:13 used six 'int' cinit
| | `-IntegerLiteral 0x15ab125c8 <col:19> 'int' 6
| |-DeclStmt 0x15b024be8 <line:30:9, col:74>
| | `-VarDecl 0x15ab4e750 <col:9, col:73> col:19 used site 'NSString *' cinit
| | `-ObjCMessageExpr 0x15ab50b90 <col:26, col:73> 'NSString * _Nullable':'NSString *' selector=initWithUTF8String:
| | |-ObjCMessageExpr 0x15ab4eb58 <col:27, col:42> 'NSString *' selector=alloc class='NSString'
| | `-ImplicitCastExpr 0x15ab50b78 <col:63> 'const char * _Nonnull':'const char *' <NoOp>
| | `-ImplicitCastExpr 0x15ab50b60 <col:63> 'char *' <ArrayToPointerDecay>
| | `-StringLiteral 0x15ab4ebc8 <col:63> 'char [9]' lvalue "starming"
| |-DeclStmt 0x15b0252a8 <line:31:9, col:31>
| | `-VarDecl 0x15b024c18 <col:9, col:28> col:13 used rank 'int' cinit
| | `-BinaryOperator 0x15b024d20 <col:20, col:28> 'int' '+'
| | |-ImplicitCastExpr 0x15b024cf0 <col:20> 'int' <LValueToRValue>
| | | `-DeclRefExpr 0x15b024c80 <col:20> 'int' lvalue Var 0x15ab124a8 'eight' 'int'
| | `-ImplicitCastExpr 0x15b024d08 <col:28> 'int' <LValueToRValue>
| | `-DeclRefExpr 0x15b024cb8 <col:28> 'int' lvalue Var 0x15ab12560 'six' 'int'
| `-CallExpr 0x15b026c48 <line:32:9, col:40> 'void'
| |-ImplicitCastExpr 0x15b026c30 <col:9> 'void (*)(id, ...)' <FunctionToPointerDecay>
| | `-DeclRefExpr 0x15b0252c0 <col:9> 'void (id, ...)' Function 0x15b024d48 'NSLog' 'void (id, ...)'
| |-ImplicitCastExpr 0x15b026c80 <col:15, col:16> 'id':'id' <BitCast>
| | `-ObjCStringLiteral 0x15b025340 <col:15, col:16> 'NSString *'
| | `-StringLiteral 0x15b025318 <col:16> 'char [11]' lvalue "%@ rank %d"
| |-ImplicitCastExpr 0x15b026c98 <col:30> 'NSString *' <LValueToRValue>
| | `-DeclRefExpr 0x15b025360 <col:30> 'NSString *' lvalue Var 0x15ab4e750 'site' 'NSString *'
| `-ImplicitCastExpr 0x15b026cb0 <col:36> 'int' <LValueToRValue>
| `-DeclRefExpr 0x15b025398 <col:36> 'int' lvalue Var 0x15b024c18 'rank' 'int'
`-ReturnStmt 0x15b026d38 <line:34:5, col:12>
`-IntegerLiteral 0x15b026d18 <col:12> 'int' 0
這一步會檢查語法的正確性,給出警告,報錯,以及修改建議.生成的內容叫做抽象語法樹AST.
生成AST之后就可以開始生成IR代碼了
執行:
clang -O3 -S -fobjc-arc -emit-llvm main.m -o main.ll
輸出main.ll文件,路徑和main.m相同,是可讀的.
這里"-O3"是LLVM的優化策略,有-O1,-O3,-Os,也可以不設置.
如果設置了bitcode,還可以進一步優化.
接下來生成匯編
clang -S -fobjc-arc main.m -o main.s
生成目標文件
clang -fmodules -c main.m -o main.o
生成可執行文件并執行
clang main.o -o main
./main
輸出
starming rank 14
2.從Xcode觀察編譯過程
這里的過程可以在buildsetting,Build Phases 和 Build Rules中進行配置
首先是預編譯,可以看到new build system等內容.
然后是編譯cocoapods的targets,包括創建framework(cocoapods使用use framework),copy頭文件,以及編譯.m文件
接下來是主target,其實是與cocoapods的targes一致的,這一步主target也會被打包成framework.
也是拷貝.h文件,編譯swift文件,編譯.m文件,編譯xib文件,拷貝資源文件,
接下來執行cocoapods腳本,Build Phases的腳本.
最后拷貝swift標準庫以及簽名.
每一個都可以點開詳情.
比如編譯.m文件可以看到clang信息,這些基本是可以在build setting中進行配置的.
前面是CompileC任務描述
然后是切換路徑
最后clang -x objective-c -target x86_64-apple-ios12.0-simulator ...就是編譯的命令
- 編譯的流程
1.處理文件信息
2.執行CocoaPod編譯前腳本
3.編譯.m文件(h文件是不需要編譯的),執行clang命令
4.鏈接framework
5.拷貝和編譯xib,bundle文件
6.編譯 ImageAssets
7.處理 info.plist
8.執行CocoaPod腳本
9.拷貝Swift標準庫
10.創建.app文件和簽名
3.配置編譯選項
Build settings設置在build的過程中各個階段的選項,clang的配置就屬于這個范圍.
Build Phases構建可執行文件的規則,指定 target 的依賴項目,指定在target build之前需要先build的依賴.
在Compile Source中指定必須編譯的文件,這些文件同樣會根據Build Setting和Build Rules里的設置來處理.
在Link Binary With Libraries里會列出所有的靜態庫和動態庫,它們會和編譯生成的目標文件鏈接.
把靜態資源拷貝到bundle里.
另外還可以通過在build phases里添加自定義腳本來做些事情,比如像CocoaPods所做的那樣.Build Rules指定不同文件類型如何編譯,每條build rule指定了該類型如何處理以及輸出在哪,可以增加新規則對特定文件類型添加處理方法.
上面這些都是在Xcode UI中可視化的,這些信息最終需要以文件的格式保存下來,那就是.pbxproj文件,
路徑在[項目名稱].xcodeproj包里的project.pbxproj.
打開這個文件,在最后一行有一個
rootObject = F7036F002511EC050031CE83 /* Project object */;
搜索這個rootObject ID,可以找到PBXProject section,
這個文件就是以section為單位描述配置.
/* Begin PBXProject section */
F7036F002511EC050031CE83 /* Project object */ = {
isa = PBXProject;
attributes = {
CLASSPREFIX = XX;
LastUpgradeCheck = 1310;
ORGANIZATIONNAME = XXXX;
TargetAttributes = {
F7036F072511EC050031CE83 = {
CreatedOnToolsVersion = 11.6;
LastSwiftMigration = 1240;
};
};
};
...
這PBXProject section里找到target
targets = (
F7036F072511EC050031CE83 /* XXXXX */,
);
再搜索這個ID,就可以找到更多配置,這個.pbxproj文件就是以這種id索引的方式進行記錄和查找.
比如繼續順著這個ID,可以找到更多的定義,可以看到buildPhases,buildConfiguration.
再順著找可以看到cocoapods, copy resource等等定義.
/* Begin PBXNativeTarget section */
F7036F072511EC050031CE83 /* XXXXX */ = {
isa = PBXNativeTarget;
buildConfigurationList = F7036F212511EC080031CE83 /* Build configuration list for PBXNativeTarget "XXXXX" */;
buildPhases = (
2E9C3A1138A1AED934114EBC /* [CP] Check Pods Manifest.lock */,
F7036F042511EC050031CE83 /* Sources */,
F7036F052511EC050031CE83 /* Frameworks */,
F7036F062511EC050031CE83 /* Resources */,
34DB7EF940E3C9401AD2798F /* [CP] Embed Pods Frameworks */,
25DB3E569FEFB4DC91CF364D /* [CP] Copy Pods Resources */,
);
...