@@ -35,6 +35,8 @@ internal func _abstract(
35
35
/// type.
36
36
@_objcRuntimeName ( _TtCs11_AnyKeyPath)
37
37
public class AnyKeyPath : Hashable , _AppendKeyPath {
38
+ internal var _isPureStructKeyPath : Bool ?
39
+ internal var _pureStructValueOffset : Int = 0
38
40
/// The root type for this key path.
39
41
@inlinable
40
42
public static var rootType : Any . Type {
@@ -150,47 +152,110 @@ public class AnyKeyPath: Hashable, _AppendKeyPath {
150
152
) -> Self {
151
153
_internalInvariant ( bytes > 0 && bytes % 4 == 0 ,
152
154
" capacity must be multiple of 4 bytes " )
153
- let result = Builtin . allocWithTailElems_1 ( self , ( bytes/ 4 ) . _builtinWordValue,
155
+ let keypath = Builtin . allocWithTailElems_1 ( self , ( bytes/ 4 ) . _builtinWordValue,
154
156
Int32 . self)
155
- result . _kvcKeyPathStringPtr = nil
156
- let base = UnsafeMutableRawPointer ( Builtin . projectTailElems ( result ,
157
+ keypath . _kvcKeyPathStringPtr = nil
158
+ let base = UnsafeMutableRawPointer ( Builtin . projectTailElems ( keypath ,
157
159
Int32 . self) )
158
160
body ( UnsafeMutableRawBufferPointer ( start: base, count: bytes) )
159
- return result
161
+ keypath. _computeOffsetForPureStructKeypath ( )
162
+ return keypath
160
163
}
161
-
162
164
final internal func withBuffer< T> ( _ f: ( KeyPathBuffer ) throws -> T ) rethrows -> T {
163
165
defer { _fixLifetime ( self ) }
164
166
165
167
let base = UnsafeRawPointer ( Builtin . projectTailElems ( self , Int32 . self) )
166
168
return try f ( KeyPathBuffer ( base: base) )
167
169
}
168
170
169
- @usableFromInline // Exposed as public API by MemoryLayout<Root>.offset(of:)
170
- internal var _storedInlineOffset : Int ? {
171
- return withBuffer {
172
- var buffer = $0
171
+ public var isPureStructKeyPath : Bool {
172
+ return _isPureStructKeyPath ?? false
173
+ }
173
174
174
- // The identity key path is effectively a stored keypath of type Self
175
- // at offset zero
176
- if buffer. data. isEmpty { return 0 }
175
+ internal func isClass( _ item: Any . Type ) -> Bool {
176
+ // Displays "warning: 'is' test is always true" at compile time, but that's not actually the case.
177
+ return ( item is AnyObject )
178
+ }
177
179
178
- var offset = 0
179
- while true {
180
- let ( rawComponent, optNextType) = buffer. next ( )
181
- switch rawComponent. header. kind {
182
- case . struct:
183
- offset += rawComponent. _structOrClassOffset
180
+ // TODO: Find a quicker way to see if this is a tuple.
181
+ internal func isTuple( _ item: Any ) -> Bool {
182
+ // Unwraps type information if possible.
183
+ // Otherwise, everything the Mirror sees would be "Optional<Any.Type>".
184
+ func unwrapType< T> ( _ any: T ) -> Any {
185
+ let mirror = Mirror ( reflecting: any)
186
+ guard mirror. displayStyle == . optional, let first = mirror. children. first else {
187
+ return any
188
+ }
189
+ return first. value
190
+ }
184
191
185
- case . class, . computed, . optionalChain, . optionalForce, . optionalWrap, . external:
186
- return . none
192
+ let mirror = Mirror ( reflecting: unwrapType ( item) )
193
+ let description = mirror. description
194
+ let offsetOfFirstBracketForMirrorDescriptionOfTuple = 11
195
+ let idx = description. index ( description. startIndex, offsetBy: offsetOfFirstBracketForMirrorDescriptionOfTuple)
196
+ if description [ idx] == " ( " {
197
+ return true
198
+ }
199
+ return false
200
+ }
201
+
202
+ // If this keypath traverses structs only, it'll have a predictable memory layout.
203
+ // We can then jump to the value directly in _projectReadOnly().
204
+ internal func _computeOffsets( ) -> ( offset: Int , isPureStruct: Bool , isTuple: Bool ) {
205
+ _pureStructValueOffset = 0
206
+ var isPureStruct = true
207
+ var _isTuple = false
208
+ defer {
209
+ _isPureStructKeyPath = isPureStruct
210
+ }
211
+ withBuffer {
212
+ var buffer = $0
213
+ if buffer. data. isEmpty {
214
+ if isClass ( Self . _rootAndValueType. root) {
215
+ isPureStruct = false
216
+ }
217
+ } else {
218
+ while true {
219
+ let ( rawComponent, optNextType) = buffer. next ( )
220
+ if isTuple ( optNextType as Any ) {
221
+ isPureStruct = false
222
+ _isTuple = true
223
+ }
224
+ switch rawComponent. header. kind {
225
+ case . struct:
226
+ _pureStructValueOffset += rawComponent. _structOrClassOffset
227
+ case . class, . computed, . optionalChain, . optionalForce, . optionalWrap, . external:
228
+ isPureStruct = false
229
+ }
230
+ if optNextType == nil {
231
+ break
232
+ }
233
+ }
187
234
}
235
+ }
236
+ return ( _pureStructValueOffset, isPureStruct, _isTuple)
237
+ }
238
+
239
+ internal func _computeOffsetForPureStructKeypath( ) {
240
+ _ = _computeOffsets ( )
241
+ }
188
242
189
- if optNextType == nil { return . some( offset) }
243
+ // This function was refactored since _computeOffsets() was performing
244
+ // essentially the same computation.
245
+ @usableFromInline // Exposed as public API by MemoryLayout<Root>.offset(of:)
246
+ internal var _storedInlineOffset : Int ? {
247
+ // TODO: Cache this value in a similar manner to _pureStructValueOffset.
248
+ // The current design assumes keypath read and write operations will be called
249
+ // much more often than MemoryLayout<Root>.offset(of:).
250
+ let offsetInformation = _computeOffsets ( )
251
+ if offsetInformation. isPureStruct || offsetInformation. isTuple {
252
+ return . some( offsetInformation. offset)
253
+ } else {
254
+ return . none
190
255
}
191
256
}
192
257
}
193
- }
258
+
194
259
195
260
/// A partially type-erased key path, from a concrete root type to any
196
261
/// resulting value type.
@@ -246,6 +311,17 @@ public class KeyPath<Root, Value>: PartialKeyPath<Root> {
246
311
247
312
@usableFromInline
248
313
internal final func _projectReadOnly( from root: Root ) -> Value {
314
+
315
+ //One performance improvement is to skip right to Value
316
+ //if this keypath traverses through structs only.
317
+ if isPureStructKeyPath
318
+ {
319
+ return withUnsafeBytes ( of: root) {
320
+ let pointer = $0. baseAddress. unsafelyUnwrapped. advanced ( by: _pureStructValueOffset)
321
+ return pointer. assumingMemoryBound ( to: Value . self) . pointee
322
+ }
323
+ }
324
+
249
325
// TODO: For perf, we could use a local growable buffer instead of Any
250
326
var curBase : Any = root
251
327
return withBuffer {
@@ -302,6 +378,14 @@ public class WritableKeyPath<Root, Value>: KeyPath<Root, Value> {
302
378
@usableFromInline
303
379
internal func _projectMutableAddress( from base: UnsafePointer < Root > )
304
380
-> ( pointer: UnsafeMutablePointer < Value > , owner: AnyObject ? ) {
381
+
382
+ // Don't declare "p" above this if-statement; it may slow things down.
383
+ if isPureStructKeyPath
384
+ {
385
+ let p = UnsafeRawPointer ( base) . advanced ( by: _pureStructValueOffset)
386
+ return ( pointer: UnsafeMutablePointer (
387
+ mutating: p. assumingMemoryBound ( to: Value . self) ) , owner: nil )
388
+ }
305
389
var p = UnsafeRawPointer ( base)
306
390
var type : Any . Type = Root . self
307
391
var keepAlive : AnyObject ?
@@ -2252,6 +2336,19 @@ extension _AppendKeyPath /* where Self == ReferenceWritableKeyPath<T,U> */ {
2252
2336
}
2253
2337
}
2254
2338
2339
+ /// Updates information pertaining to the types associated with each key path.
2340
+ ///
2341
+ /// Note: Currently we only distinguish between keypaths that traverse only structs to get to the final value,
2342
+ /// and all other types. This is done for performance reasons.
2343
+ /// Other type information may be handled in the future to improve performance.
2344
+ internal func _processAppendingKeyPathType( root: inout AnyKeyPath , leaf: AnyKeyPath ) {
2345
+ root. _isPureStructKeyPath = root. isPureStructKeyPath && leaf. isPureStructKeyPath
2346
+ if let isPureStruct = root. _isPureStructKeyPath, isPureStruct {
2347
+ root. _computeOffsetForPureStructKeypath ( )
2348
+ }
2349
+ }
2350
+
2351
+
2255
2352
@usableFromInline
2256
2353
internal func _tryToAppendKeyPaths< Result: AnyKeyPath > (
2257
2354
root: AnyKeyPath ,
@@ -2277,7 +2374,13 @@ internal func _tryToAppendKeyPaths<Result: AnyKeyPath>(
2277
2374
}
2278
2375
return _openExistential ( rootValue, do: open2)
2279
2376
}
2280
- return _openExistential ( rootRoot, do: open)
2377
+ var returnValue : AnyKeyPath = _openExistential ( rootRoot, do: open)
2378
+ _processAppendingKeyPathType ( root: & returnValue, leaf: leaf)
2379
+ if let returnValue = returnValue as? Result
2380
+ {
2381
+ return returnValue
2382
+ }
2383
+ return nil
2281
2384
}
2282
2385
2283
2386
@usableFromInline
@@ -2289,7 +2392,7 @@ internal func _appendingKeyPaths<
2289
2392
leaf: KeyPath < Value , AppendedValue >
2290
2393
) -> Result {
2291
2394
let resultTy = type ( of: root) . appendedType ( with: type ( of: leaf) )
2292
- return root. withBuffer {
2395
+ var returnValue : AnyKeyPath = root. withBuffer {
2293
2396
var rootBuffer = $0
2294
2397
return leaf. withBuffer {
2295
2398
var leafBuffer = $0
@@ -2423,6 +2526,8 @@ internal func _appendingKeyPaths<
2423
2526
return unsafeDowncast ( result, to: Result . self)
2424
2527
}
2425
2528
}
2529
+ _processAppendingKeyPathType ( root: & returnValue, leaf: leaf)
2530
+ return returnValue as! Result
2426
2531
}
2427
2532
2428
2533
// The distance in bytes from the address point of a KeyPath object to its
0 commit comments