Erlo

Swift 的 MemoryLayout 是如何工作的(1)

时间:2020-09-16   阅读:226次   来源:开源中国
页面报错
点赞

前言

自从在 搜狐技术产品 公众号看过 一文看破Swift枚举本质 后,就一直计划在该文章的基础更加深入地挖掘一下 Swift 枚举的内存布局但是,Swift 枚举的内存布局 涉及的内容比较多。所以,就先把 Swift 的 MemoryLayout 是如何工作的 部分拆出来单独写两篇文章。

希望读者阅读本文后,能够从 Swift 编译器 的视角了解 MemoryLayout 是如何工作的。

本文会按照以下顺序进行讲解:

  • MemoryLayout 的 API 介绍

  • 编译器与 SIL

  • 编译器与 内置类型

  • 编译器与 IR

MemoryLayout

Swift 中,MemoryLayout 用于获取特定类型的内存布局信息。


作为一个枚举,它包含3个静态变量,分别返回 size stride alignment 信息。

Review 2 SE-0101: Reconfiguring sizeof and related functions into a unified MemoryLayout struct 解释了为什么是枚举而不是结构体

更多内存对齐相关知识,请参阅 size-stride-alignment

struct Point {    let x: Double    let y: Double    let isFilled: Bool}


我们现在以上面的结构体 Point为例,对3个静态变量进行简单的介绍:

size

size代表 Point 类型在内存中占用的空间。

  • xDouble 类型,占用 8 byte

  • yDouble 类型,占用 8 byte

  • isFilledBool 类型,占用 1 byte

所以,MemoryLayout.size == 17

stride

stride翻译成中文是“步伐”,代表 Array 中两个对象起始位置之间的距离。 为了提高性能,编译器会通过在 size 的基础上增加 7 个 byte 的方式进行内存对齐

MemoryLayout.stride == 24

alignment

同 stride 一样,为了提高性能,任何的对象都会先进行内存对齐再使用。

因为 Point 结构体中,占用空间最大的是 Double 类型。所以, MemoryLayout.alignment == 8

SIL

本文后续将会以下面的函数为目标进行分析。

func getSize() -> Int {    return MemoryLayout<Int16>.size}

该函数的实现非常简单,它会返回 Int16 类型的 size 信息。

在实际场景中,Swift 编译器会按照以下方式进行对源码进行处理。我们后续会依次介绍每个阶段。



Parse/Sema

Parse/Sema 阶段会通过源码构建 AST,并组装类型信息。

xcrun swiftc -dump-ast file.swift  


(source_file "file.swift"  (func_decl range=[file.swift:7:1 - line:9:1] "getSize()" interface type='() -> Int' access=internal    (parameter_list range=[file.swift:7:13 - line:7:14])    (result      (type_ident        (component id='Int' bind=Swift.(file).Int)))    (brace_stmt range=[file.swift:7:23 - line:9:1]      (return_stmt range=[file.swift:8:5 - line:8:32]        (member_ref_expr type='Int' location=file.swift:8:32 range=[file.swift:8:12 - line:8:32] decl=Swift.(file).MemoryLayout.size [with (substitution_map generic_signature= (substitution T -> Int16))]          (type_expr type='MemoryLayout.Type' location=file.swift:8:12 range=[file.swift:8:12 - line:8:30] typerepr='MemoryLayout'))))))


SILGen

SILGen 会通过 AST 信息产出以 sil_stage raw 语言版本的代码。

xcrun swiftc -emit-silgen -O file.swift | swift demangle

为了提高可读性,下面的输出都会通过 swift demangle 进行一次解析。

sil_stage raw
import Builtinimport Swiftimport SwiftShims
func getSize() -> Int
// mainsil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer>>) -> Int32 {bb0(%0 : $Int32, %1 : $UnsafeMutablePointer>>): %2 = integer_literal $Builtin.Int32, 0 // user: %3 %3 = struct $Int32 (%2 : $Builtin.Int32) // user: %4 return %3 : $Int32 // id: %4} // end sil function 'main'
// getSize()sil hidden [ossa] @file.getSize() -> Swift.Int : $@convention(thin) () -> Int {bb0: // 获得 MemoryLayout.Type 类型 %0 = metatype $@thin MemoryLayout.Type // user: %2
// 获得 静态属性 size 的 get 方法 // function_ref static MemoryLayout.size.getter %1 = function_ref @static Swift.MemoryLayout.size.getter : Swift.Int : $@convention(method) <τ_0_0> (@thin MemoryLayout<τ_0_0>.Type) -> Int // user: %2
// 调用 静态属性 size 的 get 方法,参数是 MemoryLayout.Type,并返回一个 Int 类型的值 %2 = apply %1(%0) : $@convention(method) <τ_0_0> (@thin MemoryLayout<τ_0_0>.Type) -> Int // user: %3 // 返回结果 return %2 : $Int // id: %3} // end sil function 'file.getSize() -> Swift.Int'
// 静态属性 size 的 get 方法// static MemoryLayout.size.gettersil [transparent] [serialized] @static Swift.MemoryLayout.size.getter : Swift.Int : $@convention(method) <τ_0_0> (@thin MemoryLayout<τ_0_0>.Type) -> Int

Mandatory inlining 与 @_transparent

Guaranteed Optimization and Diagnostic Passes 是一类比较特殊的Pass。即使开发者传入的优化命令是 none,该类优化也会被强制执行。

我们下面要讲的 Mandatory inlining 就属于其中的一种。

@_transparent

大部分的 Swift 开发者都见过一类很特殊的函数 Transparent function

这类函数被编译时,会在 Mandatory SIL passes 阶段被强制内联处理。

MemoryLayout 源码

以本次研究的 MemoryLayout 为例, 它对应的源码如下所示:

@frozen public enum MemoryLayout {  @_transparent public static var size: Swift.Int {    @_transparent get {    return Int(Builtin.sizeof(T.self))  }  }  @_transparent public static var stride: Swift.Int {    @_transparent get {    return Int(Builtin.strideof(T.self))  }  }  @_transparent public static var alignment: Swift.Int {    @_transparent get {    return Int(Builtin.alignof(T.self))  }  }}

从上面的源码,我们可以发现三个函数都被 @_transparent 修饰。

并且,size 部分的源码很简单:

  • 调用 Builtin.sizeof 获取 T.self 的大小

  • 将返回值转为 Int 类型

iOS 开发者,可以在下面的路径找 MemoryLayout 对应的源码。/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/swift/Swift.swiftmodule/arm64.swiftinterface

Mandatory SIL passes

下面,我们看看代码经过 Mandatory inlining 处理后的情况

xcrun swiftc -emit-sil -Onone file.swift | swift demangle


通过上面的编译命令处理后,size  函数对应 SIL 如下所示:


//  静态属性 size 的 get 方法实现,对应的 Swift 版本就是  Int(Builtin.sizeof(T.self))// static MemoryLayout.size.gettersil public_external [transparent] [serialized] @static Swift.MemoryLayout.size.getter : Swift.Int : $@convention(method)  (@thin MemoryLayout.Type) -> Int {bb0(%0 : $@thin MemoryLayout.Type):  // 获得 T.Type 类型。  %1 = metatype $@thick T.Type                    // user: %2  // 调用内置函数 sizeof 获取 T.Type 类型的 size 信息,返回结果是 Builtin.Word 类型  %2 = builtin "sizeof"(%1 : $@thick T.Type) : $Builtin.Word // user: %3  // 调用 sextOrBitCast_Word_Int64 将 Builtin.Word 类型的结果转为 Builtin.Int64 类型  %3 = builtin "sextOrBitCast_Word_Int64"(%2 : $Builtin.Word) : $Builtin.Int64 // user: %4  // 将 Builtin.Int64 类型转为 Int 类型  %4 = struct $Int (%3 : $Builtin.Int64)          // user: %5  return %4 : $Int                                // id: %5} // end sil function 'static Swift.MemoryLayout.size.getter : Swift.Int'


getSize 函数对  var size: Swift.Int 的调用也变成了 Int(Builtin.sizeof(T.self))

sil_stage canonical
// getSize()sil hidden @file.getSize() -> Swift.Int : $@convention(thin) () -> Int {bb0: %0 = metatype $@thick Int16.Type // user: %1 %1 = builtin "sizeof"(%0 : $@thick Int16.Type) : $Builtin.Word // user: %2 %2 = builtin "sextOrBitCast_Word_Int64"(%1 : $Builtin.Word) : $Builtin.Int64 // user: %3 %3 = struct $Int (%2 : $Builtin.Int64) // user: %4 return %3 : $Int // id: %4} // end sil function 'file.getSize() -> Swift.Int'

内置类型

在进一步分析 Builtin.sizeof 之前,我们先看看 Int16

Int16

MemoryLayout 类似,我们可以在

评论留言

还没有评论留言,赶紧来抢楼吧~~

吐槽小黑屋()

* 这里是“吐槽小黑屋”,所有人可看,只保留当天信息。

  • Erlo吐槽

    Erlo.vip2021-07-29 14:38:43Hello、欢迎使用吐槽小黑屋,这就是个吐槽的地方。
  • 返回顶部

      给这篇文章打个标签吧~

      棒极了 糟糕透顶 好文章 PHP JAVA JS 小程序 Python SEO MySql 确认