Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

KeyPathͷಛ௃ 3

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

҉໧ʹ 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

Slide 9

Slide 9 text

KeyPath࣮૷ͷ֓ཁ 9

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

KeyPathͷ࣮૷ৄࡉ 13

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

SIL্ͷKeyPathදݱ 15

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

LLVM IR্ͷKeyPathදݱ 28

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

KeyPathͷϥϯλΠϜ࣮૷ 34

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

• ίϯϙʔωϯτ͝ͱʹ۩ମܕ ΛऔΓग़ͯ͠౤Өʢ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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

༨ஊ: 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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

ೋॏαϯΫ໰୊ 49

Slide 51

Slide 51 text

ೋॏαϯΫ໰୊ ϥϯλΠϜ͸୯ҰͷγάωνϟͰ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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

KeyPath଎͍ͨ͘͠ 53

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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