diff --git a/stdlib/public/core/KeyPath.swift b/stdlib/public/core/KeyPath.swift index 12bdb3e579778..9d3ecd54cc24c 100644 --- a/stdlib/public/core/KeyPath.swift +++ b/stdlib/public/core/KeyPath.swift @@ -47,6 +47,9 @@ public class AnyKeyPath: Hashable, _AppendKeyPath { return _rootAndValueType.value } + /// Used to store the offset from the root to the value + /// in the case of a pure struct KeyPath. + /// It's a regular kvcKeyPathStringPtr otherwise. internal final var _kvcKeyPathStringPtr: UnsafePointer? /// The hash value. @@ -118,12 +121,77 @@ public class AnyKeyPath: Hashable, _AppendKeyPath { } } } + + /* + The following pertains to 32-bit architectures only. + We assume everything is a valid pointer to a potential + _kvcKeyPathStringPtr except for the first 4KB page which is reserved + for the nil pointer. Note that we have to distinguish between a valid + keypath offset of 0, and the nil pointer itself. + We use maximumOffsetOn32BitArchitecture + 1 for this case. + + The variable maximumOffsetOn32BitArchitecture is duplicated in the two + functions below since having it as a global would make accesses slower, + given getOffsetFromStorage() gets called on each KeyPath read. Further, + having it as an instance variable in AnyKeyPath would increase the size + of AnyKeyPath by 8 bytes. + TODO: Find a better method of refactoring this variable if possible. + */ + + func assignOffsetToStorage(offset: Int) { + let maximumOffsetOn32BitArchitecture = 4094 + + guard offset >= 0 else { + return + } + // TODO: This just gets the architecture size (32 or 64 bits). + // Is there a more efficient way? Something in Builtin maybe? + let architectureSize = MemoryLayout.size + if architectureSize == 8 { + _kvcKeyPathStringPtr = UnsafePointer(bitPattern: -offset - 1) + } + else { + if offset <= maximumOffsetOn32BitArchitecture { + _kvcKeyPathStringPtr = UnsafePointer(bitPattern: (offset + 1)) + } + else { + _kvcKeyPathStringPtr = nil + } + } + } + + func getOffsetFromStorage() -> Int? { + let maximumOffsetOn32BitArchitecture = 4094 + guard _kvcKeyPathStringPtr != nil else { + return nil + } + + let architectureSize = MemoryLayout.size + if architectureSize == 8 { + let offset = -Int(bitPattern: _kvcKeyPathStringPtr) - 1 + guard offset >= 0 else { + // This happens to be an actual _kvcKeyPathStringPtr, not an offset, if we get here. + return nil + } + return offset + } + else { + let offset = Int(bitPattern: _kvcKeyPathStringPtr) - 1 + if (offset <= maximumOffsetOn32BitArchitecture) { + return offset + } + return nil + } + } // SPI for the Foundation overlay to allow interop with KVC keypath-based // APIs. public var _kvcKeyPathString: String? { @_semantics("keypath.kvcKeyPathString") get { + guard self.getOffsetFromStorage() == nil else { + return nil + } guard let ptr = _kvcKeyPathStringPtr else { return nil } return String(validatingUTF8: ptr) @@ -165,7 +233,7 @@ public class AnyKeyPath: Hashable, _AppendKeyPath { let base = UnsafeRawPointer(Builtin.projectTailElems(self, Int32.self)) return try f(KeyPathBuffer(base: base)) } - + @usableFromInline // Exposed as public API by MemoryLayout.offset(of:) internal var _storedInlineOffset: Int? { return withBuffer { @@ -246,6 +314,16 @@ public class KeyPath: PartialKeyPath { @usableFromInline internal final func _projectReadOnly(from root: Root) -> Value { + + // One performance improvement is to skip right to Value + // if this keypath traverses through structs only. + if let offset = getOffsetFromStorage() { + return withUnsafeBytes(of: root) { + let pointer = $0.baseAddress.unsafelyUnwrapped.advanced(by: offset) + return pointer.assumingMemoryBound(to: Value.self).pointee + } + } + // TODO: For perf, we could use a local growable buffer instead of Any var curBase: Any = root return withBuffer { @@ -302,6 +380,17 @@ public class WritableKeyPath: KeyPath { @usableFromInline internal func _projectMutableAddress(from base: UnsafePointer) -> (pointer: UnsafeMutablePointer, owner: AnyObject?) { + + // One performance improvement is to skip right to Value + // if this keypath traverses through structs only. + + // Don't declare "p" above this if-statement; it may slow things down. + if let offset = getOffsetFromStorage() + { + let p = UnsafeRawPointer(base).advanced(by: offset) + return (pointer: UnsafeMutablePointer( + mutating: p.assumingMemoryBound(to: Value.self)), owner: nil) + } var p = UnsafeRawPointer(base) var type: Any.Type = Root.self var keepAlive: AnyObject? @@ -2252,6 +2341,24 @@ extension _AppendKeyPath /* where Self == ReferenceWritableKeyPath */ { } } +/// Updates information pertaining to the types associated with each KeyPath. +/// +/// Note: Currently we only distinguish between keypaths that traverse +/// only structs to get to the final value, and all other types. +/// This is done for performance reasons. +/// Other type information may be handled in the future to improve performance. +internal func _processOffsetForAppendedKeyPath( + appendedKeyPath: inout AnyKeyPath, + root: AnyKeyPath, + leaf: AnyKeyPath +) { + if let rootOffset = root.getOffsetFromStorage(), + let leafOffset = leaf.getOffsetFromStorage() + { + appendedKeyPath.assignOffsetToStorage(offset: rootOffset + leafOffset) + } +} + @usableFromInline internal func _tryToAppendKeyPaths( root: AnyKeyPath, @@ -2270,7 +2377,10 @@ internal func _tryToAppendKeyPaths( let typedRoot = unsafeDowncast(root, to: KeyPath.self) let typedLeaf = unsafeDowncast(leaf, to: KeyPath.self) - let result = _appendingKeyPaths(root: typedRoot, leaf: typedLeaf) + var result:AnyKeyPath = _appendingKeyPaths(root: typedRoot, + leaf: typedLeaf) + _processOffsetForAppendedKeyPath(appendedKeyPath: &result, + root: root, leaf: leaf) return unsafeDowncast(result, to: Result.self) } return _openExistential(leafValue, do: open3) @@ -2289,7 +2399,7 @@ internal func _appendingKeyPaths< leaf: KeyPath ) -> Result { let resultTy = type(of: root).appendedType(with: type(of: leaf)) - return root.withBuffer { + var returnValue: AnyKeyPath = root.withBuffer { var rootBuffer = $0 return leaf.withBuffer { var leafBuffer = $0 @@ -2307,8 +2417,9 @@ internal func _appendingKeyPaths< // KVC-compatible. let appendedKVCLength: Int, rootKVCLength: Int, leafKVCLength: Int - if let rootPtr = root._kvcKeyPathStringPtr, - let leafPtr = leaf._kvcKeyPathStringPtr { + if root.getOffsetFromStorage() == nil, leaf.getOffsetFromStorage() == nil, + let rootPtr = root._kvcKeyPathStringPtr, + let leafPtr = leaf._kvcKeyPathStringPtr { rootKVCLength = Int(_swift_stdlib_strlen(rootPtr)) leafKVCLength = Int(_swift_stdlib_strlen(leafPtr)) // root + "." + leaf @@ -2404,25 +2515,36 @@ internal func _appendingKeyPaths< } // Build the KVC string if there is one. - if let kvcStringBuffer = kvcStringBuffer { - let rootPtr = root._kvcKeyPathStringPtr.unsafelyUnwrapped - let leafPtr = leaf._kvcKeyPathStringPtr.unsafelyUnwrapped - _memcpy(dest: kvcStringBuffer, - src: rootPtr, - size: UInt(rootKVCLength)) - kvcStringBuffer.advanced(by: rootKVCLength) - .storeBytes(of: 0x2E /* '.' */, as: CChar.self) - _memcpy(dest: kvcStringBuffer.advanced(by: rootKVCLength + 1), - src: leafPtr, - size: UInt(leafKVCLength)) - result._kvcKeyPathStringPtr = - UnsafePointer(kvcStringBuffer.assumingMemoryBound(to: CChar.self)) - kvcStringBuffer.advanced(by: rootKVCLength + leafKVCLength + 1) - .storeBytes(of: 0 /* '\0' */, as: CChar.self) + if root.getOffsetFromStorage() == nil, + leaf.getOffsetFromStorage() == nil { + if let kvcStringBuffer = kvcStringBuffer { + let rootPtr = root._kvcKeyPathStringPtr.unsafelyUnwrapped + let leafPtr = leaf._kvcKeyPathStringPtr.unsafelyUnwrapped + _memcpy( + dest: kvcStringBuffer, + src: rootPtr, + size: UInt(rootKVCLength)) + kvcStringBuffer.advanced(by: rootKVCLength) + .storeBytes(of: 0x2E /* '.' */, as: CChar.self) + _memcpy( + dest: kvcStringBuffer.advanced(by: rootKVCLength + 1), + src: leafPtr, + size: UInt(leafKVCLength)) + result._kvcKeyPathStringPtr = + UnsafePointer(kvcStringBuffer.assumingMemoryBound(to: CChar.self)) + kvcStringBuffer.advanced(by: rootKVCLength + leafKVCLength + 1) + .storeBytes(of: 0 /* '\0' */, as: CChar.self) + } } return unsafeDowncast(result, to: Result.self) } } + _processOffsetForAppendedKeyPath( + appendedKeyPath: &returnValue, + root: root, + leaf: leaf + ) + return returnValue as! Result } // The distance in bytes from the address point of a KeyPath object to its @@ -2505,10 +2627,17 @@ public func _swift_getKeyPath(pattern: UnsafeMutableRawPointer, let (keyPathClass, rootType, size, _) = _getKeyPathClassAndInstanceSizeFromPattern(patternPtr, arguments) + var pureStructOffset: UInt32? = nil + // Allocate the instance. let instance = keyPathClass._create(capacityInBytes: size) { instanceData in // Instantiate the pattern into the instance. - _instantiateKeyPathBuffer(patternPtr, instanceData, rootType, arguments) + pureStructOffset = _instantiateKeyPathBuffer( + patternPtr, + instanceData, + rootType, + arguments + ) } // Adopt the KVC string from the pattern. @@ -2523,7 +2652,9 @@ public func _swift_getKeyPath(pattern: UnsafeMutableRawPointer, instance._kvcKeyPathStringPtr = kvcStringPtr.assumingMemoryBound(to: CChar.self) } - + if instance._kvcKeyPathStringPtr == nil, let offset = pureStructOffset { + instance.assignOffsetToStorage(offset: Int(offset)) + } // If we can cache this instance as a shared instance, do so. if let oncePtr = oncePtr { // Try to replace a null pointer in the cache variable with the instance @@ -3041,6 +3172,8 @@ internal struct GetKeyPathClassAndInstanceSizeFromPattern var leaf: Any.Type! var genericEnvironment: UnsafeRawPointer? let patternArgs: UnsafeRawPointer? + var structOffset: UInt32 = 0 + var isPureStruct: [Bool] = [] init(patternArgs: UnsafeRawPointer?) { self.patternArgs = patternArgs @@ -3248,6 +3381,8 @@ internal struct InstantiateKeyPathBuffer: KeyPathPatternVisitor { var genericEnvironment: UnsafeRawPointer? let patternArgs: UnsafeRawPointer? var base: Any.Type + var structOffset: UInt32 = 0 + var isPureStruct: [Bool] = [] init(destData: UnsafeMutableRawBufferPointer, patternArgs: UnsafeRawPointer?, @@ -3320,6 +3455,12 @@ internal struct InstantiateKeyPathBuffer: KeyPathPatternVisitor { mutable: Bool, offset: KeyPathPatternStoredOffset) { let previous = updatePreviousComponentAddr() + switch kind { + case .struct: + isPureStruct.append(true) + default: + isPureStruct.append(false) + } switch kind { case .class: // A mutable class property can end the reference prefix. @@ -3336,6 +3477,12 @@ internal struct InstantiateKeyPathBuffer: KeyPathPatternVisitor { mutable: mutable, inlineOffset: value) pushDest(header) + switch kind { + case .struct: + structOffset += value + default: + break + } case .outOfLine(let offset): let header = RawKeyPathComponent.Header(storedWithOutOfLineOffset: kind, mutable: mutable) @@ -3353,6 +3500,7 @@ internal struct InstantiateKeyPathBuffer: KeyPathPatternVisitor { case .struct: offset = UInt32(metadataPtr.load(fromByteOffset: Int(offsetOfOffset), as: UInt32.self)) + structOffset += offset } let header = RawKeyPathComponent.Header(storedWithOutOfLineOffset: kind, @@ -3381,6 +3529,7 @@ internal struct InstantiateKeyPathBuffer: KeyPathPatternVisitor { setter: UnsafeRawPointer?, arguments: KeyPathPatternComputedArguments?, externalArgs: UnsafeBufferPointer?) { + isPureStruct.append(false) let previous = updatePreviousComponentAddr() let settable = setter != nil // A nonmutating settable property can end the reference prefix. @@ -3520,16 +3669,19 @@ internal struct InstantiateKeyPathBuffer: KeyPathPatternVisitor { } mutating func visitOptionalChainComponent() { + isPureStruct.append(false) let _ = updatePreviousComponentAddr() let header = RawKeyPathComponent.Header(optionalChain: ()) pushDest(header) } mutating func visitOptionalWrapComponent() { + isPureStruct.append(false) let _ = updatePreviousComponentAddr() let header = RawKeyPathComponent.Header(optionalWrap: ()) pushDest(header) } mutating func visitOptionalForceComponent() { + isPureStruct.append(false) let _ = updatePreviousComponentAddr() let header = RawKeyPathComponent.Header(optionalForce: ()) pushDest(header) @@ -3560,6 +3712,8 @@ internal struct ValidatingInstantiateKeyPathBuffer: KeyPathPatternVisitor { var sizeVisitor: GetKeyPathClassAndInstanceSizeFromPattern var instantiateVisitor: InstantiateKeyPathBuffer let origDest: UnsafeMutableRawPointer + var structOffset: UInt32 = 0 + var isPureStruct: [Bool] = [] init(sizeVisitor: GetKeyPathClassAndInstanceSizeFromPattern, instantiateVisitor: InstantiateKeyPathBuffer) { @@ -3589,6 +3743,8 @@ internal struct ValidatingInstantiateKeyPathBuffer: KeyPathPatternVisitor { instantiateVisitor.visitStoredComponent(kind: kind, mutable: mutable, offset: offset) checkSizeConsistency() + structOffset = instantiateVisitor.structOffset + isPureStruct.append(contentsOf: instantiateVisitor.isPureStruct) } mutating func visitComputedComponent(mutating: Bool, idKind: KeyPathComputedIDKind, @@ -3617,32 +3773,41 @@ internal struct ValidatingInstantiateKeyPathBuffer: KeyPathPatternVisitor { setter: setter, arguments: arguments, externalArgs: externalArgs) + // Note: For this function and the ones below, modification of structOffset + // is omitted since these types of KeyPaths won't have a pureStruct + // offset anyway. + isPureStruct.append(contentsOf: instantiateVisitor.isPureStruct) checkSizeConsistency() } mutating func visitOptionalChainComponent() { sizeVisitor.visitOptionalChainComponent() instantiateVisitor.visitOptionalChainComponent() + isPureStruct.append(contentsOf: instantiateVisitor.isPureStruct) checkSizeConsistency() } mutating func visitOptionalWrapComponent() { sizeVisitor.visitOptionalWrapComponent() instantiateVisitor.visitOptionalWrapComponent() + isPureStruct.append(contentsOf: instantiateVisitor.isPureStruct) checkSizeConsistency() } mutating func visitOptionalForceComponent() { sizeVisitor.visitOptionalForceComponent() instantiateVisitor.visitOptionalForceComponent() + isPureStruct.append(contentsOf: instantiateVisitor.isPureStruct) checkSizeConsistency() } mutating func visitIntermediateComponentType(metadataRef: MetadataReference) { sizeVisitor.visitIntermediateComponentType(metadataRef: metadataRef) instantiateVisitor.visitIntermediateComponentType(metadataRef: metadataRef) + isPureStruct.append(contentsOf: instantiateVisitor.isPureStruct) checkSizeConsistency() } mutating func finish() { sizeVisitor.finish() instantiateVisitor.finish() + isPureStruct.append(contentsOf: instantiateVisitor.isPureStruct) checkSizeConsistency() } @@ -3661,7 +3826,7 @@ internal func _instantiateKeyPathBuffer( _ origDestData: UnsafeMutableRawBufferPointer, _ rootType: Any.Type, _ arguments: UnsafeRawPointer -) { +) -> UInt32? { let destHeaderPtr = origDestData.baseAddress.unsafelyUnwrapped var destData = UnsafeMutableRawBufferPointer( start: destHeaderPtr.advanced(by: MemoryLayout.size), @@ -3713,6 +3878,17 @@ internal func _instantiateKeyPathBuffer( endOfReferencePrefixComponent.storeBytes(of: componentHeader, as: RawKeyPathComponent.Header.self) } + var isPureStruct = true + var offset: UInt32? = nil + + for value in walker.isPureStruct { + isPureStruct = isPureStruct && value + } + + if isPureStruct { + offset = walker.structOffset + } + return offset } #if SWIFT_ENABLE_REFLECTION