Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Swift KeyPath Internals

Yuta Saito
October 04, 2022

Swift KeyPath Internals

Yuta Saito

October 04, 2022
Tweet

More Decks by Yuta Saito

Other Decks in Programming

Transcript

  1. Swift KeyPath Internals
    Θ͍Θ͍swiftc #38
    @kateinoigakukun
    1

    View Slide

  2. ۙگ
    • ݚڀ: αΠζ࠷దԽؤுͬͯΔ
    • ϚΠΫϩϕϯνͰ͔͠ޮ͔ͳ͍࠷దԽ
    • Ruby: VenturaͰಈ͘Α͏ʹؤுͬͨ
    • SwiftWasm: ϓϩμΫγϣϯࣄྫग़͖ͯͯ͏Ε͍͠
    2

    View Slide

  3. KeyPathͷಛ௃
    3

    View Slide

  4. KeyPath ศར
    ཚ๫ʹݴ͏ͱ getter ͱ setter ΛऔΓճ͢දݱ
    ৭ΜͳॴͰ࢖ΘΕͯΔ
    • subscript(dynamicMember: KeyPath) -> Z
    • func map(_ keyPath: KeyPath) ->
    Publishers.MapKeyPath
    • Key Paths Expressions as Functions
    4

    View Slide

  5. KeyPath Ή͔͍ͣ͠
    ҙຯ͸ಉ͕ͩ͡ɺಈ࡞͸શ͘ҧ͏ɻ
    foo[\.bar]
    foo.bar
    5

    View Slide

  6. KeyPath ͓͍ͦ
    struct Foo {
    let bar: Int
    }
    let foo = Foo(bar: 42)
    @_optimize(none)
    func getter() { _ = foo.bar }
    @_optimize(none)
    func keyPathGetter() { _ = foo[keyPath: \.bar] }
    print("Simple Getter =>", ContinuousClock().measure {
    for _ in 1...10000000 { getter() }
    })
    print("KeyPath Getter =>", ContinuousClock().measure {
    for _ in 1...10000000 { keyPathGetter() }
    })
    $ swiftc main.swift
    $ ./main
    Simple Getter => 0.046666295 seconds
    KeyPath Getter => 1.590084352 seconds
    ʢ࠷దԽ͠ͳ͍ͱʣ
    34ഒ ͓͍ͦ
    !
    6

    View Slide

  7. KeyPath ࢖ͬͯΔ…ʁ
    ࢖ΘΕͯແͦ͞͏ͳϚΠφʔػೳ
    • \[String: Int].["foo"]
    • \[Int].last?.littleEndian
    • class KeyPath: Hashable, Equatable
    • PartialKeyPath.appending(path:)
    let arrayDescription: PartialKeyPath> = \.description
    let stringLength: PartialKeyPath = \.count
    let arrayDescriptionLength = arrayDescription.appending(path: stringLength)
    7

    View Slide

  8. ҉໧ʹ subscript indexΩϟϓνϟͯ͠Δέʔ
    ε
    KeyPath͕SendableʹͳΕͳ͍࿩ by iceman
    @dynamicMemberLookup struct Box {
    var value: Value
    subscript(dynamicMember keyPath: KeyPath) -> T {
    get { value[keyPath: keyPath] }
    }
    }
    struct ContentView: View {
    @State private var state = Box(value: Dog())
    var body: some View {
    TextField("", text: $state.name)
    // TextField("", text: _state.projectedValue[dynamicMember: \Box.[dynamicMember: \Dog.name]])
    }
    }
    8

    View Slide

  9. KeyPath࣮૷ͷ֓ཁ
    9

    View Slide

  10. ͔ͳΓ୯७Խͨ͠KeyPathͷ࣮૷
    let kp0 = \Foo.bar
    // -> let kp0 = KeyPath(getter: { $0.bar })
    let kp1 = \Foo.[42]
    // -> let kp1 = KeyPath(getter: { $0[42] })
    ࣮ࡍ͸΋ͬͱෳࡶ
    10

    View Slide

  11. KeyPath࣮૷ͷา͖ํʢίϯύΠϥ෦෼ʣ
    ࣮ߦॱɻҙຯղੳ·Ͱলུ
    File Description
    lib/SILGen/SILGenExpr.cpp KeyPathInst SIL໋ྩͱΞΫηαΛੜ੒
    lib/SILOptimizer/SILCombiner/
    SILCombinerApplyVisitors.cpp
    getter/setterΞΫηε΁࠷దԽ
    lib/IRGen/GenKeyPath.cpp ϥϯλΠϜ͕ಡΉͨΊͷKeyPathσʔλͱα
    ϯΫΛੜ੒
    11

    View Slide

  12. KeyPath࣮૷ͷา͖ํʢϥϯλΠϜ෦෼ʣ
    stdlib/public/core/KeyPath.swift
    • ίΞͷϩδοΫ͢΂͕ͯهड़͞ΕͯΔ
    • ΠϯελϯεԽ
    • ΞΫηε
    • ൺֱͳͲ
    • SwiftͰॻ͔Ε͍ͯΔ
    12

    View Slide

  13. KeyPathͷ࣮૷ৄࡉ
    13

    View Slide

  14. ΋͘͡
    1. SIL্ͷKeyPathදݱ
    2. LLVM IR্ͷKeyPathදݱ
    3. KeyPathͷϥϯλΠϜ࣮૷
    4. ίϯύΠϥ࠷దԽ
    14

    View Slide

  15. SIL্ͷKeyPathදݱ
    15

    View Slide

  16. SIL্ͷදݱ
    // keyPathGetter()
    sil hidden [Onone] @$s4main13keyPathGetteryyF : $@convention(thin) () -> () {
    bb0:
    // 1. άϩʔόϧม਺ `foo` Λϩʔυ
    %0 = global_addr @$s4main3fooAA3FooVvp : $*Foo // user: %1
    %1 = load %0 : $*Foo // user: %4
    // 2. `\Foo.bar` ͷKeyPathΛੜ੒
    %2 = keypath $KeyPath, (root $Foo; stored_property #Foo.bar : $Int) // users: %10, %7
    // 3. `foo` ΛελοΫʹΞϩέʔτ
    %3 = alloc_stack $Foo // users: %4, %9, %7
    store %1 to %3 : $*Foo // id: %4
    // 4. `foo` ͔ΒKeyPathܦ༝Ͱ `bar` Λऔಘ
    %5 = function_ref @swift_getAtKeyPath
    : $@convention(thin) <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @guaranteed KeyPath<τ_0_0, τ_0_1>) -> @out τ_0_1 // user: %7
    %6 = alloc_stack $Int // users: %8, %7
    %7 = apply %5(%6, %3, %2)
    : $@convention(thin) <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @guaranteed KeyPath<τ_0_0, τ_0_1>) -> @out τ_0_1
    dealloc_stack %6 : $*Int // id: %8
    dealloc_stack %3 : $*Foo // id: %9
    strong_release %2 : $KeyPath // id: %10
    %11 = tuple () // user: %12
    return %11 : $() // id: %12
    } // end sil function '$s4main13keyPathGetteryyF'
    16

    View Slide

  17. KeyPathInstͷߏ଄
    keypath /* ಘΒΕΔKeyPathܕ */, (
    root /* ϧʔτͷܕ */;
    /* ύεͷཁૉ1 ʢKeyPathPatternComponentʣ */
    /* ύεͷཁૉ2 ʢKeyPathPatternComponentʣ */
    /* ύεͷཁૉN ʢKeyPathPatternComponentʣ */
    )
    17

    View Slide

  18. KeyPathPatternComponent ͷछྨ
    class KeyPathPatternComponent {
    ...
    enum class Kind: unsigned {
    StoredProperty, // \Foo.bar (stored)
    GettableProperty, // \Foo.bar (getter), \Foo.[42] (subscript)
    SettableProperty, // GettableProperty + setter
    TupleElement, // \(Int, String).1
    OptionalChain, // \Foo.bar?.baz
    OptionalForce, // \Foo.bar!
    OptionalWrap, // \Foo.bar?.baz
    };
    };
    18

    View Slide

  19. StoredProperty
    struct Foo {
    let bar: Int
    }
    // `\Foo.bar` =>
    %2 = keypath $KeyPath, (
    root $Foo;
    stored_property #Foo.bar : $Int
    )
    19

    View Slide

  20. StoredProperty
    struct Foo {
    var bar: Int
    }
    // `\Foo.bar` =>
    %2 = keypath $WritableKeyPath, (
    root $Foo;
    stored_property #Foo.bar : $Int
    )
    20

    View Slide

  21. TupleElement
    typealias Foo = (
    bar: Int,
    baz: String
    )
    // `\Foo.bar` =>
    %2 = keypath $WritableKeyPath<(bar: Int, baz: String), Int>, (
    root $(bar: Int, String);
    tuple_element #0 : $Int
    )
    21

    View Slide

  22. GettableProperty
    struct Foo {
    var bar: Int { 42 }
    }
    // `\Foo.bar` =>
    %2 = keypath $KeyPath, (
    root $Foo;
    gettable_property $Int,
    id @$s4main3FooV3barSivg : $@convention(method) (Foo) -> Int,
    getter @$s4main3FooV3barSivpACTK : $@convention(thin) (@in_guaranteed Foo) -> @out Int
    )
    22

    View Slide

  23. GettableProperty (Subscript)
    struct Foo {
    subscript(x: Int) -> Int { 0xbadbabe }
    }
    // `\Foo.[42]` =>
    %2 = integer_literal $Builtin.Int64, 0xdeadbeef
    %3 = struct $Int (%2 : $Builtin.Int64)
    %4 = keypath $KeyPath, (
    root $Foo;
    gettable_property $Int,
    id @$s4main3FooVyS2icig : $@convention(method) (Int, Foo) -> Int,
    getter @$s4main3FooVyS2icipACTK : $@convention(thin) (@in_guaranteed Foo, UnsafeRawPointer) -> @out Int,
    indices [%$0 : $Int : $Int],
    indices_equals @$sSiTH : $@convention(thin) (UnsafeRawPointer, UnsafeRawPointer) -> Bool,
    indices_hash @$sSiTh : $@convention(thin) (UnsafeRawPointer) -> Int
    ) (%3)
    ஋ʢindicesʣɺൺֱɺϋογϡͷͨΊͷؔ਺ΛΩϟϓνϟ
    23

    View Slide

  24. GettableProperty (Subscript)
    • ϥϯλΠϜ͔Βݺ͹ΕΔ༻ͷαϯΫ
    • ͢΂ͯͷcomputed subscriptΛ୯ҰͷγάωνϟͰݺͼग़ͨ͢Ίʹඞཁ
    // key path getter for Foo.subscript(_:) : Foo
    sil shared [thunk] @$s4main3FooVyS2icipACTK : $@convention(thin) (@in_guaranteed Foo, UnsafeRawPointer) -> @out Int {
    // %0 // user: %8
    // %1 // user: %3
    // %2 // user: %4
    bb0(%0 : $*Int, %1 : $*Foo, %2 : $UnsafeRawPointer):
    %3 = load %1 : $*Foo // user: %7
    %4 = pointer_to_address %2 : $UnsafeRawPointer to $*Int // user: %5
    %5 = load %4 : $*Int // user: %7
    // function_ref Foo.subscript.getter
    %6 = function_ref @$s4main3FooVyS2icig : $@convention(method) (Int, Foo) -> Int // user: %7
    %7 = apply %6(%5, %3) : $@convention(method) (Int, Foo) -> Int // user: %8
    store %7 to %0 : $*Int // id: %8
    %9 = tuple () // user: %10
    return %9 : $() // id: %10
    } // end sil function '$s4main3FooVyS2icipACTK'
    24

    View Slide

  25. SettableProperty
    struct Foo {
    var bar: Int { get { 42 } set { } }
    }
    // `\Foo.bar` =>
    %2 = keypath $WritableKeyPath, (
    root $Foo;
    settable_property $Int,
    id @$s4main3FooV3barSivg : $@convention(method) (Foo) -> Int,
    getter @$s4main3FooV3barSivpACTK : $@convention(thin) (@in_guaranteed Foo) -> @out Int,
    setter @$s4main3FooV3barSivpACTk : $@convention(thin) (@in_guaranteed Int, @inout Foo) -> ()
    )
    25

    View Slide

  26. GettableProperty + GettableProperty
    struct Foo {
    struct Bar { var baz: Int { 42 } }
    var bar: Bar { Bar() }
    }
    // `\Foo.bar.baz` =>
    %2 = keypath $KeyPath, (
    root $Foo;
    gettable_property $Foo.Bar,
    id @$s4main3FooV3barAC3BarVvg : $@convention(method) (Foo) -> Foo.Bar,
    getter @$s4main3FooV3barAC3BarVvpACTK : $@convention(thin) (@in_guaranteed Foo) -> @out Foo.Bar;
    gettable_property $Int,
    id @$s4main3FooV3BarV3bazSivg : $@convention(method) (Foo.Bar) -> Int,
    getter @$s4main3FooV3BarV3bazSivpAETK : $@convention(thin) (@in_guaranteed Foo.Bar) -> @out Int
    )
    26

    View Slide

  27. OptionalChain + OptionalWrap
    struct Foo {
    struct Bar { var baz: Int { 42 } }
    var bar: Bar? { Bar() }
    }
    // `\Foo.bar.baz` =>
    %2 = keypath $KeyPath>, (
    root $Foo;
    gettable_property $Optional,
    id @$s4main3FooV3barAC3BarVSgvg : $@convention(method) (Foo) -> Optional,
    getter @$s4main3FooV3barAC3BarVSgvpACTK : $@convention(thin) (@in_guaranteed Foo) -> @out Optional;
    optional_chain : $Foo.Bar;
    gettable_property $Int,
    id @$s4main3FooV3BarV3bazSivg : $@convention(method) (Foo.Bar) -> Int,
    getter @$s4main3FooV3BarV3bazSivpAETK : $@convention(thin) (@in_guaranteed Foo.Bar) -> @out Int;
    optional_wrap : $Optional
    )
    27

    View Slide

  28. LLVM IR্ͷKeyPathදݱ
    28

    View Slide

  29. ͔͜͜Βઌɺ͜ͷίʔυΛର৅ʹ࿩ΛਐΊΔɻ
    struct Foo {
    subscript(x: Int) -> Int { 0xbadbabe }
    }
    let foo = Foo()
    @_optimize(none)
    func keyPathGetter() { _ = foo[keyPath: \Foo.[0xdeadbeef]] }
    29

    View Slide

  30. LLVM IR্ͷදݱ
    • KeyPath Object (Not ABI)
    • KeyPathΠϯελϯε͕อ࣋͢Δ৘ใ
    • ͍ͭ΋Ϣʔβ͕औΓճͯ͠Δͷ͸͜Ε
    • KeyPath Pattern (ABI)
    • PropertyͷಛੑͱKeyPathͱͯ͠ͷ஋ͷಛੑΛදݱ
    • ϥϯλΠϜ͕Pattern͔ΒKeyPath ObjectΛੜ੒͢Δ
    30

    View Slide

  31. KeyPath Pattern -> KeyPath Object (ΠϯελϯεԽ)
    ΠϯελϯεԽʹ͸Ҿ਺ΛऔΔ৔߹͕͋Δ
    • Ҿ਺: IndicesɺGeneric Params
    • Ҿ਺͕ඞཁͳ͍৔߹͸Πϯελϯε͕Ωϟογϡ͞ΕΔ
    func withIndices(_ x: Int, y: String) -> AnyKeyPath {
    return \Foo.[x, y]
    }
    func withGenericContext(_ x: X.Type) -> AnyKeyPath {
    return \Blah.[x]
    }
    31

    View Slide

  32. KeyPath Pattern in LLVM IR (Gettable Subscript)
    \Foo.[0xdeadbeef]
    @keypath = private global <{ i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32 }> <{
    ; oncePtr (0 if not cacheable, otherwise a relative pointer to the cache space)
    i32 0,
    ; --- KeyPathComponentHeader ---
    ; genericEnvironment
    i32 0,
    ; rootMetadataRef
    i32 cast_relptr (i64 symbolic_ref @$s4main3FooVMn), ; nominal type descriptor for main.Foo
    ; leafMetadataRef
    i32 cast_relptr (i64 symbolic_ref @$sSi), ; mangled name for Swift.Int ("Si\0")
    ; kvcString
    i32 0,
    ; KeyPathBuffer.Header
    i32 0b00000000000000000000000000011000, ; (size: 24, hasReferencePrefix: false, trivial: false)
    ; --- RawKeyPathComponent ---
    ; RawKeyPathComponent.Header
    i32 0b00000010000010000000000000000000, ; (hasComputedArguments: true, discriminator: computedTag(2))
    ; RawKeyPathComponent.idValue
    i32 cast_relptr (i64 (i64)* @"$s4main3FooVyS2icig" to i64), ; main.Foo.subscript.getter : (Swift.Int) -> Swift.Int
    ; RawKeyPathComponent.getter
    i32 cast_relptr (i64 (i64)* @"$s4main3FooVyS2icipACTK" to i64), ; key path getter for main.Foo.subscript(Swift.Int) -> Swift.Int : main.Foo
    ; --- KeyPathPatternComputedArguments ---
    ; KeyPathPatternComputedArguments.getLayout
    i32 cast_relptr ({ i64, i64 } (i8*)* @keypath_get_arg_layout to i64),
    ; KeyPathPatternComputedArguments.witnesses
    i32 cast_relptr ({ i8*, void (i8*, i8*, i64)*, i1 (i8*, i8*)*, i64 (i8*)* }* @keypath_witnesses to i64),
    ; KeyPathPatternComputedArguments.initializer
    i32 cast_relptr (void (i8*, i8*)* @keypath_arg_init to i64),
    }>, section ".rodata", align 8
    32

    View Slide

  33. KeyPathੜ੒ in LLVM IR
    ; 1. Argument Bufferͷ૊Έཱͯ
    %1 = alloca i8, i64 8, align 16
    %2 = getelementptr inbounds i8, i8* %1, i64 0
    %3 = bitcast i8* %2 to %TSi*
    %._value = getelementptr inbounds %TSi, %TSi* %3, i32 0, i32 0
    store i64 0xdeadbeef, i64* %._value, align 8
    ; 2. KeyPath Objectͷੜ੒
    %4 = call %swift.refcounted* @swift_getKeyPath(i8* @keypath_pattern, i8* %1)
    33

    View Slide

  34. KeyPathͷϥϯλΠϜ࣮૷
    34

    View Slide

  35. swift_getKeyPath
    KeyPath patternΛarguments͔ΒKeyPath objectΛੜ੒͢ΔϥϯλΠϜؔ਺
    @_cdecl("swift_getKeyPathImpl")
    public func _swift_getKeyPath(pattern: UnsafeMutableRawPointer,
    arguments: UnsafeRawPointer)
    -> UnsafeRawPointer
    35

    View Slide

  36. swift_getKeyPath
    1. oncePtrͷΩϟογϡνΣοΫʢεϨουηʔϑʣ
    2. KeyPathΠϯελϯεͷੜ੒
    1. Pattern͔ΒKeyPathαϒΫϥεͱΦϒδΣΫταΠζΛܭࢉ
    2. Patternͱarguments͔ΒΦϒδΣΫτΛ૊Έཱͯ
    3. Ωϟογϡʹอଘ
    36

    View Slide

  37. 1. oncePtrͷΩϟογϡνΣοΫ
    let oncePtrPtr = pattern
    let oncePtrOffset = oncePtrPtr.load(as: Int32.self)
    let oncePtr: UnsafeRawPointer?
    if oncePtrOffset != 0 {
    let theOncePtr = _resolveRelativeAddress(oncePtrPtr, oncePtrOffset)
    oncePtr = theOncePtr
    // See whether we already instantiated this key path.
    // This is a non-atomic load because the instantiated pointer will be
    // written with a release barrier, and loads of the instantiated key path
    // ought to carry a dependency through this loaded pointer.
    let existingInstance = theOncePtr.load(as: UnsafeRawPointer?.self)
    if let existingInstance = existingInstance {
    // Return the instantiated object at +1.
    let object = Unmanaged.fromOpaque(existingInstance)
    // TODO: This retain will be unnecessary once we support global objects
    // with inert refcounting.
    _ = object.retain()
    return existingInstance
    }
    } else {
    oncePtr = nil
    }
    37

    View Slide

  38. 2. KeyPathΠϯελϯεͷੜ੒
    // Instantiate a new key path object modeled on the pattern.
    // Do a pass to determine the class of the key path we'll be instantiating
    // and how much space we'll need for it.
    let (keyPathClass, rootType, size, _)
    = _getKeyPathClassAndInstanceSizeFromPattern(patternPtr, arguments)
    // Allocate the instance.
    let instance = keyPathClass._create(capacityInBytes: size) { instanceData in
    // Instantiate the pattern into the instance.
    _instantiateKeyPathBuffer(patternPtr, instanceData, rootType, arguments)
    }
    38

    View Slide

  39. 2. KeyPathΠϯελϯεͷੜ੒
    Tail-alloc͞ΕͨόοϑΝ͸KeyPathBufferͱͯ͠࢖ΘΕΔ
    public class AnyKeyPath: Hashable, _AppendKeyPath {
    ...
    internal static func _create(
    capacityInBytes bytes: Int,
    initializedBy body: (UnsafeMutableRawBufferPointer) -> Void
    ) -> Self {
    _internalInvariant(bytes > 0 && bytes % 4 == 0,
    "capacity must be multiple of 4 bytes")
    let result = Builtin.allocWithTailElems_1(self, (bytes/4)._builtinWordValue,
    Int32.self)
    result._kvcKeyPathStringPtr = nil
    let base = UnsafeMutableRawPointer(Builtin.projectTailElems(result,
    Int32.self))
    body(UnsafeMutableRawBufferPointer(start: base, count: bytes))
    return result
    }
    ...
    }
    39

    View Slide

  40. 3. Ωϟογϡʹอଘ
    • CAS (Compared-and-Swap)ͰΞτ
    ϛοΫʹΩϟογϡྖҬʹॻ͖ࠐΈ
    • ΋͠ଞͷεϨου͕Ωϟογϡॻ͖
    ࠐΈʹ੒ޭͨ͠ΒͦΕΛ࠾༻ͯ͠ɺ
    ࡞ͬͨΦϒδΣΫτΛഇغ
    // Try to replace a null pointer in the cache variable with the instance
    // pointer.
    let instancePtr = Unmanaged.passRetained(instance)
    while true {
    let (oldValue, won) = Builtin.cmpxchg_seqcst_seqcst_Word(
    oncePtr._rawValue,
    0._builtinWordValue,
    UInt(bitPattern: instancePtr.toOpaque())._builtinWordValue)
    // If the exchange succeeds, then the instance we formed is the canonical
    // one.
    if Bool(won) {
    break
    }
    // Otherwise, someone raced with us to instantiate the key path pattern
    // and won. Their instance should be just as good as ours, so we can take
    // that one and let ours get deallocated.
    if let existingInstance = UnsafeRawPointer(bitPattern: Int(oldValue)) {
    // Return the instantiated object at +1.
    let object = Unmanaged.fromOpaque(existingInstance)
    // TODO: This retain will be unnecessary once we support global objects
    // with inert refcounting.
    _ = object.retain()
    // Release the instance we created.
    instancePtr.release()
    return existingInstance
    } else {
    // Try the cmpxchg again if it spuriously failed.
    continue
    }
    }
    40

    View Slide

  41. swift_getAtKeyPath
    KeyPath objectͱRootͷ஋͔ΒValueΛऔΓग़͢ϥϯλΠϜؔ਺
    @_silgen_name("swift_getAtKeyPath")
    public // COMPILER_INTRINSIC
    func _getAtKeyPath(
    root: Root,
    keyPath: KeyPath
    ) -> Value {
    return keyPath._projectReadOnly(from: root)
    }
    41

    View Slide

  42. • ίϯϙʔωϯτ͝ͱʹ۩ମܕ
    ΛऔΓग़ͯ͠౤Өʢprojectʣͯ͠
    ͍͘
    • δΣωϦοΫλΠϓͳͷͰOpaque
    Pointer
    @usableFromInline
    internal final func _projectReadOnly(from root: Root) -> Value {
    // TODO: For perf, we could use a local growable buffer instead of Any
    var curBase: Any = root
    return withBuffer {
    var buffer = $0
    if buffer.data.isEmpty {
    return unsafeBitCast(root, to: Value.self)
    }
    while true {
    let (rawComponent, optNextType) = buffer.next()
    let valueType = optNextType ?? Value.self
    let isLast = optNextType == nil
    func project(_ base: CurValue) -> Value? {
    func project2(_: NewValue.Type) -> Value? {
    switch rawComponent._projectReadOnly(base,
    to: NewValue.self, endingWith: Value.self) {
    case .continue(let newBase):
    if isLast {
    _internalInvariant(NewValue.self == Value.self,
    "key path does not terminate in correct type")
    return unsafeBitCast(newBase, to: Value.self)
    } else {
    curBase = newBase
    return nil
    }
    case .break(let result):
    return result
    }
    }
    return _openExistential(valueType, do: project2)
    }
    if let result = _openExistential(curBase, do: project) {
    return result
    }
    }
    }
    }
    42

    View Slide

  43. internal struct RawKeyPathComponent {
    ...
    internal func _projectReadOnly(
    _ base: CurValue,
    to: NewValue.Type,
    endingWith: LeafValue.Type
    ) -> ProjectionResult {
    switch value {
    ...
    case .get(id: _, accessors: let accessors, argument: let argument),
    .mutatingGetSet(id: _, accessors: let accessors, argument: let argument),
    .nonmutatingGetSet(id: _, accessors: let accessors, argument: let argument):
    return .continue(accessors.getter()(
    base,
    argument?.data.baseAddress ?? accessors._value,
    argument?.data.count ?? 0)
    )
    ...
    }
    ...
    }
    getterΛݺΜͩΓɺstoredPropertyΛऔΓग़ͨ͠Γ͢Δ
    43

    View Slide

  44. getterݺͼग़͠
    internal typealias Getter = @convention(thin)
    (CurValue, UnsafeRawPointer, Int) -> NewValue
    accessors.getter()(
    base,
    argument?.data.baseAddress ?? accessors._value,
    argument?.data.count ?? 0
    )
    ݺͼग़͞ΕΔؔ਺
    // key path getter for Foo.subscript(_:) : Foo
    sil shared [thunk] @$s4main3FooVyS2icipACTK : $@convention(thin) (@in_guaranteed Foo, UnsafeRawPointer) -> @out Int
    44

    View Slide

  45. ༨ஊ: getterఆٛʹόϑΝαΠζͷύϥϝʔλ͕଍Γͯͳ͍
    • ଍Γͯͳ͍
    • αΠζ΍Argument Buffer͕ඞཁͳ
    ͍έʔεͰ͸লུ͞Εͯ͠·͏
    • γάωνϟෆҰக͸WasmͰ͸
    Τϥʔ
    • ৗʹ3ύϥϝʔλు͘Α͏ʹύον
    ౰ͯͯΔ
    ఆٛ
    sil shared [thunk] @$s4main3FooVyS2icipACTK : $@convention(thin) (
    @in_guaranteed Foo, // base
    UnsafeRawPointer // KeyPath Argument Buffer
    ) -> @out Int
    ݺͼग़͠ଆ
    let ret = accessors.getter()(
    base,
    argument?.data.baseAddress ?? accessors._value,
    argument?.data.count ?? 0
    )
    45

    View Slide

  46. KeyPath Argument BufferͷϨΠΞ΢τ
    ྫ1. subscript(x: Int) -> Int
    Offset Type
    0
    Int
    46

    View Slide

  47. KeyPath Argument BufferͷϨΠΞ΢τ
    ྫ2. subscript(x: T, y: Int) -> Int
    Offset Type
    0
    T = Int
    8
    Int
    47

    View Slide

  48. KeyPath Argument BufferͷϨΠΞ΢τ
    ྫ3. subscript(x: T, y: Int) -> Int
    Offset Type
    0
    T
    sizeof(T) Int
    sizeof(T) + 8 %swift.type* T
    sizeof(T) + 16 witness table for T: Hashable
    48

    View Slide

  49. ೋॏαϯΫ໰୊
    49

    View Slide

  50. ೋॏαϯΫ໰୊
    key path getter͕δΣωϦοΫύϥϝʔλΛ࣋ͭ৔߹ɺLLVM IRͰ͸Ҿ਺ʹϝλλΠ
    ϓͱWitness Table͕౉͞ΕΔɻ
    // key path getter for Bar.subscript(_:_:) : BarA
    sil [thunk] @$s4main3BarVySix_SitcluipSHRzlACxTK : $@convention(thin) (
    @in_guaranteed Bar,
    UnsafeRawPointer
    ) -> @out Int

    define swiftcc void @"$s4main3BarVySix_SitcluipSHRzlACxTK"(
    %TSi* noalias nocapture sret(%TSi) %0,
    %T4main3BarV* noalias nocapture dereferenceable(24) %1,
    i8* %2,
    %swift.type* %T,
    i8** %T.Hashable
    )
    50

    View Slide

  51. ೋॏαϯΫ໰୊
    ϥϯλΠϜ͸୯ҰͷγάωνϟͰkey path getterΛݺͼग़͍ͨ͠ͷͰɺ
    δΣωϦοΫύϥϝʔλΛArgument Buffer͔ΒऔΓग़ͯ͠key path getterΛݺͼग़
    ͢αϯΫΛߋʹੜ੒͢Δɻ
    define private swiftcc void @keypath_get(%TSi* noalias nocapture sret(%TSi) %0, %T4main3BarV* noalias nocapture %1, i8* %2, i64 %3) {
    entry:
    %4 = sub i64 %3, 16
    %5 = getelementptr inbounds i8, i8* %2, i64 %4
    %6 = bitcast i8* %5 to %swift.type**
    %"\CF\84_0_0" = load %swift.type*, %swift.type** %6, align 8
    %7 = getelementptr inbounds %swift.type*, %swift.type** %6, i32 1
    %8 = bitcast %swift.type** %7 to i8***
    %"\CF\84_0_0.Hashable" = load i8**, i8*** %8, align 8
    ; key path getter for Bar.subscript(_:_:) : BarA
    call swiftcc void @"$s4main3BarVySix_SitcluipSHRzlACxTK"(
    %TSi* noalias nocapture sret(%TSi) %0, ; return value
    %T4main3BarV* noalias nocapture dereferenceable(24) %1, ; base
    i8* %2, ; KeyPath Argument Buffer
    %swift.type* %"\CF\84_0_0", ; generic parameter
    i8** %"\CF\84_0_0.Hashable" ; Hashable witness table
    )
    ret void
    }
    51

    View Slide

  52. ೋॏαϯΫ໰୊
    • KeyPath getter͸SILϨϕϧͰ࡞ΒΕΔ΍ͭͱɺIRGenͰ࡞ΒΕΔαϯΫ͕͋Δɻ
    • IRGenϨϕϧͷαϯΫ͸ɺΩϟϓνϟ͞ΕͨδΣωϦοΫύϥϝʔλ͕͋Δͱ
    ͖ɺ
    όοϑΝ͔ΒϝλλΠϓΛऔΓग़ͯ͠SILϨϕϧͰ࡞ΒΕͨgetterʹ
    ϑΥϫʔυ͢Δɻ
    • ݺͼग़͠ن໿ͷ࣮૷͕IRGenͱSILGenʹඈͼࢄͬͯΔ
    52

    View Slide

  53. KeyPath଎͍ͨ͘͠
    53

    View Slide

  54. Flame Graph Search ic
    [[stack]]
    _..
    __sw..
    _swi..
    $s5benchyyXEfU0_
    swift_getAtKeyPath
    __swift_instantiateCanonicalPrespecializedGen..
    _swift_getGen..
    swift_getAtKeyPath
    bench
    _swift_getGe..
    swift..
    getCache
    $s5bench13keyPathGetteryyF
    s..
    s..
    $s..
    $..
    main
    s..
    _start
    _swift_getGenericMetadata
    s..
    getCache
    $sypSgMf
    swift::C..
    swift_g..
    $..
    $ss7KeyPathC16_projectReadOnly4fromq_x_tFq_s0aB6BufferVXEfU_
    __libc_start_main
    getC..
    swift_getAtK..
    __swift_instantia..
    $ss19RawKeyPa..
    $ss5ClockPsE7measurey8DurationQzyyKXEKF
    _..
    __swift_inst..
    swift_..
    sw..
    _sw..
    https://gist.github.com/kateinoigakukun/d540dfdabe1948258d0860c7c6f462ee 54

    View Slide

  55. KeyPath଎͍ͨ͘͠ ʢίϯύΠϥͰͰ͖Δ͜ͱʣ
    ϥϯλΠϜؔ਺ͷݺͼग़͠Λফ͢ʢSILCombiner in SIL Optimizerʣ
    %kp = keypath $KeyPath, (
    root $Foo;
    gettable_property $Int,
    id @$s4main3FooV3barSivg : $@convention(method) (Foo) -> Int,
    getter @$s4main3FooV3barSivpACTK : $@convention(thin) (@in_guaranteed Foo) -> @out Int
    )
    %fn = function_ref @swift_getAtKeyPath
    %ret = alloc_stack $Int
    apply %fn(%ret, %base, %kp)

    // function_ref key path getter for Foo.bar : Foo
    %getter = function_ref @$s4main3FooV3barSivpACTK
    %ret = alloc_stack $Int
    apply %getter(%ret, %base) : $@convention(thin) (@in_guaranteed Foo) -> @out Int
    55

    View Slide

  56. SILCombiner ͷ੍໿
    • Argument BufferΛҾ਺ʹऔΔ৔߹͸࠷దԽͰ͖ͳ͍ɻ
    • Argument BufferͷϝϞϦϨΠΞ΢τ͸IRGenͰ૊·ΕΔͷͰɺ
    SIL Optimizerͷஈ֊ͰҾ਺Λ૊ΈཱͯΒΕͳ͍ͨΊɻ
    // key path getter for Bar.subscript(_:_:) : BarA
    sil [thunk] @$s4main3BarVySix_SitcluipSHRzlACxTK : $@convention(thin) (
    @in_guaranteed Bar,
    UnsafeRawPointer
    ) -> @out Int
    56

    View Slide

  57. KeyPath଎͍ͨ͘͠ ʢϢʔβίʔυͰͰ͖Δ͜ͱʣ
    • ଎౓͕ඞཁͳΒgetter/setterΛ࢖͏
    • Argument Bufferਚ͖ͷKeyPathͳΒͰ͖Δ͚ͩΩϟογϡ͢Δ
    57

    View Slide

  58. ల๬
    • Wasm༻ͷϋοΫ͸ྲྀੴʹͦͷ··ΞοϓετϦʔϜͰ͖ͳ͍ͷͰɺ
    ઌʹϦϑΝΫλϦϯά͍ͨ͠ɻ
    • ϦϑΝΫλϦϯάͷ෭࣍ޮՌͰɺArgument Buffer෇͖ͷKeyPathΞΫηε࠷దԽ
    Ͱ͖ΔΑ͏ʹͳΔ͔΋ɻ
    58

    View Slide