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

swift进阶 学习大纲

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

1. swift编译流程

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

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

swift进阶二:编译流程 & 类结构探索_第1张图片
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 
                         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>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer>>):
  // 创建全局变量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)

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

    swift进阶二:编译流程 & 类结构探索_第2张图片
    image.png

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

    swift进阶二:编译流程 & 类结构探索_第3张图片
    image.png

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

    swift进阶二:编译流程 & 类结构探索_第4张图片
    image.png

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

    swift进阶二:编译流程 & 类结构探索_第5张图片
    image.png

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

    swift进阶二:编译流程 & 类结构探索_第6张图片
    image.png

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

    swift进阶二:编译流程 & 类结构探索_第7张图片
    image.png

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

VSCode调试验证

    1. VSCode打开Swift源码,搜索_swift_allocObject_,加入断点
      swift进阶二:编译流程 & 类结构探索_第8张图片
      image.png
    1. run起来后,在底部编辑区逐行输入:
      (ps: 编辑器判断语法是否结束(花括号对称),来判断输入是否完成
class HTPerson {
       var age: Int = 18
       var name: String = "ht"
}

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

swift进阶二:编译流程 & 类结构探索_第9张图片
image.png

  • 进入swift_slowAlloc内部,可以看到是申请size大小的堆空间,并返回了空间的指针地址
    swift进阶二:编译流程 & 类结构探索_第10张图片
    image.png
  • 这就是swiftalloc申请内存空间返回一个object指针。

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

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

reinterpret_cast:强制类型转换符

【用法】
new_type a = reinterpret_cast (value)

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

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

// object对象类型为HeapObject,通过metadata(元数据)初始化
  new (object) HeapObject(metadata);
swift进阶二:编译流程 & 类结构探索_第11张图片
image.png

2.2 类的大小

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

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

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

swift进阶二:编译流程 & 类结构探索_第12张图片
image.png

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

image.png

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

  • 其次,通过VSCode编译,查看大小
swift进阶二:编译流程 & 类结构探索_第13张图片
image.png

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

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

VSCode点击进入HeapObject结构:

swift进阶二:编译流程 & 类结构探索_第14张图片
image.png

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

  • 结构体大小,由内部属性决定,当前TargetMetadata结构体只有Kind一个属性,类型为StoredPointer(本质是 unsigned long类型,8字节)
    swift进阶二:编译流程 & 类结构探索_第15张图片
    image.png

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

swift进阶二:编译流程 & 类结构探索_第16张图片
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对象。
swift进阶二:编译流程 & 类结构探索_第17张图片
image.png
  • 简版流程图
    swift进阶二:编译流程 & 类结构探索_第18张图片
    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的探索,才刚刚起步

你可能感兴趣的:(swift进阶二:编译流程 & 类结构探索)