美文网首页修行
Swift底层探索(一):编译流程

Swift底层探索(一):编译流程

作者: HotPotCat | 来源:发表于2020-12-05 21:14 被阅读0次

Swift编译

Swift编译究竟是一个怎样的过程呢?从Swift语言到cpu能够识别的机器码这之间究竟经过了哪些步骤呢?
我们先写一个简单的HotpotCat类如下:

import Foundation

class HotpotCat {
    var name: String = "Hotpot"
    var age: Int = 1
}

var hotpot = HotpotCat()

通过默认的初始化器我们构造了一个实例对象hotpot,类比OC在这个过程中alloc(内存分配) init(初始化)。那么Swift这个默认的初始化器到底做了什么操作?这里我们需要了解SIL(Swift intermediate language),再阅读SIL之前,先了解下什么是SIL。从字面意思理解它是一门中间语言。
iOS开发的语言不管是OC还是Swift后端都是通过LLVM进行编译的,编译过程如下:

image.png

OC通过clang编译器,编译成IR,然后生成可执行文件.o(也就会机器码);
Swift通过Swift编译器编译成IR,然后生成可执行文件。

那么一个Swift文件编译的整个过程中都经历了什么呢?


Swift编译过程
  • 1.Parse :解析器是一个简易的递归下降解析器(在 lib/Parse 中实现),并带有完整手动编码的词法分析器。通过parse进行词法分析;
  • 2.Semantic Analysis: 语义分析阶段(在 lib/Sema 中实现)负责获取已解析的 AST(抽象语法树)并将其转换为格式正确且类型检查完备的 AST,以及在源代码中提示出现语义问题的警告或错误。语义分析包含类型推断,如果可以成功推导出类型,则表明此时从已经经过类型检查的最终 AST 生成代码是安全的;
  • 3.Clang Importer:Clang 导入器(Clang Importer):Clang 导入器(在 lib/ClangImporter 中实现)负责导入 Clang 模块,并将导出的 C 或 Objective-C API 映射到相应的 Swift API 中。最终导入的 AST 可以被语义分析引用。
  • 4.SIL 生成(SIL Generation):Swift 中间语言(Swift Intermediate Language,SIL)是一门高级且专用于 Swift 的中间语言,适用于对 Swift 代码的进一步分析和优化。SIL 生成阶段(在 lib/SILGen 中实现)将经过类型检查的 AST 弱化为所谓的「原始」SIL。SIL 的设计在 docs/SIL.rst 有所描述。这个过程生成RAW SIL(原生SIL,代码量很大,不会进行类型检查,代码优化)
  • 5.SIL 保证转换(SIL Guaranteed Transformations):SIL 保证转换阶段(在 lib/SILOptimizer/Mandatory中实现)负责执行额外且影响程序正确性的数据流诊断(比如使用未初始化的变量)。这些转换的最终结果是「规范」SIL。
  • 6.SIL 优化(SIL Optimizations):SIL 优化阶段(在 lib/Analysislib/ARClib/LoopTransforms 以及 lib/Transforms 中实现)负责对程序执行额外的高级且专用于 Swift 的优化,包括(例如)自动引用计数优化、去虚拟化、以及通用的专业化
    通过-emit-sil命令生成优化过后的 SIL Opt Canonical SIL。这个也是我们一般阅读的SIL代码;
  • 7.LLVM IR 生成(LLVM IR Generation):IR 生成阶段(在 lib/IRGen 中实现)将 SIL 弱化为 LLVM LR,此时 LLVM 可以继续优化并生成机器码。
    通过IRGen生成 IR;
  • 8.然后生成机器码交给机器进行识别。

Swift和OC的区别也就是中间SIL生成的这一部分。我们一般阅读经过优化后的SIL文件。

swift在编译的过程中使用的前段编译器是swiftc,和OC中使用的Clang是有区别的。可以通过swiftc -h查看swiftc都能哪些功能:

USAGE: swiftc
MODES:
  -dump-ast              Parse and type-check input file(s) and dump AST(s)
  -dump-parse            Parse input file(s) and dump AST(s)
  -dump-pcm              Dump debugging information about a precompiled Clang module
  -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               Emit LLVM BC file(s)
  -emit-executable       Emit a linked executable
  -emit-imported-modules Emit a list of the imported modules
  -emit-ir               Emit LLVM IR file(s)
  -emit-library          Emit a linked library
  -emit-object           Emit object file(s) (-c)
  -emit-pcm              Emit a precompiled Clang module from a module map
  -emit-sibgen           Emit serialized AST + raw SIL file(s)
  -emit-sib              Emit serialized AST + canonical SIL file(s)
  -emit-silgen           Emit raw SIL file(s)
  -emit-sil              Emit canonical SIL file(s)
  -index-file            Produce index data for a source file
  -parse                 Parse input file(s)
  -print-ast             Parse and type-check input file(s) and pretty print AST(s)
  -resolve-imports       Parse and resolve imports in input file(s)
  -typecheck             Parse and type-check input file(s)

简单分析下
-dump-parse
Parse input file(s) and dump AST(s)、分析输出AST

swiftc -dump-parse main.swift >> ./main.parse

-dump-ast
Parse and type-check input file(s) and dump AST(s)。分析并且检查类型输出AST。

swiftc -dump-ast main.swift >> ./main.ast

从以上可以看出这两个都生成了抽象语法树,-dump-ast多了类型检查。我们摘录一段对比就可以看出一个bind Swift String,一个为none。

//-dump-parse
    (pattern_binding_decl range=[main.swift:11:5 - line:11:24]
      (pattern_typed
        (pattern_named 'name')
        (type_ident
          (component id='String' bind=none)))

//-dump-ast
    (pattern_binding_decl range=[main.swift:11:5 - line:11:24]
      (pattern_typed type='String'
        (pattern_named type='String' 'name')
        (type_ident
          (component id='String' bind=Swift.(file).String)))

-emit-silgen
Emit raw SIL file(s),简单理解为生成未加工的SIL文件。

swiftc -emit-silgen main.swift >> ./main.silgen
image.png

-emit-sil
Emit canonical SIL file(s),经过优化处理的SIL文件

swiftc -emit-sil main.swift >> ./main.sil
image.png

对比两者可以看到经过优化的SIL多了引用计数相关的操作,那么猜测引用计数是在SIL优化这一步进行处理的。

-emit-ir
Emit LLVM IR file(s), 生成LLVM的IR中间表示层文件

swiftc -emit-ir main.swift >> ./main.ir

-emit-assembly
Emit assembly file(s) (-S),生成汇编语言

swiftc -emit-assembly  main.swift >> ./main.assembly

-emit-bc
Emit LLVM BC file(s),生成字节码二进制

swiftc -emit-bc  main.swift >> ./main.bc

-o
生成可执行的二进制文件

swiftc -o  main.o  main.swift

这里main.swift文件是一个只依赖了Foundation库的文件,直接使用-emit就可以了,这么直接编译iOS项目中的Swift源文件会报错,因为依赖了UIKit或其它库,需要额外指定-target和-sdk。

  • sdk 直接指定Xcode对应的SDK.
//模拟器
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk`
//真机
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk`
//mac
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk

可以通过命令代替真实路径,当然真实路径也没问题,以下分别对应模拟器、真机、mac 路径:
xcrun --show-sdk-path --sdk iphonesimulator/iphoneos/macosx

  • target (指令集-apple-ios版本/指令集-apple-ios版本-simulator
    这里在操作时用mac/iphone代替apple也是可以的,分别指代maciphone平台。当然TVwatch也同理。
    对应目标平台i386x86_64armv7armv7sarm64arm64e等,这里iOS版本最低iOS7。
    如:arm64e-apple-ios14.0x86_64-apple-ios14-simulatorarmv7-iphone-ios12.0arm64-mac-macosx11.0
//模拟器
swiftc -emit-sil -sdk $(xcrun --show-sdk-path --sdk iphonesimulator)  -target x86_64-apple-ios14.0-simulator ViewController.swift >> ./ViewController.sil
//真机
swiftc -emit-sil -sdk $(xcrun --show-sdk-path --sdk iphoneos)  -target arm64e-apple-ios14.0 ViewController.swift >> ./ViewController.sil
//mac
swiftc -emit-sil -sdk $(xcrun --show-sdk-path --sdk macosx) -target arm64-apple-macosx11.0 ViewController.swift >> ./ViewController.sil

更详细的介绍可以观看官方讲解视频

SIL分析

接着上面的HotpotCat例子,我们直接在终端生成SIL文件并保存为main.sil查看。(当然也可以尝试其它命令-emit-silgen,-dump-ast)。

swiftc -emit-sil main.swift >> ./main.sil
class HotpotCat {
  @_hasStorage @_hasInitialValue var name: String { get set }
  @_hasStorage @_hasInitialValue var age: Int { get set }
  @objc deinit
  init()
}

可以看到HotpotCat有两个存储属性,有一个init方法,有一个objc标识的deinit方法。

main分析

继续看sil文件会找到一个main函数,这个main函数其实就是swift隐藏的main函数,swift不过进行了省略而已。

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s4main6hotpotAA9HotpotCatCvp     // id: %2
  %3 = global_addr @$s4main6hotpotAA9HotpotCatCvp : $*HotpotCat // user: %7
  %4 = metatype $@thick HotpotCat.Type            // user: %6
  // function_ref HotpotCat.__allocating_init()
  %5 = function_ref @$s4main9HotpotCatCACycfC : $@convention(method) (@thick HotpotCat.Type) -> @owned HotpotCat // user: %6
  %6 = apply %5(%4) : $@convention(method) (@thick HotpotCat.Type) -> @owned HotpotCat // user: %7
  store %6 to %3 : $*HotpotCat                    // id: %7
  %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'

在SIL中以@作为标识符名称前缀

  • @mian 标识这是swift入口函数;
  • @convention(c)标识这是一个C函数,有两个参数Int32和UnsafeMutablePointer的指针,返回Int32;
  • alloc_global分配一个全局变量s4main6hotpotAA9HotpotCatCvp,这个变量也就是变量hotpot,名字是经过混写之后的(name manager)。可以通过xcrun去还原查看。
 xcrun  swift-demangle s4main6hotpotAA9HotpotCatCvp
$s4main6hotpotAA9HotpotCatCvp ---> main.hotpot : main.HotpotCat
  • %3~%9表示当前寄存器,这里的寄存器和register read读取的寄存器不是一个东西,这里寄存器是虚拟的会一直递增,可以理解为常量,经过赋值之后不会再改变。在运行的时候会直接对应到真实的寄存器。
  • global_addr 拿到全局变量hotpot的地址赋值给%3。
  • metatype 拿到HotpotCat的 Metadata 赋值给%4。也就是元类型。
  • function_ref 拿到函数s4main9HotpotCatCACycfC的地址给到%5。这个函数我们xcrun看一下其实是$s4main9HotpotCatCACycfC ---> main.HotpotCat.__allocating_init() -> main.HotpotCat。当然SIL里面也有注释。
  • apply %5(%4)就是调用__allocating_init,参数是我们的元类型。返回值为我们要的实例变量。
  • store 将得到的实例变量%6给到%3,也就是将实例变量地址放到全局变量中。
  • 最后也就是构建一个0返回,相当于main函数的 retern 0。

Swift实例对象

__allocating_init()分析

那么s4main9HotpotCatCACycfC(__allocating_init)究竟干了什么呢?

// HotpotCat.__allocating_init()
sil hidden [exact_self_class] @$s4main9HotpotCatCACycfC : $@convention(method) (@thick HotpotCat.Type) -> @owned HotpotCat {
// %0 "$metatype"
bb0(%0 : $@thick HotpotCat.Type):
  %1 = alloc_ref $HotpotCat                       // user: %3
  // function_ref HotpotCat.init()
  %2 = function_ref @$s4main9HotpotCatCACycfc : $@convention(method) (@owned HotpotCat) -> @owned HotpotCat // user: %3
  %3 = apply %2(%1) : $@convention(method) (@owned HotpotCat) -> @owned HotpotCat // user: %4
  return %3 : $HotpotCat                          // id: %4
} // end sil function '$s4main9HotpotCatCACycfC'
  • alloc_ref 创建HotpotCat实例对象(在堆上分配内存空间),引用计数为1。alloc_ref
    官方原文:
Allocates an object of reference type T. The object will be initialized with retain count 1;
  • function_ref 获取init方法,这里注意s4main9HotpotCatCACycfcs4main9HotpotCatCACycfC最后一个字母不同,xcrun验证一下确实是init()方法。
  • apply 调用init方法,初始化实例变量,参数是实例变量地址,最后返回。

xcode断点验证

我们可以设置一个符号断点(__allocating_init)验证一下:


image.png
SwiftBasic`HotpotCat.__allocating_init():
->  0x100003be0 <+0>:  push   rbp
    0x100003be1 <+1>:  mov    rbp, rsp
    0x100003be4 <+4>:  push   r13
    0x100003be6 <+6>:  push   rax
    0x100003be7 <+7>:  mov    esi, 0x28
    0x100003bec <+12>: mov    edx, 0x7
    0x100003bf1 <+17>: mov    rdi, r13
    0x100003bf4 <+20>: call   0x100003d5e               ; symbol stub for: swift_allocObject
    0x100003bf9 <+25>: mov    r13, rax
    0x100003bfc <+28>: call   0x100003c40               ; SwiftBasic.HotpotCat.init() -> SwiftBasic.HotpotCat at main.swift:10
    0x100003c01 <+33>: add    rsp, 0x8
    0x100003c05 <+37>: pop    r13
    0x100003c07 <+39>: pop    rbp
    0x100003c08 <+40>: ret    

Swift源码验证

同样可以通过VSCode调试源码,在swift的REPL(命令交互行)


image.png

swift_allocObject

在定义变量hotpot敲回车前,我们给_swift_allocObject_加个断点:
这个方法最终是创建我们当前的实例对象

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;
}
image.png

敲回车后看一下本地变量:大小需要40字节,8字节对齐。

这里对齐的目的是对于64位cpu而言,一次能读取8个字节(连续)。在这个过程当中连续读8个字节最快,对于创建出来的实例对象应该是8的倍数,偶数。8的倍数是为了在整个内存寻址周期的过程当中更加的具有效率,不足8的倍数会补足。目的是以空间换时间来提高访问存储效率。这里掩码为7代表的就是8字节对齐。

image.png

swift_slowAlloc

接着我们看swift_slowAlloc这个函数:

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
    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;
}

p这里可以看到在堆中创建size大小的空间。这个size存储我们当前的实例变量

#if defined(__APPLE__)
    p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
    p = malloc(size);

swift_allocObject函数的返回值是HeapObject,意味着我们创建出来的实例变量在内存当中是HeapObject。

  new (object) HeapObject(metadata);

创建HeapObject的参数是metadata,也就是通过元数据初始化HeapObject这个结构体。

HeapObject

HeapObject内容如下(c++代码):

#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts 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)
  { }

可以看到这函数需要metadata(元数据)和refCounts(引用计数)。可以看到metadata是一个指针类型(8字节),refCounts可以看到是一个InlineRefCounts。

InlineRefCounts、RefCounts

InlineRefCounts定义如下,RefCounts点进去可以看到是一个class。

typedef RefCounts<InlineRefCountBits> InlineRefCounts;

class RefCounts {
  std::atomic<RefCountBits> refCounts;

  // Out-of-line slow paths.

所以refCounts也是一个指针类型(8字节)。上面我们在VSCode调试的时候看到hotpot对象requiredSize占40字节,那么这里其实Int占8字节,String占16字节。
我们也可以通过代码直接验证

print(MemoryLayout<String>.size)
print(MemoryLayout<Int>.stride)
print(class_getInstanceSize(HotpotCat.self))
16
8
40

那么对我们的hotpot实例对象本质是一个HeapObject结构体(默认16字节大小)。相比于OC实例对象本质是结构体objc_object,他有一个isa指针默认8字节。swift相比oc多了一个refCounted。

  • swift 内存分配:__allcoating_init—>swift_allocObject—>swift_allocObject—>swift_slowAlloc—>Malloc;
  • Swift对象的内存结构为HeapObject,有两个属性:Metadata、RefCount默认占用16字节;
  • init初始化变量,和OC中是一致的。

类对象

类结构是HeapMetadata结构,OC中类结构是objc_class。

HeapMetadata

using HeapMetadata = TargetHeapMetadata<InProcess>;

从源码中可以看到HeapMetadata是一个别名,实际是TargetHeapMetadata。

TargetHeapMetadata

template <typename Runtime>
struct TargetHeapMetadata : TargetMetadata<Runtime> {
  using HeaderType = TargetHeapMetadataHeader<Runtime>;

  TargetHeapMetadata() = default;
  constexpr TargetHeapMetadata(MetadataKind kind)
    : TargetMetadata<Runtime>(kind) {}
#if SWIFT_OBJC_INTEROP
  constexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa)
    : TargetMetadata<Runtime>(isa) {}
#endif
};
using HeapMetadata = TargetHeapMetadata<InProcess>;

TargetHeapMetadata是一个模板类型,接收一个参数InProcess, InProcess定义了一些需要的数据结构。看TargetHeapMetadata源码发现其中并没有属性,只有初始化方法,初始化方法是MetadataKind kind,那么我们看看他的父结构体TargetMetadata。

TargetMetadata

private:
  /// The kind. Only valid for non-class metadata; getKind() must be used to get
  /// the kind value.
  StoredPointer Kind;
public:
  /// Get the metadata kind.
  MetadataKind getKind() const {
    return getEnumeratedMetadataKind(Kind);
  }
  
  /// Set the metadata kind.
  void setKind(MetadataKind kind) {
    Kind = static_cast<StoredPointer>(kind);
  }

可以看到有一个StoredPointer Kind

  using StoredPointer = typename Runtime::StoredPointer;

再后头看下InProcess,可以找到

  using StoredPointer = uintptr_t;

继续看uintptr_t,可以看到是unsigned long类型,那么kind就是unsigned long类型。kind其实是来区分是哪种类型的元数据。

typedef unsigned long           uintptr_t;

TargetHeapMetadata中点开MetadataKind可以看到一个MetadataKind.def文件,点开.

MetadataKind.def

/// A class type.
NOMINALTYPEMETADATAKIND(Class, 0)

/// A struct type.
NOMINALTYPEMETADATAKIND(Struct, 0 | MetadataKindIsNonHeap)

/// An enum type.
/// If we add reference enums, that needs to go here.
NOMINALTYPEMETADATAKIND(Enum, 1 | MetadataKindIsNonHeap)

/// An optional type.
NOMINALTYPEMETADATAKIND(Optional, 2 | MetadataKindIsNonHeap)

/// A foreign class, such as a Core Foundation class.
METADATAKIND(ForeignClass, 3 | MetadataKindIsNonHeap)

/// A type whose value is not exposed in the metadata system.
METADATAKIND(Opaque, 0 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// A tuple.
METADATAKIND(Tuple, 1 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// A monomorphic function.
METADATAKIND(Function, 2 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// An existential type.
METADATAKIND(Existential, 3 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// A metatype.
METADATAKIND(Metatype, 4 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// An ObjC class wrapper.
METADATAKIND(ObjCClassWrapper, 5 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// An existential metatype.
METADATAKIND(ExistentialMetatype, 6 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// A heap-allocated local variable using statically-generated metadata.
METADATAKIND(HeapLocalVariable, 0 | MetadataKindIsNonType)

/// A heap-allocated local variable using runtime-instantiated metadata.
METADATAKIND(HeapGenericLocalVariable,
             0 | MetadataKindIsNonType | MetadataKindIsRuntimePrivate)

/// A native error object.
METADATAKIND(ErrorObject,
             1 | MetadataKindIsNonType | MetadataKindIsRuntimePrivate)

这里面记录了我们当前所有元数据类型。
kind种类:

name value
Class 0x0
Struct 0x200
Enum 0x201
Optional 0x202
ForeignClass 0x202
Opaque 0x300
Tuple 0x301
Function 0x302
Existential 0x303
Metatype 0x304
ObjCClassWrapper 0x305
ExistentialMetatype 0x306
HeapLocalVariable 0x400
HeapGenericLocalVariable 0x500
ErrorObject 0x501
LastEnumerated 0x7FF

回到TargetMetadata,只有一个kind属性,在这个文件中我们往下找看看,706行可以看到有一个getClassObject方法,返回类型为TargetClassMetadata

  const TargetClassMetadata<Runtime> *getClassObject() const;

实现为:

  template<> inline const ClassMetadata *
  Metadata::getClassObject() const {
    switch (getKind()) {
    case MetadataKind::Class: {
      // Native Swift class metadata is also the class object.
      return static_cast<const ClassMetadata *>(this);
    }
    case MetadataKind::ObjCClassWrapper: {
      // Objective-C class objects are referenced by their Swift metadata wrapper.
      auto wrapper = static_cast<const ObjCClassWrapperMetadata *>(this);
      return wrapper->Class;
    }
    // Other kinds of types don't have class objects.
    default:
      return nullptr;
    }
  }

  void *allocateMetadata(size_t size, size_t align);

本身是匹配kind返回对应的值,就是上表对应kind的值。如果是class那么把this也就是当前指针强转为ClassMetadata.
通过lldb po metadata 验证为class

Stop reason: exec
po metadata->getKind()
Class
po metadata->getClassObject()
0x0000000110effc70
x/8g 0x0000000110effc70
//这里就是元数据里面记录的数据了
0x110effc70: 0x0000000110effc38 0x000000011976e420
0x110effc80: 0x00007fff201d3af0 0x0000803000000000
0x110effc90: 0x0000000110f880c2 0x0000000000000002
0x110effca0: 0x0000000700000028 0x00000010000000a8

那么意味着TargetMetadata也就是TargetClassMetadata,那么我们认为的内存中的结构体也就是TargetClassMetadata。

TargetClassMetadata

那么在TargetClassMetadata中我们可以看到以下数据

  /// Swift-specific class flags.
  ClassFlags Flags;

  /// The address point of instances of this type.
  uint32_t InstanceAddressPoint;

  /// The required size of instances of this type.
  /// 'InstanceAddressPoint' bytes go before the address point;
  /// 'InstanceSize - InstanceAddressPoint' bytes go after it.
  uint32_t InstanceSize;

  /// The alignment mask of the address point of instances of this type.
  uint16_t InstanceAlignMask;

  /// Reserved for runtime use.
  uint16_t Reserved;

  /// The total size of the class object, including prefix and suffix
  /// extents.
  uint32_t ClassSize;

  /// The offset of the address point within the class object.
  uint32_t ClassAddressPoint;
private:
  /// An out-of-line Swift-specific description of the type, or null
  /// if this is an artificial subclass.  We currently provide no
  /// supported mechanism for making a non-artificial subclass
  /// dynamically.
  TargetSignedPointer<Runtime, const TargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;

那么它还有一个父类TargetAnyClassMetadata

TargetAnyClassMetadata

template <typename Runtime>
struct TargetAnyClassMetadata : public TargetHeapMetadata<Runtime> {
  using StoredPointer = typename Runtime::StoredPointer;
  using StoredSize = typename Runtime::StoredSize;

TargetAnyClassMetadata 继承了TargetHeapMetadata

template <typename Runtime>
struct TargetHeapMetadata : TargetMetadata<Runtime> {
  using HeaderType = TargetHeapMetadataHeader<Runtime>;

  TargetHeapMetadata() = default;
  constexpr TargetHeapMetadata(MetadataKind kind)
    : TargetMetadata<Runtime>(kind) {}
#if SWIFT_OBJC_INTEROP
  constexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa)
    : TargetMetadata<Runtime>(isa) {}
#endif
};
using HeapMetadata = TargetHeapMetadata<InProcess>;

TargetHeapMetadata又继承了TargetMetadata,TargetMetadata有一个属性kind。

经过源码的阅读我们可以得到

Metadata::Class
TargetClassMetadata(所有的属性)->TargetAnyClassMetadata(kind,superclass,cacheData)->TargetHeapMetadata->TargetMetadata(kind)
  • 如果Metadata是一个class,那么kind这个枚举值返回TargetClassMetadata。
  • 而TargetClassMetadata继承自TargetAnyClassMetadata,anyclass有3个属性,继承过来的kind以及superclass和cacheData
  • TargetAnyClassMetadata 继承了TargetHeapMetadata
  • TargetHeapMetadata 继承了TargetMetadata,他有一个属性kind

那么所有的这些就构成了我们Class内存结构(ClassMetadata + AnyClassMetadata + TargetMetadata),metadata数据结构体为:

struct swift_class_t{
        void *kind; //isa, kind(unsigned long)//如果和OC交互了就是isa指针
        void *superClass;
        void *cacheData
        void *data
        uint32_t flags; //4
        uint32_t instanceAddressOffset; //4
        uint32_t instanceSize;//4
        uint16_t instanceAlignMask; //2
        uint16_t reserved; //2

        uint32_t classSize; //4
        uint32_t classAddressOffset; //4
        void *description;
 // ...
 };

Swift属性

存储属性

要么是常量(let修饰),要么是变量(var修饰)

class HotpotCat {
    var name: String = "Hotpot"
    let age: Int = 1
}

var hotpot = HotpotCat()

对于HotpotCat中age、name来说都是我们的变量存储属性,在SIL中可以看到

class HotpotCat {
  @_hasStorage @_hasInitialValue var name: String { get set }
  @_hasStorage @_hasInitialValue var age: Int { get set }
  @objc deinit
  init()
}
image.png

0x0000000100008188 存储的是metadata,0x0000000400000003存储的是refcount,0x0000746f70746f48 0xe600000000000000 存储的是String,0x0000000000000001存储的是Int。

计算属性

不占存储空间,本质是get/set方法
如果我们给计算属性赋值会发生什么呢?

class HotpotCat {
    var name: String = "Hotpot"
    var age: Int {
        get{
           3
        }
        set{
            age = newValue
        }
    }
}

var hotpot = HotpotCat()
hotpot.age = 6
print(hotpot.age)
image.png

可以看到发生了递归调用,自己调用自己。
那么写一个正确的计算属性

class HotpotCat {
    var width: Double = 10;//8字节
    var area: Double{//不占存储空间
        get{
            pow(width, 2)
        }
        set{
            width = sqrt(newValue)
        }
    }
}

var hotpot = HotpotCat()
print(class_getInstanceSize(HotpotCat.self))

输出

24

这也就证明计算属性确实不占内存空间。
我们生成sil文件看看计算属性到底是什么?

swiftc -emit-sil main.swift >> ./main.sil && open main.sil
class HotpotCat {
  @_hasStorage @_hasInitialValue var width: Double { get set }
  var area: Double { get set }
  @objc deinit
  init()
}
  • 可以看到存储属性使用_hasStorage修饰的,计算属性没有。
// HotpotCat.area.getter
sil hidden @$s4main9HotpotCatC4areaSdvg : $@convention(method) (@guaranteed HotpotCat) -> Double {
// %0 "self"                                      // users: %3, %2, %1
bb0(%0 : $HotpotCat):
  debug_value %0 : $HotpotCat, let, name "self", argno 1 // id: %1
  %2 = class_method %0 : $HotpotCat, #HotpotCat.width!getter : (HotpotCat) -> () -> Double, $@convention(method) (@guaranteed HotpotCat) -> Double // user: %3
  %3 = apply %2(%0) : $@convention(method) (@guaranteed HotpotCat) -> Double // user: %7
  %4 = float_literal $Builtin.FPIEEE64, 0x4000000000000000 // 2 // user: %5
  %5 = struct $Double (%4 : $Builtin.FPIEEE64)    // user: %7
  // function_ref pow
  %6 = function_ref @pow : $@convention(c) (Double, Double) -> Double // user: %7
  %7 = apply %6(%3, %5) : $@convention(c) (Double, Double) -> Double // user: %8
  return %7 : $Double                             // id: %8
} // end sil function '$s4main9HotpotCatC4areaSdvg'

// HotpotCat.area.setter
sil hidden @$s4main9HotpotCatC4areaSdvs : $@convention(method) (Double, @guaranteed HotpotCat) -> () {
// %0 "newValue"                                  // users: %5, %2
// %1 "self"                                      // users: %7, %6, %3
bb0(%0 : $Double, %1 : $HotpotCat):
  debug_value %0 : $Double, let, name "newValue", argno 1 // id: %2
  debug_value %1 : $HotpotCat, let, name "self", argno 2 // id: %3
  // function_ref sqrt
  %4 = function_ref @sqrt : $@convention(c) (Double) -> Double // user: %5
  %5 = apply %4(%0) : $@convention(c) (Double) -> Double // user: %7
  %6 = class_method %1 : $HotpotCat, #HotpotCat.width!setter : (HotpotCat) -> (Double) -> (), $@convention(method) (Double, @guaranteed HotpotCat) -> () // user: %7
  %7 = apply %6(%5, %1) : $@convention(method) (Double, @guaranteed HotpotCat) -> ()
  %8 = tuple ()                                   // user: %9
  return %8 : $()                                 // id: %9
} // end sil function '$s4main9HotpotCatC4areaSdvs'
  • 可以看到就是get和set方法。
  • oc中的方法存储在objc_class:Method_List中,swift方法存储在metadata中。

属性观察者

willSet会在新值赋值之前调用,didSet会在新值赋值之后调用。

class HotpotCat {
    var name: String = "hotpot" {
        //新值存储前调用
        willSet{
            print("willSet newValue: \(newValue) oldValue: \(name)")
        }
        //新值存储后调用
        didSet{
            print("didSet oldValue: \(oldValue) newValue: \(name)")
        }
    }
}

var hotpot = HotpotCat()
hotpot.name = "cat"
willSet newValue: cat oldValue: hotpot
didSet oldValue: hotpot newValue: cat

查看一下SIL文件,我们在设置name的时候首先调用set方法,我们直接看SIL文件的name setter方法

属性观察者实现
这也就解释了为什么willSet能访问newValue和self,didSet能访问oldValue和self。
再继续看下willset(s4main9HotpotCatC4nameSSvw)方法的实现
sil private @$s4main9HotpotCatC4nameSSvw : $@convention(method) (@guaranteed String, @guaranteed HotpotCat) -> () {
// %0 "newValue"                                  // users: %31, %2
// %1 "self"                                      // users: %48, %3
bb0(%0 : $String, %1 : $HotpotCat):
  debug_value %0 : $String, let, name "newValue", argno 1 // id: %2
  debug_value %1 : $HotpotCat, let, name "self", argno 2 // id: %3
  %4 = integer_literal $Builtin.Word, 1 

可以看到newValue是编译器自己帮我们取得,let类型。didSet同理。
那我们自己指定变量名呢?

sil private @$s4main9HotpotCatC4nameSSvw : $@convention(method) (@guaranteed String, @guaranteed HotpotCat) -> () {
// %0 "myNewValue"                                // users: %31, %2
// %1 "self"                                      // users: %48, %3
bb0(%0 : $String, %1 : $HotpotCat):
  debug_value %0 : $String, let, name "myNewValue", argno 1 // id: %2
  debug_value %1 : $HotpotCat, let, name "self", argno 2 // id: %3
  %4 = integer_literal $Builtin.Word, 1           // user: %6

可以看到SIL里面已经变成我们自己起名的变量名。

那么如果我们再init方法里面调用self.name,属性观察者会被调用么?

class HotpotCat {
    var name: String = "hotpot" {
        //新值存储前调用
        willSet{
            print("willSet newValue: \(newValue) oldValue: \(name)")
        }
        //新值存储后调用
        didSet{
            print("didSet oldValue: \(oldValue) newValue: \(name)")
        }
    }
    init() {
        //不会调用属性观察者
        self.name = "cat"
    }
}

var hotpot = HotpotCat()

很明显控制台没有输出,为什么呢?
init方法是做初始化用的。在这个过程中访问oldValue或者其它属性会获取到未知状态的值,所以swift禁止操作,这也就是swift安全的体现。


image.png

我们再看一段有意思的代码

class HotpotCat {
    var name: String = "hotpot" {
        //新值存储前调用
        willSet {
            print("HotpotCat name willSet")
        }
        //新值存储后调用
        didSet {
            print("HotpotCat name didSet")
        }
    }
    var width: Double = 10
    var area: Double {
        get {
            print("HotpotCat area get")
            return pow(width, 2)
        }
        set {
            print("HotpotCat area set")
            width = sqrt(newValue)
        }
    }
}

class MyHotpotCat: HotpotCat {
    override var name: String {
        willSet {
            print("MyHotpotCat name willset")
        }
        didSet {
            print("MyHotpotCat name didSet")
        }
    }
    override var area: Double {
        willSet {
            print("MyHotpotCat area willset")
        }
        didSet {
            print("MyHotpotCat area didSet")
        }
    }
    override init() {
        super.init()
        self.name = "cat"
        self.area = 100
    }
}

var myHotpot = MyHotpotCat()

控制台打印

MyHotpotCat name willset
HotpotCat name willSet
HotpotCat name didSet
MyHotpotCat name didSet
MyHotpotCat area willset
HotpotCat area set
MyHotpotCat area didSet

这里要注意下name的调用顺序,可以简单理解为一个栈(先进后出)。


image.png
  • 定义的存储属性,可以添加属性观察者。
  • 继承的存储属性,可以添加属性观察者。
  • 继承的计算属性,可以添加属性观察者。
  • init方法中自己本身的属性不会调用属性观察者,继承的属性会调用自己和父类的属性观察者。(self.init已经对属性做了初始化操作)
  • 计算属性本身不能添加属性观察者,因为自己本身已经实现了set/get。

配置脚本生成SIL文件

每次跑命令生成SIL文件都比较麻烦,我们可以直接添加一个Target直接将命令放在脚本中执行:
TARGETS -> Other -> Aggregate

image.png
Build Phases -> + -> New Run Script Phase
image.png
#这里需要注意路径问题,在icloud里面的路径会报错,文件和路径替换为自己的工程文件和路径。
rm -rf ${SRCROOT}/main.sil
swiftc -emit-sil ${SRCROOT}/SwiftEnum/main.swift | xcrun swift-demangle  >> ./main.sil && open main.sil

附录

SIL参考文档
SIL手册
OC转Swift
参考阅读
https://swift.org/swift-compiler/#compiler-architecture
https://www.imooc.com/article/273543
https://blog.csdn.net/qq_41790914/article/details/106457729
https://www.jianshu.com/p/fb6923e3a7be
https://zhuanlan.zhihu.com/p/101898915
https://www.jianshu.com/p/440d760a7392?from=singlemessage
https://zhuanlan.zhihu.com/p/112465903
https://blog.csdn.net/wjnhub/article/details/107818429
枚举解析
LLVM相关内容:
https://zhuanlan.zhihu.com/p/102028114
https://zhuanlan.zhihu.com/p/102250532
https://zhuanlan.zhihu.com/p/102270840
https://zhuanlan.zhihu.com/p/102716482
https://zhuanlan.zhihu.com/p/103674744

相关文章

网友评论

    本文标题:Swift底层探索(一):编译流程

    本文链接:https://www.haomeiwen.com/subject/ziiiwktx.html