Date Notes
2020-08-16 首次提交

Preface

《Swift 拾遗》是一个关于 Swift 的新文章专辑,这个系列的文章将不会涉及基本语法的层面,而是尝试从底层「拾」起之前所忽视的内容。今天我们将一起简单探究 Swift 中的闭包(Closure)。

闭包捕获

局部变量

我们将参数和返回值类型为 Int 的函数(也可称作闭包)(Int) -> Int 易名为 Closure,并定义一个函数返回该闭包类型:

// main.swift

typealias Closure = (Int) -> Int

func foo() -> Closure {
    var bar = 5

    func baz(_ val: Int) -> Int {
        bar += val // BREAKPOINT 🔴
        return bar
    }

    bar = 10

    return baz // BREAKPOINT 🔴
}

var f = foo()

f(1) // 11
f(2) // 13
f(3) // 16

通过 f(n) 函数多次执行的结果,我们发现局部变量 bar 似乎在每次调用后并没有被销毁,原因其实是 bazbar 进行了「捕获」,接下来我们将仔细研究这一过程。

demo`foo():
    0x100000d70 <+0>:   pushq  %rbp
    0x100000d71 <+1>:   movq   %rsp, %rbp
    0x100000d74 <+4>:   subq   $0x20, %rsp
    0x100000d78 <+8>:   leaq   0x299(%rip), %rdi
    0x100000d7f <+15>:  movl   $0x18, %esi
    0x100000d84 <+20>:  movl   $0x7, %edx
    ; ⚠️:这里调用了 swift_allocObject,即分配了堆空间
    0x100000d89 <+25>:  callq  0x100000f14               ; symbol stub for: swift_allocObject
    ; alloc 函数调用后将返回堆空间首地址,并存储在 rax 寄存器
    0x100000d8e <+30>:  movq   %rax, %rcx
    0x100000d91 <+33>:  addq   $0x10, %rcx
    0x100000d95 <+37>:  movq   %rcx, %rdx
    ; 初始化:将立即数 5 存储到 rax + 0x10 地址,即偏移 16 个字节
    0x100000d98 <+40>:  movq   $0x5, 0x10(%rax)
    ; 二次赋值:将立即数 10 存储到 rax + 0x10 地址
    0x100000da0 <+48>:  movq   $0xa, 0x10(%rax)
->  0x100000da8 <+56>:  movq   %rax, %rdi
    ; 将 rax 寄存器内容(即封装了 bar 的堆空间地址)移动到 rbp - 0x8
    0x100000dab <+59>:  movq   %rax, -0x8(%rbp)
    0x100000daf <+63>:  movq   %rcx, -0x10(%rbp)
    0x100000db3 <+67>:  callq  0x100000f32               ; symbol stub for: swift_retain
    0x100000db8 <+72>:  movq   -0x8(%rbp), %rdi
    0x100000dbc <+76>:  movq   %rax, -0x18(%rbp)
    0x100000dc0 <+80>:  callq  0x100000f2c               ; symbol stub for: swift_release
    0x100000dc5 <+85>:  movq   -0x10(%rbp), %rax
    ; 将 rip + 0x130(0x0000000100000f00)地址内容(即包装 baz 的函数的地址)移动到 rax 寄存器
    0x100000dc9 <+89>:  leaq   0x130(%rip), %rax         ; partial apply forwarder for baz #1 (Swift.Int) -> Swift.Int in demo.foo() -> (Swift.Int) -> Swift.Int at <compiler-generated>
    ; 将 rbp - 0x8 地址内容(即封装了 bar 的堆空间地址)移动到 rdx 寄存器
    0x100000dd0 <+96>:  movq   -0x8(%rbp), %rdx
    0x100000dd4 <+100>: addq   $0x20, %rsp
    0x100000dd8 <+104>: popq   %rbp
    0x100000dd9 <+105>: retq

demo`main:
    0x100000bd0 <+0>:   pushq  %rbp
    0x100000bd1 <+1>:   movq   %rsp, %rbp
    0x100000bd4 <+4>:   pushq  %r13
    0x100000bd6 <+6>:   subq   $0xc8, %rsp
    0x100000bdd <+13>:  movl   %edi, -0x54(%rbp)
    0x100000be0 <+16>:  movq   %rsi, -0x60(%rbp)
    0x100000be4 <+20>:  callq  0x100000d70               ; demo.foo() -> (Swift.Int) -> Swift.Int at main.swift:5
    ; 函数调用后将把返回值(即 0x0000000100000f00,包装 baz 的函数的地址)存储到 rax 寄存器
    0x100000be9 <+25>:  leaq   0x1450(%rip), %rcx        ; demo.f : (Swift.Int) -> Swift.Int
    0x100000bf0 <+32>:  xorl   %edi, %edi
    0x100000bf2 <+34>:  movl   %edi, %esi
    ; 将 rax 寄存器内容(即包装 baz 的函数的地址)移动到 rip + 0x1445(0x100002040)
    0x100000bf4 <+36>:  movq   %rax, 0x1445(%rip)        ; demo.f : (Swift.Int) -> Swift.Int
    ; 将 rdx 寄存器内容(即封装了 bar 的堆空间地址)移动到 rip + 0x1446(0x100002048)
    0x100000bfb <+43>:  movq   %rdx, 0x1446(%rip)        ; demo.f : (Swift.Int) -> Swift.Int + 8
->  0x100000c02 <+50>:  movq   %rcx, %rdi
    0x100000c05 <+53>:  leaq   -0x20(%rbp), %rax
    0x100000c09 <+57>:  movq   %rsi, -0x68(%rbp)
    0x100000c0d <+61>:  movq   %rax, %rsi
    0x100000c10 <+64>:  movl   $0x20, %edx
    0x100000c15 <+69>:  movq   -0x68(%rbp), %rcx
    0x100000c19 <+73>:  callq  0x100000f1a               ; symbol stub for: swift_beginAccess
    ; 将 rip + 0x141b(0x100002040)地址内容(即包装 baz 的函数的地址)移动到 rax 寄存器
    0x100000c1e <+78>:  movq   0x141b(%rip), %rax        ; demo.f : (Swift.Int) -> Swift.Int
    ; 将 rip + 0x141c(0x100002048)地址内容(即封装了 bar 的堆空间地址)移动到 rcx 寄存器
    0x100000c25 <+85>:  movq   0x141c(%rip), %rcx        ; demo.f : (Swift.Int) -> Swift.Int + 8
    0x100000c2c <+92>:  movq   %rcx, %rdi
    ; 将 rax 寄存器内容(即包装 baz 的函数的地址)移动到 rbp - 0x70
    0x100000c2f <+95>:  movq   %rax, -0x70(%rbp)
    ; 将 rcx 寄存器内容(即封装了 bar 的堆空间地址)移动到 rbp - 0x78
    0x100000c33 <+99>:  movq   %rcx, -0x78(%rbp)
    0x100000c37 <+103>: callq  0x100000f32               ; symbol stub for: swift_retain
    0x100000c3c <+108>: leaq   -0x20(%rbp), %rdi
    0x100000c40 <+112>: movq   %rax, -0x80(%rbp)
    0x100000c44 <+116>: callq  0x100000f26               ; symbol stub for: swift_endAccess
    ; 将立即数 1(f(1) 中的参数)移动到 edi 寄存器
    0x100000c49 <+121>: movl   $0x1, %edi
    ; 将 rbp - 0x78 地址内容(即封装了 bar 的堆空间地址)移动到 r13 寄存器
    0x100000c4e <+126>: movq   -0x78(%rbp), %r13
    ; 将 rbp - 0x70 地址内容(即包装 baz 的函数的地址)移动到 rax 寄存器
    0x100000c52 <+130>: movq   -0x70(%rbp), %rax
    ; 函数调用,使用 si 进入
    0x100000c56 <+134>: callq  *%rax
    0x100000c58 <+136>: movq   -0x78(%rbp), %rdi
    0x100000c5c <+140>: movq   %rax, -0x88(%rbp)
    0x100000c63 <+147>: callq  0x100000f2c               ; symbol stub for: swift_release
    0x100000c68 <+152>: leaq   0x13d1(%rip), %rax        ; demo.f : (Swift.Int) -> Swift.Int
    0x100000c6f <+159>: xorl   %r8d, %r8d
    0x100000c72 <+162>: movl   %r8d, %ecx
    0x100000c75 <+165>: movq   %rax, %rdi
    0x100000c78 <+168>: leaq   -0x38(%rbp), %rsi
    0x100000c7c <+172>: movl   $0x20, %edx
    0x100000c81 <+177>: callq  0x100000f1a               ; symbol stub for: swift_beginAccess
    0x100000c86 <+182>: movq   0x13b3(%rip), %rax        ; demo.f : (Swift.Int) -> Swift.Int
    0x100000c8d <+189>: movq   0x13b4(%rip), %rcx        ; demo.f : (Swift.Int) -> Swift.Int + 8
    0x100000c94 <+196>: movq   %rcx, %rdi
    0x100000c97 <+199>: movq   %rax, -0x90(%rbp)
    0x100000c9e <+206>: movq   %rcx, -0x98(%rbp)
    0x100000ca5 <+213>: callq  0x100000f32               ; symbol stub for: swift_retain
    0x100000caa <+218>: leaq   -0x38(%rbp), %rdi
    0x100000cae <+222>: movq   %rax, -0xa0(%rbp)
    0x100000cb5 <+229>: callq  0x100000f26               ; symbol stub for: swift_endAccess
    0x100000cba <+234>: movl   $0x2, %edi
    0x100000cbf <+239>: movq   -0x98(%rbp), %r13
    0x100000cc6 <+246>: movq   -0x90(%rbp), %rax
    0x100000ccd <+253>: callq  *%rax
    0x100000ccf <+255>: movq   -0x98(%rbp), %rdi
    0x100000cd6 <+262>: movq   %rax, -0xa8(%rbp)
    0x100000cdd <+269>: callq  0x100000f2c               ; symbol stub for: swift_release
    0x100000ce2 <+274>: leaq   0x1357(%rip), %rax        ; demo.f : (Swift.Int) -> Swift.Int
    0x100000ce9 <+281>: xorl   %r8d, %r8d
    0x100000cec <+284>: movl   %r8d, %ecx
    0x100000cef <+287>: movq   %rax, %rdi
    0x100000cf2 <+290>: leaq   -0x50(%rbp), %rsi
    0x100000cf6 <+294>: movl   $0x20, %edx
    0x100000cfb <+299>: callq  0x100000f1a               ; symbol stub for: swift_beginAccess
    0x100000d00 <+304>: movq   0x1339(%rip), %rax        ; demo.f : (Swift.Int) -> Swift.Int
    0x100000d07 <+311>: movq   0x133a(%rip), %rcx        ; demo.f : (Swift.Int) -> Swift.Int + 8
    0x100000d0e <+318>: movq   %rcx, %rdi
    0x100000d11 <+321>: movq   %rax, -0xb0(%rbp)
    0x100000d18 <+328>: movq   %rcx, -0xb8(%rbp)
    0x100000d1f <+335>: callq  0x100000f32               ; symbol stub for: swift_retain
    0x100000d24 <+340>: leaq   -0x50(%rbp), %rdi
    0x100000d28 <+344>: movq   %rax, -0xc0(%rbp)
    0x100000d2f <+351>: callq  0x100000f26               ; symbol stub for: swift_endAccess
    0x100000d34 <+356>: movl   $0x3, %edi
    0x100000d39 <+361>: movq   -0xb8(%rbp), %r13
    0x100000d40 <+368>: movq   -0xb0(%rbp), %rax
    0x100000d47 <+375>: callq  *%rax
    0x100000d49 <+377>: movq   -0xb8(%rbp), %rdi
    0x100000d50 <+384>: movq   %rax, -0xc8(%rbp)
    0x100000d57 <+391>: callq  0x100000f2c               ; symbol stub for: swift_release
    0x100000d5c <+396>: xorl   %eax, %eax
    0x100000d5e <+398>: addq   $0xc8, %rsp
    0x100000d65 <+405>: popq   %r13
    0x100000d67 <+407>: popq   %rbp
    0x100000d68 <+408>: retq


demo`partial apply for baz #1 (_:) in foo():
->  0x100000f00 <+0>: pushq  %rbp
    0x100000f01 <+1>: movq   %rsp, %rbp
    ; rdi 未改变,即第一个参数(1)
    ; 将 r13 寄存器内容(即封装了 bar 的堆空间地址)移动到 rsi 寄存器(即第二个参数)
    0x100000f04 <+4>: movq   %r13, %rsi
    0x100000f07 <+7>: popq   %rbp
    ; 继续 si
    0x100000f08 <+8>: jmp    0x100000e00               ; baz #1 (Swift.Int) -> Swift.Int in demo.foo() -> (Swift.Int) -> Swift.Int at main.swift:8

demo`baz #1 (_:) in foo():
->  0x100000e00 <+0>:   pushq  %rbp
    0x100000e01 <+1>:   movq   %rsp, %rbp
    0x100000e04 <+4>:   subq   $0x90, %rsp
    0x100000e0b <+11>:  xorl   %eax, %eax
    0x100000e0d <+13>:  movl   %eax, %ecx
    0x100000e0f <+15>:  xorl   %eax, %eax
    0x100000e11 <+17>:  leaq   -0x8(%rbp), %rdx
    ; 将 rdi 寄存器内容(即 1)移动到 rbp - 0x48 地址
    0x100000e15 <+21>:  movq   %rdi, -0x48(%rbp)
    0x100000e19 <+25>:  movq   %rdx, %rdi
    ; 将 rsi 寄存器内容(即封装了 bar 的堆空间地址)移动到 rbp - 0x50 地址
    0x100000e1c <+28>:  movq   %rsi, -0x50(%rbp)
    0x100000e20 <+32>:  movl   %eax, %esi
    0x100000e22 <+34>:  movl   $0x8, %edx
    0x100000e27 <+39>:  movq   %rdx, -0x58(%rbp)
    0x100000e2b <+43>:  movq   %rcx, -0x60(%rbp)
    0x100000e2f <+47>:  movl   %eax, -0x64(%rbp)
    0x100000e32 <+50>:  callq  0x100000f0e               ; symbol stub for: memset
    0x100000e37 <+55>:  leaq   -0x10(%rbp), %rcx
    0x100000e3b <+59>:  movq   %rcx, %rdi
    0x100000e3e <+62>:  movl   -0x64(%rbp), %esi
    0x100000e41 <+65>:  movq   -0x58(%rbp), %rdx
    0x100000e45 <+69>:  callq  0x100000f0e               ; symbol stub for: memset
    ; 将 rbp - 0x48 地址内容(即 1)移动到 rcx 寄存器
    0x100000e4a <+74>:  movq   -0x48(%rbp), %rcx
    0x100000e4e <+78>:  movq   %rcx, -0x8(%rbp)
    ; 将 rbp - 0x50 地址内容(即封装了 bar 的堆空间地址)移动到 rdx 寄存器
    0x100000e52 <+82>:  movq   -0x50(%rbp), %rdx
    ; 将 rdx 寄存器内容(即封装了 bar 的堆空间地址)偏移 0x10(即 bar 的堆空间地址),结果存储在 rdx 寄存器
    0x100000e56 <+86>:  addq   $0x10, %rdx
    0x100000e5d <+93>:  movq   %rdx, -0x10(%rbp)
    0x100000e61 <+97>:  movq   %rdx, %rdi
    0x100000e64 <+100>: leaq   -0x28(%rbp), %rsi
    0x100000e68 <+104>: movl   $0x21, %r8d
    ; 将 rdx 寄存器内容(即 bar 的堆空间地址)移动到 rbp - 0x70 地址
    0x100000e6e <+110>: movq   %rdx, -0x70(%rbp)
    0x100000e72 <+114>: movq   %r8, %rdx
    0x100000e75 <+117>: movq   -0x60(%rbp), %rcx
    0x100000e79 <+121>: callq  0x100000f1a               ; symbol stub for: swift_beginAccess
    0x100000e7e <+126>: movq   -0x48(%rbp), %rcx
    ; 再次将 rbp - 0x50 地址内容(即封装了 bar 的堆空间地址)移动到 rdx 寄存器
    0x100000e82 <+130>: movq   -0x50(%rbp), %rdx
    ; 将 rdx 寄存器内容(即封装了 bar 的堆空间地址)偏移 0x10(即 bar 的堆空间地址)与 rcx 寄存器内容(即 1)相加,结果存储在 rcx 寄存器
    0x100000e86 <+134>: addq   0x10(%rdx), %rcx
    0x100000e8a <+138>: seto   %r9b
    0x100000e8e <+142>: testb  $0x1, %r9b
    0x100000e92 <+146>: movq   %rcx, -0x78(%rbp)
    0x100000e96 <+150>: jne    0x100000ef0               ; <+240> at main.swift:9:13
    0x100000e98 <+152>: movq   -0x70(%rbp), %rax
    0x100000e9c <+156>: movq   -0x78(%rbp), %rcx
    0x100000ea0 <+160>: movq   %rcx, (%rax)
    0x100000ea3 <+163>: leaq   -0x28(%rbp), %rdi
    0x100000ea7 <+167>: callq  0x100000f26               ; symbol stub for: swift_endAccess
    0x100000eac <+172>: xorl   %edx, %edx
    0x100000eae <+174>: movl   %edx, %ecx
    0x100000eb0 <+176>: leaq   -0x40(%rbp), %rax
    0x100000eb4 <+180>: movl   $0x20, %edx
    0x100000eb9 <+185>: movq   -0x70(%rbp), %rdi
    0x100000ebd <+189>: movq   %rax, %rsi
    0x100000ec0 <+192>: movq   %rax, -0x80(%rbp)
    0x100000ec4 <+196>: callq  0x100000f1a               ; symbol stub for: swift_beginAccess
    ; 将 rbp - 0x70 地址内容(即 bar 的堆空间地址)移动到 rax 寄存器
    0x100000ec9 <+201>: movq   -0x70(%rbp), %rax
    ; 将 rax 寄存器的值(即 0x000000000000000b)移动到 rax 寄存器
    0x100000ecd <+205>: movq   (%rax), %rax
    0x100000ed0 <+208>: movq   -0x80(%rbp), %rdi
    ; 将 rax 寄存器内容(0x000000000000000b)移动到 rbp - 0x88 地址
    0x100000ed4 <+212>: movq   %rax, -0x88(%rbp)
    0x100000edb <+219>: callq  0x100000f26               ; symbol stub for: swift_endAccess
    ; 将 rbp - 0x88 地址内容(0x000000000000000b)移动到 rax 寄存器
    0x100000ee0 <+224>: movq   -0x88(%rbp), %rax
    0x100000ee7 <+231>: addq   $0x90, %rsp
    0x100000eee <+238>: popq   %rbp
    0x100000eef <+239>: retq
    0x100000ef0 <+240>: ud2

由以上汇编代码我们可以得出,当闭包内部使用了外部局部变量时,将自动分配堆空间进行捕获;在后续调用闭包时,将自动将创建堆空间地址作为第二个参数传递到闭包中并使用;因此即使局部变量所在的栈空间被回收,堆空间仍将保留一份。这里的堆空间布局类似于 class 类型实例变量的内存布局。

另外需要注意的是,swift_allocObjectfoo 中被调用,因此如果我们声明一个新变量例如 var f2 = foo(),将会创建另外一份新的堆空间,f(1)f2(1) 的调用将产生隔离。

那么当同时存在两个或多个函数时,局部变量将被分配一次还是多次堆空间呢?

// main.swift

typealias Closure = (Int) -> Int

func foo() -> (Closure, Closure) {
    var i = 5
    var j = 10

    func a(_ val: Int) -> Int {
        i += val
        j += val
        return i + j
    }

    func b(_ val: Int) -> Int {
        i -= val
        j -= val
        return i + j
    }

    return (a, b) // BREAKPOINT 🔴
}

var (a, b) = foo()

a(1) // 17
b(2) // 13
a(3) // 19
b(4) // 11

转换为汇编如下:

demo`foo():
    0x100001880 <+0>:   pushq  %rbp
    0x100001881 <+1>:   movq   %rsp, %rbp
    0x100001884 <+4>:   subq   $0x70, %rsp
    0x100001888 <+8>:   leaq   0x789(%rip), %rax
    0x10000188f <+15>:  movl   $0x18, %ecx
    0x100001894 <+20>:  movl   $0x7, %edx
    0x100001899 <+25>:  movq   %rax, %rdi
    0x10000189c <+28>:  movq   %rcx, %rsi
    0x10000189f <+31>:  movq   %rdx, -0x8(%rbp)
    0x1000018a3 <+35>:  movq   %rax, -0x10(%rbp)
    0x1000018a7 <+39>:  movq   %rcx, -0x18(%rbp)
    ; ⚠️:这里调用了 swift_allocObject,即分配了堆空间
    0x1000018ab <+43>:  callq  0x100001ed8               ; symbol stub for: swift_allocObject
    ; alloc 函数调用后将返回堆空间首地址,并存储在 rax 寄存器
    0x1000018b0 <+48>:  movq   %rax, %rcx
    0x1000018b3 <+51>:  addq   $0x10, %rcx
    0x1000018b7 <+55>:  movq   %rcx, %rdx
    ; i 初始化:将立即数 5 存储到 rax + 0x10 地址,即偏移 16 个字节
    0x1000018ba <+58>:  movq   $0x5, 0x10(%rax)
    0x1000018c2 <+66>:  movq   -0x10(%rbp), %rdi
    0x1000018c6 <+70>:  movq   -0x18(%rbp), %rsi
    0x1000018ca <+74>:  movq   -0x8(%rbp), %rdx
    ; 将 rax 寄存器内容(即封装了 i 的堆空间地址)移动到 rbp - 0x20
    0x1000018ce <+78>:  movq   %rax, -0x20(%rbp)
    0x1000018d2 <+82>:  movq   %rcx, -0x28(%rbp)
    ; ⚠️:这里又一次调用了 swift_allocObject,即再次分配了堆空间
    0x1000018d6 <+86>:  callq  0x100001ed8               ; symbol stub for: swift_allocObject
    ; alloc 函数调用后将返回堆空间首地址,并存储在 rax 寄存器
    0x1000018db <+91>:  movq   %rax, %rcx
    0x1000018de <+94>:  addq   $0x10, %rcx
    0x1000018e2 <+98>:  movq   %rcx, %rdx
    ; j 初始化:将立即数 10 存储到 rax + 0x10 地址,即偏移 16 个字节
    0x1000018e5 <+101>: movq   $0xa, 0x10(%rax)
    0x1000018ed <+109>: movq   -0x20(%rbp), %rdi
    ; 将 rax 寄存器内容(即封装了 j 的堆空间地址)移动到 rbp - 0x30
    0x1000018f1 <+113>: movq   %rax, -0x30(%rbp)
    0x1000018f5 <+117>: movq   %rcx, -0x38(%rbp)
->  0x1000018f9 <+121>: callq  0x100001ef6               ; symbol stub for: swift_retain
    0x1000018fe <+126>: movq   -0x30(%rbp), %rdi
    0x100001902 <+130>: movq   %rax, -0x40(%rbp)
    0x100001906 <+134>: callq  0x100001ef6               ; symbol stub for: swift_retain
    0x10000190b <+139>: leaq   0x72e(%rip), %rdi
    0x100001912 <+146>: movl   $0x20, %ecx
    0x100001917 <+151>: movq   %rcx, %rsi
    0x10000191a <+154>: movq   -0x8(%rbp), %rdx
    0x10000191e <+158>: movq   %rax, -0x48(%rbp)
    0x100001922 <+162>: movq   %rcx, -0x50(%rbp)
    ; ⚠️:这里又一次调用了 swift_allocObject,即分配了堆空间
    0x100001926 <+166>: callq  0x100001ed8               ; symbol stub for: swift_allocObject
    ; alloc 函数调用后将返回堆空间首地址,并存储在 rax 寄存器
    ; 将 rbp - 0x20 地址内容(即封装了 i 的堆空间地址)移动到 rcx 寄存器
    0x10000192b <+171>: movq   -0x20(%rbp), %rcx
    ; 将 rcx 寄存器内容(即封装了 i 的堆空间地址)移动到 rax + 0x10
    0x10000192f <+175>: movq   %rcx, 0x10(%rax)
    ; 将 rbp - 0x30 地址内容(即封装了 j 的堆空间地址)移动到 rdx 寄存器
    0x100001933 <+179>: movq   -0x30(%rbp), %rdx
    ; 将 rdx 寄存器内容(即封装了 j 的堆空间地址)移动到 rax + 0x18
    0x100001937 <+183>: movq   %rdx, 0x18(%rax)
    0x10000193b <+187>: movq   %rcx, %rdi
    ; 将 rax 寄存器内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rbp - 0x58
    0x10000193e <+190>: movq   %rax, -0x58(%rbp)
    0x100001942 <+194>: callq  0x100001ef6               ; symbol stub for: swift_retain
    0x100001947 <+199>: movq   -0x30(%rbp), %rdi
    0x10000194b <+203>: movq   %rax, -0x60(%rbp)
    0x10000194f <+207>: callq  0x100001ef6               ; symbol stub for: swift_retain
    0x100001954 <+212>: leaq   0x70d(%rip), %rdi
    0x10000195b <+219>: movq   -0x50(%rbp), %rsi
    0x10000195f <+223>: movq   -0x8(%rbp), %rdx
    0x100001963 <+227>: movq   %rax, -0x68(%rbp)
    ; ⚠️:这里又一次调用了 swift_allocObject,即分配了堆空间
    0x100001967 <+231>: callq  0x100001ed8               ; symbol stub for: swift_allocObject
    ; alloc 函数调用后将返回堆空间首地址,并存储在 rax 寄存器
    ; 将 rbp - 0x20 地址内容(即封装了 i 的堆空间地址)移动到 rcx 寄存器
    0x10000196c <+236>: movq   -0x20(%rbp), %rcx
    ; 将 rcx 寄存器内容(即封装了 i 的堆空间地址)移动到 rax + 0x10
    0x100001970 <+240>: movq   %rcx, 0x10(%rax)
    ; 将 rbp - 0x30 地址内容(即封装了 j 的堆空间地址)移动到 rdx 寄存器
    0x100001974 <+244>: movq   -0x30(%rbp), %rdx
    ; 将 rdx 寄存器内容(即封装了 j 的堆空间地址)移动到 rax + 0x18
    0x100001978 <+248>: movq   %rdx, 0x18(%rax)
    0x10000197c <+252>: movq   %rdx, %rdi
    ; 将 rax 寄存器内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rbp - 0x70
    0x10000197f <+255>: movq   %rax, -0x70(%rbp)
    0x100001983 <+259>: callq  0x100001ef0               ; symbol stub for: swift_release
    0x100001988 <+264>: movq   -0x20(%rbp), %rdi
    0x10000198c <+268>: callq  0x100001ef0               ; symbol stub for: swift_release
    0x100001991 <+273>: movq   -0x28(%rbp), %rax
    0x100001995 <+277>: movq   -0x38(%rbp), %rcx
    ; 将 rip + 0x2a0(0x100001C40)地址内容(即包装 a 的函数的地址)移动到 rax 寄存器
    0x100001999 <+281>: leaq   0x2a0(%rip), %rax         ; partial apply forwarder for a #1 (Swift.Int) -> Swift.Int in demo.foo() -> ((Swift.Int) -> Swift.Int, (Swift.Int) -> Swift.Int) at <compiler-generated>
    ; 将 rip + 0x519(0x100001EC0)地址内容(即包装 b 的函数的地址)移动到 rcx 寄存器
    0x1000019a0 <+288>: leaq   0x519(%rip), %rcx         ; partial apply forwarder for b #1 (Swift.Int) -> Swift.Int in demo.foo() -> ((Swift.Int) -> Swift.Int, (Swift.Int) -> Swift.Int) at <compiler-generated>
    ; 将 rbp - 0x58 地址内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rdx 寄存器
    ; (lldb) register read rdx => rdx = 0x00000001007bdcc0
    ; (lldb) x/4xg 0x00000001007bdcc0
    ; 0x1007bdcc0: 0x0000000100002040 0x0000000000000002
    ; 0x1007bdcd0: 0x00000001007bdbf0 0x00000001007bdca0(i、j)
    0x1000019a7 <+295>: movq   -0x58(%rbp), %rdx
    ; 将 rbp - 0x70 地址内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 r8 寄存器
    ; (lldb) register read r8 => r8 = 0x0000000100404120
    ; (lldb) x/4xg 0x0000000100404120
    ; 0x100404120: 0x0000000100002068 0x0000000000000002
    ; 0x100404130: 0x00000001007bdbf0 0x00000001007bdca0(i、j)
    0x1000019ab <+299>: movq   -0x70(%rbp), %r8
    0x1000019af <+303>: addq   $0x70, %rsp
    0x1000019b3 <+307>: popq   %rbp
    0x1000019b4 <+308>: retq

demo`main:
    0x100001650 <+0>:   pushq  %rbp
    0x100001651 <+1>:   movq   %rsp, %rbp
    0x100001654 <+4>:   pushq  %r13
    0x100001656 <+6>:   subq   $0xf8, %rsp
    0x10000165d <+13>:  movl   %edi, -0x6c(%rbp)
    0x100001660 <+16>:  movq   %rsi, -0x78(%rbp)
    0x100001664 <+20>:  callq  0x100001880               ; demo.foo() -> ((Swift.Int) -> Swift.Int, (Swift.Int) -> Swift.Int) at main.swift:5
    ; 函数调用后将把返回值存储到 rax 寄存器
    0x100001669 <+25>:  leaq   0x19d0(%rip), %rsi        ; demo.a : (Swift.Int) -> Swift.Int
    0x100001670 <+32>:  xorl   %edi, %edi
    0x100001672 <+34>:  movl   %edi, %r9d
    ; 搬运返回值
    0x100001675 <+37>:  movq   %rax, 0x19c4(%rip)        ; demo.a : (Swift.Int) -> Swift.Int
    ; 将 rdx 寄存器内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rip + 0x19c5(0x100003048)
    0x10000167c <+44>:  movq   %rdx, 0x19c5(%rip)        ; demo.a : (Swift.Int) -> Swift.Int + 8
    0x100001683 <+51>:  movq   %rcx, 0x19c6(%rip)        ; demo.b : (Swift.Int) -> Swift.Int
    ; 将 r8 寄存器内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rip + 0x19c7(0x100003058)
    0x10000168a <+58>:  movq   %r8, 0x19c7(%rip)         ; demo.b : (Swift.Int) -> Swift.Int + 8
->  0x100001691 <+65>:  movq   %rsi, %rdi
    0x100001694 <+68>:  leaq   -0x20(%rbp), %rsi
    0x100001698 <+72>:  movl   $0x20, %edx
    0x10000169d <+77>:  movq   %r9, %rcx
    0x1000016a0 <+80>:  callq  0x100001ede               ; symbol stub for: swift_beginAccess
    0x1000016a5 <+85>:  movq   0x1994(%rip), %rax        ; demo.a : (Swift.Int) -> Swift.Int
    ; 将 rip + 0x1995 地址内容(0x100003048,即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rcx 寄存器
    0x1000016ac <+92>:  movq   0x1995(%rip), %rcx        ; demo.a : (Swift.Int) -> Swift.Int + 8
    0x1000016b3 <+99>:  movq   %rcx, %rdi
    0x1000016b6 <+102>: movq   %rax, -0x80(%rbp)
    ; 将 rcx 寄存器内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rbp - 0x88
    0x1000016ba <+106>: movq   %rcx, -0x88(%rbp)
    0x1000016c1 <+113>: callq  0x100001ef6               ; symbol stub for: swift_retain
    0x1000016c6 <+118>: leaq   -0x20(%rbp), %rdi
    0x1000016ca <+122>: movq   %rax, -0x90(%rbp)
    0x1000016d1 <+129>: callq  0x100001eea               ; symbol stub for: swift_endAccess
    ; 将立即数 1(a(1) 中的参数)移动到 edi 寄存器
    0x1000016d6 <+134>: movl   $0x1, %edi
    ; 将 rbp - 0x88 地址内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 r13 寄存器
    0x1000016db <+139>: movq   -0x88(%rbp), %r13
    0x1000016e2 <+146>: movq   -0x80(%rbp), %rax
    ; a(1) 函数调用,使用 si 进入
    0x1000016e6 <+150>: callq  *%rax
    0x1000016e8 <+152>: movq   -0x88(%rbp), %rdi
    0x1000016ef <+159>: movq   %rax, -0x98(%rbp)
    0x1000016f6 <+166>: callq  0x100001ef0               ; symbol stub for: swift_release
    0x1000016fb <+171>: leaq   0x194e(%rip), %rax        ; demo.b : (Swift.Int) -> Swift.Int
    0x100001702 <+178>: xorl   %r10d, %r10d
    0x100001705 <+181>: movl   %r10d, %ecx
    0x100001708 <+184>: movq   %rax, %rdi
    0x10000170b <+187>: leaq   -0x38(%rbp), %rsi
    0x10000170f <+191>: movl   $0x20, %edx
    0x100001714 <+196>: callq  0x100001ede               ; symbol stub for: swift_beginAccess
    0x100001719 <+201>: movq   0x1930(%rip), %rax        ; demo.b : (Swift.Int) -> Swift.Int
    ; 将 rip + 0x1931 地址内容(0x100003058,即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rcx 寄存器
    0x100001720 <+208>: movq   0x1931(%rip), %rcx        ; demo.b : (Swift.Int) -> Swift.Int + 8
    0x100001727 <+215>: movq   %rcx, %rdi
    0x10000172a <+218>: movq   %rax, -0xa0(%rbp)
    ; 将 rcx 寄存器内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rbp - 0xa8
    0x100001731 <+225>: movq   %rcx, -0xa8(%rbp)
    0x100001738 <+232>: callq  0x100001ef6               ; symbol stub for: swift_retain
    0x10000173d <+237>: leaq   -0x38(%rbp), %rdi
    0x100001741 <+241>: movq   %rax, -0xb0(%rbp)
    0x100001748 <+248>: callq  0x100001eea               ; symbol stub for: swift_endAccess
    ; 将立即数 2(b(2) 中的参数)移动到 edi 寄存器
    0x10000174d <+253>: movl   $0x2, %edi
    ; 将 rbp - 0xa8 地址内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 r13 寄存器
    0x100001752 <+258>: movq   -0xa8(%rbp), %r13
    0x100001759 <+265>: movq   -0xa0(%rbp), %rax
    ; b(2) 函数调用,使用 si 进入
    0x100001760 <+272>: callq  *%rax
    0x100001762 <+274>: movq   -0xa8(%rbp), %rdi
    0x100001769 <+281>: movq   %rax, -0xb8(%rbp)
    0x100001770 <+288>: callq  0x100001ef0               ; symbol stub for: swift_release
    0x100001775 <+293>: leaq   0x18c4(%rip), %rax        ; demo.a : (Swift.Int) -> Swift.Int
    0x10000177c <+300>: xorl   %r10d, %r10d
    0x10000177f <+303>: movl   %r10d, %ecx
    0x100001782 <+306>: movq   %rax, %rdi
    0x100001785 <+309>: leaq   -0x50(%rbp), %rsi
    0x100001789 <+313>: movl   $0x20, %edx
    0x10000178e <+318>: callq  0x100001ede               ; symbol stub for: swift_beginAccess
    0x100001793 <+323>: movq   0x18a6(%rip), %rax        ; demo.a : (Swift.Int) -> Swift.Int
    0x10000179a <+330>: movq   0x18a7(%rip), %rcx        ; demo.a : (Swift.Int) -> Swift.Int + 8
    0x1000017a1 <+337>: movq   %rcx, %rdi
    0x1000017a4 <+340>: movq   %rax, -0xc0(%rbp)
    0x1000017ab <+347>: movq   %rcx, -0xc8(%rbp)
    0x1000017b2 <+354>: callq  0x100001ef6               ; symbol stub for: swift_retain
    0x1000017b7 <+359>: leaq   -0x50(%rbp), %rdi
    0x1000017bb <+363>: movq   %rax, -0xd0(%rbp)
    0x1000017c2 <+370>: callq  0x100001eea               ; symbol stub for: swift_endAccess
    0x1000017c7 <+375>: movl   $0x3, %edi
    0x1000017cc <+380>: movq   -0xc8(%rbp), %r13
    0x1000017d3 <+387>: movq   -0xc0(%rbp), %rax
    0x1000017da <+394>: callq  *%rax
    0x1000017dc <+396>: movq   -0xc8(%rbp), %rdi
    0x1000017e3 <+403>: movq   %rax, -0xd8(%rbp)
    0x1000017ea <+410>: callq  0x100001ef0               ; symbol stub for: swift_release
    0x1000017ef <+415>: leaq   0x185a(%rip), %rax        ; demo.b : (Swift.Int) -> Swift.Int
    0x1000017f6 <+422>: xorl   %r10d, %r10d
    0x1000017f9 <+425>: movl   %r10d, %ecx
    0x1000017fc <+428>: movq   %rax, %rdi
    0x1000017ff <+431>: leaq   -0x68(%rbp), %rsi
    0x100001803 <+435>: movl   $0x20, %edx
    0x100001808 <+440>: callq  0x100001ede               ; symbol stub for: swift_beginAccess
    0x10000180d <+445>: movq   0x183c(%rip), %rax        ; demo.b : (Swift.Int) -> Swift.Int
    0x100001814 <+452>: movq   0x183d(%rip), %rcx        ; demo.b : (Swift.Int) -> Swift.Int + 8
    0x10000181b <+459>: movq   %rcx, %rdi
    0x10000181e <+462>: movq   %rax, -0xe0(%rbp)
    0x100001825 <+469>: movq   %rcx, -0xe8(%rbp)
    0x10000182c <+476>: callq  0x100001ef6               ; symbol stub for: swift_retain
    0x100001831 <+481>: leaq   -0x68(%rbp), %rdi
    0x100001835 <+485>: movq   %rax, -0xf0(%rbp)
    0x10000183c <+492>: callq  0x100001eea               ; symbol stub for: swift_endAccess
    0x100001841 <+497>: movl   $0x4, %edi
    0x100001846 <+502>: movq   -0xe8(%rbp), %r13
    0x10000184d <+509>: movq   -0xe0(%rbp), %rax
    0x100001854 <+516>: callq  *%rax
    0x100001856 <+518>: movq   -0xe8(%rbp), %rdi
    0x10000185d <+525>: movq   %rax, -0xf8(%rbp)
    0x100001864 <+532>: callq  0x100001ef0               ; symbol stub for: swift_release
    0x100001869 <+537>: xorl   %eax, %eax
    0x10000186b <+539>: addq   $0xf8, %rsp
    0x100001872 <+546>: popq   %r13
    0x100001874 <+548>: popq   %rbp
    0x100001875 <+549>: retq

demo`partial apply for a #1 (_:) in foo():
->  0x100001c40 <+0>:  pushq  %rbp
    0x100001c41 <+1>:  movq   %rsp, %rbp
    ; rdi 未改变,即第一个参数(1)
    ; 将 r13 + 0x10 地址内容(即封装了 i 的堆空间地址)移动到 rsi 寄存器(即第二个参数)
    0x100001c44 <+4>:  movq   0x10(%r13), %rsi
    ; 将 r13 + 0x18 地址内容(即封装了 j 的堆空间地址)移动到 rdx 寄存器(即第三个参数)
    0x100001c48 <+8>:  movq   0x18(%r13), %rdx
    0x100001c4c <+12>: popq   %rbp
    0x100001c4d <+13>: jmp    0x1000019e0               ; a #1 (Swift.Int) -> Swift.Int in demo.foo() -> ((Swift.Int) -> Swift.Int, (Swift.Int) -> Swift.Int) at main.swift:9

如上,首先每个需要被分配到堆上的局部变量(如 ij)均会各自创建一份独立的堆空间,并将这些局部变量存储其中,保证其作用域超过函数而不被立即释放;之后会根据函数(闭包)个数创建相应个数的堆空间,并将前者的堆空间地址放置在新的堆空间中,注意编译器虽然根据每个函数都创建了对应的堆空间,但堆空间的内容却是一致的,均引用了相同的局部变量堆空间;因此这些函数实际调用时,将访问到同一片内存区域的局部变量值。

全局变量

这次我们将 bar 声明为全局变量:

// main.swift

typealias Closure = (Int) -> Int

var bar = 5 // BREAKPOINT 🔴

func foo() -> Closure {
    func baz(_ val: Int) -> Int {
        bar += val // BREAKPOINT 🔴
        return bar
    }

    bar = 10

    return baz // BREAKPOINT 🔴
}

var f = foo()

f(1) // 11
f(2) // 13
f(3) // 16

此时我们可以从主函数的汇编中看出,bar 被移动到了通过 %rip 寄存器偏移的地址,而 %rip 寄存器固定存储下一条指令的位置,因此 bar 确实属于全局变量。我们也可以通过 (lldb) po withUnsafePointer(to: &bar) { print("\($0)") } 验证其内存地址位于 0x0000000100002050

demo`main:
    0x100000ae0 <+0>:   pushq  %rbp
    0x100000ae1 <+1>:   movq   %rsp, %rbp
    0x100000ae4 <+4>:   pushq  %r13
    0x100000ae6 <+6>:   subq   $0x118, %rsp              ; imm = 0x118
    ; 移动立即数 5 到 rip + 0x1558 地址(0x100002050)
->  0x100000aed <+13>:  movq   $0x5, 0x1558(%rip)        ; _dyld_private + 4
    0x100000af8 <+24>:  movl   %edi, -0x6c(%rbp)
    ; ...

而当断点执行到 foo 函数内部时,此时与局部变量不同的是,并没有存在 swift_allocObject 的符号桩:

demo`foo():
    0x100000d90 <+0>:  pushq  %rbp
    0x100000d91 <+1>:  movq   %rsp, %rbp
    0x100000d94 <+4>:  subq   $0x30, %rsp
    0x100000d98 <+8>:  leaq   0x12b1(%rip), %rdi        ; demo.bar : Swift.Int
    0x100000d9f <+15>: xorl   %eax, %eax
    0x100000da1 <+17>: movl   %eax, %ecx
    0x100000da3 <+19>: leaq   -0x18(%rbp), %rdx
    0x100000da7 <+23>: movl   $0x21, %esi
    0x100000dac <+28>: movq   %rsi, -0x20(%rbp)
    0x100000db0 <+32>: movq   %rdx, %rsi
    0x100000db3 <+35>: movq   -0x20(%rbp), %r8
    0x100000db7 <+39>: movq   %rdx, -0x28(%rbp)
    0x100000dbb <+43>: movq   %r8, %rdx
    0x100000dbe <+46>: movq   %rcx, -0x30(%rbp)
    0x100000dc2 <+50>: callq  0x100000f14               ; symbol stub for: swift_beginAccess
    0x100000dc7 <+55>: movq   $0xa, 0x127e(%rip)        ; _dyld_private + 4
    0x100000dd2 <+66>: movq   -0x28(%rbp), %rdi
    0x100000dd6 <+70>: callq  0x100000f20               ; symbol stub for: swift_endAccess
->  0x100000ddb <+75>: leaq   0xe(%rip), %rax           ; baz #1 (Swift.Int) -> Swift.Int in demo.foo() -> (Swift.Int) -> Swift.Int at main.swift:9
    0x100000de2 <+82>: movq   -0x30(%rbp), %rdx
    0x100000de6 <+86>: addq   $0x30, %rsp
    0x100000dea <+90>: popq   %rbp
    0x100000deb <+91>: retq

而最终在 baz 函数内部:

demo`baz #1 (_:) in foo():
    0x100000df0 <+0>:   pushq  %rbp
    0x100000df1 <+1>:   movq   %rsp, %rbp
    0x100000df4 <+4>:   subq   $0x70, %rsp
    0x100000df8 <+8>:   leaq   0x1251(%rip), %rax        ; demo.bar : Swift.Int
    0x100000dff <+15>:  xorl   %ecx, %ecx
    0x100000e01 <+17>:  xorl   %esi, %esi
    0x100000e03 <+19>:  leaq   -0x8(%rbp), %rdx
    ; 将 rdi 寄存器内容(1)移动到 rbp - 0x40 地址
    0x100000e07 <+23>:  movq   %rdi, -0x40(%rbp)
    0x100000e0b <+27>:  movq   %rdx, %rdi
    0x100000e0e <+30>:  movl   $0x8, %edx
    0x100000e13 <+35>:  movq   %rax, -0x48(%rbp)
    0x100000e17 <+39>:  movq   %rcx, -0x50(%rbp)
    0x100000e1b <+43>:  callq  0x100000f0e               ; symbol stub for: memset
    ; 将 rbp - 0x40 地址内容(1)移动到 rax 寄存器
    0x100000e20 <+48>:  movq   -0x40(%rbp), %rax
    0x100000e24 <+52>:  movq   %rax, -0x8(%rbp)
    0x100000e28 <+56>:  movq   -0x48(%rbp), %rdi
->  0x100000e2c <+60>:  leaq   -0x20(%rbp), %rsi
    0x100000e30 <+64>:  movl   $0x21, %edx
    0x100000e35 <+69>:  movq   -0x50(%rbp), %rcx
    0x100000e39 <+73>:  callq  0x100000f14               ; symbol stub for: swift_beginAccess
    ; 将 rbp - 0x40 地址内容(1)移动到 rax 寄存器
    0x100000e3e <+78>:  movq   -0x40(%rbp), %rax
    ; 将 rip + 0x1207(0x100002050)地址内容(bar)与 rax 寄存器内容(即 1)相加,结果存储在 rip + 0x1207 地址
    0x100000e42 <+82>:  addq   0x1207(%rip), %rax        ; demo.bar : Swift.Int
    0x100000e49 <+89>:  seto   %r8b

如上,全局变量并不会被闭包「捕获」,这是因为全局变量的作用域使得其无需捕获也可访问。

Reference