今天我们来对iOS开发的常用工具Xcode的编译流程进行一个简单的了解和分析
OC:source code > Clang -> LLVM -> Backend -> Executable
Clang做的事情是词法分析-> token流->语法分析-> AST -> LLVM IR
AST(Abstract Syntax Tree 抽象语法树)
IR(intermediate representation 中间代码)
swift:source code > swiftc -> LLVM -> Backend -> Executable
swiftc:swift AST -> Raw Swift IL -> Canonical Swift IL -> LLVM IR
Raw Swift IL : Swift特有的中间代码
Canonical Swift IL:将Raw Swift IL进行降级简化成更加简洁的中间代码版本
swift的编译器前端是swiftc,于Clang相比,LLVM的前端编译过程中,AST和IR之间,多了一层SIL(Swift Intermediate Language),这么做的目的是希望弥补clang编译器的一些缺陷,比如无法执行一些高级分析,可靠的诊断和优化,而 AST 和LLVM IR 都不是合适的选择。因此,SIL应运而生,用来解决现有的缺陷
AST(Abstract Syntax Tree 抽象语法树)
生成AST的过程
词法分析(lexical analysis)
也叫扫描器,让源代码的字符流根据构词规范生成token流
tokenize:tokenize就是按照一定的规则,例如token令牌(通常代表关键字,变量名,语法符号等),将代码分割为一个个的“串”,也就是语法单元)。涉及到词法解析的时候,常会用到tokennize。
语法分析(parse analysis)是编译过程的一个逻辑阶段。语法分析的任务是在词法分析的基础上将单词序列组合成语法树,如“程序”,“语句”,“表达式”等等.语法分析程序判断源程序在结构上是否正确。源程序的结构由上下文无关文法描述。
Clang:
AST的转换例子
@property (nonatomic, strong) NSString *haoyuString;
PS:属性(Property)是Objective-C语言的其中一个特性,它把类对象中的实例变量及其读写方法统一的封装
生成的AST如下:
ObjCPropertyDecl 0x7f96f693a400 <line:13:1, col:41> col:41 haoyuString 'NSString *' readwrite nonatomic strong
| |-ObjCMethodDecl 0x7f96f693a478 <col:41> col:41 implicit - haoyuString 'NSString *'
| |-ObjCMethodDecl 0x7f96f693a4f8 <col:41> col:41 implicit - setHaoyuString: 'void'
| | `-ParmVarDecl 0x7f96f693a578 <col:41> col:41 haoyuString 'NSString *'
常见的数据类型声明在AST中表示
NSString *str = @"hahahah"; //oc中赋值代码
AST中的表示为:
VarDecl 0x7fd06cc91ae8 <line:24:5, col:22> col:15 str 'NSString *__strong' cinit //声明局部变量
| | | `-ObjCStringLiteral 0x7fd06cc91ba8 <col:21, col:22> 'NSString *' //声明变量类型
| | | `-StringLiteral 0x7fd06cc91b88 'char [8]' lvalue "hahahah" //右边的字符串有char类型表示
String:StringLiteral
NSInteger ,Int: IntegerLiteral
Float: FloatingLiteral
Array: ObjCArrayLiteral
Dictionary : ObjCDictionaryLiteral
将源代码生成语法树 AST:
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
Swiftc:
生成AST的方式和Clang类似,这里着重介绍下swiftc编译器的SIL(Swift Intermediate Language )
1.生成的main.swift文件中编写如下代码
import Foundation
class Teacher {
var age: Int = 18
var name: String = "Tom"
}
var person = Teacher()
person.age = 6
通过终端进入main.swift所在的文件夹,输入如下指令:
swiftc -emit-sil main.swift //生成了main.sil文件
// 打开`main.sil` 文件,首先看到了Teacher的声明
class Teacher {
@_hasStorage @_hasInitialValue var age: Int { get set }
@_hasStorage @_hasInitialValue var name: String { get set }
@objc deinit
init()
}
@_hasStorage @_hasInitialValue var person: Teacher { get set }
// person
sil_global hidden @main.person : main.Teacher : $Teacher
- @_hasStorage表示的是储存属性
- @_hasInitialValue表示的是具有初始值
- @sil_global表示的是全局变量
- 一个析构方法deinit
- 一个初始化函数init()
这里声明了Teacher类,并定义了一个全局的person属性,属于Teacher类
2.看下main.sil文件中的main 函数
每个程序的开始都是main函数,swift语言也不例外,但是swift中的main函数被隐藏了,main.swift文件就代表了整个main函数,在文件里写的代码会在main中运行
// main函数,相当于c程序入口函数int main(int argc, char * argv[])
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
//%0,%1……在SIL也叫寄存器,这里我们可以理解为我们日常开发的常量,
//一旦赋值之后就不可以在修改,如果SIL中还要继续使用,那么就不断的累加数字
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
//初始化全局变量person alloc_global是创建一个全局变量
alloc_global @main.person : main.Teacher // id: %2
//创建对之前由alloc_global初始化的全局变量地址的引用
//global_addr是拿到全局变量的地址,赋值给%3
%3 = global_addr @main.person : main.Teacher : $*Teacher // users: %7, %8
//元类型Teacher
//metatype是拿到Teacher的Metaldata赋值给%4
%4 = metatype $@thick Teacher.Type // user: %6
// 方法__allocating_init()的引用
//接下来就是将__allocating_init()的函数地址赋值给%5
%5 = function_ref @main.Teacher.__allocating_init() -> main.Teacher : $@convention(method) (@thick Teacher.Type) -> @owned Teacher // user: %6
//调用函数__allocating_init,并传入参数元类型Teacher
//apply是调用函数,这里是调用%5也就是__allocating_init(),%4是参数,并将返回值给%6
%6 = apply %5(%4) : $@convention(method) (@thick Teacher.Type) -> @owned Teacher // user: %7
//将函数__allocating_init的结果存入person的引用
//然后将%6的值存储到%3,也就是我们刚刚创建的全局变量的地址
store %6 to %3 : $*Teacher // id: %7
//开始访问全局变量地址的引用
%8 = begin_access [read] [dynamic] %3 : $*Teacher // users: %9, %11
//将内容载入%9
%9 = load %8 : $*Teacher // users: %16, %14, %15, %10
//引用计数加一
strong_retain %9 : $Teacher // id: %10
//结束访问
end_access %8 : $*Teacher // id: %11
//创建字面量6
%12 = integer_literal $Builtin.Int64, 6 // user: %13
//生成Int值6,swift中Int是结构体
%13 = struct $Int (%12 : $Builtin.Int64) // user: %15
//Teacher.age的setter方法
%14 = class_method %9 : $Teacher, #Teacher.age!setter : (Teacher) -> (Int) -> (), $@convention(method) (Int, @guaranteed Teacher) -> () // user: %15
//调用setter方法,传入Int值6,和类实例本身
%15 = apply %14(%13, %9) : $@convention(method) (Int, @guaranteed Teacher) -> ()
//引用计数减一
strong_release %9 :
//创建字面量0
%17 = integer_literal $Builtin.Int32, 0 // user: %18
//生成Int值0,swift中Int是结构体
%18 = struct $Int32 (%17 : $Builtin.Int32) // user: %19
//最后将0从main函数中返回出去
return %18 : $Int32 // id: %16
} // end sil function 'main'
- @main 这里是标示我们当前main.swift的入口函数,SIL中的标示符以 @作为前缀
- %0,%1……在SIL也叫寄存器,这里我们可以理解为我们日常开发的常量,一旦赋值之后就不可以在修改,如果SIL中还要继续使用,那么就不断的累加数字
- alloc_global是创建一个全局变量
- global_addr是拿到全局变量的地址,赋值给%3
- metatype是拿到Teacher的Metaldata赋值给%4
- 接下来就是将__allocating_init()的函数地址赋值给%5
- apply是调用函数,这里是调用%5也就是__allocating_init(),%4是参数,并将返回值给%6
- 然后将%6的值存储到%3,也就是我们刚刚创建的全局变量的地址
- 然后是构建Int并return
3. 这里面有些固定搭配
1.初始化一个引用
//初始化全局变量person
alloc_global @main.person : main.Teacher
//创建对之前由alloc_global初始化的全局变量地址的引用
%3 = global_addr @main.person : main.Teacher
2.调用一个方法
// 方法__allocating_init()的引用
%5 = function_ref @main.Teacher.__allocating_init() -> main.Teacher : $@convention(method) (@thick Teacher.Type) -> @owned Teacher
//调用函数__allocating_init,并传入参数元类型Teacher
%6 = apply %5(%4) : $@convention(method) (@thick Teacher.Type) -> @owned Teacher
3.获得swfit的基本类型
//创建字面量6
%12 = integer_literal $Builtin.Int64, 6
//生成Int值6,swift中Int是结构体
%13 = struct $Int (%12 : $Builtin.Int64)
swiftc命令:
生成可执行文件:swiftc -o main.out main.swift
生成抽象语法树的命令(AST):swiftc main.swift -dump-ast
生成中间语言(SIL):swiftc main.swift -emit-sil
LLVM中间表示层(LLVM IR):swiftc main.swift -emit -ir
生成汇编语言:swiftc main.swift -emit-assembly