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

  2. ۙگ • ݚڀ: αΠζ࠷దԽؤுͬͯΔ • ϚΠΫϩϕϯνͰ͔͠ޮ͔ͳ͍࠷దԽ • Ruby: VenturaͰಈ͘Α͏ʹؤுͬͨ •

    SwiftWasm: ϓϩμΫγϣϯࣄྫग़͖ͯͯ͏Ε͍͠ 2
  3. KeyPathͷಛ௃ 3

  4. KeyPath ศར ཚ๫ʹݴ͏ͱ getter ͱ setter ΛऔΓճ͢දݱ ৭ΜͳॴͰ࢖ΘΕͯΔ • subscript(dynamicMember:

    KeyPath<X, Y>) -> Z • func map<T>(_ keyPath: KeyPath<Self.Output, T>) -> Publishers.MapKeyPath<Self, T> • Key Paths Expressions as Functions 4
  5. KeyPath Ή͔͍ͣ͠ ҙຯ͸ಉ͕ͩ͡ɺಈ࡞͸શ͘ҧ͏ɻ foo[\.bar] foo.bar 5

  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
  7. KeyPath ࢖ͬͯΔ…ʁ ࢖ΘΕͯແͦ͞͏ͳϚΠφʔػೳ • \[String: Int].["foo"] • \[Int].last?.littleEndian • class

    KeyPath: Hashable, Equatable • PartialKeyPath.appending(path:) let arrayDescription: PartialKeyPath<Array<Int>> = \.description let stringLength: PartialKeyPath<String> = \.count let arrayDescriptionLength = arrayDescription.appending(path: stringLength) 7
  8. ҉໧ʹ subscript indexΩϟϓνϟͯ͠Δέʔ ε KeyPath͕SendableʹͳΕͳ͍࿩ by iceman @dynamicMemberLookup struct Box<Value>

    { var value: Value subscript<T>(dynamicMember keyPath: KeyPath<Value, T>) -> 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<Dog>.[dynamicMember: \Dog.name]]) } } 8
  9. KeyPath࣮૷ͷ֓ཁ 9

  10. ͔ͳΓ୯७Խͨ͠KeyPathͷ࣮૷ let kp0 = \Foo.bar // -> let kp0 =

    KeyPath(getter: { $0.bar }) let kp1 = \Foo.[42] // -> let kp1 = KeyPath(getter: { $0[42] }) ࣮ࡍ͸΋ͬͱෳࡶ 10
  11. KeyPath࣮૷ͷา͖ํʢίϯύΠϥ෦෼ʣ ࣮ߦॱɻҙຯղੳ·Ͱলུ File Description lib/SILGen/SILGenExpr.cpp KeyPathInst SIL໋ྩͱΞΫηαΛੜ੒ lib/SILOptimizer/SILCombiner/ SILCombinerApplyVisitors.cpp getter/setterΞΫηε΁࠷దԽ

    lib/IRGen/GenKeyPath.cpp ϥϯλΠϜ͕ಡΉͨΊͷKeyPathσʔλͱα ϯΫΛੜ੒ 11
  12. KeyPath࣮૷ͷา͖ํʢϥϯλΠϜ෦෼ʣ stdlib/public/core/KeyPath.swift • ίΞͷϩδοΫ͢΂͕ͯهड़͞ΕͯΔ • ΠϯελϯεԽ • ΞΫηε • ൺֱͳͲ

    • SwiftͰॻ͔Ε͍ͯΔ 12
  13. KeyPathͷ࣮૷ৄࡉ 13

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

    14
  15. SIL্ͷKeyPathදݱ 15

  16. SIL্ͷදݱ // keyPathGetter() sil hidden [Onone] @$s4main13keyPathGetteryyF : [email protected](thin) ()

    -> () { bb0: // 1. άϩʔόϧม਺ `foo` Λϩʔυ %0 = global_addr @$s4main3fooAA3FooVvp : $*Foo // user: %1 %1 = load %0 : $*Foo // user: %4 // 2. `\Foo.bar` ͷKeyPathΛੜ੒ %2 = keypath $KeyPath<Foo, Int>, (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 : [email protected](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<Foo, Int>(%6, %3, %2) : [email protected](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<Foo, Int> // id: %10 %11 = tuple () // user: %12 return %11 : $() // id: %12 } // end sil function '$s4main13keyPathGetteryyF' 16
  17. KeyPathInstͷߏ଄ keypath /* ಘΒΕΔKeyPathܕ */, ( root /* ϧʔτͷܕ */;

    /* ύεͷཁૉ1 ʢKeyPathPatternComponentʣ */ /* ύεͷཁૉ2 ʢKeyPathPatternComponentʣ */ /* ύεͷཁૉN ʢKeyPathPatternComponentʣ */ ) 17
  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
  19. StoredProperty struct Foo { let bar: Int } // `\Foo.bar`

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

    => %2 = keypath $WritableKeyPath<Foo, Int>, ( root $Foo; stored_property #Foo.bar : $Int ) 20
  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
  22. GettableProperty struct Foo { var bar: Int { 42 }

    } // `\Foo.bar` => %2 = keypath $KeyPath<Foo, Int>, ( root $Foo; gettable_property $Int, id @$s4main3FooV3barSivg : [email protected](method) (Foo) -> Int, getter @$s4main3FooV3barSivpACTK : [email protected](thin) (@in_guaranteed Foo) -> @out Int ) 22
  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<Foo, Int>, ( root $Foo; gettable_property $Int, id @$s4main3FooVyS2icig : [email protected](method) (Int, Foo) -> Int, getter @$s4main3FooVyS2icipACTK : [email protected](thin) (@in_guaranteed Foo, UnsafeRawPointer) -> @out Int, indices [%$0 : $Int : $Int], indices_equals @$sSiTH : [email protected](thin) (UnsafeRawPointer, UnsafeRawPointer) -> Bool, indices_hash @$sSiTh : [email protected](thin) (UnsafeRawPointer) -> Int ) (%3) ஋ʢindicesʣɺൺֱɺϋογϡͷͨΊͷؔ਺ΛΩϟϓνϟ 23
  24. GettableProperty (Subscript) • ϥϯλΠϜ͔Βݺ͹ΕΔ༻ͷαϯΫ • ͢΂ͯͷcomputed subscriptΛ୯ҰͷγάωνϟͰݺͼग़ͨ͢Ίʹඞཁ // key path

    getter for Foo.subscript(_:) : Foo sil shared [thunk] @$s4main3FooVyS2icipACTK : [email protected](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 : [email protected](method) (Int, Foo) -> Int // user: %7 %7 = apply %6(%5, %3) : [email protected](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
  25. SettableProperty struct Foo { var bar: Int { get {

    42 } set { } } } // `\Foo.bar` => %2 = keypath $WritableKeyPath<Foo, Int>, ( root $Foo; settable_property $Int, id @$s4main3FooV3barSivg : [email protected](method) (Foo) -> Int, getter @$s4main3FooV3barSivpACTK : [email protected](thin) (@in_guaranteed Foo) -> @out Int, setter @$s4main3FooV3barSivpACTk : [email protected](thin) (@in_guaranteed Int, @inout Foo) -> () ) 25
  26. GettableProperty + GettableProperty struct Foo { struct Bar { var

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

    baz: Int { 42 } } var bar: Bar? { Bar() } } // `\Foo.bar.baz` => %2 = keypath $KeyPath<Foo, Optional<Int>>, ( root $Foo; gettable_property $Optional<Foo.Bar>, id @$s4main3FooV3barAC3BarVSgvg : [email protected](method) (Foo) -> Optional<Foo.Bar>, getter @$s4main3FooV3barAC3BarVSgvpACTK : [email protected](thin) (@in_guaranteed Foo) -> @out Optional<Foo.Bar>; optional_chain : $Foo.Bar; gettable_property $Int, id @$s4main3FooV3BarV3bazSivg : [email protected](method) (Foo.Bar) -> Int, getter @$s4main3FooV3BarV3bazSivpAETK : [email protected](thin) (@in_guaranteed Foo.Bar) -> @out Int; optional_wrap : $Optional<Int> ) 27
  28. LLVM IR্ͷKeyPathදݱ 28

  29. ͔͜͜Βઌɺ͜ͷίʔυΛର৅ʹ࿩ΛਐΊΔɻ struct Foo { subscript(x: Int) -> Int { 0xbadbabe

    } } let foo = Foo() @_optimize(none) func keyPathGetter() { _ = foo[keyPath: \Foo.[0xdeadbeef]] } 29
  30. LLVM IR্ͷදݱ • KeyPath Object (Not ABI) • KeyPathΠϯελϯε͕อ࣋͢Δ৘ใ •

    ͍ͭ΋Ϣʔβ͕औΓճͯ͠Δͷ͸͜Ε • KeyPath Pattern (ABI) • PropertyͷಛੑͱKeyPathͱͯ͠ͷ஋ͷಛੑΛදݱ • ϥϯλΠϜ͕Pattern͔ΒKeyPath ObjectΛੜ੒͢Δ 30
  31. KeyPath Pattern -> KeyPath Object (ΠϯελϯεԽ) ΠϯελϯεԽʹ͸Ҿ਺ΛऔΔ৔߹͕͋Δ • Ҿ਺: IndicesɺGeneric

    Params • Ҿ਺͕ඞཁͳ͍৔߹͸Πϯελϯε͕Ωϟογϡ͞ΕΔ func withIndices(_ x: Int, y: String) -> AnyKeyPath { return \Foo.[x, y] } func withGenericContext<X: Hashable>(_ x: X.Type) -> AnyKeyPath { return \Blah.[x] } 31
  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
  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
  34. KeyPathͷϥϯλΠϜ࣮૷ 34

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

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

    Ωϟογϡʹอଘ 36
  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<AnyKeyPath>.fromOpaque(existingInstance) // TODO: This retain will be unnecessary once we support global objects // with inert refcounting. _ = object.retain() return existingInstance } } else { oncePtr = nil } 37
  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
  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
  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<AnyKeyPath>.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
  41. swift_getAtKeyPath KeyPath objectͱRootͷ஋͔ΒValueΛऔΓग़͢ϥϯλΠϜؔ਺ @_silgen_name("swift_getAtKeyPath") public // COMPILER_INTRINSIC func _getAtKeyPath<Root, Value>(

    root: Root, keyPath: KeyPath<Root, Value> ) -> Value { return keyPath._projectReadOnly(from: root) } 41
  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<CurValue>(_ base: CurValue) -> Value? { func project2<NewValue>(_: 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
  43. internal struct RawKeyPathComponent { ... internal func _projectReadOnly<CurValue, NewValue, LeafValue>(

    _ base: CurValue, to: NewValue.Type, endingWith: LeafValue.Type ) -> ProjectionResult<NewValue, LeafValue> { 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
  44. getterݺͼग़͠ internal typealias Getter<CurValue, NewValue> = @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 : [email protected](thin) (@in_guaranteed Foo, UnsafeRawPointer) -> @out Int 44
  45. ༨ஊ: getterఆٛʹόϑΝαΠζͷύϥϝʔλ͕଍Γͯͳ͍ • ଍Γͯͳ͍ • αΠζ΍Argument Buffer͕ඞཁͳ ͍έʔεͰ͸লུ͞Εͯ͠·͏ • γάωνϟෆҰக͸WasmͰ͸

    Τϥʔ • ৗʹ3ύϥϝʔλు͘Α͏ʹύον ౰ͯͯΔ ఆٛ sil shared [thunk] @$s4main3FooVyS2icipACTK : [email protected](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
  46. KeyPath Argument BufferͷϨΠΞ΢τ ྫ1. subscript(x: Int) -> Int Offset Type

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

    -> Int Offset Type 0 T = Int 8 Int 47
  48. KeyPath Argument BufferͷϨΠΞ΢τ ྫ3. subscript<T>(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
  49. ೋॏαϯΫ໰୊ 49

  50. ೋॏαϯΫ໰୊ key path getter͕δΣωϦοΫύϥϝʔλΛ࣋ͭ৔߹ɺLLVM IRͰ͸Ҿ਺ʹϝλλΠ ϓͱWitness Table͕౉͞ΕΔɻ // key path

    getter for Bar.subscript<A>(_:_:) : <A>BarA sil [thunk] @$s4main3BarVySix_SitcluipSHRzlACxTK : [email protected](thin) <T where T : Hashable> ( @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
  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<A>(_:_:) : <A>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
  52. ೋॏαϯΫ໰୊ • KeyPath getter͸SILϨϕϧͰ࡞ΒΕΔ΍ͭͱɺIRGenͰ࡞ΒΕΔαϯΫ͕͋Δɻ • IRGenϨϕϧͷαϯΫ͸ɺΩϟϓνϟ͞ΕͨδΣωϦοΫύϥϝʔλ͕͋Δͱ ͖ɺ όοϑΝ͔ΒϝλλΠϓΛऔΓग़ͯ͠SILϨϕϧͰ࡞ΒΕͨgetterʹ ϑΥϫʔυ͢Δɻ •

    ݺͼग़͠ن໿ͷ࣮૷͕IRGenͱSILGenʹඈͼࢄͬͯΔ 52
  53. KeyPath଎͍ͨ͘͠ 53

  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
  55. KeyPath଎͍ͨ͘͠ ʢίϯύΠϥͰͰ͖Δ͜ͱʣ ϥϯλΠϜؔ਺ͷݺͼग़͠Λফ͢ʢSILCombiner in SIL Optimizerʣ %kp = keypath $KeyPath<Foo,

    Int>, ( root $Foo; gettable_property $Int, id @$s4main3FooV3barSivg : [email protected](method) (Foo) -> Int, getter @$s4main3FooV3barSivpACTK : [email protected](thin) (@in_guaranteed Foo) -> @out Int ) %fn = function_ref @swift_getAtKeyPath %ret = alloc_stack $Int apply %fn<Foo, Int>(%ret, %base, %kp) ↓ // function_ref key path getter for Foo.bar : Foo %getter = function_ref @$s4main3FooV3barSivpACTK %ret = alloc_stack $Int apply %getter(%ret, %base) : [email protected](thin) (@in_guaranteed Foo) -> @out Int 55
  56. SILCombiner ͷ੍໿ • Argument BufferΛҾ਺ʹऔΔ৔߹͸࠷దԽͰ͖ͳ͍ɻ • Argument BufferͷϝϞϦϨΠΞ΢τ͸IRGenͰ૊·ΕΔͷͰɺ SIL Optimizerͷஈ֊ͰҾ਺Λ૊ΈཱͯΒΕͳ͍ͨΊɻ

    // key path getter for Bar.subscript<A>(_:_:) : <A>BarA sil [thunk] @$s4main3BarVySix_SitcluipSHRzlACxTK : [email protected](thin) <T where T : Hashable> ( @in_guaranteed Bar, UnsafeRawPointer ) -> @out Int 56
  57. KeyPath଎͍ͨ͘͠ ʢϢʔβίʔυͰͰ͖Δ͜ͱʣ • ଎౓͕ඞཁͳΒgetter/setterΛ࢖͏ • Argument Bufferਚ͖ͷKeyPathͳΒͰ͖Δ͚ͩΩϟογϡ͢Δ 57

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