一、 编程语言类型
1.1 解释型语言
python
、 JS
就属于常见的 解释型语言
。他们无需生成 可执行文件
就能够执行。
他们的执行过程如下:
1.2 编译型语言
我们常用的 C\C++
、 Objective-C
、 Swift
则属于 编译型语言
需要经过下面的过程生成 可执行文件
后才能执行。
二、 LLVM 概述
LLVM
是 构架编译器(compiler)
的 框架系统
,以 C++
编写而成,用于优化以任意程序语言编写的程序的 编译时间(compile-time)
、 链接时间(link-time)
、 运行时间(run-time)
以及 空闲时间(idle-time)
,对开发者保持开放,并兼容已有脚本。
LLVM
计划启动于2000年,最初由美国UIUC大学的 ChrisLattner
博士主持开展。2006年 ChrisLattner
加盟 AppleInc
并致力于 LLVM
在 Apple
开发体系中的应用。 Apple
也是 LLVM
计划的主要资助者。
目前 LLVM
已经被 苹果IOS开发工具
、 Xilinx Vivado
、 Facebook
、 Google
等各大公司采用。
ChrisLattner
也是 Swift
之父。给大佬🧎♂️了
2.1 传统编译器设计
编译器前端
编译器前端的任务是 解析源代码
。它会进行: 词法分析
, 语法分析
, 语义分析
,检查源代码是否存在错误
,然后构建 抽象语法树(Abstract Syntax Tree, AST)
。 LLVM
的前端还会生成 中间代码(intermediate representation,IR)
。
优化器
优化器负责进行 各种优化
, 改善代码的运行时间
,例如消除冗余计算等。
后端 / 代码生成器
将代码映射到 目标指令集
, 生成机器语言
, 并且 进行机器相关的代码优化
。
2.2 iOS的编译器架构
ObjectiveC / C / C++
使用的编译器前端是 Clang
, Swift
是 Swift
, 后端
都是 LLVM
。
2.3 LLVM的设计
当编译器决定支持多种源语言或多种硬件架构时, LLVM
最重要的地方就来了。
其他的编译器如 GCC
是非常成功的一款编译器,但由于它是作为整体应用程序设计的,因此它的用途受到了很大的限制。
LLVM 设计的最重要方面是,使用 通用的代码表示形式(IR)
,它是用来在编译器中表示代码的形式。所以 LLVM
可以为任何编程语言 独立编写前端
,并且可以为任意硬件架构 独立编写后端
。
三、 Clang
对于我们的开发人员来说,接触最多的就是我们的 Clang
。
Clang
是 LLVM
项目中的一个子项目。它是基于 LLVM架构
的 轻量级编译器
,诞生之初是为了替代 GCC
,提供更快的编译速度。它是负责编译 C、C++、Objecte-C
的编译器,它属于整个 LLVM
架构中的,编译器前端。对于开发者来说,研究 Clang
可以给我们带来很多好处。
四、 Objective-C 的编译流程探索
我们以下面的代码为例进行探索
Copy #import <Foundation/Foundation.h>
int addResult(int a, int b) {
return a + b;
}
#define ADD_FUNCTION(x, y) addResult(x, y)
int main(int argc, const char * argv[]) {
@autoreleasepool {
int numA = 1;
int b1 = 1;
int b2 = 2;
int numB = b1 + b2;
int result = ADD_FUNCTION(numA, numB);
NSLog(@"结果是:%d", result);
}
return 0;
}
4.1 编译阶段概览
通过下面的命令我们可以列出完整的编译阶段:
Copy clang -ccc-print-phases main.m
2:编译
进行词法分析、语法分析、检测语法是否正确,最终生成IR
3:后端
这里LLVM会通过一个一个的Pass(可以理解为一个节点)去优化,每个Pass做一些事情,最终生成汇编代码
5:链接
链接需要的动态库和静态库,生成相应的镜像可执行文件
4.2 预处理
Copy clang -E main.m >> 预处理.m
通过对比,很明显的发现这里的宏定义被替换掉了。
4.3 编译阶段
词法分析
这里会把代码切成一个个 Token
,比如 大小括号
, 等于号
还有 字符串
等。
Copy clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
语法分析
语法分析的任务是 验证语法是否正确
。在词法分析的基础上将单词序列组合成各类语法短语,如 “程序”
, “语句”
, “表达式”
等,然后将所有节点组成 抽象语法树(AbstractSyntaxTree,AST)
。语法分析其目的就是 对源程序进行分析判断,在结构上是否正确 。
Copy clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
生产中间代码IR
Copy clang -S -fobjc-arc -emit-llvm main.m
这里会生成 .ll
文件,抽取一段 addResult
相关代码简单进行一下分析:
Copy ; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @addResult(i32 %0, i32 %1) #1 {
%3 = alloca i32, align 4 // 开辟 32 bit 空间, 4个字节, 为 %3
%4 = alloca i32, align 4
store i32 %0, i32* %3, align 4 // 将 %0 参数存到 %3
store i32 %1, i32* %4, align 4
%5 = load i32, i32* %3, align 4 // 从 %3 中读取到 %5
%6 = load i32, i32* %4, align 4
%7 = add nsw i32 %5, %6 // %5 + %6 的值存到 %7
ret i32 %7 // 返回 %7 32bit
}
main
函数:
Copy ; Function Attrs: noinline optnone ssp uwtable
define i32 @main(i32 %0, i8** %1) #2 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
%5 = alloca i8**, align 8
%6 = alloca i32, align 4
%7 = alloca i32, align 4
%8 = alloca i32, align 4
%9 = alloca i32, align 4
%10 = alloca i32, align 4
store i32 0, i32* %3, align 4
store i32 %0, i32* %4, align 4
store i8** %1, i8*** %5, align 8
%11 = call i8* @llvm.objc.autoreleasePoolPush() #3
store i32 1, i32* %6, align 4
store i32 1, i32* %7, align 4
store i32 2, i32* %8, align 4
%12 = load i32, i32* %7, align 4
%13 = load i32, i32* %8, align 4
%14 = add nsw i32 %12, %13
store i32 %14, i32* %9, align 4
%15 = load i32, i32* %6, align 4
%16 = load i32, i32* %9, align 4
%17 = call i32 @addResult(i32 %15, i32 %16)
store i32 %17, i32* %10, align 4
%18 = load i32, i32* %10, align 4
notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*), i32 %18)
call void @llvm.objc.autoreleasePoolPop(i8* %11)
ret i32 0
}
编译优化
这里 debug
模式下默认没有进行优化。
我们传入对应的优化等级,再生成一次 .ll
看看(这里用了和 release
一样的 Os
):
Copy clang -Os -S -fobjc-arc -emit-llvm main.m -o main优化过.ll
addResult
Copy ; Function Attrs: norecurse nounwind optsize readnone ssp uwtable willreturn
define i32 @addResult(i32 %0, i32 %1) local_unnamed_addr #1 {
%3 = add nsw i32 %1, %0
ret i32 %3
}
main
Copy ; Function Attrs: optsize ssp uwtable
define i32 @main(i32 %0, i8** nocapture readnone %1) local_unnamed_addr #2 {
%3 = tail call i8* @llvm.objc.autoreleasePoolPush() #3
notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*), i32 4) #5, !clang.arc.no_objc_arc_exceptions !9
tail call void @llvm.objc.autoreleasePoolPop(i8* %3) #3
ret i32 0
}
这里发现优化后的中间代码简介了不少,并且代码中冗余的计算逻辑直接生成了结果
bitCode
这是 xcode7
以后开启 bitcode
苹果会做进一步的优化,生成 bc
的中间代码。我们可以试试用优化后的 IR
代码生成 bc
代码。
Copy clang -emit-llvm -c main优化过.ll -o main优化过.bc
这里的 bc
文件无法使用文本方式进行查看
生成汇编代码
可以通过的 .bc
或者 .ll
代码生成汇编代码
Copy clang -S -fobjc-arc main优化过.bc -o main优化过.s
或
clang -S -fobjc-arc main未优化.ll -o main未优化.s
对比一下优化前后的汇编代码,发现优化后的指令少了很多:
生成目标文件
Copy clang -fmodules -c main未优化.s -o main未优化.o
目标文件的生成,是 汇编器
以汇编代码作为 输入
,将 汇编代码
转换为 机器代码
,最后输出 目标文件(object-file)
,这个阶段就是属于 编译器后端
的工作了。
查看符号
Copy xcrun nm -nm main未优化.o
输出:
Copy (undefined) external _NSLog
(undefined) external ___CFConstantStringClassReference
(undefined) external _objc_autoreleasePoolPop
(undefined) external _objc_autoreleasePoolPush
0000000000000000 (__TEXT,__text) external _addResult
0000000000000020 (__TEXT,__text) external _main
000000000000008e (__TEXT,__ustring) non-external l_.str
_NSLog
是一个是 undefined external
的
有兴趣可以移步 通过符号表找到符号 有介绍查找 _NSLog
的过程
生成可执行文件
连接器
把编译产生的 .o文件
和 (dylib .a)文件
,生成一个 mach-o文件(可执行文件)
。
报错:
Copy Undefined symbols for architecture x86_64:
"_NSLog", referenced from:
_main in main.o
"___CFConstantStringClassReference", referenced from:
CFString in main.o
"_objc_autoreleasePoolPop", referenced from:
_main in main.o
"_objc_autoreleasePoolPush", referenced from:
_main in main.o
ld: symbol(s) not found for architecture x86_64
因为这里用到了 Foundation
框架内的符号 NSLog
不能直接生成,我们加上参数
Copy clang -framework Foundation main.o -o main
生成成功:
查看符号
输出:
Copy (undefined) external _NSLog (from Foundation)
(undefined) external ___CFConstantStringClassReference (from CoreFoundation)
(undefined) external _objc_autoreleasePoolPop (from libobjc)
(undefined) external _objc_autoreleasePoolPush (from libobjc)
(undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
0000000100003ed0 (__TEXT,__text) external _addResult
0000000100003ef0 (__TEXT,__text) external _main
0000000100008018 (__DATA,__data) non-external __dyld_private
这里的符号就已经和对应的框架对应起来了,如: _NSLog (from Foundation)
。而且 mach-o
文件中的偏移地址也有了。
五、 Swift 编译流程
分析输出AST
swiftc main.swift -dump-parse
分析并且检查类型输出AST
swiftc main.swift -dump-ast
生成中间体语言(SIL),未优化
swiftc main.swift -emit-silgen
生成中间体语言(SIL),优化后的
swiftc main.swift -emit-sil
生成LLVM中间体语言 (.ll文件)
swiftc main.swift -emit-ir
生成LLVM中间体语言 (.bc文件)
swiftc main.swift -emit-bc
生成汇编
swiftc main.swift -emit-assembly
编译生成可执行.out文件
swiftc -o main.o main.swift
参考
LLVM
Clang 1 Which OSX library to link against (command line) to use NSLog?