一、Swift 编译简介

  • Swift 的编译环境配置和编译流程,请参考我之前的博客:Swift之源码编译的环境搭建和编译流程;
  • 新建一个 Swift 工程,在 main.swift 中创建一个 YDWTeacher 类,并通过默认的初始化器,创建一个实例对象并赋值给 t,如下:
 class YDWTeacher { var age: Int = 18 var name: String = "YDW"  } let t = YDWTeacher()
  • 然后在终端中查看抽象语法树:swiftc -dump-ast main.swift,如下:

  • 接下来,要研究的是这个初始化器到底做了一个什么样的操作?因此引入 SIL (Swift intermediate language);
  • iOS 的开发语言,不管是 OC 还是 Swift,底层都是通过 LLVM 编译的,生成 .o 可执行文件,如下所示:

  • 不难看出:

    • OC 中通过 clang 编译器,编译成 IR,然后再生成可执行文件.o(即机器码);
    • swift 中通过 swiftc 编译器,编译成 IR,然后再生成可执行文件;
  • 再来看一下:一个 Swift 文件的编译过程经历哪些步骤:

  • 下面是 Swift 中的编译流程,其中 SIL(Swift Intermediate Language),是 Swift 编译过程中的中间代码,主要用于进一步分析和优化 Swift 代码。如下图所示,SIL 位于在 AST 和 LLVM IR 之间:

  • Swift 与 OC 的区别在于 Swift 生成了高级的 SIL;Swift 在编译的过程中使用的前端编译器是 Swiftc,和我们之前的 OC 中使用的 clang 是有所区别的。
  • 通过 swiftc -h 终端命令,查看 swiftc 能做什么:

  • 分析说明:

    • -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 检查文件类型

二、SIL

① 什么是 SIL 分析?
  • SIL 依赖于 swift 的类型系统和声明,所以 SIL 语法是 swift 的延伸。一个 sil 文件是一个增加了 SIL 定义的 swift 源文件;
  • SIL 文件中没有隐式 import,如果使用 swift 或者 Buildin 标准组件的话必须明确的引入;
  • SIL 函数由一个或多个 block 组成,一个 block 是一个指令的线性序列,每个 block 中的最后一条指令将控制转移到另一个 block,或从函数返回。
  • 如果想要对 SIL 的内容进行详细地探索,请参考:2015 LLVM Developers’ Meeting。
② SIL 分析 mian 函数
  • 查看抽象语法树之后,继续在终端中调用 swiftc -emit-sil main.swift >> ./main.sil && code main.sil 命令,生成 main.sil 文件;
  • 用 VSCode 打开 SIL 文件:
// main
//`@main`:标识当前main.swift的`入口函数`,SIL中的标识符名称以`@`作为前缀
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {//`%0、%1` 在SIL中叫做寄存器,可以理解为开发中的常量,一旦赋值就不可修改,如果还想继续使用,就需要不断的累加数字(注意:这里的寄存器,与`register read`中的寄存器是有所区别的,这里是指`虚拟寄存器`,而`register read`中是`真寄存器`)
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
//`alloc_global`:创建一个`全局变量`,即代码中的`t`alloc_global @$s4main1tAA10YDWTeacherCvp        // id: %2
//`global_addr`:获取全局变量地址,并赋值给寄存器%3%3 = global_addr @$s4main1tAA10YDWTeacherCvp : $*YDWTeacher // user: %7
//`metatype`获取`YDWTeacher`的`MetaData`赋值给%4%4 = metatype $@thick YDWTeacher.Type           // user: %6
//将`__allocating_init`的函数地址赋值给 %5// function_ref YDWTeacher.__allocating_init()%5 = function_ref @$s4main10YDWTeacherCACycfC : $@convention(method) (@thick YDWTeacher.Type) -> @owned YDWTeacher // user: %6
//`apply`调用 `__allocating_init` 初始化一个变量,赋值给%6%6 = apply %5(%4) : $@convention(method) (@thick YDWTeacher.Type) -> @owned YDWTeacher // user: %7
//将%6的值存储到%3,即全局变量的地址(这里与前面的%3形成一个闭环)store %6 to %3 : $*YDWTeacher                   // id: %7
//构建`Int`,并`return`%8 = integer_literal $Builtin.Int32, 0          // user: %9%9 = struct $Int32 (%8 : $Builtin.Int32)        // user: %10return %9 : $Int32                              // id: %10
} // end sil function 'main'
  • 分析:

    • @main 这⾥标识当前 main.swift 的⼊⼝函数,SIL 中的标识符名称以 @ 作为前缀;
    • %0, %1… 在 SIL 也叫做寄存器,这⾥可以理解为⽇常开发中的常量,⼀旦赋值之后就不可以再修改,如果 SIL 中还要继续使⽤,那么就不断的累加数值。 同时这⾥所说的寄存器是虚拟的,最终运⾏到机器上,会使⽤真的寄存器;
    • alloc_gobal:创建⼀个全局变量;
    • global_addr: 拿到全局变量的地址,赋值给 %3;
    • metatype 拿到 YDWTeacher 的 Metadata 赋值给 %4 将 __allocating_init 的函数地址赋值给 %5;
    • __apply 调⽤ __allocating_init , 并把返回值给 %6;
    • 将 %6 的值存储到 %3(也就是刚刚创建的全局变量的地址);
    • 构建 Int , 并 return;
  • 注意:code 命令是在 .zshrc 中做了如下配置,可以在终端中指定软件打开相应文件:
$ open .zshrc
// ****** 添加以下别名
alias subl='/Applications/SublimeText.app/Contents/SharedSupport/bin/subl'
alias code='/Applications/Visual\ Studio\ Code.app/Contents/Resources/app/bin/code'// ****** 使用
$ code main.sil// 如果想SIL文件高亮,需要安装插件:VSCode SIL
  • 从 SIL 文件中,可以看出,代码是经过混淆的,可以通过以下命令还原,以s4main1tAA10YDWTeacherCvp 为例:xcrun swift-demangle s4main1tAA10YDWTeacherCvp,结果如下:
 xcrun swift-demangle s4main1tAA10YDWTeacherCvp$s4main1tAA10YDWTeacherCvp ---> main.t : main.YDWTeacher
  • 在 SIL 文件中搜索 s4main10YDWTeacherCACycfC,其内部实现主要是分配内存+初始化变量:

    • allocing_ref:创建一个 YDWTeacher 的实例对象,当前实例对象的引用计数为1;
    • 调用init方法;
 // ********* main入口函数中代码 *********%5 = function_ref @$s4main10YDWTeacherCACycfC : $@convention(method) (@thick YDWTeacher.Type) -> @owned YDWTeacher // s4main10YDWTeacherCACycfC 实际就是__allocating_init()// YDWTeacher.__allocating_init()sil hidden [exact_self_class] @$s4main10YDWTeacherCACycfC : $@convention(method) (@thick YDWTeacher.Type) -> @owned YDWTeacher {// %0 "$metatype"bb0(%0 : $@thick YDWTeacher.Type):// 堆上分配内存空间%1 = alloc_ref $YDWTeacher                      // user: %3// function_ref YDWTeacher.init() 初始化当前变量%2 = function_ref @$s4main10YDWTeacherCACycfc : $@convention(method) (@owned YDWTeacher) -> @owned YDWTeacher // user: %3// 返回%3 = apply %2(%1) : $@convention(method) (@owned YDWTeacher) -> @owned YDWTeacher // user: %4return %3 : $YDWTeacher                         // id: %4} // end sil function '$s4main10YDWTeacherCACycfC'
  • SIL语言对于Swift源码的分析是非常重要的,关于其更多的语法信息,可以参考:Swift Intermediate Language (SIL)。

三、符号断点调试

  • 在我们的 TestSwift 工程中设置“__allocating_init”符号断点;

  • 然后执行,可以看到:内部调用的是swift_allocObject;

四、源码分析

  • 在 VSCode 中的REPL(命令交互行,类似于python的,可以在这里编写代码)中编写如下代码(也可以拷贝),并搜索 *_swift_allocObject 函数加一个断点,如下所示:

  • 然后初始化一个实例对象t,回车:

  • 这里的 Local 中可以看出:requiredSize 是内存大小,requiredAlignmentMask 是内存对齐方式;requiredAlignmentMask 是 swift 中的字节对齐方式,这个和 OC 中是一样的,必须是8的倍数,不足的会自动补齐,目的是以空间换时间,来提高内存操作效率;
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,size_t requiredSize,size_t requiredAlignmentMask) {assert(isAlignmentMask(requiredAlignmentMask));auto object = reinterpret_cast<HeapObject *>(swift_slowAlloc(requiredSize, requiredAlignmentMask));// NOTE: this relies on the C++17 guaranteed semantics of no null-pointer// check on the placement new allocator which we have observed on Windows,// Linux, and macOS.new (object) HeapObject(metadata);// If leak tracking is enabled, start tracking this object.SWIFT_LEAKS_START_TRACKING_OBJECT(object);SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);return object;
}
  • swift_allocObject 的源码如下,主要分为:

    • 通过 swift_slowAlloc 分配内存,并进行内存字节对齐;
    • 通过 new + HeapObject + metadata 初始化一个实例对象;
    • 函数的返回值是 HeapObject 类型,所以当前对象的内存结构就是 HeapObject 的内存结构;
  • 进入 swift_slowAlloc 函数,其内部主要是通过 malloc 在堆中分配 size 大小的内存空间,并返回内存地址,主要是用于存储实例变量:
void *swift::swift_slowAlloc(size_t size, size_t alignMask) {void *p;// This check also forces "default" alignment to use AlignedAlloc.if (alignMask <= MALLOC_ALIGN_MASK) {#if defined(__APPLE__)p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else// 堆中创建size大小的内存空间,用于存储实例变量p = malloc(size);
#endif} else {size_t alignment = (alignMask == ~(size_t(0)))? _swift_MinAllocationAlignment: alignMask + 1;p = AlignedAlloc(size, alignment);}if (!p) swift::crash("Could not allocate memory.");return p;
}
  • 进入 HeapObject 初始化方法,需要两个参数:metadata、refCounts:
struct HeapObject {/// This is always a valid pointer to a metadata object.HeapMetadata const *metadata;SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;#ifndef __swift__HeapObject() = default;// Initialize a HeapObject header as appropriate for a newly-allocated object.constexpr HeapObject(HeapMetadata const *newMetadata) : metadata(newMetadata), refCounts(InlineRefCounts::Initialized){ }// Initialize a HeapObject header for an immortal objectconstexpr HeapObject(HeapMetadata const *newMetadata,InlineRefCounts::Immortal_t immortal): metadata(newMetadata), refCounts(InlineRefCounts::Immortal){ }
  • 分析:

    • 其中 metadata 类型是 HeapMetadata,是一个指针类型,占8字节;
    • refCounts(引用计数,类型是 InlineRefCounts,而 InlineRefCounts 是一个类 RefCounts 的别名,占8个字节),swift 采用 arc 引用计数;

五、总结

  • 对于实例对象 t 来说,其本质是一个 HeapObject 结构体,默认 16 字节内存大小(metadata 8字节 + refCounts 8字节),与 OC 的对比如下:

    • OC 中实例对象的本质是结构体,是以 objc_object 为模板继承的,其中有一个 isa 指针,占 8 字节;
    • Swift 中实例对象,默认的比 OC 中多了一个。refCounted 引用计数大小,默认属性占 16 字节;
  • Swift 中对象的内存分配流程是:_allocating_init --> swift_allocObject --> _swift_allocObject --> swift_slowAlloc --> malloc;
  • init 在其中的职责就是初始化变量,这点与 OC 中是一致的。

Swift之深入解析“对象”的底层原理相关推荐

  1. Swift之深入解析“属性”的底层原理

    一.存储属性(Stored Property) ① 什么是存储属性? 存储在实例的内存中的属性,只有一份: 存储属性可以是变量存储属性(用关键字 var 定义),也可以是常量存储属性(用关键字 let ...

  2. Swift之深入解析“类”的底层原理

    Objective-C 类 熟练 OC 的一定对上面这张图不陌生,没错,这就是 Apple 官方的实例对象.类.元类关系图,形象地展示说明了 isa 的指向关系.superclass 的指向关系以及元 ...

  3. Swift之深入解析“泛型”的底层原理

    一.泛型简介 ① Swift 泛型 Swift 提供了泛型可以写出灵活且可重用的函数和类型. Swift 标准库是通过泛型代码构建出来的,Swift 的数组和字典类型都是泛型集. 泛型可以创建一个 I ...

  4. iOS之深入解析类加载的底层原理:类如何加载到内存中

    一.App 启动与 dylb 加载 App 启动会由 libdyld.dylib 库先于 main 函数调用 start,执行 _dyld_start 方法,然后运用汇编实现调用 dyldbootst ...

  5. iOS之深入解析YYModel的底层原理

    一.前言 YYModel 是由 ibireme 开发的一套小而精美的模型转换框架,采用分类的形式,无需继承框架的某个基类就可以方便地完成模型的转换,且内部做了自动类型转换和安全处理,可以有效地防止因模 ...

  6. iOS之深入解析KVO的底层原理

    一.KVO 简介 ① 概念 KVO 全称 Key Value Observing,是苹果提供的一套事件通知机制,允许对象监听另一个对象特定属性的改变,并在改变时接收到事件. 由于 KVO 的实现机制, ...

  7. iOS之深入解析KVC的底层原理和自定义KVC的实现

    一.KVC 简介 ① 定义 KVC 是 Key-Value Coding 的简称,中文译义为键值编码. KVC 是指 iOS 的开发中,可以允许开发者通过 Key 名直接访问对象的属性,或者给对象的属 ...

  8. iOS之深入解析Runloop的底层原理

    一.Runloop 简介 ① 什么是 Runloop ? RunLoop 是事件接收和分发机制的一个实现,是线程相关的基础框架的一部分,一个 RunLoop 就是一个事件处理的循环,用来不停的调度工作 ...

  9. iOS之深入解析渲染的底层原理

    一.计算机渲染原理 ① CPU 与 GPU 的架构 对于现代计算机系统,简单来说可以大概视作三层架构:硬件.操作系统与进程.对于移动端来说,进程就是 App,而 CPU 与 GPU 是硬件层面的重要组 ...

最新文章

  1. DP UVALive 6506 Padovan Sequence
  2. RIP协议的问题解决方案
  3. 教你在64位Win7系统下使用ObRegisterCallbacks内核函数来实现进程保护
  4. BZOJ3277 串 【广义后缀自动机】
  5. Actionscript3.0动画编程中的几种特效举例
  6. 全局变量、局部变量、静态全局变量、静态局部变量在内存里的区别(转)
  7. C#连接sql server
  8. android谷歌补丁日期,久违的Android更新补丁:多年前的坑,谷歌终于给填上了
  9. web前端工程师必须掌握的24条宝贵经验!
  10. “华为搜索”正海外内测;苹果5亿美元和解“降速门”;Firefox隐藏HTTPS | 极客头条...
  11. 公安部起草《“十三五”平安中国建设规划》并公开征求意见
  12. ros中web端通过 按钮加载本地静态 pgm 地图显示在canvas画布中
  13. python爬虫网页图片并保存到本地
  14. SOA面向服务架构详解
  15. 而立之年,第一篇博客,
  16. VS2010提示未能正确加载包
  17. Nginx proxy_pass指令(代理配置)
  18. AD域账号日常维护常用操作
  19. 从月薪3千到3万,需要努力多久?
  20. 数据分析师只适合男生吗,女生可不可以胜任?

热门文章

  1. [网络流24题]太空飞行计划
  2. 小米oj 有多少个公差为2的等差数列
  3. phpstorm知识点
  4. emqtt 试用(二)验证 emq 和 mosquito 的共享订阅
  5. AutoComplete - 自动完成插件
  6. Windows 系统下Git安装图解
  7. C#数组和集合专题4(Hashtable类)
  8. javascript王者归来--属性和方法的类型
  9. 有一次去校内的某个礼堂看电影,在门口有个长得很斯文的陌生人一脸神秘地跟我说:师弟,能不能进去之后,把电影票从厕所的气窗扔出来给我……...
  10. [Java设计模式]期末作业参考