Share
Embed
Swift KeyPath Internals
https://iosdiscord.connpass.com/event/260804/
YouTube: https://www.youtube.com/watch?v=gFsvraver4U
Gist: https://gist.github.com/kateinoigakukun/090c2af523805975edaf2482d736c47b
Yuta Saito
October 04, 2022
Transcript
58 slides
-
ۙگ • ݚڀ: αΠζ࠷దԽؤுͬͯΔ • ϚΠΫϩϕϯνͰ͔͠ޮ͔ͳ͍࠷దԽ • Ruby: VenturaͰಈ͘Α͏ʹؤுͬͨ •
SwiftWasm: ϓϩμΫγϣϯࣄྫग़͖ͯͯ͏Ε͍͠ 2 -
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 -
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 -
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 -
҉ʹ 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 -
͔ͳΓ୯७Խͨ͠KeyPathͷ࣮ let kp0 = \Foo.bar // -> let kp0 =
KeyPath(getter: { $0.bar }) let kp1 = \Foo.[42] // -> let kp1 = KeyPath(getter: { $0[42] }) ࣮ࡍͬͱෳࡶ 10 -
KeyPath࣮ͷา͖ํʢίϯύΠϥ෦ʣ ࣮ߦॱɻҙຯղੳ·Ͱলུ File Description lib/SILGen/SILGenExpr.cpp KeyPathInst SIL໋ྩͱΞΫηαΛੜ lib/SILOptimizer/SILCombiner/ SILCombinerApplyVisitors.cpp getter/setterΞΫηε࠷దԽ
lib/IRGen/GenKeyPath.cpp ϥϯλΠϜ͕ಡΉͨΊͷKeyPathσʔλͱα ϯΫΛੜ 11 -
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<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 : $@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<Foo, Int>(%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<Foo, Int> // id: %10 %11 = tuple () // user: %12 return %11 : $() // id: %12 } // end sil function '$s4main13keyPathGetteryyF' 16 -
KeyPathInstͷߏ keypath /* ಘΒΕΔKeyPathܕ */, ( root /* ϧʔτͷܕ */;
/* ύεͷཁૉ1 ʢKeyPathPatternComponentʣ */ /* ύεͷཁૉ2 ʢKeyPathPatternComponentʣ */ /* ύεͷཁૉN ʢKeyPathPatternComponentʣ */ ) 17 -
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 -
StoredProperty struct Foo { let bar: Int } // `\Foo.bar`
=> %2 = keypath $KeyPath<Foo, Int>, ( root $Foo; stored_property #Foo.bar : $Int ) 19 -
StoredProperty struct Foo { var bar: Int } // `\Foo.bar`
=> %2 = keypath $WritableKeyPath<Foo, Int>, ( root $Foo; stored_property #Foo.bar : $Int ) 20 -
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 -
GettableProperty struct Foo { var bar: Int { 42 }
} // `\Foo.bar` => %2 = keypath $KeyPath<Foo, Int>, ( root $Foo; gettable_property $Int, id @$s4main3FooV3barSivg : $@convention(method) (Foo) -> Int, getter @$s4main3FooV3barSivpACTK : $@convention(thin) (@in_guaranteed Foo) -> @out Int ) 22 -
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 : $@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 -
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 -
SettableProperty struct Foo { var bar: Int { get {
42 } set { } } } // `\Foo.bar` => %2 = keypath $WritableKeyPath<Foo, Int>, ( 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 -
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 : $@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 -
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 : $@convention(method) (Foo) -> Optional<Foo.Bar>, getter @$s4main3FooV3barAC3BarVSgvpACTK : $@convention(thin) (@in_guaranteed Foo) -> @out Optional<Foo.Bar>; 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<Int> ) 27 -
͔͜͜Βઌɺ͜ͷίʔυΛରʹΛਐΊΔɻ struct Foo { subscript(x: Int) -> Int { 0xbadbabe
} } let foo = Foo() @_optimize(none) func keyPathGetter() { _ = foo[keyPath: \Foo.[0xdeadbeef]] } 29 -
LLVM IR্ͷදݱ • KeyPath Object (Not ABI) • KeyPathΠϯελϯε͕อ࣋͢Δใ •
͍ͭϢʔβ͕औΓճͯ͠Δͷ͜Ε • KeyPath Pattern (ABI) • PropertyͷಛੑͱKeyPathͱͯ͠ͷͷಛੑΛදݱ • ϥϯλΠϜ͕Pattern͔ΒKeyPath ObjectΛੜ͢Δ 30 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
• ίϯϙʔωϯτ͝ͱʹ۩ମܕ ΛऔΓग़ͯ͠Өʢ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 -
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 -
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 : $@convention(thin) (@in_guaranteed Foo, UnsafeRawPointer) -> @out Int 44 -
༨ஊ: 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 -
KeyPath Argument BufferͷϨΠΞτ ྫ2. subscript<T = Int>(x: T, y: Int)
-> Int Offset Type 0 T = Int 8 Int 47 -
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 -
ೋॏαϯΫ key path getter͕δΣωϦοΫύϥϝʔλΛ࣋ͭ߹ɺLLVM IRͰҾʹϝλλΠ ϓͱWitness Table͕͞ΕΔɻ // key path
getter for Bar.subscript<A>(_:_:) : <A>BarA sil [thunk] @$s4main3BarVySix_SitcluipSHRzlACxTK : $@convention(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 -
ೋॏαϯΫ ϥϯλΠϜ୯ҰͷγάωνϟͰ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 -
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 -
KeyPath͍ͨ͘͠ ʢίϯύΠϥͰͰ͖Δ͜ͱʣ ϥϯλΠϜؔͷݺͼग़͠Λফ͢ʢSILCombiner in SIL Optimizerʣ %kp = keypath $KeyPath<Foo,
Int>, ( 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<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) : $@convention(thin) (@in_guaranteed Foo) -> @out Int 55 -
SILCombiner ͷ੍ • Argument BufferΛҾʹऔΔ߹࠷దԽͰ͖ͳ͍ɻ • Argument BufferͷϝϞϦϨΠΞτIRGenͰ·ΕΔͷͰɺ SIL Optimizerͷஈ֊ͰҾΛΈཱͯΒΕͳ͍ͨΊɻ
// key path getter for Bar.subscript<A>(_:_:) : <A>BarA sil [thunk] @$s4main3BarVySix_SitcluipSHRzlACxTK : $@convention(thin) <T where T : Hashable> ( @in_guaranteed Bar, UnsafeRawPointer ) -> @out Int 56