前言
自从在 搜狐技术产品 公众号看过 一文看破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
类型在内存中占用的空间。
x
是Double
类型,占用 8 bytey
是Double
类型,占用 8 byteisFilled
是Bool
类型,占用 1 byte
所以,MemoryLayout
。
stride
stride
翻译成中文是“步伐”,代表 Array
中两个对象起始位置之间的距离。 为了提高性能,编译器会通过在 size 的基础上增加 7 个 byte 的方式进行内存对齐
MemoryLayout
alignment
同 stride 一样,为了提高性能,任何的对象都会先进行内存对齐再使用。
因为 Point
结构体中,占用空间最大的是 Double
类型。所以, MemoryLayout
。
SIL
本文后续将会以下面的函数为目标进行分析。
func getSize() -> Int {
return MemoryLayoutInt16>.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 Builtin
import Swift
import SwiftShims
func getSize() -> Int
// main
sil [ossa]
>>) -> Int32 { : $ (Int32, UnsafeMutablePointerbb0(%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] Int : $ () -> Int { .getSize() -> Swift.
bb0:
// 获得 MemoryLayout
.Type 类型 %0 = metatype $ MemoryLayout
.Type // user: %2
// 获得 静态属性 size 的 get 方法
// function_ref static MemoryLayout.size.getter
%1 = function_ref Swift.MemoryLayout.size.getter : Swift.Int : $ ( MemoryLayout.Type) -> Int // user: %2
// 调用 静态属性 size 的 get 方法,参数是 MemoryLayout
.Type,并返回一个 Int 类型的值 %2 = apply %1
(%0) : $ ( MemoryLayout.Type) -> Int // user: %3 // 返回结果
return %2 : $Int // id: %3
} // end sil function 'file.getSize() -> Swift.Int'
// 静态属性 size 的 get 方法
// static MemoryLayout.size.getter
sil [transparent] [serialized] Int : $ ( MemoryLayout.Type) -> Int Swift.MemoryLayout.size.getter : Swift.
Mandatory inlining 与 @_transparent
Guaranteed Optimization and Diagnostic Passes
是一类比较特殊的Pass
。即使开发者传入的优化命令是 none
,该类优化也会被强制执行。
我们下面要讲的 Mandatory inlining
就属于其中的一种。
@_transparent
大部分的 Swift
开发者都见过一类很特殊的函数 Transparent function
。
这类函数被编译时,会在 Mandatory SIL passes
阶段被强制内联处理。
MemoryLayout
源码
以本次研究的 MemoryLayout
为例, 它对应的源码如下所示:
public enum MemoryLayout
{ public static var size: Swift.Int {
get {
return Int(Builtin.sizeof(T.self))
}
}
public static var stride: Swift.Int {
get {
return Int(Builtin.strideof(T.self))
}
}
public static var alignment: Swift.Int {
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.getter
sil public_external [transparent] [serialized] Int : $
( MemoryLayout Swift.MemoryLayout.size.getter : Swift..Type) -> Int { bb0(%0 : $ MemoryLayout
.Type): // 获得 T.Type 类型。
%1 = metatype $ T.Type // user: %2
// 调用内置函数 sizeof 获取 T.Type 类型的 size 信息,返回结果是 Builtin.Word 类型
%2 = builtin "sizeof"
(%1 : $ 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
类似,我们可以在