「这是我参与2022初次更文挑战的第11天,活动概况查看:2022初次更文挑战」

iOS开发言语,不论是OC仍是Swift,都是经过LLVM进行编译的,终究生成.o文件,其编译流程如下图:

Swift(三)-类的SIL文件分析

  • OC经过clang编译器,编译成IR,然后再生成可执行文件.o(也便是咱们的机器码);
  • Swift则是经过Swift编译器生成IR,然后再生成可执行文件.o

Swift编译流程

Swift言语编译流程图如下:

Swift(三)-类的SIL文件分析

  • Swift代码经过-dump-parse命令进行语法剖析,生成笼统语法树AST
  • 笼统语法树经过-dump-ast进行语义剖析(比方类型查看是否正确,是否安全);
  • 语义剖析之后,Swift代码将会降级为SIL,也便是Swift中心言语(Swift intermediate language);
  • SIL分为Raw SIL(原生的,没有开启优化选项)和SILOpt Canonical SIL(经过优化的);
  • 终究经过LLVM降级为IR,然后经过后段代码编译为不同架构的机器码

流程中触及的命令如下:

// 剖析输出AST
swiftc main.swift -dump-parse 
// 剖析并且查看类型输出AST 
swiftc main.swift -dump-ast 
// 生成中心体言语(SIL),未优化
swiftc main.swift -emit-silgen 
// 生成中心体言语(SIL),优化后的
swiftc main.swift -emit-sil
// 生成LLVM中心体言语 (.ll文件)
swiftc main.swift -emit-ir
// 生成LLVM中心体言语 (.bc文件)
swiftc main.swift -emit-bc
// 生成汇编
swiftc main.swift -emit-assembly
// 编译生成可执行.out文件
swiftc -o main.o main.swift

相关于OC,在Swift的编译进程中多了SILSIL会对咱们的代码进行安全查看,比方如下代码: 在OC中:

Swift(三)-类的SIL文件分析

咱们定义的int8_t类型的计算成果将会出错,由于int8_t只有 一个字节,其运算成果现已溢出,在OC中给出了一个过错的运算成果;咱们再来看在Siwft中会产生什么:

Swift(三)-类的SIL文件分析

咱们看到,同样的代码,在Swift中的编译阶段就会报错,防止后续产生不可预知的过错;这些都是由于SIL存在的成果;

SIL文件剖析

那么SIL究竟有着什么样的语法规矩呢?咱们来生成一个SIL文件进行剖析;咱们先写一段简略的代码:

Swift(三)-类的SIL文件分析

接下来咱们将这段代码生成SIL文件:

Swift(三)-类的SIL文件分析

为了方便观看,咱们也能够经过swiftc main.swift -emit-sil > ./main.sil指令将SIL文件输出为main.sil文件;

Swift(三)-类的SIL文件分析

由于SIL文件内容较多,咱们只挑选重要的部分剖析:

Teacher的声明

首要,咱们先看一下TeacherSIL阶段的声明部分:

Swift(三)-类的SIL文件分析

Teacher的定义中能够看到有初始化的存储属性agename,还有一个标识为@objcdeinit函数,以及默许的初始化器init()

main函数

Swift(三)-类的SIL文件分析

  • @main:标识入口函数,在SIL@是作为标识符的;
  • %0%9:这写在SIL中也被称为寄存器,能够理解为开发进程中的常量,一旦赋值,将无法再次修改(所以后边数字会一向累加);需求注意的是,此处的寄存器虚拟寄存器,终究运转到详细的设备上时,会运用真实的寄存器
  • alloc_global @$s4main1tAA7TeacherCvp:分配一个全局变量,该全局变量的名称是混写的,咱们能够经过终端指令xcrun swift-demangle将其还原:

Swift(三)-类的SIL文件分析

能够看到此处对应的是main文件中的t变量,对应的是main中的Teacher

  • %3 = global_addr @$s4main1tAA7TeacherCvp:拿到该全局变量的地址给%3
  • %4 = metatype $@thick Teacher.Type:获取Teacher.Type的元类型给%4
  • 依据注释能够知道%5Teacher.__allocating_init()函数的引证,也便是其指针地址;
  • %6 = apply %5(%4):将%5(%4)的成果,也便是Teacher的实例变量,赋值给%6
  • store %6 to %3:将%6也便是实例变量的内存地址,存放到%3这个全局变量中;
  • Swift底层中Int便是一个Struct类型,%8%9是在构建一个Int32的整数类型0,类似与咱们OCmain函数终究的return 0

关于SIL语法规矩,请查看SIL官方文档

__allocating_init()函数

在上述SIL代码中调用了s4main7TeacherCACycfC也便是Teacher.__allocating_init()函数来创立当前的实例对象的;咱们在SIL中定位到该函数:

Swift(三)-类的SIL文件分析

  • 该函数需求一个Teacher.Type的元类型,咱们能够将此元类型理解为isa
  • %1 = alloc_ref $Teacheralloc_ref会创立一个Teacher的实例变量,其引证计数初始化为1alloc_ref实际上也便是去堆区请求内存空间;假如标识为objcSwift类将会运用Objective-C+allocWithZone:初始化办法;怎么验证呢?咱们在代码中增加如下断点:

Swift(三)-类的SIL文件分析

运转程序,查看汇编指令:

Swift(三)-类的SIL文件分析

咱们看到将会调用Teacher.__allocating_init()函数,那么该函数是怎么完成的呢?断点进入该函数:

Swift(三)-类的SIL文件分析

__allocating_init()函数中主要调用了swift_allocObjectTeacher.init()

  • swift_allocObject在堆区找到合适的内存空间初始化;
  • Teacher.init()初始化成员变量;

这是一个朴实的Swift类,那么假如咱们将Teacher继承自NSObject会产生什么呢?

Swift(三)-类的SIL文件分析

咱们重复上述汇编调试步骤,进入Teacher.__allocating_init()函数:

Swift(三)-类的SIL文件分析

咱们发现,初始化函数变成了objc_allocWithZone以及objc_msgSend

  • objc_allocWithZone调用malloc函数请求内存空间;
  • objc_msgSend发送init音讯;

swift_allocObject

在前边咱们现已剖分出Swift类初始化进程中会调用swift_allocObject,那么该函数做了什么呢?咱们需求借助于Swift源码进行剖析; 在该目录下找到stdlib->public->runtime->HeapObject.cpp文件,该文件是和咱们的Swift类初始化相关的文件;在该文件中定位到_swift_allocObject_函数,这是一个私有函数,其是被swift::swift_allocObject调用起来的:

Swift(三)-类的SIL文件分析

_swift_allocObject_完成如下:

Swift(三)-类的SIL文件分析

该函数有三个参数:

  • HdapMetadata const *metadata:元数据类型;
  • requiredSize:所需求的巨细;
  • requiredAlignmentMask:对齐所需求的掩码,能够从objc的源码中得知,其为7,由于是8字节对齐;

requiredSizerequiredAlignmentMask传递给函数swift_slowAlloc,该函数返回了一个HeapObject类型的指针; reinterpret_cast用来做指针类型的转化;

  • new (object) HeapObject(metadata)HeapObject初始化;

那么swift_slowAlloc是用来干什么的呢?

swift_slowAlloc

其完成如下:

Swift(三)-类的SIL文件分析

swift_slowAlloc函数中,调用了malloc函数来开辟内存空间;

流程总结

那么,咱们大致能够总结出Swift对象进行内存分配的流程:

  1. 首要会调用_allocating_init():该函数有编译器生成;
  2. 关于纯Swift类将会再调用swift_allocObject()函数;
  3. 然后在swift_allocObjec()总会调用私有函数_swift_allocObject
  4. 然后经过函数swift_slowAlloc调用malloc来请求堆区的内存空间;