【Swift底层】Swift底层探索方法介绍

Swift底层探索方法介绍

在探索Swift一些底层原理的时候,常常会无从下手,分享几个可行的路子。

基础

  1. Swift源码定义了写swift程序的基本功能层,如基本的数据类型,通用的数据结构,全局函数,常用的抽象协议等。我们也可以通过研究标准库的代码来学习swift中各类数据结构和方法的底层实现。
  2. iOS开发的语言不管是OC还是Swift都是通过LLVM进行编译的。如下图所示:

swift的前端编译器为swiftc。一个Swift文件的编译从编译器的角度会经过如下步骤。

  1. Code经过词法分析、语法分析生成AST抽象语法树
  2. AST通过SILGen生成原生SIL文件(代码量很大,没有进行类型检查等)
  3. 原生SIL文件优化生成最终SIL文件
  4. 该SIL文件通过IRGen生成IR
  5. IR生成机器码

注:生成可执行文件的流程中,swiftc和clang的区别就在于swiftc多了生成SIL文件这一步。

方案说明

swiftc

在上述介绍的swift编译流程中,我们可以通过swiftc -h查看常用指令

perl 复制代码
MODES:
  -dump-ast              解析和类型检查源文件 & 转换成 AST
  -dump-parse            解析源文件 & 转换成 AST
  -emit-assembly         生成汇编文件
  -emit-bc               生成 LLVM Bitcode 文件
  -emit-executable       生成已链接的可执行文件
  -emit-imported-modules 生成已导入的库
  -emit-ir               生成 LLVM IR 文件
  -emit-library          生成已连接的库
  -emit-object           生成目标文件
  -emit-silgen           生成 raw SIL 文件(第一个阶段)
  -emit-sil              生成 canonical SIL 文件(第2个阶段)
  -index-file            为源文件生成索引数据
  -print-ast             解析和类型检查源文件 & 转换成更简约的格式更好的 AST
  -typecheck             解析和类型检查源文件
  ···

就可读性而言,我们常选择sil文件来进行分析。以下主要对sil文档的分析进行简单介绍。如果想查看更多信息,可以查看官方文档

IR基本语法

rust 复制代码
// 数组
[<elementnumber> x <elementtype>]
// example
 alloca [24 x i8], align 8 24个i8都是0

// 结构体
%swift.refcounted = type { %swift.type*, i64 }
//表示形式
%T = type {<type list>} //这种和C语⾔的结构体类似

// 指针类型
<type> *
//表示形式
i64* //64位的整形

// getelementptr指令:获取数组和结构体的成员
<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <id x>}* 
<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*

sil文件常见指令

  • @main:入口【sil中的标识符名称以@为前缀】
  • %1%2:sil中的寄存器(常量),一旦赋值不可修改
  • alloc_global:分配一个全局变量
  • metatype:获取元类型
  • function_ref:获取函数指针
  • apply:调用方法
  • alloc_ref:分配一个类型为T的实例对象,默认的引用计数为1,也就是在堆上分配内存空间
  • alloc_stack:在栈区为变量分配内存空间
  • integer_literal:构建一个整形变量(整形变量在swift中其实为结构体类型。)
  • store {A} to {B}:将A存储到指定变量B中

注:在SIL文件中常常可以看到类似s4main6personAA11PandaPersonCvp这样经过混淆的变量名,可以通过xcrun swift-demangle s4main6personAA11PandaPersonCvp指令来将其还原。

寄存器

在sil文件中可以看到许多%0,%1,代表的是寄存器。请注意这里的寄存器和我们平时研究编译原理中的寄存器不太一样。 我们在工程中打开调试模式并开启Always Show Dissasembly

之后通过register read命令拿到的这些寄存器信息中的寄存器是不同的。如下图所示,这些寄存器是真实存在的。

https://oscimg.oschina.net/oscnet/up-edcf14ac116a4ae6600907bf16426f3cfb5.png

而我们的sil文件中的寄存器是虚拟的。我们可以理解为常量【一旦赋值无法改变】。运行的时候会对应到真实的寄存器。

Example

首先我们创建一个main.swift文件

ini 复制代码
class PandaPerson {
    var name: String = "Letty"
    var age: Int = 18
}

let person = PandaPerson()

我们现在要探索的就是let person = PandaPerson()这句代码背后做了什么事情。类比OC创建对象,alloc内存分配,init初始化操作。而Swift提供了这样默认的初始化器,我们将通过SIL文件来探索。

css 复制代码
# 执行下述命令生成sil文件
swiftc -emit-sil main.swift > main.sil

首先我们可以找到入口函数@main

swift 复制代码
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32

下面具体看看@main函数中在创建对象部分做了什么事

less 复制代码
  // 1. 分配一个全局变量person
  alloc_global @$s4main6personAA11PandaPersonCvp
  // 2. 为该变量分配内存空间(即创建实例对象)
  %3 = global_addr @$s4main6personAA11PandaPersonCvp : $*PandaPerson
  // 3. 获取PandaPerson元类型
  %4 = metatype $@thick PandaPerson.Type
  // 4. 获取PandaPerson的__allocating_init()函数指针
  %5 = function_ref @$s4main11PandaPersonCACycfC : $@convention(method) (@thick PandaPerson.Type) -> @owned PandaPerson
  // 5. 调用__allocating_init()方法,将方法返回值存至%6
  %6 = apply %5(%4) : $@convention(method) (@thick PandaPerson.Type) -> @owned PandaPerson
  // 6. 将创建的对象存储到第三步中为全局变量person分配的内存空间中
  store %6 to %3 : $*PandaPerson

  ···

swift源码

仓库地址: github.com/apple/swift

目录结构

less 复制代码
graph TD
A(swift-main) --> B("docs:文档")
A --> C("stdlib:swift源码")
A --> D("lib:c++源码")
A --> E("include:c++头文件")
A --> I("···")

常用索引

  • 标准库:./stdlib/public/core

    • map、filter:./stdlib/public/core/Sequence.swift
    • flatMap、compactMap、reduce:./stdlib/public/core/SequenceAlgorithms.swift
    • Substring:./stdlib/public/core/Substring.swift
    • Optional:./stdlib/public/core/Optional.swift
  • MetaData:./include/swift/ABI/MetaData.h

总结

SIL文件可以帮助我们探究方法调用流程和部分数据结构的底层实现,swift源码则更贴近开发者,帮助我们了解swift对于数据和方法等设计及其原理。具体问题具体分析,笔者建议两者可以结合使用。

相关推荐
Jinkey2 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
程序猿看视界8 小时前
如何在 UniApp 中实现 iOS 版本更新检测
ios·uniapp·版本更新
dr李四维11 小时前
iOS构建版本以及Hbuilder打iOS的ipa包全流程
前端·笔记·ios·产品运营·产品经理·xcode
️ 邪神12 小时前
【Android、IOS、Flutter、鸿蒙、ReactNative 】自定义View
flutter·ios·鸿蒙·reactnative·anroid
比格丽巴格丽抱1 天前
flutter项目苹果编译运行打包上线
flutter·ios
网络安全-老纪1 天前
iOS应用网络安全之HTTPS
web安全·ios·https
1024小神1 天前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
lzhdim1 天前
iPhone 17 Air看点汇总:薄至6mm 刷新苹果轻薄纪录
ios·iphone
安和昂1 天前
【iOS】知乎日报第四周总结
ios
麦田里的守望者江1 天前
KMP 中的 expect 和 actual 声明
android·ios·kotlin