Xcode的编译过程

今天我们来对iOS开发的常用工具Xcode的编译流程进行一个简单的了解和分析

image

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)是编译过程的一个逻辑阶段。语法分析的任务是在词法分析的基础上将单词序列组合成语法树,如“程序”,“语句”,“表达式”等等.语法分析程序判断源程序在结构上是否正确。源程序的结构由上下文无关文法描述。

image

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
  1. @_hasStorage表示的是储存属性
  2. @_hasInitialValue表示的是具有初始值
  3. @sil_global表示的是全局变量
  4. 一个析构方法deinit
  5. 一个初始化函数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'
  1. @main 这里是标示我们当前main.swift的入口函数,SIL中的标示符以 @作为前缀
  2. %0,%1……在SIL也叫寄存器,这里我们可以理解为我们日常开发的常量,一旦赋值之后就不可以在修改,如果SIL中还要继续使用,那么就不断的累加数字
  3. alloc_global是创建一个全局变量
  4. global_addr是拿到全局变量的地址,赋值给%3
  5. metatype是拿到Teacher的Metaldata赋值给%4
  6. 接下来就是将__allocating_init()的函数地址赋值给%5
  7. apply是调用函数,这里是调用%5也就是__allocating_init(),%4是参数,并将返回值给%6
  8. 然后将%6的值存储到%3,也就是我们刚刚创建的全局变量的地址
  9. 然后是构建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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容