swift进阶二:编译流程 & 类结构探索

swift进阶 学习大纲

  • 上一节,我们完成了源码编译。本节,我们探索Swift编译流程类结构
  1. swift编译流程
  2. 类结构
  • 强烈建议先阅读LLVM入门,再开始本节的阅读

1. swift编译流程

在了解swift编译流程前,我们需要知道LLVM是什么(👉 LLVM入门)。

LLVM架构编译器。可以为任何编程语言独立编写前端,也可以为任何硬件架构独立编写后端

image.png

比如: swift语言使用的前端编译器swiftc,而oc语言使用的前端编译器Clang,但它们都会将源代码编译为IR中间代码,交给LLVM,而LLVM会输出指定硬件架构(如手机arm64、电脑x86_64)的.O 机器可执行文件

  • occlang编译流程,我们在LLVM入门中分析得十分清楚。
  • 现在,我们开始swift编译全流程分析。

可参考WWDC - 2015 LLVM视频

1.1 语法命令

  • 打开终端,输入swiftc -h,查看语法命令:
  -dump-ast             语法和类型检查,打印AST语法树
  -dump-parse           语法检查,打印AST语法树
  -dump-pcm             转储有关预编译Clang模块的调试信息
  -dump-scope-maps <expanded-or-list-of-line:column>
                         Parse and type-check input file(s) and dump the scope map(s)
  -dump-type-info        Output YAML dump of fixed-size types from all imported modules
  -dump-type-refinement-contexts
                         Type-check input file(s) and dump type refinement contexts(s)
  -emit-assembly         Emit assembly file(s) (-S)
  -emit-bc               输出一个LLVM的BC文件
  -emit-executable       输出一个可执行文件
  -emit-imported-modules 展示导入的模块列表
  -emit-ir               展示IR中间代码
  -emit-library          输出一个dylib动态库
  -emit-object           输出一个.o机器文件
  -emit-pcm              Emit a precompiled Clang module from a module map
  -emit-sibgen           输出一个.sib的原始SIL文件
  -emit-sib              输出一个.sib的标准SIL文件
  -emit-silgen           展示原始SIL文件
  -emit-sil              展示标准的SIL文件
  -index-file            为源文件生成索引数据
  -parse                 解析文件
  -print-ast             解析文件并打印(漂亮/简洁的)语法树
  -resolve-imports       解析import导入的文件
  -typecheck             检查文件类型
  • 新建一个HTDemoswift项目,新建HTPerson.swift文件,测试代码:
class HTPerson {
    var age: Int = 18
    var name: String = "ht"
}

let t = HTPerson()

拓展命令:

  1. 打印结果输出文档命令
命令语句后 +  `>> ./XXX && open XXX`

// 命令语句: swiftc -emit-sil HTPerson.swift
// 输出文件: >> ./HTPerson.sil
// 打开文件: open HTPerson.sil
例如: swiftc -emit-sil HTPerson.swift >> ./HTPerson.sil && open HTPerson.sil
  1. 另一个相同命令
命令语句后 + ` | col -b > XXX`

// 命令语句为:`swiftc -print-ast HTPerson.swift`
// 输出文档为`ast.swift`
例如: swiftc -dump-ast HTPerson.swift | col -b > ast.swift

1.2 Swift编译流程

image.png

Swift编译流程

  1. swift源码编译为AST语法树
    swiftc -dump-ast HTPerson.swift >> ast.swift
  2. 生成SIL源码
    swiftc -emit-sil HTPerson.swift >> ./HTPerson.sil
  3. 生成IR中间代码
    swiftc -emit-ir HTPerson.swift >> ir.swift
  4. 输出.o机器文件
    swiftc -emit-object HTPerson.swift

(ps:以上是各环节关键命令其他命令自行尝试查看

1.3 SIL分析

SIL(Swift intermediate language):swift中间语言

调用swiftc -emit-sil HTPerson.swift >> ./HTPerson.sil命令,生成HTPerson.sil文件。用VSCode打开SIL文件

1.3.1 main函数分析
// main 
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  // 创建全局变量HTPerson
  alloc_global @$s8HTPerson1tA2ACvp               // id: %2    
  // 读取全局变量HTPerson地址,赋值给%3
  %3 = global_addr @$s8HTPerson1tA2ACvp : $*HTPerson // user: %7  
  // metatype读取HTPerson的Type(Metadata),赋值给%4
  %4 = metatype $@thick HTPerson.Type             // user: %6
  // 将HTPerson.__allocating_init() 函数地址给%5
  %5 = function_ref @$s8HTPersonAACABycfC : $@convention(method) (@thick HTPerson.Type) -> @owned HTPerson // user: %6
  //  调用%5函数,将返回值给%6
  %6 = apply %5(%4) : $@convention(method) (@thick HTPerson.Type) -> @owned HTPerson // user: %7
  // 将%6存储到%3 (%3是HTPerson类型)
  store %6 to %3 : $*HTPerson                     // id: %7
  // 构建Int并return
  %8 = integer_literal $Builtin.Int32, 0          // user: %9
  %9 = struct $Int32 (%8 : $Builtin.Int32)        // user: %10
  return %9 : $Int32                              // id: %10
} // end sil function 'main'
  1. @main标识当前HTPerson.swift文件的入口函数SIL标识符号名称以@作为前缀
  2. %0,%1...在SIL中也叫寄存器类似代码中的常量,一旦赋值不可修改。如果SIL中还要继续使用,就需要使用寄存器

(此寄存器是虚拟的,只作为临时标识最终运行到机器,会使用寄存器

1.3.2 实例化分析
  • HTPerson()实际调用如下:
// HTPerson.__allocating_init()
sil hidden [exact_self_class] @$s8HTPersonAACABycfC : $@convention(method) (@thick HTPerson.Type) -> @owned HTPerson {
// %0 "$metatype"
bb0(%0 : $@thick HTPerson.Type):
  // 读取HTPerson的`alloc_ref`方法地址,给%1
  %1 = alloc_ref $HTPerson                        // user: %3
  // 读取HTPerson.init()函数地址,给%2
  %2 = function_ref @$s8HTPersonAACABycfc : $@convention(method) (@owned HTPerson) -> @owned HTPerson // user: %3
  // 调用`alloc_ref`创建一个HTPerson实例对象,给%3
  %3 = apply %2(%1) : $@convention(method) (@owned HTPerson) -> @owned HTPerson // user: %4
  // 返回%3(HTPerosn类型)
  return %3 : $HTPerson                           // id: %4
} // end sil function '$s8HTPersonAACABycfC'
  • 类似的SIL分析,可以多看几个实例,慢慢找感觉

2. 类结构

对象初始化

  • OC: [[HTPerosn alloc] init]
    一般alloc申请内存空间并创建对象init对象进行统一初始化处理。
  • Swift:HTPerson()
    直接()完成了对象的创建

我们一起去探索Swift对象创建流程:

2.1 对象的创建(alloc)

  • 创建测试代码实例化处加断点

    image.png

  • 顶部Debug->Debug workflow -> Always Show Disassembly,勾选汇编模式:

    image.png

  • 运行代码,可以看到是调用了__allocating_init()进行的实例化。

    image.png

  • 添加__allocating_init符号断点,继续运行,发现内部调用了swift_allocObject

    image.png

  • 添加swift_allocObject符号断点,继续运行,发现内部调用了_swift_allocObject_后,继续调用swift_slowAlloc

    image.png

  • 添加swift_slowAlloc符号断点,继续运行,发现内部调用了malloc_zone_malloc进行内存申请:

    image.png

  • swift对象创建流程
    __allocating_init -> swift_allocObject -> _swift_allocObject_ -> swift_slowAlloc -> malloc_zone_malloc

VSCode调试验证

    1. VSCode打开Swift源码,搜索_swift_allocObject_,加入断点
      image.png
    1. run起来后,在底部编辑区逐行输入:
      (ps: 编辑器判断语法是否结束(花括号对称),来判断输入是否完成
class HTPerson {
       var age: Int = 18
       var name: String = "ht"
}

再输入var p = HTPerson()创建变量,按回车,会进入断点:

image.png

  • 进入swift_slowAlloc内部,可以看到是申请size大小的堆空间,并返回了空间的指针地址
    image.png
  • 这就是swiftalloc申请内存空间返回一个object指针。

  • 返回后,使用reinterpret_cast强转为HeapObject类型。

// 堆中申请内存空间,地址返回给object
 auto object = reinterpret_cast<HeapObject *>( swift_slowAlloc(requiredSize, requiredAlignmentMask));  

reinterpret_cast:强制类型转换符

【用法】
new_type a = reinterpret_cast <new_type> (value)

  • value的值转成new_type类型avalue的值一模一样比特位不变。
  • reinterpret_cast用在任意指针(或引用类型之间的转换,以及指针足够大整数类型之间的转换;从整数类型(包括枚举类型)到指针类型无视大小

注意: 此时object是强转的HeapObject类型,实际是一个指向内存空间对象指针,而HeapObject需要使用metadata进行初始化

// object对象类型为HeapObject,通过metadata(元数据)初始化
  new (object) HeapObject(metadata);
image.png

2.2 类的大小

在探索init初始化操作之前,我们先了解一下类的大小

  • 首先,通过Xcode工程打印类的大小:

【总大小】40字节 (分别使用MemoryLayoutclass_getInstanceSize打印大小)

image.png

【age】:是struct类型64位系统下与Int64大小一样。占8字节

image.png

【name】: 是struct类型,打印发现,占16字节

  • 其次,通过VSCode编译,查看大小
image.png

Q:OC类大小,就是isa指针大小,8字节,为什么Swift类16字节

  • 最后,探索Swift类的组成(16字节):

VSCode点击进入HeapObject结构:

image.png

【metadata】:是 HeapMetedata类型,进入探究:TargetHeapMetadata->TargetMetadata,是struct结构体。

  • 结构体大小,由内部属性决定,当前TargetMetadata结构体只有Kind一个属性,类型为StoredPointer(本质是 unsigned long类型,8字节)
    image.png

【refCounts】:是InlineRefCounts类型,进入探究:InlineRefCounts->RefCounts,是class类型,占8字节swift也使用ARC进行内存管理

image.png

HeapObject结构:

struct HeapObject {
   let metadata: UnsafeRawPointer   // 8字节
   let strongRef: UInt32            // 4字节
   let weakRef: UInt32              // 4字节
   【... 自定义属性 ...】             // ...
}
  • 总结
  1. swift类本质是HeapObject
  2. HeapObject默认大小为16字节metadata(struct)8字节refCounts(class)8字节
  3. HTPersonage(Int)占8字节name(String)占16字节

所以HTPersonsize40字节

2.3 对象的初始化(init)

  • alloc申请内存空间并拿到空间地址后,通过metadata(元数据)初始化HeapObject对象。
image.png
  • 简版流程图
    image.png
  • swift类结构
struct swift_class_t {
    unsigned long Kind;             // 8字节 (swift中是Kind,OC中是isa)
    void *   Superclass;            // 8字节 父类
    void *   CacheData[2];          // 16字节 缓存数据(2个元素)
    void *   Data ;                 // 8字节 自定义数据
    uint32_t Flags;                 // 4字节
    uint32_t InstanceAddressPoint;  // 4字节
    uint32_t InstanceSize;          // 4字节
    uint16_t InstanceAlignMask;     // 2字节
    uint16_t Reserved;              // 2字节
    uint32_t ClassSize;             // 4字节
    uint32_t ClassAddressPoint;     // 4字节
    void *   Description;           // 8字节 描述
}
  • 附上完整流程图。(大图,放大观看)
    image.png

本节,熟悉了swift编译流程swift类结构,关于swift的探索,才刚刚起步 💪

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容