diff --git a/stdlib/public/core/CMakeLists.txt b/stdlib/public/core/CMakeLists.txt index 9d99ec71a49a2..ce2cc3f36a0d5 100644 --- a/stdlib/public/core/CMakeLists.txt +++ b/stdlib/public/core/CMakeLists.txt @@ -52,6 +52,11 @@ set(SWIFTLIB_ESSENTIAL CTypes.swift DebuggerSupport.swift Dictionary.swift + DictionaryBridging.swift + DictionaryBuilder.swift + DictionaryCasting.swift + DictionaryStorage.swift + DictionaryVariant.swift DropWhile.swift Dump.swift EmptyCollection.swift @@ -69,7 +74,6 @@ set(SWIFTLIB_ESSENTIAL # if we do so, the compiler crashes. AnyHashable.swift # END WORKAROUND - HashedCollectionsAnyHashableExtensions.swift Hasher.swift Hashing.swift HashTable.swift @@ -92,6 +96,8 @@ set(SWIFTLIB_ESSENTIAL Mirrors.swift.gyb Misc.swift MutableCollection.swift + NativeDictionary.swift + NativeSet.swift NewtypeWrapper.swift NormalizedCodeUnitIterator.swift ObjectIdentifier.swift @@ -118,6 +124,12 @@ set(SWIFTLIB_ESSENTIAL SequenceWrapper.swift Set.swift SetAlgebra.swift + SetAnyHashableExtensions.swift + SetBridging.swift + SetBuilder.swift + SetCasting.swift + SetStorage.swift + SetVariant.swift ShadowProtocols.swift Shims.swift Slice.swift diff --git a/stdlib/public/core/Dictionary.swift b/stdlib/public/core/Dictionary.swift index ee01ad01e6531..c2caa525eef83 100644 --- a/stdlib/public/core/Dictionary.swift +++ b/stdlib/public/core/Dictionary.swift @@ -10,8 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftShims - // Implementation notes // ==================== // @@ -1643,2538 +1641,241 @@ internal enum _MergeError: Error { case keyCollision } +extension Dictionary { + /// The position of a key-value pair in a dictionary. + /// + /// Dictionary has two subscripting interfaces: + /// + /// 1. Subscripting with a key, yielding an optional value: + /// + /// v = d[k]! + /// + /// 2. Subscripting with an index, yielding a key-value pair: + /// + /// (k, v) = d[i] + @_fixed_layout + public struct Index { + // Index for native dictionary is efficient. Index for bridged NSDictionary + // is not, because neither NSEnumerator nor fast enumeration support moving + // backwards. Even if they did, there is another issue: NSEnumerator does + // not support NSCopying, and fast enumeration does not document that it is + // safe to copy the state. So, we cannot implement Index that is a value + // type for bridged NSDictionary in terms of Cocoa enumeration facilities. + + @_frozen + @usableFromInline + internal enum _Variant { + case native(_NativeDictionary.Index) #if _runtime(_ObjC) -/// Equivalent to `NSDictionary.allKeys`, but does not leave objects on the -/// autorelease pool. -@inlinable -internal func _stdlib_NSDictionary_allKeys( - _ nsd: _NSDictionary -) -> _HeapBuffer { - let count = nsd.count - let storage = _HeapBuffer( - _HeapBufferStorage.self, count, count) - - nsd.getObjects(nil, andKeys: storage.baseAddress, count: count) - return storage -} + case cocoa(_CocoaDictionary.Index) #endif + } -//===--- Compiler conversion/casting entry points for Dictionary ----===// - -/// Perform a non-bridged upcast that always succeeds. -/// -/// - Precondition: `BaseKey` and `BaseValue` are base classes or base `@objc` -/// protocols (such as `AnyObject`) of `DerivedKey` and `DerivedValue`, -/// respectively. -@inlinable -public func _dictionaryUpCast( - _ source: Dictionary -) -> Dictionary { - var result = Dictionary(minimumCapacity: source.count) - - for (k, v) in source { - result[k as! BaseKey] = (v as! BaseValue) - } - return result -} + @usableFromInline + internal var _variant: _Variant -/// Called by the casting machinery. -@_silgen_name("_swift_dictionaryDownCastIndirect") -internal func _dictionaryDownCastIndirect( - _ source: UnsafePointer>, - _ target: UnsafeMutablePointer>) { - target.initialize(to: _dictionaryDownCast(source.pointee)) -} + @inlinable + @inline(__always) + internal init(_variant: _Variant) { + self._variant = _variant + } -/// Implements a forced downcast. This operation should have O(1) complexity. -/// -/// The cast can fail if bridging fails. The actual checks and bridging can be -/// deferred. -/// -/// - Precondition: `DerivedKey` is a subtype of `BaseKey`, `DerivedValue` is -/// a subtype of `BaseValue`, and all of these types are reference types. -@inlinable -public func _dictionaryDownCast( - _ source: Dictionary -) -> Dictionary { + @inlinable + @inline(__always) + internal init(_native index: _NativeDictionary.Index) { + self.init(_variant: .native(index)) + } #if _runtime(_ObjC) - if _isClassOrObjCExistential(BaseKey.self) - && _isClassOrObjCExistential(BaseValue.self) - && _isClassOrObjCExistential(DerivedKey.self) - && _isClassOrObjCExistential(DerivedValue.self) { - - switch source._variant { - case .native(let native): - // Note: it is safe to treat the buffer as immutable here because - // Dictionary will not mutate buffer with reference count greater than 1. - return Dictionary(_immutableCocoaDictionary: native.bridged()) - case .cocoa(let cocoa): - return Dictionary(_immutableCocoaDictionary: cocoa.object) + @inlinable + @inline(__always) + internal init(_cocoa index: _CocoaDictionary.Index) { + self.init(_variant: .cocoa(index)) } - } #endif - return _dictionaryDownCastConditional(source)! -} - -/// Called by the casting machinery. -@_silgen_name("_swift_dictionaryDownCastConditionalIndirect") -internal func _dictionaryDownCastConditionalIndirect( - _ source: UnsafePointer>, - _ target: UnsafeMutablePointer> -) -> Bool { - if let result: Dictionary - = _dictionaryDownCastConditional(source.pointee) { - target.initialize(to: result) - return true } - return false } -/// Implements a conditional downcast. -/// -/// If the cast fails, the function returns `nil`. All checks should be -/// performed eagerly. -/// -/// - Precondition: `DerivedKey` is a subtype of `BaseKey`, `DerivedValue` is -/// a subtype of `BaseValue`, and all of these types are reference types. -@inlinable -public func _dictionaryDownCastConditional< - BaseKey, BaseValue, DerivedKey, DerivedValue ->( - _ source: Dictionary -) -> Dictionary? { - - var result = Dictionary() - for (k, v) in source { - guard let k1 = k as? DerivedKey, let v1 = v as? DerivedValue - else { return nil } - result[k1] = v1 +extension Dictionary.Index { +#if _runtime(_ObjC) + @usableFromInline @_transparent + internal var _guaranteedNative: Bool { + return _canBeClass(Key.self) == 0 || _canBeClass(Value.self) == 0 } - return result -} -/// This protocol is only used for compile-time checks that -/// every buffer type implements all required operations. -internal protocol _DictionaryBuffer { - associatedtype Key - associatedtype Value - associatedtype Index - - var startIndex: Index { get } - var endIndex: Index { get } - func index(after i: Index) -> Index - func index(forKey key: Key) -> Index? - var count: Int { get } - - func contains(_ key: Key) -> Bool - func lookup(_ key: Key) -> Value? - func lookup(_ index: Index) -> (key: Key, value: Value) - func key(at index: Index) -> Key - func value(at index: Index) -> Value -} - -/// An instance of this class has all `Dictionary` data tail-allocated. -/// Enough bytes are allocated to hold the bitmap for marking valid entries, -/// keys, and values. The data layout starts with the bitmap, followed by the -/// keys, followed by the values. -// -// See the docs at the top of the file for more details on this type -// -// NOTE: The precise layout of this type is relied on in the runtime -// to provide a statically allocated empty singleton. -// See stdlib/public/stubs/GlobalObjects.cpp for details. -@_fixed_layout // FIXME(sil-serialize-all) -@usableFromInline -@_objc_non_lazy_realization -internal class _RawDictionaryStorage: _SwiftNativeNSDictionary { - /// The current number of occupied entries in this dictionary. - @usableFromInline - @nonobjc - internal final var _count: Int - - /// The maximum number of elements that can be inserted into this set without - /// exceeding the hash table's maximum load factor. - @usableFromInline - @nonobjc - internal final var _capacity: Int - - /// The scale of this dictionary. The number of buckets is 2 raised to the - /// power of `scale`. - @usableFromInline - @nonobjc - internal final var _scale: Int - - @usableFromInline - internal final var _seed: Hasher._Seed + // Allow the optimizer to consider the surrounding code unreachable if Element + // is guaranteed to be native. + @usableFromInline @_transparent + internal func _cocoaPath() { + if _guaranteedNative { + _conditionallyUnreachable() + } + } +#endif - @usableFromInline - @nonobjc - internal final var _rawKeys: UnsafeMutableRawPointer + @usableFromInline @_transparent + internal var _asNative: _NativeDictionary.Index { + switch _variant { + case .native(let nativeIndex): + return nativeIndex +#if _runtime(_ObjC) + case .cocoa: + _sanityCheckFailure("internal error: does not contain a native index") +#endif + } + } - @usableFromInline - @nonobjc - internal final var _rawValues: UnsafeMutableRawPointer - - // This type is made with allocWithTailElems, so no init is ever called. - // But we still need to have an init to satisfy the compiler. - @nonobjc - internal init(_doNotCallMe: ()) { - _sanityCheckFailure("This class cannot be directly initialized") +#if _runtime(_ObjC) + @usableFromInline @_transparent + internal var _asCocoa: _CocoaDictionary.Index { + switch _variant { + case .native: + _sanityCheckFailure("internal error: does not contain a Cocoa index") + case .cocoa(let cocoaIndex): + return cocoaIndex + } } +#endif +} +extension Dictionary.Index: Equatable { @inlinable - @nonobjc - internal final var _bucketCount: Int { - @inline(__always) get { return 1 &<< _scale } + public static func == ( + lhs: Dictionary.Index, + rhs: Dictionary.Index + ) -> Bool { + switch (lhs._variant, rhs._variant) { + case (.native(let lhsNative), .native(let rhsNative)): + return lhsNative == rhsNative + #if _runtime(_ObjC) + case (.cocoa(let lhsCocoa), .cocoa(let rhsCocoa)): + lhs._cocoaPath() + return lhsCocoa == rhsCocoa + default: + _preconditionFailure("Comparing indexes from different dictionaries") + #endif + } } +} +extension Dictionary.Index: Comparable { @inlinable - @nonobjc - internal final var _metadata: UnsafeMutablePointer<_HashTable.Word> { - @inline(__always) get { - let address = Builtin.projectTailElems(self, _HashTable.Word.self) - return UnsafeMutablePointer(address) + public static func < ( + lhs: Dictionary.Index, + rhs: Dictionary.Index + ) -> Bool { + switch (lhs._variant, rhs._variant) { + case (.native(let lhsNative), .native(let rhsNative)): + return lhsNative < rhsNative + #if _runtime(_ObjC) + case (.cocoa(let lhsCocoa), .cocoa(let rhsCocoa)): + lhs._cocoaPath() + return lhsCocoa < rhsCocoa + default: + _preconditionFailure("Comparing indexes from different dictionaries") + #endif } } +} - // The _HashTable struct contains pointers into tail-allocated storage, so - // this is unsafe and needs `_fixLifetime` calls in the caller. +extension Dictionary.Index: Hashable { @inlinable - @nonobjc - internal final var _hashTable: _HashTable { - @inline(__always) get { - return _HashTable(words: _metadata, bucketCount: _bucketCount) + public func hash(into hasher: inout Hasher) { + #if _runtime(_ObjC) + switch _variant { + case .native(let nativeIndex): + hasher.combine(0 as UInt8) + hasher.combine(nativeIndex.bucket) + case .cocoa(let cocoaIndex): + _cocoaPath() + hasher.combine(1 as UInt8) + hasher.combine(cocoaIndex.currentKeyIndex) } + #else + hasher.combine(_asNative.bucket) + #endif } } -/// The storage class for the singleton empty set. -/// The single instance of this class is created by the runtime. -@_fixed_layout -@usableFromInline -internal class _EmptyDictionarySingleton: _RawDictionaryStorage { - @nonobjc - internal override init(_doNotCallMe: ()) { - _sanityCheckFailure("This class cannot be directly initialized") - } +extension Dictionary { + /// An iterator over the members of a `Dictionary`. + @_fixed_layout + public struct Iterator { + // Dictionary has a separate IteratorProtocol and Index because of + // efficiency and implementability reasons. + // + // Native dictionaries have efficient indices. + // Bridged NSDictionary instances don't. + // + // Even though fast enumeration is not suitable for implementing + // Index, which is multi-pass, it is suitable for implementing a + // IteratorProtocol, which is being consumed as iteration proceeds. + @usableFromInline + @_frozen + internal enum _Variant { + case native(_NativeDictionary.Iterator) #if _runtime(_ObjC) - @objc - internal required init( - objects: UnsafePointer, - forKeys: UnsafeRawPointer, - count: Int - ) { - _sanityCheckFailure("This class cannot be directly initialized") - } + case cocoa(_CocoaDictionary.Iterator) #endif -} - -#if _runtime(_ObjC) -extension _EmptyDictionarySingleton: _NSDictionaryCore { - @objc(copyWithZone:) - internal func copy(with zone: _SwiftNSZone?) -> AnyObject { - return self - } - - @objc - internal var count: Int { - return 0 - } - - @objc(countByEnumeratingWithState:objects:count:) - internal func countByEnumerating( - with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>, - objects: UnsafeMutablePointer?, count: Int - ) -> Int { - // Even though we never do anything in here, we need to update the - // state so that callers know we actually ran. - - var theState = state.pointee - if theState.state == 0 { - theState.state = 1 // Arbitrary non-zero value. - theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects) - theState.mutationsPtr = _fastEnumerationStorageMutationsPtr } - state.pointee = theState - return 0 - } + @usableFromInline + internal var _variant: _Variant - @objc(objectForKey:) - internal func object(forKey aKey: AnyObject) -> AnyObject? { - return nil - } + @inlinable + internal init(_variant: _Variant) { + self._variant = _variant + } - @objc(keyEnumerator) - internal func keyEnumerator() -> _NSEnumerator { - return _SwiftEmptyNSEnumerator() - } + @inlinable + internal init(_native: _NativeDictionary.Iterator) { + self.init(_variant: .native(_native)) + } - @objc(getObjects:andKeys:count:) - internal func getObjects( - _ objects: UnsafeMutablePointer?, - andKeys keys: UnsafeMutablePointer?, - count: Int) { - // Do nothing, we're empty - } -} +#if _runtime(_ObjC) + @inlinable + internal init(_cocoa: _CocoaDictionary.Iterator) { + self.init(_variant: .cocoa(_cocoa)) + } #endif - -extension _RawDictionaryStorage { - /// The empty singleton that is used for every single Dictionary that is - /// created without any elements. The contents of the storage should never - /// be mutated. - @inlinable - @nonobjc - internal static var empty: _EmptyDictionarySingleton { - return Builtin.bridgeFromRawPointer( - Builtin.addressof(&_swiftEmptyDictionarySingleton)) } } -// See the docs at the top of this file for a description of this type -@_fixed_layout // FIXME(sil-serialize-all) -@usableFromInline -final internal class _DictionaryStorage - : _RawDictionaryStorage, _NSDictionaryCore { - // This type is made with allocWithTailElems, so no init is ever called. - // But we still need to have an init to satisfy the compiler. - @nonobjc - override internal init(_doNotCallMe: ()) { - _sanityCheckFailure("This class cannot be directly initialized") - } - +extension Dictionary.Iterator { #if _runtime(_ObjC) - @objc - internal required init( - objects: UnsafePointer, - forKeys: UnsafeRawPointer, - count: Int - ) { - _sanityCheckFailure("This class cannot be directly initialized") + @usableFromInline @_transparent + internal var _guaranteedNative: Bool { + return _canBeClass(Key.self) == 0 || _canBeClass(Value.self) == 0 } -#endif - deinit { - guard _count > 0 else { return } - if !_isPOD(Key.self) { - let keys = self._keys - for index in _hashTable { - (keys + index.bucket).deinitialize(count: 1) - } - } - if !_isPOD(Value.self) { - let values = self._values - for index in _hashTable { - (values + index.bucket).deinitialize(count: 1) - } + /// Allow the optimizer to consider the surrounding code unreachable if + /// Dictionary is guaranteed to be native. + @usableFromInline @_transparent + internal func _cocoaPath() { + if _guaranteedNative { + _conditionallyUnreachable() } - _count = 0 - _fixLifetime(self) } +#endif - @inlinable - final internal var _keys: UnsafeMutablePointer { - @inline(__always) + @usableFromInline @_transparent + internal var _asNative: _NativeDictionary.Iterator { get { - return self._rawKeys.assumingMemoryBound(to: Key.self) + switch _variant { + case .native(let nativeIterator): + return nativeIterator +#if _runtime(_ObjC) + case .cocoa: + _sanityCheckFailure("internal error: does not contain a native index") +#endif + } } - } - - @inlinable - final internal var _values: UnsafeMutablePointer { - @inline(__always) - get { - return self._rawValues.assumingMemoryBound(to: Value.self) + set { + self._variant = .native(newValue) } } - - internal var asNative: _NativeDictionary { - return _NativeDictionary(self) - } - - @usableFromInline - @_effects(releasenone) - internal static func reallocate( - original: _RawDictionaryStorage, - capacity: Int - ) -> (storage: _DictionaryStorage, rehash: Bool) { - _sanityCheck(capacity >= original._count) - let scale = _HashTable.scale(forCapacity: capacity) - let rehash = (scale != original._scale) - let newStorage = _DictionaryStorage.allocate(scale: scale) - return (newStorage, rehash) - } - - @usableFromInline - @_effects(releasenone) - static internal func allocate(capacity: Int) -> _DictionaryStorage { - let scale = _HashTable.scale(forCapacity: capacity) - return allocate(scale: scale) - } - - static internal func allocate(scale: Int) -> _DictionaryStorage { - // The entry count must be representable by an Int value; hence the scale's - // peculiar upper bound. - _sanityCheck(scale >= 0 && scale < Int.bitWidth - 1) - - let bucketCount = 1 &<< scale - let wordCount = _UnsafeBitset.wordCount(forCapacity: bucketCount) - let storage = Builtin.allocWithTailElems_3( - _DictionaryStorage.self, - wordCount._builtinWordValue, _HashTable.Word.self, - bucketCount._builtinWordValue, Key.self, - bucketCount._builtinWordValue, Value.self) - - let metadataAddr = Builtin.projectTailElems(storage, _HashTable.Word.self) - let keysAddr = Builtin.getTailAddr_Word( - metadataAddr, wordCount._builtinWordValue, _HashTable.Word.self, - Key.self) - let valuesAddr = Builtin.getTailAddr_Word( - keysAddr, bucketCount._builtinWordValue, Key.self, - Value.self) - storage._count = 0 - storage._capacity = _HashTable.capacity(forScale: scale) - storage._scale = scale - storage._rawKeys = UnsafeMutableRawPointer(keysAddr) - storage._rawValues = UnsafeMutableRawPointer(valuesAddr) - - // We use a slightly different hash seed whenever we change the size of the - // hash table, so that we avoid certain copy operations becoming quadratic, - // without breaking value semantics. (For background details, see - // https://bugs.swift.org/browse/SR-3268) - - // FIXME: Use true per-instance seeding instead. Per-capacity seeding still - // leaves hash values the same in same-sized tables, which may affect - // operations on two tables at once. (E.g., union.) - storage._seed = ( - Hasher._seed.0 ^ UInt64(truncatingIfNeeded: scale), - Hasher._seed.1) - // Initialize hash table metadata. - storage._hashTable.clear() - return storage - } - -#if _runtime(_ObjC) - @objc(copyWithZone:) - internal func copy(with zone: _SwiftNSZone?) -> AnyObject { - return self - } - - @objc - internal var count: Int { - return _count - } - - @objc(keyEnumerator) - internal func keyEnumerator() -> _NSEnumerator { - return _SwiftDictionaryNSEnumerator(asNative) - } - - @objc(countByEnumeratingWithState:objects:count:) - internal func countByEnumerating( - with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>, - objects: UnsafeMutablePointer?, count: Int - ) -> Int { - var theState = state.pointee - if theState.state == 0 { - theState.state = 1 // Arbitrary non-zero value. - theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects) - theState.mutationsPtr = _fastEnumerationStorageMutationsPtr - theState.extra.0 = CUnsignedLong(asNative.startIndex.bucket) - } - - // Test 'objects' rather than 'count' because (a) this is very rare anyway, - // and (b) the optimizer should then be able to optimize away the - // unwrapping check below. - if _slowPath(objects == nil) { - return 0 - } - - let unmanagedObjects = _UnmanagedAnyObjectArray(objects!) - var index = _HashTable.Index(bucket: Int(theState.extra.0)) - let endIndex = asNative.endIndex - _precondition(index == endIndex || _hashTable.isOccupied(index)) - var stored = 0 - for i in 0.. AnyObject? { - guard let nativeKey = _conditionallyBridgeFromObjectiveC(aKey, Key.self) - else { return nil } - - let (index, found) = asNative.find(nativeKey) - guard found else { return nil } - let value = asNative.uncheckedValue(at: index) - return _bridgeAnythingToObjectiveC(value) - } - - @objc(getObjects:andKeys:count:) - internal func getObjects( - _ objects: UnsafeMutablePointer?, - andKeys keys: UnsafeMutablePointer?, - count: Int) { - _precondition(count >= 0, "Invalid count") - guard count > 0 else { return } - var i = 0 // Current position in the output buffers - switch (_UnmanagedAnyObjectArray(keys), _UnmanagedAnyObjectArray(objects)) { - case (let unmanagedKeys?, let unmanagedObjects?): - for (key, value) in asNative { - unmanagedObjects[i] = _bridgeAnythingToObjectiveC(value) - unmanagedKeys[i] = _bridgeAnythingToObjectiveC(key) - i += 1 - guard i < count else { break } - } - case (let unmanagedKeys?, nil): - for (key, _) in asNative { - unmanagedKeys[i] = _bridgeAnythingToObjectiveC(key) - i += 1 - guard i < count else { break } - } - case (nil, let unmanagedObjects?): - for (_, value) in asNative { - unmanagedObjects[i] = _bridgeAnythingToObjectiveC(value) - i += 1 - guard i < count else { break } - } - case (nil, nil): - // Do nothing. - break - } - } -#endif -} - -/// A wrapper around _RawDictionaryStorage that provides most of the -/// implementation of Dictionary. -@usableFromInline -@_fixed_layout -internal struct _NativeDictionary { - @usableFromInline - internal typealias Element = (key: Key, value: Value) - - /// See this comments on _RawDictionaryStorage and its subclasses to - /// understand why we store an untyped storage here. - @usableFromInline - internal var _storage: _RawDictionaryStorage - - /// Constructs an instance from the empty singleton. - @inlinable - internal init() { - self._storage = _RawDictionaryStorage.empty - } - - /// Constructs a dictionary adopting the given storage. - @inlinable - internal init(_ storage: _RawDictionaryStorage) { - self._storage = storage - } - - @usableFromInline - @_effects(releasenone) - internal init(capacity: Int) { - let scale = _HashTable.scale(forCapacity: capacity) - self._storage = _DictionaryStorage.allocate(scale: scale) - } - -#if _runtime(_ObjC) - @inlinable - internal init(_ cocoa: _CocoaDictionary) { - self.init(cocoa, capacity: cocoa.count) - } - - @inlinable - internal init(_ cocoa: _CocoaDictionary, capacity: Int) { - _sanityCheck(cocoa.count <= capacity) - self.init(capacity: capacity) - for (key, value) in cocoa { - insertNew( - key: _forceBridgeFromObjectiveC(key, Key.self), - value: _forceBridgeFromObjectiveC(value, Value.self)) - } - } -#endif -} - -extension _NativeDictionary { // Primitive fields - @inlinable - internal var capacity: Int { - @inline(__always) - get { - return _assumeNonNegative(_storage._capacity) - } - } - - @inlinable - internal var hashTable: _HashTable { - @inline(__always) get { - return _storage._hashTable - } - } - - // This API is unsafe and needs a `_fixLifetime` in the caller. - @inlinable - internal var _keys: UnsafeMutablePointer { - return _storage._rawKeys.assumingMemoryBound(to: Key.self) - } - - @inlinable - internal var _values: UnsafeMutablePointer { - return _storage._rawValues.assumingMemoryBound(to: Value.self) - } -} - -extension _NativeDictionary { // Low-level unchecked operations - @inlinable - @inline(__always) - internal func uncheckedKey(at index: Index) -> Key { - defer { _fixLifetime(self) } - _sanityCheck(hashTable.isOccupied(index)) - return _keys[index.bucket] - } - - @inlinable - @inline(__always) - internal func uncheckedValue(at index: Index) -> Value { - defer { _fixLifetime(self) } - _sanityCheck(hashTable.isOccupied(index)) - return _values[index.bucket] - } - - @usableFromInline - @inline(__always) - internal func uncheckedInitialize( - at index: Index, - toKey key: Key, - value: Value) { - defer { _fixLifetime(self) } - _sanityCheck(hashTable.isValid(index)) - (_keys + index.bucket).initialize(to: key) - (_values + index.bucket).initialize(to: value) - } - - @usableFromInline - @inline(__always) - internal func uncheckedDestroy(at index: Index) { - defer { _fixLifetime(self) } - _sanityCheck(hashTable.isOccupied(index)) - (_keys + index.bucket).deinitialize(count: 1) - (_values + index.bucket).deinitialize(count: 1) - } -} - -extension _NativeDictionary { // Low-level lookup operations - @inlinable - @inline(__always) - internal func hashValue(for key: Key) -> Int { - return key._rawHashValue(seed: _storage._seed) - } - - @inlinable - @inline(__always) - internal func find(_ key: Key) -> (index: Index, found: Bool) { - return find(key, hashValue: self.hashValue(for: key)) - } - - /// Search for a given element, assuming it has the specified hash value. - /// - /// If the element is not present in this set, return the position where it - /// could be inserted. - @inlinable - @inline(__always) - internal func find( - _ key: Key, - hashValue: Int - ) -> (index: Index, found: Bool) { - let hashTable = self.hashTable - var index = hashTable.idealIndex(forHashValue: hashValue) - while hashTable._isOccupied(index) { - if uncheckedKey(at: index) == key { - return (index, true) - } - index = hashTable.index(wrappedAfter: index) - } - return (index, false) - } -} - -extension _NativeDictionary { // ensureUnique - @inlinable - internal mutating func resize(capacity: Int) { - let capacity = Swift.max(capacity, self.capacity) - let result = _NativeDictionary( - _DictionaryStorage.allocate(capacity: capacity)) - if count > 0 { - for index in hashTable { - let key = (_keys + index.bucket).move() - let value = (_values + index.bucket).move() - result._unsafeInsertNew(key: key, value: value) - } - // Clear out old storage, ensuring that its deinit won't overrelease the - // elements we've just moved out. - _storage._hashTable.clear() - _storage._count = 0 - } - _storage = result._storage - } - - @inlinable - internal mutating func copy(capacity: Int) -> Bool { - let capacity = Swift.max(capacity, self.capacity) - let (newStorage, rehash) = _DictionaryStorage.reallocate( - original: _storage, - capacity: capacity) - let result = _NativeDictionary(newStorage) - if count > 0 { - if rehash { - for index in hashTable { - result._unsafeInsertNew( - key: self.uncheckedKey(at: index), - value: self.uncheckedValue(at: index)) - } - } else { - result.hashTable.copyContents(of: hashTable) - result._storage._count = self.count - for index in hashTable { - let key = uncheckedKey(at: index) - let value = uncheckedValue(at: index) - result.uncheckedInitialize(at: index, toKey: key, value: value) - } - } - } - _storage = result._storage - return rehash - } - - /// Ensure storage of self is uniquely held and can hold at least `capacity` - /// elements. Returns true iff contents were rehashed. - @inlinable - @inline(__always) - internal mutating func ensureUnique(isUnique: Bool, capacity: Int) -> Bool { - if _fastPath(capacity <= self.capacity && isUnique) { - return false - } - guard isUnique else { - return copy(capacity: capacity) - } - resize(capacity: capacity) - return true - } - - @inlinable - internal mutating func reserveCapacity(_ capacity: Int, isUnique: Bool) { - _ = ensureUnique(isUnique: isUnique, capacity: capacity) - } -} - -extension _NativeDictionary: _DictionaryBuffer { - @usableFromInline - internal typealias Index = _HashTable.Index - - @inlinable - internal var startIndex: Index { - return hashTable.startIndex - } - - @inlinable - internal var endIndex: Index { - return hashTable.endIndex - } - - @inlinable - internal func index(after index: Index) -> Index { - return hashTable.index(after: index) - } - - @inlinable - internal func index(forKey key: Key) -> Index? { - if count == 0 { - // Fast path that avoids computing the hash of the key. - return nil - } - let (index, found) = find(key) - return found ? index : nil - } - - @inlinable - internal var count: Int { - @inline(__always) get { - return _assumeNonNegative(_storage._count) - } - } - - @inlinable - @inline(__always) - func contains(_ key: Key) -> Bool { - return find(key).found - } - - @inlinable - @inline(__always) - func lookup(_ key: Key) -> Value? { - if count == 0 { - // Fast path that avoids computing the hash of the key. - return nil - } - let (index, found) = self.find(key) - return found ? self.uncheckedValue(at: index) : nil - } - - @inlinable - @inline(__always) - func lookup(_ index: Index) -> (key: Key, value: Value) { - _precondition(hashTable.isOccupied(index), - "Attempting to access Dictionary elements using an invalid Index") - let key = self.uncheckedKey(at: index) - let value = self.uncheckedValue(at: index) - return (key, value) - } - - @inlinable - @inline(__always) - func key(at index: Index) -> Key { - _precondition(hashTable.isOccupied(index), - "Attempting to access Dictionary elements using an invalid Index") - return self.uncheckedKey(at: index) - } - - @inlinable - @inline(__always) - func value(at index: Index) -> Value { - _precondition(hashTable.isOccupied(index), - "Attempting to access Dictionary elements using an invalid Index") - return self.uncheckedValue(at: index) - } -} - -#if _runtime(_ObjC) -extension _NativeDictionary { // Bridging - @usableFromInline - internal func bridged() -> _NSDictionary { - // We can zero-cost bridge if our keys are verbatim - // or if we're the empty singleton. - - // Temporary var for SOME type safety before a cast. - let nsDictionary: _NSDictionaryCore - - if _storage === _RawDictionaryStorage.empty || count == 0 { - nsDictionary = _RawDictionaryStorage.empty - } else if _isBridgedVerbatimToObjectiveC(Key.self), - _isBridgedVerbatimToObjectiveC(Value.self) { - nsDictionary = unsafeDowncast( - _storage, - to: _DictionaryStorage.self) - } else { - nsDictionary = _SwiftDeferredNSDictionary(self) - } - - // Cast from "minimal NSDictionary" to "NSDictionary" - // Note that if you actually ask Swift for this cast, it will fail. - // Never trust a shadow protocol! - return unsafeBitCast(nsDictionary, to: _NSDictionary.self) - } -} -#endif - -// This function has a highly visible name to make it stand out in stack traces. -@usableFromInline -@inline(never) -internal func KEY_TYPE_OF_DICTIONARY_VIOLATES_HASHABLE_REQUIREMENTS( - _ keyType: Any.Type -) -> Never { - _assertionFailure( - "Fatal error", - """ - Duplicate keys of type '\(keyType)' were found in a Dictionary. - This usually means either that the type violates Hashable's requirements, or - that members of such a dictionary were mutated after insertion. - """, - flags: _fatalErrorFlags()) -} - -extension _NativeDictionary { // Insertions - /// Insert a new element into uniquely held storage. - /// Storage must be uniquely referenced with adequate capacity. - /// The `key` must not be already present in the Dictionary. - @inlinable - internal func _unsafeInsertNew(key: Key, value: Value) { - _sanityCheck(count + 1 <= capacity) - let hashValue = self.hashValue(for: key) - if _isDebugAssertConfiguration() { - // In debug builds, perform a full lookup and trap if we detect duplicate - // elements -- these imply that the Element type violates Hashable - // requirements. This is generally more costly than a direct insertion, - // because we'll need to compare elements in case of hash collisions. - let (index, found) = find(key, hashValue: hashValue) - guard !found else { - KEY_TYPE_OF_DICTIONARY_VIOLATES_HASHABLE_REQUIREMENTS(Key.self) - } - hashTable.insert(index) - uncheckedInitialize(at: index, toKey: key, value: value) - } else { - let index = hashTable.insertNew(hashValue: hashValue) - uncheckedInitialize(at: index, toKey: key, value: value) - } - _storage._count += 1 - } - - /// Insert a new entry into uniquely held storage. - /// Storage must be uniquely referenced. - /// The `key` must not be already present in the Dictionary. - @inlinable - internal mutating func insertNew(key: Key, value: Value) { - _ = ensureUnique(isUnique: true, capacity: count + 1) - _unsafeInsertNew(key: key, value: value) - } - - /// Same as find(_:), except assume a corresponding key/value pair will be - /// inserted if it doesn't already exist, and mutated if it does exist. When - /// this function returns, the storage is guaranteed to be native, uniquely - /// held, and with enough capacity for a single insertion (if the key isn't - /// already in the dictionary.) - @inlinable - @inline(__always) - internal mutating func mutatingFind( - _ key: Key, - isUnique: Bool - ) -> (index: Index, found: Bool) { - let (index, found) = find(key) - - // Prepare storage. - // If `key` isn't in the dictionary yet, assume that this access will end - // up inserting it. (If we guess wrong, we might needlessly expand - // storage; that's fine.) Otherwise this can only be a removal or an - // in-place mutation. - let rehashed = ensureUnique( - isUnique: isUnique, - capacity: count + (found ? 0 : 1)) - guard rehashed else { return (index, found) } - let (i, f) = find(key) - if f != found { - KEY_TYPE_OF_DICTIONARY_VIOLATES_HASHABLE_REQUIREMENTS(Key.self) - } - return (i, found) - } - - @inlinable - internal func _insert(at index: Index, key: Key, value: Value) { - _sanityCheck(count < capacity) - hashTable.insert(index) - uncheckedInitialize(at: index, toKey: key, value: value) - _storage._count += 1 - } - - @inlinable - internal mutating func updateValue( - _ value: Value, - forKey key: Key, - isUnique: Bool - ) -> Value? { - let (index, found) = mutatingFind(key, isUnique: isUnique) - if found { - let oldValue = (_values + index.bucket).move() - (_values + index.bucket).initialize(to: value) - // FIXME: Replacing the old key with the new is unnecessary, unintuitive, - // and actively harmful to some usecases. We shouldn't do it. - // rdar://problem/32144087 - (_keys + index.bucket).pointee = key - return oldValue - } - _insert(at: index, key: key, value: value) - return nil - } - - @inlinable - internal mutating func setValue( - _ value: Value, - forKey key: Key, - isUnique: Bool - ) { - let (index, found) = mutatingFind(key, isUnique: isUnique) - if found { - (_values + index.bucket).pointee = value - // FIXME: Replacing the old key with the new is unnecessary, unintuitive, - // and actively harmful to some usecases. We shouldn't do it. - // rdar://problem/32144087 - (_keys + index.bucket).pointee = key - } else { - _insert(at: index, key: key, value: value) - } - } -} - -extension _NativeDictionary: _HashTableDelegate { - @inlinable - @inline(__always) - internal func hashValue(at index: Index) -> Int { - return hashValue(for: uncheckedKey(at: index)) - } - - @inlinable - @inline(__always) - internal func moveEntry(from source: Index, to target: Index) { - (_keys + target.bucket) - .moveInitialize(from: _keys + source.bucket, count: 1) - (_values + target.bucket) - .moveInitialize(from: _values + source.bucket, count: 1) - } -} - -extension _NativeDictionary { // Deletion - @inlinable - internal func _delete(at index: Index) { - hashTable.delete(at: index, with: self) - _storage._count -= 1 - _sanityCheck(_storage._count >= 0) - } - - @inlinable - @inline(__always) - internal mutating func uncheckedRemove( - at index: Index, - isUnique: Bool - ) -> Element { - _sanityCheck(hashTable.isOccupied(index)) - let rehashed = ensureUnique(isUnique: isUnique, capacity: capacity) - _sanityCheck(!rehashed) - let oldKey = (_keys + index.bucket).move() - let oldValue = (_values + index.bucket).move() - _delete(at: index) - return (oldKey, oldValue) - } - - @inlinable - @inline(__always) - internal mutating func remove(at index: Index, isUnique: Bool) -> Element { - _precondition(hashTable.isOccupied(index), "Invalid index") - return uncheckedRemove(at: index, isUnique: isUnique) - } - - @usableFromInline - internal mutating func removeAll(isUnique: Bool) { - guard isUnique else { - let scale = self._storage._scale - _storage = _DictionaryStorage.allocate(scale: scale) - return - } - for index in hashTable { - (_keys + index.bucket).deinitialize(count: 1) - (_values + index.bucket).deinitialize(count: 1) - } - hashTable.clear() - _storage._count = 0 - } -} - -extension _NativeDictionary { // High-level operations - @inlinable - internal func mapValues( - _ transform: (Value) throws -> T - ) rethrows -> _NativeDictionary { - let result = _NativeDictionary(capacity: capacity) - // Because the keys in the current and new buffer are the same, we can - // initialize to the same locations in the new buffer, skipping hash value - // recalculations. - for index in hashTable { - let key = self.uncheckedKey(at: index) - let value = self.uncheckedValue(at: index) - try result._insert(at: index, key: key, value: transform(value)) - } - return result - } - - @inlinable - internal mutating func merge( - _ keysAndValues: S, - isUnique: Bool, - uniquingKeysWith combine: (Value, Value) throws -> Value - ) rethrows where S.Element == (Key, Value) { - var isUnique = isUnique - for (key, value) in keysAndValues { - let (index, found) = mutatingFind(key, isUnique: isUnique) - isUnique = true - if found { - do { - let v = (_values + index.bucket).move() - let newValue = try combine(v, value) - (_values + index.bucket).initialize(to: newValue) - } catch _MergeError.keyCollision { - fatalError("Duplicate values for key: '\(key)'") - } - } else { - _insert(at: index, key: key, value: value) - } - } - } - - @inlinable - @inline(__always) - internal init( - grouping values: S, - by keyForValue: (S.Element) throws -> Key - ) rethrows where Value == [S.Element] { - self.init() - for value in values { - let key = try keyForValue(value) - let (index, found) = mutatingFind(key, isUnique: true) - if found { - _values[index.bucket].append(value) - } else { - _insert(at: index, key: key, value: [value]) - } - } - } -} - -#if _runtime(_ObjC) -/// An NSEnumerator that works with any _NativeDictionary of -/// verbatim bridgeable elements. Used by the various NSDictionary impls. -final internal class _SwiftDictionaryNSEnumerator - : _SwiftNativeNSEnumerator, _NSEnumerator { - - @nonobjc internal var base: _NativeDictionary - @nonobjc internal var bridgedKeys: _BridgingHashBuffer? - @nonobjc internal var nextIndex: _NativeDictionary.Index - @nonobjc internal var endIndex: _NativeDictionary.Index - - @objc - internal override required init() { - _sanityCheckFailure("don't call this designated initializer") - } - - internal init(_ base: _NativeDictionary) { - _sanityCheck(_isBridgedVerbatimToObjectiveC(Key.self)) - self.base = base - self.bridgedKeys = nil - self.nextIndex = base.startIndex - self.endIndex = base.endIndex - } - - @nonobjc - internal init(_ deferred: _SwiftDeferredNSDictionary) { - _sanityCheck(!_isBridgedVerbatimToObjectiveC(Key.self)) - self.base = deferred.native - self.bridgedKeys = deferred.bridgeKeys() - self.nextIndex = base.startIndex - self.endIndex = base.endIndex - } - - private func bridgedKey(at index: _HashTable.Index) -> AnyObject { - _sanityCheck(base.hashTable.isOccupied(index)) - if let bridgedKeys = self.bridgedKeys { - return bridgedKeys[index] - } - return _bridgeAnythingToObjectiveC(base.uncheckedKey(at: index)) - } - - @objc - internal func nextObject() -> AnyObject? { - if nextIndex == endIndex { - return nil - } - let index = nextIndex - nextIndex = base.index(after: nextIndex) - return self.bridgedKey(at: index) - } - - @objc(countByEnumeratingWithState:objects:count:) - internal func countByEnumerating( - with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>, - objects: UnsafeMutablePointer, - count: Int - ) -> Int { - var theState = state.pointee - if theState.state == 0 { - theState.state = 1 // Arbitrary non-zero value. - theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects) - theState.mutationsPtr = _fastEnumerationStorageMutationsPtr - } - - if nextIndex == endIndex { - state.pointee = theState - return 0 - } - - // Return only a single element so that code can start iterating via fast - // enumeration, terminate it, and continue via NSEnumerator. - let unmanagedObjects = _UnmanagedAnyObjectArray(objects) - unmanagedObjects[0] = self.bridgedKey(at: nextIndex) - nextIndex = base.index(after: nextIndex) - state.pointee = theState - return 1 - } -} -#endif - -#if _runtime(_ObjC) -/// This class exists for Objective-C bridging. It holds a reference to a -/// _NativeDictionary, and can be upcast to NSSelf when bridging is -/// necessary. This is the fallback implementation for situations where -/// toll-free bridging isn't possible. On first access, a _NativeDictionary -/// of AnyObject will be constructed containing all the bridged elements. -final internal class _SwiftDeferredNSDictionary - : _SwiftNativeNSDictionary, _NSDictionaryCore { - - // This stored property must be stored at offset zero. We perform atomic - // operations on it. - // - // Do not access this property directly. - @nonobjc - private var _bridgedKeys_DoNotUse: AnyObject? - - // This stored property must be stored at offset one. We perform atomic - // operations on it. - // - // Do not access this property directly. - @nonobjc - private var _bridgedValues_DoNotUse: AnyObject? - - /// The unbridged elements. - internal var native: _NativeDictionary - - internal init(_ native: _NativeDictionary) { - _sanityCheck(native.count > 0) - _sanityCheck(!_isBridgedVerbatimToObjectiveC(Key.self) || - !_isBridgedVerbatimToObjectiveC(Value.self)) - self.native = native - super.init() - } - - @objc - internal required init( - objects: UnsafePointer, - forKeys: UnsafeRawPointer, - count: Int - ) { - _sanityCheckFailure("don't call this designated initializer") - } - - @nonobjc - private var _bridgedKeysPtr: UnsafeMutablePointer { - return _getUnsafePointerToStoredProperties(self) - .assumingMemoryBound(to: Optional.self) - } - - @nonobjc - private var _bridgedValuesPtr: UnsafeMutablePointer { - return _bridgedKeysPtr + 1 - } - - /// The buffer for bridged keys, if present. - @nonobjc - private var _bridgedKeys: _BridgingHashBuffer? { - guard let ref = _stdlib_atomicLoadARCRef(object: _bridgedKeysPtr) else { - return nil - } - return unsafeDowncast(ref, to: _BridgingHashBuffer.self) - } - - /// The buffer for bridged values, if present. - @nonobjc - private var _bridgedValues: _BridgingHashBuffer? { - guard let ref = _stdlib_atomicLoadARCRef(object: _bridgedValuesPtr) else { - return nil - } - return unsafeDowncast(ref, to: _BridgingHashBuffer.self) - } - - /// Attach a buffer for bridged Dictionary keys. - @nonobjc - private func _initializeBridgedKeys(_ storage: _BridgingHashBuffer) { - _stdlib_atomicInitializeARCRef(object: _bridgedKeysPtr, desired: storage) - } - - /// Attach a buffer for bridged Dictionary values. - @nonobjc - private func _initializeBridgedValues(_ storage: _BridgingHashBuffer) { - _stdlib_atomicInitializeARCRef(object: _bridgedValuesPtr, desired: storage) - } - - @nonobjc - internal func bridgeKeys() -> _BridgingHashBuffer? { - if _isBridgedVerbatimToObjectiveC(Key.self) { return nil } - if let bridgedKeys = _bridgedKeys { return bridgedKeys } - - // Allocate and initialize heap storage for bridged keys. - let bridged = _BridgingHashBuffer.allocate( - owner: native._storage, - hashTable: native.hashTable) - for index in native.hashTable { - let object = _bridgeAnythingToObjectiveC(native.uncheckedKey(at: index)) - bridged.initialize(at: index, to: object) - } - - // Atomically put the bridged keys in place. - _initializeBridgedKeys(bridged) - return _bridgedKeys! - } - - @nonobjc - internal func bridgeValues() -> _BridgingHashBuffer? { - if _isBridgedVerbatimToObjectiveC(Value.self) { return nil } - if let bridgedValues = _bridgedValues { return bridgedValues } - - // Allocate and initialize heap storage for bridged values. - let bridged = _BridgingHashBuffer.allocate( - owner: native._storage, - hashTable: native.hashTable) - for index in native.hashTable { - let object = _bridgeAnythingToObjectiveC(native.uncheckedValue(at: index)) - bridged.initialize(at: index, to: object) - } - - // Atomically put the bridged values in place. - _initializeBridgedValues(bridged) - return _bridgedValues! - } - - @usableFromInline - internal typealias Index = _HashTable.Index - - @objc(copyWithZone:) - internal func copy(with zone: _SwiftNSZone?) -> AnyObject { - // Instances of this class should be visible outside of standard library as - // having `NSDictionary` type, which is immutable. - return self - } - - @objc(objectForKey:) - internal func object(forKey aKey: AnyObject) -> AnyObject? { - guard let nativeKey = _conditionallyBridgeFromObjectiveC(aKey, Key.self) - else { return nil } - - let (index, found) = native.find(nativeKey) - guard found else { return nil } - if let bridgedValues = bridgeValues() { - return bridgedValues[index] - } - return _bridgeAnythingToObjectiveC(native.uncheckedValue(at: index)) - } - - @inline(__always) - private func _key( - at index: Index, - bridgedKeys: _BridgingHashBuffer? - ) -> AnyObject { - if let bridgedKeys = bridgedKeys { - return bridgedKeys[index] - } - return _bridgeAnythingToObjectiveC(native.uncheckedKey(at: index)) - } - - @inline(__always) - private func _value( - at index: Index, - bridgedValues: _BridgingHashBuffer? - ) -> AnyObject { - if let bridgedValues = bridgedValues { - return bridgedValues[index] - } - return _bridgeAnythingToObjectiveC(native.uncheckedValue(at: index)) - } - - @objc - internal func keyEnumerator() -> _NSEnumerator { - if _isBridgedVerbatimToObjectiveC(Key.self) { - return _SwiftDictionaryNSEnumerator(native) - } - return _SwiftDictionaryNSEnumerator(self) - } - - @objc(getObjects:andKeys:count:) - internal func getObjects( - _ objects: UnsafeMutablePointer?, - andKeys keys: UnsafeMutablePointer?, - count: Int - ) { - _precondition(count >= 0, "Invalid count") - guard count > 0 else { return } - let bridgedKeys = bridgeKeys() - let bridgedValues = bridgeValues() - var i = 0 // Current position in the output buffers - let bucketCount = native._storage._bucketCount - - defer { _fixLifetime(self) } - - switch (_UnmanagedAnyObjectArray(keys), _UnmanagedAnyObjectArray(objects)) { - case (let unmanagedKeys?, let unmanagedObjects?): - for index in native.hashTable { - unmanagedKeys[i] = _key(at: index, bridgedKeys: bridgedKeys) - unmanagedObjects[i] = _value(at: index, bridgedValues: bridgedValues) - i += 1 - guard i < count else { break } - } - case (let unmanagedKeys?, nil): - for index in native.hashTable { - unmanagedKeys[i] = _key(at: index, bridgedKeys: bridgedKeys) - i += 1 - guard i < count else { break } - } - case (nil, let unmanagedObjects?): - for index in native.hashTable { - unmanagedObjects[i] = _value(at: index, bridgedValues: bridgedValues) - i += 1 - guard i < count else { break } - } - case (nil, nil): - // Do nothing - break - } - } - - @objc(enumerateKeysAndObjectsWithOptions:usingBlock:) - internal func enumerateKeysAndObjects( - options: Int, - using block: @convention(block) ( - Unmanaged, - Unmanaged, - UnsafeMutablePointer - ) -> Void) { - let bridgedKeys = bridgeKeys() - let bridgedValues = bridgeValues() - - defer { _fixLifetime(self) } - - var stop: UInt8 = 0 - for index in native.hashTable { - let key = _key(at: index, bridgedKeys: bridgedKeys) - let value = _value(at: index, bridgedValues: bridgedValues) - block( - Unmanaged.passUnretained(key), - Unmanaged.passUnretained(value), - &stop) - if stop != 0 { return } - } - } - - @objc - internal var count: Int { - return native.count - } - - @objc(countByEnumeratingWithState:objects:count:) - internal func countByEnumerating( - with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>, - objects: UnsafeMutablePointer?, - count: Int - ) -> Int { - var theState = state.pointee - if theState.state == 0 { - theState.state = 1 // Arbitrary non-zero value. - theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects) - theState.mutationsPtr = _fastEnumerationStorageMutationsPtr - theState.extra.0 = CUnsignedLong(native.startIndex.bucket) - } - - // Test 'objects' rather than 'count' because (a) this is very rare anyway, - // and (b) the optimizer should then be able to optimize away the - // unwrapping check below. - if _slowPath(objects == nil) { - return 0 - } - - let unmanagedObjects = _UnmanagedAnyObjectArray(objects!) - var index = _HashTable.Index(bucket: Int(theState.extra.0)) - let endIndex = native.endIndex - _precondition(index == endIndex || native.hashTable.isOccupied(index)) - var stored = 0 - - // Only need to bridge once, so we can hoist it out of the loop. - let bridgedKeys = bridgeKeys() - for i in 0.. { } -#endif - -#if _runtime(_ObjC) -@usableFromInline -@_fixed_layout -internal struct _CocoaDictionary { - @usableFromInline - internal let object: _NSDictionary - - @inlinable - internal init(_ object: _NSDictionary) { - self.object = object - } -} - -extension _CocoaDictionary: Equatable { - @usableFromInline - internal static func ==( - lhs: _CocoaDictionary, - rhs: _CocoaDictionary - ) -> Bool { - return _stdlib_NSObject_isEqual(lhs.object, rhs.object) - } -} - -extension _CocoaDictionary: _DictionaryBuffer { - @usableFromInline - internal typealias Key = AnyObject - @usableFromInline - internal typealias Value = AnyObject - - @inlinable - internal var startIndex: Index { - return Index(self, startIndex: ()) - } - - @inlinable - internal var endIndex: Index { - return Index(self, endIndex: ()) - } - - @inlinable - internal func index(after i: Index) -> Index { - var i = i - formIndex(after: &i) - return i - } - - @usableFromInline - @_effects(releasenone) - internal func formIndex(after i: inout Index) { - _precondition(i.base.object === self.object, "Invalid index") - _precondition(i.currentKeyIndex < i.allKeys.value, - "Cannot increment endIndex") - i.currentKeyIndex += 1 - } - - @usableFromInline - internal func index(forKey key: Key) -> Index? { - // Fast path that does not involve creating an array of all keys. In case - // the key is present, this lookup is a penalty for the slow path, but the - // potential savings are significant: we could skip a memory allocation and - // a linear search. - if lookup(key) == nil { - return nil - } - - let allKeys = _stdlib_NSDictionary_allKeys(object) - var keyIndex = -1 - for i in 0..= 0, - "Key was found in fast path, but not found later?") - return Index(self, allKeys, keyIndex) - } - - @inlinable - internal var count: Int { - return object.count - } - - @inlinable - @inline(__always) - internal func contains(_ key: Key) -> Bool { - return object.object(forKey: key) != nil - } - - @inlinable - @inline(__always) - internal func lookup(_ key: Key) -> Value? { - return object.object(forKey: key) - } - - @inlinable - @inline(__always) - internal func lookup(_ index: Index) -> (key: Key, value: Value) { - _precondition(index.base.object === self.object, "Invalid index") - let key: Key = index.allKeys[index.currentKeyIndex] - let value: Value = index.base.object.object(forKey: key)! - return (key, value) - } - - @inlinable - @inline(__always) - func key(at index: Index) -> Key { - _precondition(index.base.object === self.object, "Invalid index") - return index.allKeys[index.currentKeyIndex] - } - - @inlinable - @inline(__always) - func value(at index: Index) -> Value { - _precondition(index.base.object === self.object, "Invalid index") - let key = index.allKeys[index.currentKeyIndex] - return index.base.object.object(forKey: key)! - } -} - -extension _CocoaDictionary { - @inlinable - internal func mapValues( - _ transform: (Value) throws -> T - ) rethrows -> _NativeDictionary { - var result = _NativeDictionary(capacity: self.count) - for (cocoaKey, cocoaValue) in self { - let key = _forceBridgeFromObjectiveC(cocoaKey, Key.self) - let value = _forceBridgeFromObjectiveC(cocoaValue, Value.self) - try result.insertNew(key: key, value: transform(value)) - } - return result - } -} -#endif - -extension Dictionary { - @usableFromInline - @_frozen - internal enum _Variant { - case native(_NativeDictionary) -#if _runtime(_ObjC) - case cocoa(_CocoaDictionary) -#endif - } -} - -extension Dictionary._Variant { -#if _runtime(_ObjC) - @usableFromInline @_transparent - internal var guaranteedNative: Bool { - return _canBeClass(Key.self) == 0 || _canBeClass(Value.self) == 0 - } - - // Allow the optimizer to consider the surrounding code unreachable if Element - // is guaranteed to be native. - @usableFromInline @_transparent - internal func cocoaPath() { - if guaranteedNative { - _conditionallyUnreachable() - } - } -#endif - - @inlinable - internal mutating func isUniquelyReferenced() -> Bool { - switch self { - case .native: - // Note that &self drills down through .native(_NativeDictionary) to the - // first property in _NativeDictionary, which is the reference to the - // storage. - return _isUnique_native(&self) -#if _runtime(_ObjC) - case .cocoa: - cocoaPath() - // Don't consider Cocoa buffer mutable, even if it is mutable and is - // uniquely referenced. - return false -#endif - } - } - - @inlinable - internal var asNative: _NativeDictionary { - @inline(__always) - get { - switch self { - case .native(let native): - return native -#if _runtime(_ObjC) - case .cocoa: - _sanityCheckFailure("internal error: not backed by native buffer") -#endif - } - } - @inline(__always) - set { - self = .native(newValue) - } - } - -#if _runtime(_ObjC) - @inlinable - internal var asCocoa: _CocoaDictionary { - switch self { - case .native: - _sanityCheckFailure("internal error: not backed by NSDictionary") - case .cocoa(let cocoa): - return cocoa - } - } -#endif - - /// Reserves enough space for the specified number of elements to be stored - /// without reallocating additional storage. - @inlinable - internal mutating func reserveCapacity(_ capacity: Int) { - switch self { - case .native: - let isUnique = isUniquelyReferenced() - asNative.reserveCapacity(capacity, isUnique: isUnique) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - let capacity = Swift.max(cocoa.count, capacity) - self = .native(_NativeDictionary(cocoa, capacity: capacity)) -#endif - } - } - - /// The number of elements that can be stored without expanding the current - /// storage. - /// - /// For bridged storage, this is equal to the current count of the - /// collection, since any addition will trigger a copy of the elements into - /// newly allocated storage. For native storage, this is the element count - /// at which adding any more elements will exceed the load factor. - @inlinable - internal var capacity: Int { - switch self { - case .native: - return asNative.capacity -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return cocoa.count -#endif - } - } -} - -extension Dictionary._Variant: _DictionaryBuffer { - @usableFromInline - internal typealias Element = (key: Key, value: Value) - @usableFromInline - internal typealias Index = Dictionary.Index - - @inlinable - internal var startIndex: Index { - switch self { - case .native: - return Index(_native: asNative.startIndex) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return Index(_cocoa: cocoa.startIndex) -#endif - } - } - - @inlinable - internal var endIndex: Index { - switch self { - case .native: - return Index(_native: asNative.endIndex) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return Index(_cocoa: cocoa.endIndex) -#endif - } - } - - @inlinable - internal func index(after i: Index) -> Index { - switch self { - case .native: - return Index(_native: asNative.index(after: i._asNative)) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return Index(_cocoa: cocoa.index(after: i._asCocoa)) -#endif - } - } - - @inlinable - @inline(__always) - internal func index(forKey key: Key) -> Index? { - switch self { - case .native: - guard let index = asNative.index(forKey: key) else { return nil } - return Index(_native: index) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - let cocoaKey = _bridgeAnythingToObjectiveC(key) - guard let index = cocoa.index(forKey: cocoaKey) else { return nil } - return Index(_cocoa: index) -#endif - } - } - - @inlinable - internal var count: Int { - @inline(__always) - get { - switch self { - case .native: - return asNative.count -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return cocoa.count -#endif - } - } - } - - @inlinable - @inline(__always) - func contains(_ key: Key) -> Bool { - switch self { - case .native: - return asNative.contains(key) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - let cocoaKey = _bridgeAnythingToObjectiveC(key) - return cocoa.contains(cocoaKey) -#endif - } - } - - @inlinable - @inline(__always) - func lookup(_ key: Key) -> Value? { - switch self { - case .native: - return asNative.lookup(key) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - let cocoaKey = _bridgeAnythingToObjectiveC(key) - guard let cocoaValue = cocoa.lookup(cocoaKey) else { return nil } - return _forceBridgeFromObjectiveC(cocoaValue, Value.self) -#endif - } - } - - @inlinable - @inline(__always) - func lookup(_ index: Index) -> (key: Key, value: Value) { - switch self { - case .native: - return asNative.lookup(index._asNative) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - let (cocoaKey, cocoaValue) = cocoa.lookup(index._asCocoa) - let nativeKey = _forceBridgeFromObjectiveC(cocoaKey, Key.self) - let nativeValue = _forceBridgeFromObjectiveC(cocoaValue, Value.self) - return (nativeKey, nativeValue) -#endif - } - } - - @inlinable - @inline(__always) - func key(at index: Index) -> Key { - switch self { - case .native: - return asNative.key(at: index._asNative) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - let cocoaKey = cocoa.key(at: index._asCocoa) - return _forceBridgeFromObjectiveC(cocoaKey, Key.self) -#endif - } - } - - @inlinable - @inline(__always) - func value(at index: Index) -> Value { - switch self { - case .native: - return asNative.value(at: index._asNative) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - let cocoaValue = cocoa.value(at: index._asCocoa) - return _forceBridgeFromObjectiveC(cocoaValue, Value.self) -#endif - } - } -} - -extension Dictionary._Variant { - /// Same as find(_:), except assume a corresponding key/value pair will be - /// inserted if it doesn't already exist, and mutated if it does exist. When - /// this function returns, the storage is guaranteed to be native, uniquely - /// held, and with enough capacity for a single insertion (if the key isn't - /// already in the dictionary.) - @inlinable - @inline(__always) - internal mutating func mutatingFind( - _ key: Key - ) -> (index: _NativeDictionary.Index, found: Bool) { - switch self { - case .native: - let isUnique = isUniquelyReferenced() - return asNative.mutatingFind(key, isUnique: isUnique) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - var native = _NativeDictionary( - cocoa, capacity: cocoa.count + 1) - let result = native.mutatingFind(key, isUnique: true) - self = .native(native) - return result -#endif - } - } - - /// Ensure uniquely held native storage, while preserving the given index. - /// (If the variant had bridged storage, then the returned index will be the - /// corresponding native representation. Otherwise it's kept the same.) - @inlinable - @inline(__always) - internal mutating func ensureUniqueNative( - preserving index: Index - ) -> _NativeDictionary.Index { - switch self { - case .native: - let isUnique = isUniquelyReferenced() - if !isUnique { - let rehashed = asNative.copy(capacity: asNative.capacity) - _sanityCheck(!rehashed) - } - return index._asNative -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - // We have to migrate the data first. But after we do so, the Cocoa - // index becomes useless, so get the key first. - let cocoaKey = cocoa.key(at: index._asCocoa) - let native = _NativeDictionary(cocoa) - self = .native(native) - let nativeKey = _forceBridgeFromObjectiveC(cocoaKey, Key.self) - let (nativeIndex, found) = native.find(nativeKey) - _precondition(found, "Bridging did not preserve equality") - return nativeIndex -#endif - } - } - - @inlinable - internal mutating func updateValue( - _ value: Value, - forKey key: Key - ) -> Value? { - switch self { - case .native: - let isUnique = self.isUniquelyReferenced() - return asNative.updateValue(value, forKey: key, isUnique: isUnique) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - // Make sure we have space for an extra element. - var native = _NativeDictionary( - cocoa, - capacity: cocoa.count + 1) - let result = native.updateValue(value, forKey: key, isUnique: true) - self = .native(native) - return result -#endif - } - } - - @inlinable - internal mutating func setValue(_ value: Value, forKey key: Key) { - switch self { - case .native: - let isUnique = self.isUniquelyReferenced() - asNative.setValue(value, forKey: key, isUnique: isUnique) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - // Make sure we have space for an extra element. - var native = _NativeDictionary( - cocoa, - capacity: cocoa.count + 1) - native.setValue(value, forKey: key, isUnique: true) - self = .native(native) -#endif - } - } - - @inlinable - internal mutating func remove(at index: Index) -> Element { - // FIXME(performance): fuse data migration and element deletion into one - // operation. - let index = ensureUniqueNative(preserving: index) - return asNative.remove(at: index, isUnique: true) - } - - @inlinable - internal mutating func removeValue(forKey key: Key) -> Value? { - switch self { - case .native: - let (index, found) = asNative.find(key) - guard found else { return nil } - let isUnique = isUniquelyReferenced() - return asNative.uncheckedRemove(at: index, isUnique: isUnique).value -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - let cocoaKey = _bridgeAnythingToObjectiveC(key) - guard cocoa.lookup(cocoaKey) != nil else { return nil } - var native = _NativeDictionary(cocoa) - let (index, found) = native.find(key) - _precondition(found, "Bridging did not preserve equality") - let old = native.uncheckedRemove(at: index, isUnique: true).value - self = .native(native) - return old -#endif - } - } - - @inlinable - internal mutating func removeAll(keepingCapacity keepCapacity: Bool) { - if !keepCapacity { - self = .native(_NativeDictionary()) - return - } - guard count > 0 else { return } - - switch self { - case .native: - let isUnique = isUniquelyReferenced() - asNative.removeAll(isUnique: isUnique) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - self = .native(_NativeDictionary(capacity: cocoa.count)) -#endif - } - } -} - -extension Dictionary._Variant { - /// Returns an iterator over the `(Key, Value)` pairs. - /// - /// - Complexity: O(1). - @inlinable - @inline(__always) - internal func makeIterator() -> Dictionary.Iterator { - switch self { - case .native(let native): - return Dictionary.Iterator(_native: native.makeIterator()) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return Dictionary.Iterator(_cocoa: cocoa.makeIterator()) -#endif - } - } -} - -extension Dictionary._Variant { - @inlinable - internal func mapValues( - _ transform: (Value) throws -> T - ) rethrows -> _NativeDictionary { - switch self { - case .native(let native): - return try native.mapValues(transform) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return try cocoa.mapValues(transform) -#endif - } - } - - @inlinable - internal mutating func merge( - _ keysAndValues: S, - uniquingKeysWith combine: (Value, Value) throws -> Value - ) rethrows where S.Element == (Key, Value) { - switch self { - case .native: - let isUnique = isUniquelyReferenced() - try asNative.merge( - keysAndValues, - isUnique: isUnique, - uniquingKeysWith: combine) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - var native = _NativeDictionary(cocoa) - try native.merge( - keysAndValues, - isUnique: true, - uniquingKeysWith: combine) - self = .native(native) -#endif - } - } -} - -#if _runtime(_ObjC) -extension _CocoaDictionary { - @_fixed_layout // FIXME(sil-serialize-all) - @usableFromInline - internal struct Index { - // Assumption: we rely on NSDictionary.getObjects when being - // repeatedly called on the same NSDictionary, returning items in the same - // order every time. - // Similarly, the same assumption holds for NSSet.allObjects. - - /// A reference to the NSDictionary, which owns members in `allObjects`, - /// or `allKeys`, for NSSet and NSDictionary respectively. - @usableFromInline // FIXME(sil-serialize-all) - internal let base: _CocoaDictionary - // FIXME: swift-3-indexing-model: try to remove the cocoa reference, but - // make sure that we have a safety check for accessing `allKeys`. Maybe - // move both into the dictionary/set itself. - - /// An unowned array of keys. - @usableFromInline // FIXME(sil-serialize-all) - internal var allKeys: _HeapBuffer - - /// Index into `allKeys` - @usableFromInline // FIXME(sil-serialize-all) - internal var currentKeyIndex: Int - - @inlinable // FIXME(sil-serialize-all) - internal init(_ base: _CocoaDictionary, startIndex: ()) { - self.base = base - self.allKeys = _stdlib_NSDictionary_allKeys(base.object) - self.currentKeyIndex = 0 - } - - @inlinable // FIXME(sil-serialize-all) - internal init(_ base: _CocoaDictionary, endIndex: ()) { - self.base = base - self.allKeys = _stdlib_NSDictionary_allKeys(base.object) - self.currentKeyIndex = allKeys.value - } - - @inlinable // FIXME(sil-serialize-all) - internal init( - _ base: _CocoaDictionary, - _ allKeys: _HeapBuffer, - _ currentKeyIndex: Int - ) { - self.base = base - self.allKeys = allKeys - self.currentKeyIndex = currentKeyIndex - } - } -} - -extension _CocoaDictionary.Index: Equatable { - @inlinable - internal static func == ( - lhs: _CocoaDictionary.Index, - rhs: _CocoaDictionary.Index - ) -> Bool { - _precondition(lhs.base.object === rhs.base.object, - "Comparing indexes from different dictionaries") - return lhs.currentKeyIndex == rhs.currentKeyIndex - } -} - -extension _CocoaDictionary.Index: Comparable { - @inlinable - internal static func < ( - lhs: _CocoaDictionary.Index, - rhs: _CocoaDictionary.Index - ) -> Bool { - _precondition(lhs.base.object === rhs.base.object, - "Comparing indexes from different dictionaries") - return lhs.currentKeyIndex < rhs.currentKeyIndex - } -} -#endif - -extension Dictionary { - /// The position of a key-value pair in a dictionary. - /// - /// Dictionary has two subscripting interfaces: - /// - /// 1. Subscripting with a key, yielding an optional value: - /// - /// v = d[k]! - /// - /// 2. Subscripting with an index, yielding a key-value pair: - /// - /// (k, v) = d[i] - @_fixed_layout - public struct Index { - // Index for native dictionary is efficient. Index for bridged NSDictionary - // is not, because neither NSEnumerator nor fast enumeration support moving - // backwards. Even if they did, there is another issue: NSEnumerator does - // not support NSCopying, and fast enumeration does not document that it is - // safe to copy the state. So, we cannot implement Index that is a value - // type for bridged NSDictionary in terms of Cocoa enumeration facilities. - - @_frozen - @usableFromInline - internal enum _Variant { - case native(_NativeDictionary.Index) -#if _runtime(_ObjC) - case cocoa(_CocoaDictionary.Index) -#endif - } - - @usableFromInline - internal var _variant: _Variant - - @inlinable - @inline(__always) - internal init(_variant: _Variant) { - self._variant = _variant - } - - @inlinable - @inline(__always) - internal init(_native index: _NativeDictionary.Index) { - self.init(_variant: .native(index)) - } - -#if _runtime(_ObjC) - @inlinable - @inline(__always) - internal init(_cocoa index: _CocoaDictionary.Index) { - self.init(_variant: .cocoa(index)) - } -#endif - } -} - -extension Dictionary.Index { -#if _runtime(_ObjC) - @usableFromInline @_transparent - internal var _guaranteedNative: Bool { - return _canBeClass(Key.self) == 0 || _canBeClass(Value.self) == 0 - } - - // Allow the optimizer to consider the surrounding code unreachable if Element - // is guaranteed to be native. - @usableFromInline @_transparent - internal func _cocoaPath() { - if _guaranteedNative { - _conditionallyUnreachable() - } - } -#endif - - @usableFromInline @_transparent - internal var _asNative: _NativeDictionary.Index { - switch _variant { - case .native(let nativeIndex): - return nativeIndex -#if _runtime(_ObjC) - case .cocoa: - _sanityCheckFailure("internal error: does not contain a native index") -#endif - } - } - -#if _runtime(_ObjC) - @usableFromInline @_transparent - internal var _asCocoa: _CocoaDictionary.Index { - switch _variant { - case .native: - _sanityCheckFailure("internal error: does not contain a Cocoa index") - case .cocoa(let cocoaIndex): - return cocoaIndex - } - } -#endif -} - -extension Dictionary.Index: Equatable { - @inlinable - public static func == ( - lhs: Dictionary.Index, - rhs: Dictionary.Index - ) -> Bool { - switch (lhs._variant, rhs._variant) { - case (.native(let lhsNative), .native(let rhsNative)): - return lhsNative == rhsNative - #if _runtime(_ObjC) - case (.cocoa(let lhsCocoa), .cocoa(let rhsCocoa)): - lhs._cocoaPath() - return lhsCocoa == rhsCocoa - default: - _preconditionFailure("Comparing indexes from different dictionaries") - #endif - } - } -} - -extension Dictionary.Index: Comparable { - @inlinable - public static func < ( - lhs: Dictionary.Index, - rhs: Dictionary.Index - ) -> Bool { - switch (lhs._variant, rhs._variant) { - case (.native(let lhsNative), .native(let rhsNative)): - return lhsNative < rhsNative - #if _runtime(_ObjC) - case (.cocoa(let lhsCocoa), .cocoa(let rhsCocoa)): - lhs._cocoaPath() - return lhsCocoa < rhsCocoa - default: - _preconditionFailure("Comparing indexes from different dictionaries") - #endif - } - } -} - -extension Dictionary.Index: Hashable { - @inlinable - public func hash(into hasher: inout Hasher) { - #if _runtime(_ObjC) - switch _variant { - case .native(let nativeIndex): - hasher.combine(0 as UInt8) - hasher.combine(nativeIndex.bucket) - case .cocoa(let cocoaIndex): - _cocoaPath() - hasher.combine(1 as UInt8) - hasher.combine(cocoaIndex.currentKeyIndex) - } - #else - hasher.combine(_asNative.bucket) - #endif - } -} - -extension _NativeDictionary: Sequence { - @usableFromInline - @_fixed_layout - internal struct Iterator { - // The iterator is iterating over a frozen view of the collection state, so - // it keeps its own reference to the dictionary. - @usableFromInline - internal let base: _NativeDictionary - @usableFromInline - internal var iterator: _HashTable.Iterator - - @inlinable - init(_ base: _NativeDictionary) { - self.base = base - self.iterator = base.hashTable.makeIterator() - } - } - - @inlinable - internal func makeIterator() -> Iterator { - return Iterator(self) - } -} - -extension _NativeDictionary.Iterator: IteratorProtocol { - @usableFromInline - internal typealias Element = (key: Key, value: Value) - - @inlinable - internal mutating func next() -> Element? { - guard let index = iterator.next() else { return nil } - let key = base.uncheckedKey(at: index) - let value = base.uncheckedValue(at: index) - return (key, value) - } -} - -#if _runtime(_ObjC) -extension _CocoaDictionary: Sequence { - @usableFromInline - final internal class Iterator { - // Cocoa Dictionary iterator has to be a class, otherwise we cannot - // guarantee that the fast enumeration struct is pinned to a certain memory - // location. - - // This stored property should be stored at offset zero. There's code below - // relying on this. - internal var _fastEnumerationState: _SwiftNSFastEnumerationState = - _makeSwiftNSFastEnumerationState() - - // This stored property should be stored right after - // `_fastEnumerationState`. There's code below relying on this. - internal var _fastEnumerationStackBuf = _CocoaFastEnumerationStackBuf() - - internal let base: _CocoaDictionary - - internal var _fastEnumerationStatePtr: - UnsafeMutablePointer<_SwiftNSFastEnumerationState> { - return _getUnsafePointerToStoredProperties(self).assumingMemoryBound( - to: _SwiftNSFastEnumerationState.self) - } - - internal var _fastEnumerationStackBufPtr: - UnsafeMutablePointer<_CocoaFastEnumerationStackBuf> { - return UnsafeMutableRawPointer(_fastEnumerationStatePtr + 1) - .assumingMemoryBound(to: _CocoaFastEnumerationStackBuf.self) - } - - // These members have to be word-sized integers, they cannot be limited to - // Int8 just because our storage holds 16 elements: fast enumeration is - // allowed to return inner pointers to the container, which can be much - // larger. - internal var itemIndex: Int = 0 - internal var itemCount: Int = 0 - - internal init(_ base: _CocoaDictionary) { - self.base = base - } - } - - @usableFromInline - @_effects(releasenone) - internal func makeIterator() -> Iterator { - return Iterator(self) - } -} - -extension _CocoaDictionary.Iterator: IteratorProtocol { - @usableFromInline - internal typealias Element = (key: AnyObject, value: AnyObject) - - @usableFromInline - internal func next() -> Element? { - if itemIndex < 0 { - return nil - } - let base = self.base - if itemIndex == itemCount { - let stackBufCount = _fastEnumerationStackBuf.count - // We can't use `withUnsafeMutablePointer` here to get pointers to - // properties, because doing so might introduce a writeback storage, but - // fast enumeration relies on the pointer identity of the enumeration - // state struct. - itemCount = base.object.countByEnumerating( - with: _fastEnumerationStatePtr, - objects: UnsafeMutableRawPointer(_fastEnumerationStackBufPtr) - .assumingMemoryBound(to: AnyObject.self), - count: stackBufCount) - if itemCount == 0 { - itemIndex = -1 - return nil - } - itemIndex = 0 - } - let itemsPtrUP = - UnsafeMutableRawPointer(_fastEnumerationState.itemsPtr!) - .assumingMemoryBound(to: AnyObject.self) - let itemsPtr = _UnmanagedAnyObjectArray(itemsPtrUP) - let key: AnyObject = itemsPtr[itemIndex] - itemIndex += 1 - let value: AnyObject = base.object.object(forKey: key)! - return (key, value) - } -} -#endif - -extension Dictionary { - /// An iterator over the members of a `Dictionary`. - @_fixed_layout - public struct Iterator { - // Dictionary has a separate IteratorProtocol and Index because of - // efficiency and implementability reasons. - // - // Native dictionaries have efficient indices. - // Bridged NSDictionary instances don't. - // - // Even though fast enumeration is not suitable for implementing - // Index, which is multi-pass, it is suitable for implementing a - // IteratorProtocol, which is being consumed as iteration proceeds. - - @usableFromInline - @_frozen - internal enum _Variant { - case native(_NativeDictionary.Iterator) -#if _runtime(_ObjC) - case cocoa(_CocoaDictionary.Iterator) -#endif - } - - @usableFromInline - internal var _variant: _Variant - - @inlinable - internal init(_variant: _Variant) { - self._variant = _variant - } - - @inlinable - internal init(_native: _NativeDictionary.Iterator) { - self.init(_variant: .native(_native)) - } - -#if _runtime(_ObjC) - @inlinable - internal init(_cocoa: _CocoaDictionary.Iterator) { - self.init(_variant: .cocoa(_cocoa)) - } -#endif - } -} - -extension Dictionary.Iterator { -#if _runtime(_ObjC) - @usableFromInline @_transparent - internal var _guaranteedNative: Bool { - return _canBeClass(Key.self) == 0 || _canBeClass(Value.self) == 0 - } - - /// Allow the optimizer to consider the surrounding code unreachable if - /// Dictionary is guaranteed to be native. - @usableFromInline @_transparent - internal func _cocoaPath() { - if _guaranteedNative { - _conditionallyUnreachable() - } - } -#endif - - @usableFromInline @_transparent - internal var _asNative: _NativeDictionary.Iterator { - get { - switch _variant { - case .native(let nativeIterator): - return nativeIterator -#if _runtime(_ObjC) - case .cocoa: - _sanityCheckFailure("internal error: does not contain a native index") -#endif - } - } - set { - self._variant = .native(newValue) - } - } -} +} extension Dictionary.Iterator: IteratorProtocol { /// Advances to the next element and returns it, or `nil` if no next element @@ -4218,43 +1919,6 @@ extension Dictionary: CustomReflectable { } } -/// Initializes a `Dictionary` from unique members. -/// -/// Using a builder can be faster than inserting members into an empty -/// `Dictionary`. -@_fixed_layout -public // SPI(Foundation) -struct _DictionaryBuilder { - @usableFromInline - internal var _target: _NativeDictionary - @usableFromInline - internal let _requestedCount: Int - - @inlinable - public init(count: Int) { - _target = _NativeDictionary(capacity: count) - _requestedCount = count - } - - @inlinable - public mutating func add(key newKey: Key, value: Value) { - _target.insertNew(key: newKey, value: value) - } - - @inlinable - public mutating func take() -> Dictionary { - _precondition(_target.capacity > 0 || _requestedCount == 0, - "Cannot take the result twice") - _precondition(_target.count == _requestedCount, - "The number of members added does not match the promised count") - - // Prevent taking the result twice. - var result = _NativeDictionary() - swap(&result, &_target) - return Dictionary(_native: result) - } -} - extension Dictionary { /// Removes and returns the first key-value pair of the dictionary if the /// dictionary isn't empty. @@ -4299,46 +1963,6 @@ extension Dictionary { } } -//===--- Bridging ---------------------------------------------------------===// - -#if _runtime(_ObjC) -extension Dictionary { - @inlinable - public func _bridgeToObjectiveCImpl() -> _NSDictionaryCore { - switch _variant { - case .native(let nativeDictionary): - return nativeDictionary.bridged() - case .cocoa(let cocoaDictionary): - return cocoaDictionary.object - } - } - - /// Returns the native Dictionary hidden inside this NSDictionary; - /// returns nil otherwise. - public static func _bridgeFromObjectiveCAdoptingNativeStorageOf( - _ s: AnyObject - ) -> Dictionary? { - - // Try all three NSDictionary impls that we currently provide. - - if let deferred = s as? _SwiftDeferredNSDictionary { - return Dictionary(_native: deferred.native) - } - - if let nativeStorage = s as? _DictionaryStorage { - return Dictionary(_native: _NativeDictionary(nativeStorage)) - } - - if s === _RawDictionaryStorage.empty { - return Dictionary() - } - - // FIXME: what if `s` is native storage, but for different key/value type? - return nil - } -} -#endif - public typealias DictionaryIndex = Dictionary.Index public typealias DictionaryIterator = diff --git a/stdlib/public/core/DictionaryBridging.swift b/stdlib/public/core/DictionaryBridging.swift new file mode 100644 index 0000000000000..ab840c9015360 --- /dev/null +++ b/stdlib/public/core/DictionaryBridging.swift @@ -0,0 +1,758 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#if _runtime(_ObjC) + +import SwiftShims + +/// Equivalent to `NSDictionary.allKeys`, but does not leave objects on the +/// autorelease pool. +@inlinable +internal func _stdlib_NSDictionary_allKeys( + _ nsd: _NSDictionary +) -> _HeapBuffer { + let count = nsd.count + let storage = _HeapBuffer( + _HeapBufferStorage.self, count, count) + + nsd.getObjects(nil, andKeys: storage.baseAddress, count: count) + return storage +} + +extension _NativeDictionary { // Bridging + @usableFromInline + internal func bridged() -> _NSDictionary { + // We can zero-cost bridge if our keys are verbatim + // or if we're the empty singleton. + + // Temporary var for SOME type safety before a cast. + let nsDictionary: _NSDictionaryCore + + if _storage === _RawDictionaryStorage.empty || count == 0 { + nsDictionary = _RawDictionaryStorage.empty + } else if _isBridgedVerbatimToObjectiveC(Key.self), + _isBridgedVerbatimToObjectiveC(Value.self) { + nsDictionary = unsafeDowncast( + _storage, + to: _DictionaryStorage.self) + } else { + nsDictionary = _SwiftDeferredNSDictionary(self) + } + + // Cast from "minimal NSDictionary" to "NSDictionary" + // Note that if you actually ask Swift for this cast, it will fail. + // Never trust a shadow protocol! + return unsafeBitCast(nsDictionary, to: _NSDictionary.self) + } +} + +/// An NSEnumerator that works with any _NativeDictionary of +/// verbatim bridgeable elements. Used by the various NSDictionary impls. +final internal class _SwiftDictionaryNSEnumerator + : _SwiftNativeNSEnumerator, _NSEnumerator { + + @nonobjc internal var base: _NativeDictionary + @nonobjc internal var bridgedKeys: _BridgingHashBuffer? + @nonobjc internal var nextIndex: _NativeDictionary.Index + @nonobjc internal var endIndex: _NativeDictionary.Index + + @objc + internal override required init() { + _sanityCheckFailure("don't call this designated initializer") + } + + internal init(_ base: _NativeDictionary) { + _sanityCheck(_isBridgedVerbatimToObjectiveC(Key.self)) + self.base = base + self.bridgedKeys = nil + self.nextIndex = base.startIndex + self.endIndex = base.endIndex + } + + @nonobjc + internal init(_ deferred: _SwiftDeferredNSDictionary) { + _sanityCheck(!_isBridgedVerbatimToObjectiveC(Key.self)) + self.base = deferred.native + self.bridgedKeys = deferred.bridgeKeys() + self.nextIndex = base.startIndex + self.endIndex = base.endIndex + } + + private func bridgedKey(at index: _HashTable.Index) -> AnyObject { + _sanityCheck(base.hashTable.isOccupied(index)) + if let bridgedKeys = self.bridgedKeys { + return bridgedKeys[index] + } + return _bridgeAnythingToObjectiveC(base.uncheckedKey(at: index)) + } + + @objc + internal func nextObject() -> AnyObject? { + if nextIndex == endIndex { + return nil + } + let index = nextIndex + nextIndex = base.index(after: nextIndex) + return self.bridgedKey(at: index) + } + + @objc(countByEnumeratingWithState:objects:count:) + internal func countByEnumerating( + with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>, + objects: UnsafeMutablePointer, + count: Int + ) -> Int { + var theState = state.pointee + if theState.state == 0 { + theState.state = 1 // Arbitrary non-zero value. + theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects) + theState.mutationsPtr = _fastEnumerationStorageMutationsPtr + } + + if nextIndex == endIndex { + state.pointee = theState + return 0 + } + + // Return only a single element so that code can start iterating via fast + // enumeration, terminate it, and continue via NSEnumerator. + let unmanagedObjects = _UnmanagedAnyObjectArray(objects) + unmanagedObjects[0] = self.bridgedKey(at: nextIndex) + nextIndex = base.index(after: nextIndex) + state.pointee = theState + return 1 + } +} + +/// This class exists for Objective-C bridging. It holds a reference to a +/// _NativeDictionary, and can be upcast to NSSelf when bridging is +/// necessary. This is the fallback implementation for situations where +/// toll-free bridging isn't possible. On first access, a _NativeDictionary +/// of AnyObject will be constructed containing all the bridged elements. +final internal class _SwiftDeferredNSDictionary + : _SwiftNativeNSDictionary, _NSDictionaryCore { + + // This stored property must be stored at offset zero. We perform atomic + // operations on it. + // + // Do not access this property directly. + @nonobjc + private var _bridgedKeys_DoNotUse: AnyObject? + + // This stored property must be stored at offset one. We perform atomic + // operations on it. + // + // Do not access this property directly. + @nonobjc + private var _bridgedValues_DoNotUse: AnyObject? + + /// The unbridged elements. + internal var native: _NativeDictionary + + internal init(_ native: _NativeDictionary) { + _sanityCheck(native.count > 0) + _sanityCheck(!_isBridgedVerbatimToObjectiveC(Key.self) || + !_isBridgedVerbatimToObjectiveC(Value.self)) + self.native = native + super.init() + } + + @objc + internal required init( + objects: UnsafePointer, + forKeys: UnsafeRawPointer, + count: Int + ) { + _sanityCheckFailure("don't call this designated initializer") + } + + @nonobjc + private var _bridgedKeysPtr: UnsafeMutablePointer { + return _getUnsafePointerToStoredProperties(self) + .assumingMemoryBound(to: Optional.self) + } + + @nonobjc + private var _bridgedValuesPtr: UnsafeMutablePointer { + return _bridgedKeysPtr + 1 + } + + /// The buffer for bridged keys, if present. + @nonobjc + private var _bridgedKeys: _BridgingHashBuffer? { + guard let ref = _stdlib_atomicLoadARCRef(object: _bridgedKeysPtr) else { + return nil + } + return unsafeDowncast(ref, to: _BridgingHashBuffer.self) + } + + /// The buffer for bridged values, if present. + @nonobjc + private var _bridgedValues: _BridgingHashBuffer? { + guard let ref = _stdlib_atomicLoadARCRef(object: _bridgedValuesPtr) else { + return nil + } + return unsafeDowncast(ref, to: _BridgingHashBuffer.self) + } + + /// Attach a buffer for bridged Dictionary keys. + @nonobjc + private func _initializeBridgedKeys(_ storage: _BridgingHashBuffer) { + _stdlib_atomicInitializeARCRef(object: _bridgedKeysPtr, desired: storage) + } + + /// Attach a buffer for bridged Dictionary values. + @nonobjc + private func _initializeBridgedValues(_ storage: _BridgingHashBuffer) { + _stdlib_atomicInitializeARCRef(object: _bridgedValuesPtr, desired: storage) + } + + @nonobjc + internal func bridgeKeys() -> _BridgingHashBuffer? { + if _isBridgedVerbatimToObjectiveC(Key.self) { return nil } + if let bridgedKeys = _bridgedKeys { return bridgedKeys } + + // Allocate and initialize heap storage for bridged keys. + let bridged = _BridgingHashBuffer.allocate( + owner: native._storage, + hashTable: native.hashTable) + for index in native.hashTable { + let object = _bridgeAnythingToObjectiveC(native.uncheckedKey(at: index)) + bridged.initialize(at: index, to: object) + } + + // Atomically put the bridged keys in place. + _initializeBridgedKeys(bridged) + return _bridgedKeys! + } + + @nonobjc + internal func bridgeValues() -> _BridgingHashBuffer? { + if _isBridgedVerbatimToObjectiveC(Value.self) { return nil } + if let bridgedValues = _bridgedValues { return bridgedValues } + + // Allocate and initialize heap storage for bridged values. + let bridged = _BridgingHashBuffer.allocate( + owner: native._storage, + hashTable: native.hashTable) + for index in native.hashTable { + let object = _bridgeAnythingToObjectiveC(native.uncheckedValue(at: index)) + bridged.initialize(at: index, to: object) + } + + // Atomically put the bridged values in place. + _initializeBridgedValues(bridged) + return _bridgedValues! + } + + @usableFromInline + internal typealias Index = _HashTable.Index + + @objc(copyWithZone:) + internal func copy(with zone: _SwiftNSZone?) -> AnyObject { + // Instances of this class should be visible outside of standard library as + // having `NSDictionary` type, which is immutable. + return self + } + + @objc(objectForKey:) + internal func object(forKey aKey: AnyObject) -> AnyObject? { + guard let nativeKey = _conditionallyBridgeFromObjectiveC(aKey, Key.self) + else { return nil } + + let (index, found) = native.find(nativeKey) + guard found else { return nil } + if let bridgedValues = bridgeValues() { + return bridgedValues[index] + } + return _bridgeAnythingToObjectiveC(native.uncheckedValue(at: index)) + } + + @inline(__always) + private func _key( + at index: Index, + bridgedKeys: _BridgingHashBuffer? + ) -> AnyObject { + if let bridgedKeys = bridgedKeys { + return bridgedKeys[index] + } + return _bridgeAnythingToObjectiveC(native.uncheckedKey(at: index)) + } + + @inline(__always) + private func _value( + at index: Index, + bridgedValues: _BridgingHashBuffer? + ) -> AnyObject { + if let bridgedValues = bridgedValues { + return bridgedValues[index] + } + return _bridgeAnythingToObjectiveC(native.uncheckedValue(at: index)) + } + + @objc + internal func keyEnumerator() -> _NSEnumerator { + if _isBridgedVerbatimToObjectiveC(Key.self) { + return _SwiftDictionaryNSEnumerator(native) + } + return _SwiftDictionaryNSEnumerator(self) + } + + @objc(getObjects:andKeys:count:) + internal func getObjects( + _ objects: UnsafeMutablePointer?, + andKeys keys: UnsafeMutablePointer?, + count: Int + ) { + _precondition(count >= 0, "Invalid count") + guard count > 0 else { return } + let bridgedKeys = bridgeKeys() + let bridgedValues = bridgeValues() + var i = 0 // Current position in the output buffers + let bucketCount = native._storage._bucketCount + + defer { _fixLifetime(self) } + + switch (_UnmanagedAnyObjectArray(keys), _UnmanagedAnyObjectArray(objects)) { + case (let unmanagedKeys?, let unmanagedObjects?): + for index in native.hashTable { + unmanagedKeys[i] = _key(at: index, bridgedKeys: bridgedKeys) + unmanagedObjects[i] = _value(at: index, bridgedValues: bridgedValues) + i += 1 + guard i < count else { break } + } + case (let unmanagedKeys?, nil): + for index in native.hashTable { + unmanagedKeys[i] = _key(at: index, bridgedKeys: bridgedKeys) + i += 1 + guard i < count else { break } + } + case (nil, let unmanagedObjects?): + for index in native.hashTable { + unmanagedObjects[i] = _value(at: index, bridgedValues: bridgedValues) + i += 1 + guard i < count else { break } + } + case (nil, nil): + // Do nothing + break + } + } + + @objc(enumerateKeysAndObjectsWithOptions:usingBlock:) + internal func enumerateKeysAndObjects( + options: Int, + using block: @convention(block) ( + Unmanaged, + Unmanaged, + UnsafeMutablePointer + ) -> Void) { + let bridgedKeys = bridgeKeys() + let bridgedValues = bridgeValues() + + defer { _fixLifetime(self) } + + var stop: UInt8 = 0 + for index in native.hashTable { + let key = _key(at: index, bridgedKeys: bridgedKeys) + let value = _value(at: index, bridgedValues: bridgedValues) + block( + Unmanaged.passUnretained(key), + Unmanaged.passUnretained(value), + &stop) + if stop != 0 { return } + } + } + + @objc + internal var count: Int { + return native.count + } + + @objc(countByEnumeratingWithState:objects:count:) + internal func countByEnumerating( + with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>, + objects: UnsafeMutablePointer?, + count: Int + ) -> Int { + var theState = state.pointee + if theState.state == 0 { + theState.state = 1 // Arbitrary non-zero value. + theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects) + theState.mutationsPtr = _fastEnumerationStorageMutationsPtr + theState.extra.0 = CUnsignedLong(native.startIndex.bucket) + } + + // Test 'objects' rather than 'count' because (a) this is very rare anyway, + // and (b) the optimizer should then be able to optimize away the + // unwrapping check below. + if _slowPath(objects == nil) { + return 0 + } + + let unmanagedObjects = _UnmanagedAnyObjectArray(objects!) + var index = _HashTable.Index(bucket: Int(theState.extra.0)) + let endIndex = native.endIndex + _precondition(index == endIndex || native.hashTable.isOccupied(index)) + var stored = 0 + + // Only need to bridge once, so we can hoist it out of the loop. + let bridgedKeys = bridgeKeys() + for i in 0.. Bool { + return _stdlib_NSObject_isEqual(lhs.object, rhs.object) + } +} + +extension _CocoaDictionary: _DictionaryBuffer { + @usableFromInline + internal typealias Key = AnyObject + @usableFromInline + internal typealias Value = AnyObject + + @inlinable + internal var startIndex: Index { + return Index(self, startIndex: ()) + } + + @inlinable + internal var endIndex: Index { + return Index(self, endIndex: ()) + } + + @inlinable + internal func index(after i: Index) -> Index { + var i = i + formIndex(after: &i) + return i + } + + @usableFromInline + @_effects(releasenone) + internal func formIndex(after i: inout Index) { + _precondition(i.base.object === self.object, "Invalid index") + _precondition(i.currentKeyIndex < i.allKeys.value, + "Cannot increment endIndex") + i.currentKeyIndex += 1 + } + + @usableFromInline + internal func index(forKey key: Key) -> Index? { + // Fast path that does not involve creating an array of all keys. In case + // the key is present, this lookup is a penalty for the slow path, but the + // potential savings are significant: we could skip a memory allocation and + // a linear search. + if lookup(key) == nil { + return nil + } + + let allKeys = _stdlib_NSDictionary_allKeys(object) + var keyIndex = -1 + for i in 0..= 0, + "Key was found in fast path, but not found later?") + return Index(self, allKeys, keyIndex) + } + + @inlinable + internal var count: Int { + return object.count + } + + @inlinable + @inline(__always) + internal func contains(_ key: Key) -> Bool { + return object.object(forKey: key) != nil + } + + @inlinable + @inline(__always) + internal func lookup(_ key: Key) -> Value? { + return object.object(forKey: key) + } + + @inlinable + @inline(__always) + internal func lookup(_ index: Index) -> (key: Key, value: Value) { + _precondition(index.base.object === self.object, "Invalid index") + let key: Key = index.allKeys[index.currentKeyIndex] + let value: Value = index.base.object.object(forKey: key)! + return (key, value) + } + + @inlinable + @inline(__always) + func key(at index: Index) -> Key { + _precondition(index.base.object === self.object, "Invalid index") + return index.allKeys[index.currentKeyIndex] + } + + @inlinable + @inline(__always) + func value(at index: Index) -> Value { + _precondition(index.base.object === self.object, "Invalid index") + let key = index.allKeys[index.currentKeyIndex] + return index.base.object.object(forKey: key)! + } +} + +extension _CocoaDictionary { + @inlinable + internal func mapValues( + _ transform: (Value) throws -> T + ) rethrows -> _NativeDictionary { + var result = _NativeDictionary(capacity: self.count) + for (cocoaKey, cocoaValue) in self { + let key = _forceBridgeFromObjectiveC(cocoaKey, Key.self) + let value = _forceBridgeFromObjectiveC(cocoaValue, Value.self) + try result.insertNew(key: key, value: transform(value)) + } + return result + } +} + +extension _CocoaDictionary { + @_fixed_layout // FIXME(sil-serialize-all) + @usableFromInline + internal struct Index { + // Assumption: we rely on NSDictionary.getObjects when being + // repeatedly called on the same NSDictionary, returning items in the same + // order every time. + // Similarly, the same assumption holds for NSSet.allObjects. + + /// A reference to the NSDictionary, which owns members in `allObjects`, + /// or `allKeys`, for NSSet and NSDictionary respectively. + @usableFromInline // FIXME(sil-serialize-all) + internal let base: _CocoaDictionary + // FIXME: swift-3-indexing-model: try to remove the cocoa reference, but + // make sure that we have a safety check for accessing `allKeys`. Maybe + // move both into the dictionary/set itself. + + /// An unowned array of keys. + @usableFromInline // FIXME(sil-serialize-all) + internal var allKeys: _HeapBuffer + + /// Index into `allKeys` + @usableFromInline // FIXME(sil-serialize-all) + internal var currentKeyIndex: Int + + @inlinable // FIXME(sil-serialize-all) + internal init(_ base: _CocoaDictionary, startIndex: ()) { + self.base = base + self.allKeys = _stdlib_NSDictionary_allKeys(base.object) + self.currentKeyIndex = 0 + } + + @inlinable // FIXME(sil-serialize-all) + internal init(_ base: _CocoaDictionary, endIndex: ()) { + self.base = base + self.allKeys = _stdlib_NSDictionary_allKeys(base.object) + self.currentKeyIndex = allKeys.value + } + + @inlinable // FIXME(sil-serialize-all) + internal init( + _ base: _CocoaDictionary, + _ allKeys: _HeapBuffer, + _ currentKeyIndex: Int + ) { + self.base = base + self.allKeys = allKeys + self.currentKeyIndex = currentKeyIndex + } + } +} + +extension _CocoaDictionary.Index: Equatable { + @inlinable + internal static func == ( + lhs: _CocoaDictionary.Index, + rhs: _CocoaDictionary.Index + ) -> Bool { + _precondition(lhs.base.object === rhs.base.object, + "Comparing indexes from different dictionaries") + return lhs.currentKeyIndex == rhs.currentKeyIndex + } +} + +extension _CocoaDictionary.Index: Comparable { + @inlinable + internal static func < ( + lhs: _CocoaDictionary.Index, + rhs: _CocoaDictionary.Index + ) -> Bool { + _precondition(lhs.base.object === rhs.base.object, + "Comparing indexes from different dictionaries") + return lhs.currentKeyIndex < rhs.currentKeyIndex + } +} + +extension _CocoaDictionary: Sequence { + @usableFromInline + final internal class Iterator { + // Cocoa Dictionary iterator has to be a class, otherwise we cannot + // guarantee that the fast enumeration struct is pinned to a certain memory + // location. + + // This stored property should be stored at offset zero. There's code below + // relying on this. + internal var _fastEnumerationState: _SwiftNSFastEnumerationState = + _makeSwiftNSFastEnumerationState() + + // This stored property should be stored right after + // `_fastEnumerationState`. There's code below relying on this. + internal var _fastEnumerationStackBuf = _CocoaFastEnumerationStackBuf() + + internal let base: _CocoaDictionary + + internal var _fastEnumerationStatePtr: + UnsafeMutablePointer<_SwiftNSFastEnumerationState> { + return _getUnsafePointerToStoredProperties(self).assumingMemoryBound( + to: _SwiftNSFastEnumerationState.self) + } + + internal var _fastEnumerationStackBufPtr: + UnsafeMutablePointer<_CocoaFastEnumerationStackBuf> { + return UnsafeMutableRawPointer(_fastEnumerationStatePtr + 1) + .assumingMemoryBound(to: _CocoaFastEnumerationStackBuf.self) + } + + // These members have to be word-sized integers, they cannot be limited to + // Int8 just because our storage holds 16 elements: fast enumeration is + // allowed to return inner pointers to the container, which can be much + // larger. + internal var itemIndex: Int = 0 + internal var itemCount: Int = 0 + + internal init(_ base: _CocoaDictionary) { + self.base = base + } + } + + @usableFromInline + @_effects(releasenone) + internal func makeIterator() -> Iterator { + return Iterator(self) + } +} + +extension _CocoaDictionary.Iterator: IteratorProtocol { + @usableFromInline + internal typealias Element = (key: AnyObject, value: AnyObject) + + @usableFromInline + internal func next() -> Element? { + if itemIndex < 0 { + return nil + } + let base = self.base + if itemIndex == itemCount { + let stackBufCount = _fastEnumerationStackBuf.count + // We can't use `withUnsafeMutablePointer` here to get pointers to + // properties, because doing so might introduce a writeback storage, but + // fast enumeration relies on the pointer identity of the enumeration + // state struct. + itemCount = base.object.countByEnumerating( + with: _fastEnumerationStatePtr, + objects: UnsafeMutableRawPointer(_fastEnumerationStackBufPtr) + .assumingMemoryBound(to: AnyObject.self), + count: stackBufCount) + if itemCount == 0 { + itemIndex = -1 + return nil + } + itemIndex = 0 + } + let itemsPtrUP = + UnsafeMutableRawPointer(_fastEnumerationState.itemsPtr!) + .assumingMemoryBound(to: AnyObject.self) + let itemsPtr = _UnmanagedAnyObjectArray(itemsPtrUP) + let key: AnyObject = itemsPtr[itemIndex] + itemIndex += 1 + let value: AnyObject = base.object.object(forKey: key)! + return (key, value) + } +} + +//===--- Bridging ---------------------------------------------------------===// + +extension Dictionary { + @inlinable + public func _bridgeToObjectiveCImpl() -> _NSDictionaryCore { + switch _variant { + case .native(let nativeDictionary): + return nativeDictionary.bridged() + case .cocoa(let cocoaDictionary): + return cocoaDictionary.object + } + } + + /// Returns the native Dictionary hidden inside this NSDictionary; + /// returns nil otherwise. + public static func _bridgeFromObjectiveCAdoptingNativeStorageOf( + _ s: AnyObject + ) -> Dictionary? { + + // Try all three NSDictionary impls that we currently provide. + + if let deferred = s as? _SwiftDeferredNSDictionary { + return Dictionary(_native: deferred.native) + } + + if let nativeStorage = s as? _DictionaryStorage { + return Dictionary(_native: _NativeDictionary(nativeStorage)) + } + + if s === _RawDictionaryStorage.empty { + return Dictionary() + } + + // FIXME: what if `s` is native storage, but for different key/value type? + return nil + } +} + +#endif // _runtime(_ObjC) diff --git a/stdlib/public/core/DictionaryBuilder.swift b/stdlib/public/core/DictionaryBuilder.swift new file mode 100644 index 0000000000000..384a7d757d4bb --- /dev/null +++ b/stdlib/public/core/DictionaryBuilder.swift @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// Initializes a `Dictionary` from unique members. +/// +/// Using a builder can be faster than inserting members into an empty +/// `Dictionary`. +@_fixed_layout +public // SPI(Foundation) +struct _DictionaryBuilder { + @usableFromInline + internal var _target: _NativeDictionary + @usableFromInline + internal let _requestedCount: Int + + @inlinable + public init(count: Int) { + _target = _NativeDictionary(capacity: count) + _requestedCount = count + } + + @inlinable + public mutating func add(key newKey: Key, value: Value) { + _target.insertNew(key: newKey, value: value) + } + + @inlinable + public mutating func take() -> Dictionary { + _precondition(_target.capacity > 0 || _requestedCount == 0, + "Cannot take the result twice") + _precondition(_target.count == _requestedCount, + "The number of members added does not match the promised count") + + // Prevent taking the result twice. + var result = _NativeDictionary() + swap(&result, &_target) + return Dictionary(_native: result) + } +} diff --git a/stdlib/public/core/DictionaryCasting.swift b/stdlib/public/core/DictionaryCasting.swift new file mode 100644 index 0000000000000..50403ee41c7e9 --- /dev/null +++ b/stdlib/public/core/DictionaryCasting.swift @@ -0,0 +1,108 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +//===--- Compiler conversion/casting entry points for Dictionary ----===// + +/// Perform a non-bridged upcast that always succeeds. +/// +/// - Precondition: `BaseKey` and `BaseValue` are base classes or base `@objc` +/// protocols (such as `AnyObject`) of `DerivedKey` and `DerivedValue`, +/// respectively. +@inlinable +public func _dictionaryUpCast( + _ source: Dictionary +) -> Dictionary { + var result = Dictionary(minimumCapacity: source.count) + + for (k, v) in source { + result[k as! BaseKey] = (v as! BaseValue) + } + return result +} + +/// Called by the casting machinery. +@_silgen_name("_swift_dictionaryDownCastIndirect") +internal func _dictionaryDownCastIndirect( + _ source: UnsafePointer>, + _ target: UnsafeMutablePointer>) { + target.initialize(to: _dictionaryDownCast(source.pointee)) +} + +/// Implements a forced downcast. This operation should have O(1) complexity. +/// +/// The cast can fail if bridging fails. The actual checks and bridging can be +/// deferred. +/// +/// - Precondition: `DerivedKey` is a subtype of `BaseKey`, `DerivedValue` is +/// a subtype of `BaseValue`, and all of these types are reference types. +@inlinable +public func _dictionaryDownCast( + _ source: Dictionary +) -> Dictionary { + +#if _runtime(_ObjC) + if _isClassOrObjCExistential(BaseKey.self) + && _isClassOrObjCExistential(BaseValue.self) + && _isClassOrObjCExistential(DerivedKey.self) + && _isClassOrObjCExistential(DerivedValue.self) { + + switch source._variant { + case .native(let native): + // Note: it is safe to treat the buffer as immutable here because + // Dictionary will not mutate buffer with reference count greater than 1. + return Dictionary(_immutableCocoaDictionary: native.bridged()) + case .cocoa(let cocoa): + return Dictionary(_immutableCocoaDictionary: cocoa.object) + } + } +#endif + return _dictionaryDownCastConditional(source)! +} + +/// Called by the casting machinery. +@_silgen_name("_swift_dictionaryDownCastConditionalIndirect") +internal func _dictionaryDownCastConditionalIndirect( + _ source: UnsafePointer>, + _ target: UnsafeMutablePointer> +) -> Bool { + if let result: Dictionary + = _dictionaryDownCastConditional(source.pointee) { + target.initialize(to: result) + return true + } + return false +} + +/// Implements a conditional downcast. +/// +/// If the cast fails, the function returns `nil`. All checks should be +/// performed eagerly. +/// +/// - Precondition: `DerivedKey` is a subtype of `BaseKey`, `DerivedValue` is +/// a subtype of `BaseValue`, and all of these types are reference types. +@inlinable +public func _dictionaryDownCastConditional< + BaseKey, BaseValue, DerivedKey, DerivedValue +>( + _ source: Dictionary +) -> Dictionary? { + + var result = Dictionary() + for (k, v) in source { + guard let k1 = k as? DerivedKey, let v1 = v as? DerivedValue + else { return nil } + result[k1] = v1 + } + return result +} diff --git a/stdlib/public/core/DictionaryStorage.swift b/stdlib/public/core/DictionaryStorage.swift new file mode 100644 index 0000000000000..2001aa8e7ff5c --- /dev/null +++ b/stdlib/public/core/DictionaryStorage.swift @@ -0,0 +1,399 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftShims + +/// An instance of this class has all `Dictionary` data tail-allocated. +/// Enough bytes are allocated to hold the bitmap for marking valid entries, +/// keys, and values. The data layout starts with the bitmap, followed by the +/// keys, followed by the values. +// +// See the docs at the top of the file for more details on this type +// +// NOTE: The precise layout of this type is relied on in the runtime +// to provide a statically allocated empty singleton. +// See stdlib/public/stubs/GlobalObjects.cpp for details. +@_fixed_layout // FIXME(sil-serialize-all) +@usableFromInline +@_objc_non_lazy_realization +internal class _RawDictionaryStorage: _SwiftNativeNSDictionary { + /// The current number of occupied entries in this dictionary. + @usableFromInline + @nonobjc + internal final var _count: Int + + /// The maximum number of elements that can be inserted into this set without + /// exceeding the hash table's maximum load factor. + @usableFromInline + @nonobjc + internal final var _capacity: Int + + /// The scale of this dictionary. The number of buckets is 2 raised to the + /// power of `scale`. + @usableFromInline + @nonobjc + internal final var _scale: Int + + @usableFromInline + internal final var _seed: Hasher._Seed + + @usableFromInline + @nonobjc + internal final var _rawKeys: UnsafeMutableRawPointer + + @usableFromInline + @nonobjc + internal final var _rawValues: UnsafeMutableRawPointer + + // This type is made with allocWithTailElems, so no init is ever called. + // But we still need to have an init to satisfy the compiler. + @nonobjc + internal init(_doNotCallMe: ()) { + _sanityCheckFailure("This class cannot be directly initialized") + } + + @inlinable + @nonobjc + internal final var _bucketCount: Int { + @inline(__always) get { return 1 &<< _scale } + } + + @inlinable + @nonobjc + internal final var _metadata: UnsafeMutablePointer<_HashTable.Word> { + @inline(__always) get { + let address = Builtin.projectTailElems(self, _HashTable.Word.self) + return UnsafeMutablePointer(address) + } + } + + // The _HashTable struct contains pointers into tail-allocated storage, so + // this is unsafe and needs `_fixLifetime` calls in the caller. + @inlinable + @nonobjc + internal final var _hashTable: _HashTable { + @inline(__always) get { + return _HashTable(words: _metadata, bucketCount: _bucketCount) + } + } +} + +/// The storage class for the singleton empty set. +/// The single instance of this class is created by the runtime. +@_fixed_layout +@usableFromInline +internal class _EmptyDictionarySingleton: _RawDictionaryStorage { + @nonobjc + internal override init(_doNotCallMe: ()) { + _sanityCheckFailure("This class cannot be directly initialized") + } + +#if _runtime(_ObjC) + @objc + internal required init( + objects: UnsafePointer, + forKeys: UnsafeRawPointer, + count: Int + ) { + _sanityCheckFailure("This class cannot be directly initialized") + } +#endif +} + +#if _runtime(_ObjC) +extension _EmptyDictionarySingleton: _NSDictionaryCore { + @objc(copyWithZone:) + internal func copy(with zone: _SwiftNSZone?) -> AnyObject { + return self + } + + @objc + internal var count: Int { + return 0 + } + + @objc(countByEnumeratingWithState:objects:count:) + internal func countByEnumerating( + with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>, + objects: UnsafeMutablePointer?, count: Int + ) -> Int { + // Even though we never do anything in here, we need to update the + // state so that callers know we actually ran. + + var theState = state.pointee + if theState.state == 0 { + theState.state = 1 // Arbitrary non-zero value. + theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects) + theState.mutationsPtr = _fastEnumerationStorageMutationsPtr + } + state.pointee = theState + + return 0 + } + + @objc(objectForKey:) + internal func object(forKey aKey: AnyObject) -> AnyObject? { + return nil + } + + @objc(keyEnumerator) + internal func keyEnumerator() -> _NSEnumerator { + return _SwiftEmptyNSEnumerator() + } + + @objc(getObjects:andKeys:count:) + internal func getObjects( + _ objects: UnsafeMutablePointer?, + andKeys keys: UnsafeMutablePointer?, + count: Int) { + // Do nothing, we're empty + } +} +#endif + +extension _RawDictionaryStorage { + /// The empty singleton that is used for every single Dictionary that is + /// created without any elements. The contents of the storage should never + /// be mutated. + @inlinable + @nonobjc + internal static var empty: _EmptyDictionarySingleton { + return Builtin.bridgeFromRawPointer( + Builtin.addressof(&_swiftEmptyDictionarySingleton)) + } +} + +// See the docs at the top of this file for a description of this type +@_fixed_layout // FIXME(sil-serialize-all) +@usableFromInline +final internal class _DictionaryStorage + : _RawDictionaryStorage, _NSDictionaryCore { + // This type is made with allocWithTailElems, so no init is ever called. + // But we still need to have an init to satisfy the compiler. + @nonobjc + override internal init(_doNotCallMe: ()) { + _sanityCheckFailure("This class cannot be directly initialized") + } + +#if _runtime(_ObjC) + @objc + internal required init( + objects: UnsafePointer, + forKeys: UnsafeRawPointer, + count: Int + ) { + _sanityCheckFailure("This class cannot be directly initialized") + } +#endif + + deinit { + guard _count > 0 else { return } + if !_isPOD(Key.self) { + let keys = self._keys + for index in _hashTable { + (keys + index.bucket).deinitialize(count: 1) + } + } + if !_isPOD(Value.self) { + let values = self._values + for index in _hashTable { + (values + index.bucket).deinitialize(count: 1) + } + } + _count = 0 + _fixLifetime(self) + } + + @inlinable + final internal var _keys: UnsafeMutablePointer { + @inline(__always) + get { + return self._rawKeys.assumingMemoryBound(to: Key.self) + } + } + + @inlinable + final internal var _values: UnsafeMutablePointer { + @inline(__always) + get { + return self._rawValues.assumingMemoryBound(to: Value.self) + } + } + + internal var asNative: _NativeDictionary { + return _NativeDictionary(self) + } + + @usableFromInline + @_effects(releasenone) + internal static func reallocate( + original: _RawDictionaryStorage, + capacity: Int + ) -> (storage: _DictionaryStorage, rehash: Bool) { + _sanityCheck(capacity >= original._count) + let scale = _HashTable.scale(forCapacity: capacity) + let rehash = (scale != original._scale) + let newStorage = _DictionaryStorage.allocate(scale: scale) + return (newStorage, rehash) + } + + @usableFromInline + @_effects(releasenone) + static internal func allocate(capacity: Int) -> _DictionaryStorage { + let scale = _HashTable.scale(forCapacity: capacity) + return allocate(scale: scale) + } + + static internal func allocate(scale: Int) -> _DictionaryStorage { + // The entry count must be representable by an Int value; hence the scale's + // peculiar upper bound. + _sanityCheck(scale >= 0 && scale < Int.bitWidth - 1) + + let bucketCount = 1 &<< scale + let wordCount = _UnsafeBitset.wordCount(forCapacity: bucketCount) + let storage = Builtin.allocWithTailElems_3( + _DictionaryStorage.self, + wordCount._builtinWordValue, _HashTable.Word.self, + bucketCount._builtinWordValue, Key.self, + bucketCount._builtinWordValue, Value.self) + + let metadataAddr = Builtin.projectTailElems(storage, _HashTable.Word.self) + let keysAddr = Builtin.getTailAddr_Word( + metadataAddr, wordCount._builtinWordValue, _HashTable.Word.self, + Key.self) + let valuesAddr = Builtin.getTailAddr_Word( + keysAddr, bucketCount._builtinWordValue, Key.self, + Value.self) + storage._count = 0 + storage._capacity = _HashTable.capacity(forScale: scale) + storage._scale = scale + storage._rawKeys = UnsafeMutableRawPointer(keysAddr) + storage._rawValues = UnsafeMutableRawPointer(valuesAddr) + + // We use a slightly different hash seed whenever we change the size of the + // hash table, so that we avoid certain copy operations becoming quadratic, + // without breaking value semantics. (For background details, see + // https://bugs.swift.org/browse/SR-3268) + + // FIXME: Use true per-instance seeding instead. Per-capacity seeding still + // leaves hash values the same in same-sized tables, which may affect + // operations on two tables at once. (E.g., union.) + storage._seed = ( + Hasher._seed.0 ^ UInt64(truncatingIfNeeded: scale), + Hasher._seed.1) + // Initialize hash table metadata. + storage._hashTable.clear() + return storage + } + +#if _runtime(_ObjC) + @objc(copyWithZone:) + internal func copy(with zone: _SwiftNSZone?) -> AnyObject { + return self + } + + @objc + internal var count: Int { + return _count + } + + @objc(keyEnumerator) + internal func keyEnumerator() -> _NSEnumerator { + return _SwiftDictionaryNSEnumerator(asNative) + } + + @objc(countByEnumeratingWithState:objects:count:) + internal func countByEnumerating( + with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>, + objects: UnsafeMutablePointer?, count: Int + ) -> Int { + var theState = state.pointee + if theState.state == 0 { + theState.state = 1 // Arbitrary non-zero value. + theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects) + theState.mutationsPtr = _fastEnumerationStorageMutationsPtr + theState.extra.0 = CUnsignedLong(asNative.startIndex.bucket) + } + + // Test 'objects' rather than 'count' because (a) this is very rare anyway, + // and (b) the optimizer should then be able to optimize away the + // unwrapping check below. + if _slowPath(objects == nil) { + return 0 + } + + let unmanagedObjects = _UnmanagedAnyObjectArray(objects!) + var index = _HashTable.Index(bucket: Int(theState.extra.0)) + let endIndex = asNative.endIndex + _precondition(index == endIndex || _hashTable.isOccupied(index)) + var stored = 0 + for i in 0.. AnyObject? { + guard let nativeKey = _conditionallyBridgeFromObjectiveC(aKey, Key.self) + else { return nil } + + let (index, found) = asNative.find(nativeKey) + guard found else { return nil } + let value = asNative.uncheckedValue(at: index) + return _bridgeAnythingToObjectiveC(value) + } + + @objc(getObjects:andKeys:count:) + internal func getObjects( + _ objects: UnsafeMutablePointer?, + andKeys keys: UnsafeMutablePointer?, + count: Int) { + _precondition(count >= 0, "Invalid count") + guard count > 0 else { return } + var i = 0 // Current position in the output buffers + switch (_UnmanagedAnyObjectArray(keys), _UnmanagedAnyObjectArray(objects)) { + case (let unmanagedKeys?, let unmanagedObjects?): + for (key, value) in asNative { + unmanagedObjects[i] = _bridgeAnythingToObjectiveC(value) + unmanagedKeys[i] = _bridgeAnythingToObjectiveC(key) + i += 1 + guard i < count else { break } + } + case (let unmanagedKeys?, nil): + for (key, _) in asNative { + unmanagedKeys[i] = _bridgeAnythingToObjectiveC(key) + i += 1 + guard i < count else { break } + } + case (nil, let unmanagedObjects?): + for (_, value) in asNative { + unmanagedObjects[i] = _bridgeAnythingToObjectiveC(value) + i += 1 + guard i < count else { break } + } + case (nil, nil): + // Do nothing. + break + } + } +#endif +} + diff --git a/stdlib/public/core/DictionaryVariant.swift b/stdlib/public/core/DictionaryVariant.swift new file mode 100644 index 0000000000000..5699785042397 --- /dev/null +++ b/stdlib/public/core/DictionaryVariant.swift @@ -0,0 +1,518 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// This protocol is only used for compile-time checks that +/// every buffer type implements all required operations. +internal protocol _DictionaryBuffer { + associatedtype Key + associatedtype Value + associatedtype Index + + var startIndex: Index { get } + var endIndex: Index { get } + func index(after i: Index) -> Index + func index(forKey key: Key) -> Index? + var count: Int { get } + + func contains(_ key: Key) -> Bool + func lookup(_ key: Key) -> Value? + func lookup(_ index: Index) -> (key: Key, value: Value) + func key(at index: Index) -> Key + func value(at index: Index) -> Value +} + +extension Dictionary { + @usableFromInline + @_frozen + internal enum _Variant { + case native(_NativeDictionary) +#if _runtime(_ObjC) + case cocoa(_CocoaDictionary) +#endif + } +} + +extension Dictionary._Variant { +#if _runtime(_ObjC) + @usableFromInline @_transparent + internal var guaranteedNative: Bool { + return _canBeClass(Key.self) == 0 || _canBeClass(Value.self) == 0 + } + + // Allow the optimizer to consider the surrounding code unreachable if Element + // is guaranteed to be native. + @usableFromInline @_transparent + internal func cocoaPath() { + if guaranteedNative { + _conditionallyUnreachable() + } + } +#endif + + @inlinable + internal mutating func isUniquelyReferenced() -> Bool { + switch self { + case .native: + // Note that &self drills down through .native(_NativeDictionary) to the + // first property in _NativeDictionary, which is the reference to the + // storage. + return _isUnique_native(&self) +#if _runtime(_ObjC) + case .cocoa: + cocoaPath() + // Don't consider Cocoa buffer mutable, even if it is mutable and is + // uniquely referenced. + return false +#endif + } + } + + @inlinable + internal var asNative: _NativeDictionary { + @inline(__always) + get { + switch self { + case .native(let native): + return native +#if _runtime(_ObjC) + case .cocoa: + _sanityCheckFailure("internal error: not backed by native buffer") +#endif + } + } + @inline(__always) + set { + self = .native(newValue) + } + } + +#if _runtime(_ObjC) + @inlinable + internal var asCocoa: _CocoaDictionary { + switch self { + case .native: + _sanityCheckFailure("internal error: not backed by NSDictionary") + case .cocoa(let cocoa): + return cocoa + } + } +#endif + + /// Reserves enough space for the specified number of elements to be stored + /// without reallocating additional storage. + @inlinable + internal mutating func reserveCapacity(_ capacity: Int) { + switch self { + case .native: + let isUnique = isUniquelyReferenced() + asNative.reserveCapacity(capacity, isUnique: isUnique) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + let capacity = Swift.max(cocoa.count, capacity) + self = .native(_NativeDictionary(cocoa, capacity: capacity)) +#endif + } + } + + /// The number of elements that can be stored without expanding the current + /// storage. + /// + /// For bridged storage, this is equal to the current count of the + /// collection, since any addition will trigger a copy of the elements into + /// newly allocated storage. For native storage, this is the element count + /// at which adding any more elements will exceed the load factor. + @inlinable + internal var capacity: Int { + switch self { + case .native: + return asNative.capacity +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + return cocoa.count +#endif + } + } +} + +extension Dictionary._Variant: _DictionaryBuffer { + @usableFromInline + internal typealias Element = (key: Key, value: Value) + @usableFromInline + internal typealias Index = Dictionary.Index + + @inlinable + internal var startIndex: Index { + switch self { + case .native: + return Index(_native: asNative.startIndex) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + return Index(_cocoa: cocoa.startIndex) +#endif + } + } + + @inlinable + internal var endIndex: Index { + switch self { + case .native: + return Index(_native: asNative.endIndex) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + return Index(_cocoa: cocoa.endIndex) +#endif + } + } + + @inlinable + internal func index(after i: Index) -> Index { + switch self { + case .native: + return Index(_native: asNative.index(after: i._asNative)) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + return Index(_cocoa: cocoa.index(after: i._asCocoa)) +#endif + } + } + + @inlinable + @inline(__always) + internal func index(forKey key: Key) -> Index? { + switch self { + case .native: + guard let index = asNative.index(forKey: key) else { return nil } + return Index(_native: index) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + let cocoaKey = _bridgeAnythingToObjectiveC(key) + guard let index = cocoa.index(forKey: cocoaKey) else { return nil } + return Index(_cocoa: index) +#endif + } + } + + @inlinable + internal var count: Int { + @inline(__always) + get { + switch self { + case .native: + return asNative.count +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + return cocoa.count +#endif + } + } + } + + @inlinable + @inline(__always) + func contains(_ key: Key) -> Bool { + switch self { + case .native: + return asNative.contains(key) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + let cocoaKey = _bridgeAnythingToObjectiveC(key) + return cocoa.contains(cocoaKey) +#endif + } + } + + @inlinable + @inline(__always) + func lookup(_ key: Key) -> Value? { + switch self { + case .native: + return asNative.lookup(key) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + let cocoaKey = _bridgeAnythingToObjectiveC(key) + guard let cocoaValue = cocoa.lookup(cocoaKey) else { return nil } + return _forceBridgeFromObjectiveC(cocoaValue, Value.self) +#endif + } + } + + @inlinable + @inline(__always) + func lookup(_ index: Index) -> (key: Key, value: Value) { + switch self { + case .native: + return asNative.lookup(index._asNative) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + let (cocoaKey, cocoaValue) = cocoa.lookup(index._asCocoa) + let nativeKey = _forceBridgeFromObjectiveC(cocoaKey, Key.self) + let nativeValue = _forceBridgeFromObjectiveC(cocoaValue, Value.self) + return (nativeKey, nativeValue) +#endif + } + } + + @inlinable + @inline(__always) + func key(at index: Index) -> Key { + switch self { + case .native: + return asNative.key(at: index._asNative) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + let cocoaKey = cocoa.key(at: index._asCocoa) + return _forceBridgeFromObjectiveC(cocoaKey, Key.self) +#endif + } + } + + @inlinable + @inline(__always) + func value(at index: Index) -> Value { + switch self { + case .native: + return asNative.value(at: index._asNative) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + let cocoaValue = cocoa.value(at: index._asCocoa) + return _forceBridgeFromObjectiveC(cocoaValue, Value.self) +#endif + } + } +} + +extension Dictionary._Variant { + /// Same as find(_:), except assume a corresponding key/value pair will be + /// inserted if it doesn't already exist, and mutated if it does exist. When + /// this function returns, the storage is guaranteed to be native, uniquely + /// held, and with enough capacity for a single insertion (if the key isn't + /// already in the dictionary.) + @inlinable + @inline(__always) + internal mutating func mutatingFind( + _ key: Key + ) -> (index: _NativeDictionary.Index, found: Bool) { + switch self { + case .native: + let isUnique = isUniquelyReferenced() + return asNative.mutatingFind(key, isUnique: isUnique) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + var native = _NativeDictionary( + cocoa, capacity: cocoa.count + 1) + let result = native.mutatingFind(key, isUnique: true) + self = .native(native) + return result +#endif + } + } + + /// Ensure uniquely held native storage, while preserving the given index. + /// (If the variant had bridged storage, then the returned index will be the + /// corresponding native representation. Otherwise it's kept the same.) + @inlinable + @inline(__always) + internal mutating func ensureUniqueNative( + preserving index: Index + ) -> _NativeDictionary.Index { + switch self { + case .native: + let isUnique = isUniquelyReferenced() + if !isUnique { + let rehashed = asNative.copy(capacity: asNative.capacity) + _sanityCheck(!rehashed) + } + return index._asNative +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + // We have to migrate the data first. But after we do so, the Cocoa + // index becomes useless, so get the key first. + let cocoaKey = cocoa.key(at: index._asCocoa) + let native = _NativeDictionary(cocoa) + self = .native(native) + let nativeKey = _forceBridgeFromObjectiveC(cocoaKey, Key.self) + let (nativeIndex, found) = native.find(nativeKey) + _precondition(found, "Bridging did not preserve equality") + return nativeIndex +#endif + } + } + + @inlinable + internal mutating func updateValue( + _ value: Value, + forKey key: Key + ) -> Value? { + switch self { + case .native: + let isUnique = self.isUniquelyReferenced() + return asNative.updateValue(value, forKey: key, isUnique: isUnique) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + // Make sure we have space for an extra element. + var native = _NativeDictionary( + cocoa, + capacity: cocoa.count + 1) + let result = native.updateValue(value, forKey: key, isUnique: true) + self = .native(native) + return result +#endif + } + } + + @inlinable + internal mutating func setValue(_ value: Value, forKey key: Key) { + switch self { + case .native: + let isUnique = self.isUniquelyReferenced() + asNative.setValue(value, forKey: key, isUnique: isUnique) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + // Make sure we have space for an extra element. + var native = _NativeDictionary( + cocoa, + capacity: cocoa.count + 1) + native.setValue(value, forKey: key, isUnique: true) + self = .native(native) +#endif + } + } + + @inlinable + internal mutating func remove(at index: Index) -> Element { + // FIXME(performance): fuse data migration and element deletion into one + // operation. + let index = ensureUniqueNative(preserving: index) + return asNative.remove(at: index, isUnique: true) + } + + @inlinable + internal mutating func removeValue(forKey key: Key) -> Value? { + switch self { + case .native: + let (index, found) = asNative.find(key) + guard found else { return nil } + let isUnique = isUniquelyReferenced() + return asNative.uncheckedRemove(at: index, isUnique: isUnique).value +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + let cocoaKey = _bridgeAnythingToObjectiveC(key) + guard cocoa.lookup(cocoaKey) != nil else { return nil } + var native = _NativeDictionary(cocoa) + let (index, found) = native.find(key) + _precondition(found, "Bridging did not preserve equality") + let old = native.uncheckedRemove(at: index, isUnique: true).value + self = .native(native) + return old +#endif + } + } + + @inlinable + internal mutating func removeAll(keepingCapacity keepCapacity: Bool) { + if !keepCapacity { + self = .native(_NativeDictionary()) + return + } + guard count > 0 else { return } + + switch self { + case .native: + let isUnique = isUniquelyReferenced() + asNative.removeAll(isUnique: isUnique) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + self = .native(_NativeDictionary(capacity: cocoa.count)) +#endif + } + } +} + +extension Dictionary._Variant { + /// Returns an iterator over the `(Key, Value)` pairs. + /// + /// - Complexity: O(1). + @inlinable + @inline(__always) + internal func makeIterator() -> Dictionary.Iterator { + switch self { + case .native(let native): + return Dictionary.Iterator(_native: native.makeIterator()) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + return Dictionary.Iterator(_cocoa: cocoa.makeIterator()) +#endif + } + } +} + +extension Dictionary._Variant { + @inlinable + internal func mapValues( + _ transform: (Value) throws -> T + ) rethrows -> _NativeDictionary { + switch self { + case .native(let native): + return try native.mapValues(transform) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + return try cocoa.mapValues(transform) +#endif + } + } + + @inlinable + internal mutating func merge( + _ keysAndValues: S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows where S.Element == (Key, Value) { + switch self { + case .native: + let isUnique = isUniquelyReferenced() + try asNative.merge( + keysAndValues, + isUnique: isUnique, + uniquingKeysWith: combine) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + var native = _NativeDictionary(cocoa) + try native.merge( + keysAndValues, + isUnique: true, + uniquingKeysWith: combine) + self = .native(native) +#endif + } + } +} + diff --git a/stdlib/public/core/GroupInfo.json b/stdlib/public/core/GroupInfo.json index 92b80b0e0a364..34c5f217a22bc 100644 --- a/stdlib/public/core/GroupInfo.json +++ b/stdlib/public/core/GroupInfo.json @@ -111,10 +111,22 @@ "SliceBuffer.swift", "SwiftNativeNSArray.swift"], "HashedCollections": [ + "HashTable.swift", "Dictionary.swift", + "NativeDictionary.swift", + "DictionaryBridging.swift", + "DictionaryBuilder.swift", + "DictionaryCasting.swift", + "DictionaryStorage.swift", + "DictionaryVariant.swift", "Set.swift", - "HashedCollectionsAnyHashableExtensions.swift", - "HashTable.swift" + "NativeSet.swift", + "SetAnyHashableExtensions.swift", + "SetBridging.swift", + "SetBuilder.swift", + "SetCasting.swift", + "SetStorage.swift", + "SetVariant.swift" ] } ], diff --git a/stdlib/public/core/NativeDictionary.swift b/stdlib/public/core/NativeDictionary.swift new file mode 100644 index 0000000000000..72b229f4cded1 --- /dev/null +++ b/stdlib/public/core/NativeDictionary.swift @@ -0,0 +1,601 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// A wrapper around _RawDictionaryStorage that provides most of the +/// implementation of Dictionary. +@usableFromInline +@_fixed_layout +internal struct _NativeDictionary { + @usableFromInline + internal typealias Element = (key: Key, value: Value) + + /// See this comments on _RawDictionaryStorage and its subclasses to + /// understand why we store an untyped storage here. + @usableFromInline + internal var _storage: _RawDictionaryStorage + + /// Constructs an instance from the empty singleton. + @inlinable + internal init() { + self._storage = _RawDictionaryStorage.empty + } + + /// Constructs a dictionary adopting the given storage. + @inlinable + internal init(_ storage: _RawDictionaryStorage) { + self._storage = storage + } + + @usableFromInline + @_effects(releasenone) + internal init(capacity: Int) { + let scale = _HashTable.scale(forCapacity: capacity) + self._storage = _DictionaryStorage.allocate(scale: scale) + } + +#if _runtime(_ObjC) + @inlinable + internal init(_ cocoa: _CocoaDictionary) { + self.init(cocoa, capacity: cocoa.count) + } + + @inlinable + internal init(_ cocoa: _CocoaDictionary, capacity: Int) { + _sanityCheck(cocoa.count <= capacity) + self.init(capacity: capacity) + for (key, value) in cocoa { + insertNew( + key: _forceBridgeFromObjectiveC(key, Key.self), + value: _forceBridgeFromObjectiveC(value, Value.self)) + } + } +#endif +} + +extension _NativeDictionary { // Primitive fields + @inlinable + internal var capacity: Int { + @inline(__always) + get { + return _assumeNonNegative(_storage._capacity) + } + } + + @inlinable + internal var hashTable: _HashTable { + @inline(__always) get { + return _storage._hashTable + } + } + + // This API is unsafe and needs a `_fixLifetime` in the caller. + @inlinable + internal var _keys: UnsafeMutablePointer { + return _storage._rawKeys.assumingMemoryBound(to: Key.self) + } + + @inlinable + internal var _values: UnsafeMutablePointer { + return _storage._rawValues.assumingMemoryBound(to: Value.self) + } +} + +extension _NativeDictionary { // Low-level unchecked operations + @inlinable + @inline(__always) + internal func uncheckedKey(at index: Index) -> Key { + defer { _fixLifetime(self) } + _sanityCheck(hashTable.isOccupied(index)) + return _keys[index.bucket] + } + + @inlinable + @inline(__always) + internal func uncheckedValue(at index: Index) -> Value { + defer { _fixLifetime(self) } + _sanityCheck(hashTable.isOccupied(index)) + return _values[index.bucket] + } + + @usableFromInline + @inline(__always) + internal func uncheckedInitialize( + at index: Index, + toKey key: Key, + value: Value) { + defer { _fixLifetime(self) } + _sanityCheck(hashTable.isValid(index)) + (_keys + index.bucket).initialize(to: key) + (_values + index.bucket).initialize(to: value) + } + + @usableFromInline + @inline(__always) + internal func uncheckedDestroy(at index: Index) { + defer { _fixLifetime(self) } + _sanityCheck(hashTable.isOccupied(index)) + (_keys + index.bucket).deinitialize(count: 1) + (_values + index.bucket).deinitialize(count: 1) + } +} + +extension _NativeDictionary { // Low-level lookup operations + @inlinable + @inline(__always) + internal func hashValue(for key: Key) -> Int { + return key._rawHashValue(seed: _storage._seed) + } + + @inlinable + @inline(__always) + internal func find(_ key: Key) -> (index: Index, found: Bool) { + return find(key, hashValue: self.hashValue(for: key)) + } + + /// Search for a given element, assuming it has the specified hash value. + /// + /// If the element is not present in this set, return the position where it + /// could be inserted. + @inlinable + @inline(__always) + internal func find( + _ key: Key, + hashValue: Int + ) -> (index: Index, found: Bool) { + let hashTable = self.hashTable + var index = hashTable.idealIndex(forHashValue: hashValue) + while hashTable._isOccupied(index) { + if uncheckedKey(at: index) == key { + return (index, true) + } + index = hashTable.index(wrappedAfter: index) + } + return (index, false) + } +} + +extension _NativeDictionary { // ensureUnique + @inlinable + internal mutating func resize(capacity: Int) { + let capacity = Swift.max(capacity, self.capacity) + let result = _NativeDictionary( + _DictionaryStorage.allocate(capacity: capacity)) + if count > 0 { + for index in hashTable { + let key = (_keys + index.bucket).move() + let value = (_values + index.bucket).move() + result._unsafeInsertNew(key: key, value: value) + } + // Clear out old storage, ensuring that its deinit won't overrelease the + // elements we've just moved out. + _storage._hashTable.clear() + _storage._count = 0 + } + _storage = result._storage + } + + @inlinable + internal mutating func copy(capacity: Int) -> Bool { + let capacity = Swift.max(capacity, self.capacity) + let (newStorage, rehash) = _DictionaryStorage.reallocate( + original: _storage, + capacity: capacity) + let result = _NativeDictionary(newStorage) + if count > 0 { + if rehash { + for index in hashTable { + result._unsafeInsertNew( + key: self.uncheckedKey(at: index), + value: self.uncheckedValue(at: index)) + } + } else { + result.hashTable.copyContents(of: hashTable) + result._storage._count = self.count + for index in hashTable { + let key = uncheckedKey(at: index) + let value = uncheckedValue(at: index) + result.uncheckedInitialize(at: index, toKey: key, value: value) + } + } + } + _storage = result._storage + return rehash + } + + /// Ensure storage of self is uniquely held and can hold at least `capacity` + /// elements. Returns true iff contents were rehashed. + @inlinable + @inline(__always) + internal mutating func ensureUnique(isUnique: Bool, capacity: Int) -> Bool { + if _fastPath(capacity <= self.capacity && isUnique) { + return false + } + guard isUnique else { + return copy(capacity: capacity) + } + resize(capacity: capacity) + return true + } + + @inlinable + internal mutating func reserveCapacity(_ capacity: Int, isUnique: Bool) { + _ = ensureUnique(isUnique: isUnique, capacity: capacity) + } +} + +extension _NativeDictionary: _DictionaryBuffer { + @usableFromInline + internal typealias Index = _HashTable.Index + + @inlinable + internal var startIndex: Index { + return hashTable.startIndex + } + + @inlinable + internal var endIndex: Index { + return hashTable.endIndex + } + + @inlinable + internal func index(after index: Index) -> Index { + return hashTable.index(after: index) + } + + @inlinable + internal func index(forKey key: Key) -> Index? { + if count == 0 { + // Fast path that avoids computing the hash of the key. + return nil + } + let (index, found) = find(key) + return found ? index : nil + } + + @inlinable + internal var count: Int { + @inline(__always) get { + return _assumeNonNegative(_storage._count) + } + } + + @inlinable + @inline(__always) + func contains(_ key: Key) -> Bool { + return find(key).found + } + + @inlinable + @inline(__always) + func lookup(_ key: Key) -> Value? { + if count == 0 { + // Fast path that avoids computing the hash of the key. + return nil + } + let (index, found) = self.find(key) + return found ? self.uncheckedValue(at: index) : nil + } + + @inlinable + @inline(__always) + func lookup(_ index: Index) -> (key: Key, value: Value) { + _precondition(hashTable.isOccupied(index), + "Attempting to access Dictionary elements using an invalid Index") + let key = self.uncheckedKey(at: index) + let value = self.uncheckedValue(at: index) + return (key, value) + } + + @inlinable + @inline(__always) + func key(at index: Index) -> Key { + _precondition(hashTable.isOccupied(index), + "Attempting to access Dictionary elements using an invalid Index") + return self.uncheckedKey(at: index) + } + + @inlinable + @inline(__always) + func value(at index: Index) -> Value { + _precondition(hashTable.isOccupied(index), + "Attempting to access Dictionary elements using an invalid Index") + return self.uncheckedValue(at: index) + } +} + +// This function has a highly visible name to make it stand out in stack traces. +@usableFromInline +@inline(never) +internal func KEY_TYPE_OF_DICTIONARY_VIOLATES_HASHABLE_REQUIREMENTS( + _ keyType: Any.Type +) -> Never { + _assertionFailure( + "Fatal error", + """ + Duplicate keys of type '\(keyType)' were found in a Dictionary. + This usually means either that the type violates Hashable's requirements, or + that members of such a dictionary were mutated after insertion. + """, + flags: _fatalErrorFlags()) +} + +extension _NativeDictionary { // Insertions + /// Insert a new element into uniquely held storage. + /// Storage must be uniquely referenced with adequate capacity. + /// The `key` must not be already present in the Dictionary. + @inlinable + internal func _unsafeInsertNew(key: Key, value: Value) { + _sanityCheck(count + 1 <= capacity) + let hashValue = self.hashValue(for: key) + if _isDebugAssertConfiguration() { + // In debug builds, perform a full lookup and trap if we detect duplicate + // elements -- these imply that the Element type violates Hashable + // requirements. This is generally more costly than a direct insertion, + // because we'll need to compare elements in case of hash collisions. + let (index, found) = find(key, hashValue: hashValue) + guard !found else { + KEY_TYPE_OF_DICTIONARY_VIOLATES_HASHABLE_REQUIREMENTS(Key.self) + } + hashTable.insert(index) + uncheckedInitialize(at: index, toKey: key, value: value) + } else { + let index = hashTable.insertNew(hashValue: hashValue) + uncheckedInitialize(at: index, toKey: key, value: value) + } + _storage._count += 1 + } + + /// Insert a new entry into uniquely held storage. + /// Storage must be uniquely referenced. + /// The `key` must not be already present in the Dictionary. + @inlinable + internal mutating func insertNew(key: Key, value: Value) { + _ = ensureUnique(isUnique: true, capacity: count + 1) + _unsafeInsertNew(key: key, value: value) + } + + /// Same as find(_:), except assume a corresponding key/value pair will be + /// inserted if it doesn't already exist, and mutated if it does exist. When + /// this function returns, the storage is guaranteed to be native, uniquely + /// held, and with enough capacity for a single insertion (if the key isn't + /// already in the dictionary.) + @inlinable + @inline(__always) + internal mutating func mutatingFind( + _ key: Key, + isUnique: Bool + ) -> (index: Index, found: Bool) { + let (index, found) = find(key) + + // Prepare storage. + // If `key` isn't in the dictionary yet, assume that this access will end + // up inserting it. (If we guess wrong, we might needlessly expand + // storage; that's fine.) Otherwise this can only be a removal or an + // in-place mutation. + let rehashed = ensureUnique( + isUnique: isUnique, + capacity: count + (found ? 0 : 1)) + guard rehashed else { return (index, found) } + let (i, f) = find(key) + if f != found { + KEY_TYPE_OF_DICTIONARY_VIOLATES_HASHABLE_REQUIREMENTS(Key.self) + } + return (i, found) + } + + @inlinable + internal func _insert(at index: Index, key: Key, value: Value) { + _sanityCheck(count < capacity) + hashTable.insert(index) + uncheckedInitialize(at: index, toKey: key, value: value) + _storage._count += 1 + } + + @inlinable + internal mutating func updateValue( + _ value: Value, + forKey key: Key, + isUnique: Bool + ) -> Value? { + let (index, found) = mutatingFind(key, isUnique: isUnique) + if found { + let oldValue = (_values + index.bucket).move() + (_values + index.bucket).initialize(to: value) + // FIXME: Replacing the old key with the new is unnecessary, unintuitive, + // and actively harmful to some usecases. We shouldn't do it. + // rdar://problem/32144087 + (_keys + index.bucket).pointee = key + return oldValue + } + _insert(at: index, key: key, value: value) + return nil + } + + @inlinable + internal mutating func setValue( + _ value: Value, + forKey key: Key, + isUnique: Bool + ) { + let (index, found) = mutatingFind(key, isUnique: isUnique) + if found { + (_values + index.bucket).pointee = value + // FIXME: Replacing the old key with the new is unnecessary, unintuitive, + // and actively harmful to some usecases. We shouldn't do it. + // rdar://problem/32144087 + (_keys + index.bucket).pointee = key + } else { + _insert(at: index, key: key, value: value) + } + } +} + +extension _NativeDictionary: _HashTableDelegate { + @inlinable + @inline(__always) + internal func hashValue(at index: Index) -> Int { + return hashValue(for: uncheckedKey(at: index)) + } + + @inlinable + @inline(__always) + internal func moveEntry(from source: Index, to target: Index) { + (_keys + target.bucket) + .moveInitialize(from: _keys + source.bucket, count: 1) + (_values + target.bucket) + .moveInitialize(from: _values + source.bucket, count: 1) + } +} + +extension _NativeDictionary { // Deletion + @inlinable + internal func _delete(at index: Index) { + hashTable.delete(at: index, with: self) + _storage._count -= 1 + _sanityCheck(_storage._count >= 0) + } + + @inlinable + @inline(__always) + internal mutating func uncheckedRemove( + at index: Index, + isUnique: Bool + ) -> Element { + _sanityCheck(hashTable.isOccupied(index)) + let rehashed = ensureUnique(isUnique: isUnique, capacity: capacity) + _sanityCheck(!rehashed) + let oldKey = (_keys + index.bucket).move() + let oldValue = (_values + index.bucket).move() + _delete(at: index) + return (oldKey, oldValue) + } + + @inlinable + @inline(__always) + internal mutating func remove(at index: Index, isUnique: Bool) -> Element { + _precondition(hashTable.isOccupied(index), "Invalid index") + return uncheckedRemove(at: index, isUnique: isUnique) + } + + @usableFromInline + internal mutating func removeAll(isUnique: Bool) { + guard isUnique else { + let scale = self._storage._scale + _storage = _DictionaryStorage.allocate(scale: scale) + return + } + for index in hashTable { + (_keys + index.bucket).deinitialize(count: 1) + (_values + index.bucket).deinitialize(count: 1) + } + hashTable.clear() + _storage._count = 0 + } +} + +extension _NativeDictionary { // High-level operations + @inlinable + internal func mapValues( + _ transform: (Value) throws -> T + ) rethrows -> _NativeDictionary { + let result = _NativeDictionary(capacity: capacity) + // Because the keys in the current and new buffer are the same, we can + // initialize to the same locations in the new buffer, skipping hash value + // recalculations. + for index in hashTable { + let key = self.uncheckedKey(at: index) + let value = self.uncheckedValue(at: index) + try result._insert(at: index, key: key, value: transform(value)) + } + return result + } + + @inlinable + internal mutating func merge( + _ keysAndValues: S, + isUnique: Bool, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows where S.Element == (Key, Value) { + var isUnique = isUnique + for (key, value) in keysAndValues { + let (index, found) = mutatingFind(key, isUnique: isUnique) + isUnique = true + if found { + do { + let v = (_values + index.bucket).move() + let newValue = try combine(v, value) + (_values + index.bucket).initialize(to: newValue) + } catch _MergeError.keyCollision { + fatalError("Duplicate values for key: '\(key)'") + } + } else { + _insert(at: index, key: key, value: value) + } + } + } + + @inlinable + @inline(__always) + internal init( + grouping values: S, + by keyForValue: (S.Element) throws -> Key + ) rethrows where Value == [S.Element] { + self.init() + for value in values { + let key = try keyForValue(value) + let (index, found) = mutatingFind(key, isUnique: true) + if found { + _values[index.bucket].append(value) + } else { + _insert(at: index, key: key, value: [value]) + } + } + } +} + +extension _NativeDictionary: Sequence { + @usableFromInline + @_fixed_layout + internal struct Iterator { + // The iterator is iterating over a frozen view of the collection state, so + // it keeps its own reference to the dictionary. + @usableFromInline + internal let base: _NativeDictionary + @usableFromInline + internal var iterator: _HashTable.Iterator + + @inlinable + init(_ base: _NativeDictionary) { + self.base = base + self.iterator = base.hashTable.makeIterator() + } + } + + @inlinable + internal func makeIterator() -> Iterator { + return Iterator(self) + } +} + +extension _NativeDictionary.Iterator: IteratorProtocol { + @usableFromInline + internal typealias Element = (key: Key, value: Value) + + @inlinable + internal mutating func next() -> Element? { + guard let index = iterator.next() else { return nil } + let key = base.uncheckedKey(at: index) + let value = base.uncheckedValue(at: index) + return (key, value) + } +} + diff --git a/stdlib/public/core/NativeSet.swift b/stdlib/public/core/NativeSet.swift new file mode 100644 index 0000000000000..4ef796d687a27 --- /dev/null +++ b/stdlib/public/core/NativeSet.swift @@ -0,0 +1,443 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// A wrapper around _RawSetStorage that provides most of the +/// implementation of Set. +@usableFromInline +@_fixed_layout +internal struct _NativeSet { + /// See the comments on _RawSetStorage and its subclasses to understand why we + /// store an untyped storage here. + @usableFromInline + internal var _storage: _RawSetStorage + + /// Constructs an instance from the empty singleton. + @inlinable + @inline(__always) + internal init() { + self._storage = _RawSetStorage.empty + } + + /// Constructs a native set adopting the given storage. + @inlinable + @inline(__always) + internal init(_ storage: _RawSetStorage) { + self._storage = storage + } + + @usableFromInline + @_effects(releasenone) + internal init(capacity: Int) { + let scale = _HashTable.scale(forCapacity: capacity) + self._storage = _SetStorage.allocate(scale: scale) + } + +#if _runtime(_ObjC) + @inlinable + internal init(_ cocoa: _CocoaSet) { + self.init(cocoa, capacity: cocoa.count) + } + + @inlinable + internal init(_ cocoa: _CocoaSet, capacity: Int) { + _sanityCheck(cocoa.count <= capacity) + self.init(capacity: capacity) + for element in cocoa { + let nativeElement = _forceBridgeFromObjectiveC(element, Element.self) + insertNew(nativeElement, isUnique: true) + } + } +#endif +} + +extension _NativeSet { // Primitive fields + @inlinable + internal var capacity: Int { + @inline(__always) + get { + return _assumeNonNegative(_storage._capacity) + } + } + + @inlinable + internal var hashTable: _HashTable { + @inline(__always) get { + return _storage._hashTable + } + } + + // This API is unsafe and needs a `_fixLifetime` in the caller. + @inlinable + internal var _elements: UnsafeMutablePointer { + return _storage._rawElements.assumingMemoryBound(to: Element.self) + } +} + +extension _NativeSet { // Low-level unchecked operations + @inlinable + @inline(__always) + internal func uncheckedElement(at index: Index) -> Element { + defer { _fixLifetime(self) } + _sanityCheck(hashTable.isOccupied(index)) + return _elements[index.bucket] + } + + @inlinable + @inline(__always) + internal func uncheckedInitialize(at index: Index, to element: Element) { + _sanityCheck(hashTable.isValid(index)) + (_elements + index.bucket).initialize(to: element) + } +} + +extension _NativeSet { // Low-level lookup operations + @inlinable + @inline(__always) + internal func hashValue(for element: Element) -> Int { + return element._rawHashValue(seed: _storage._seed) + } + + @inlinable + @inline(__always) + internal func find(_ element: Element) -> (index: Index, found: Bool) { + return find(element, hashValue: self.hashValue(for: element)) + } + + /// Search for a given element, assuming it has the specified hash value. + /// + /// If the element is not present in this set, return the position where it + /// could be inserted. + @inlinable + @inline(__always) + internal func find( + _ element: Element, + hashValue: Int + ) -> (index: Index, found: Bool) { + let hashTable = self.hashTable + var index = hashTable.idealIndex(forHashValue: hashValue) + while hashTable._isOccupied(index) { + if uncheckedElement(at: index) == element { + return (index, true) + } + index = hashTable.index(wrappedAfter: index) + } + return (index, false) + } +} + +extension _NativeSet { // ensureUnique + @inlinable + internal mutating func resize(capacity: Int) { + let capacity = Swift.max(capacity, self.capacity) + let result = _NativeSet(_SetStorage.allocate(capacity: capacity)) + if count > 0 { + for index in hashTable { + let element = (self._elements + index.bucket).move() + result._unsafeInsertNew(element) + } + // Clear out old storage, ensuring that its deinit won't overrelease the + // elements we've just moved out. + _storage._hashTable.clear() + _storage._count = 0 + } + _storage = result._storage + } + + @inlinable + internal mutating func copy(capacity: Int) -> Bool { + let capacity = Swift.max(capacity, self.capacity) + let (newStorage, rehash) = _SetStorage.reallocate( + original: _storage, + capacity: capacity) + let result = _NativeSet(newStorage) + if count > 0 { + if rehash { + for index in hashTable { + result._unsafeInsertNew(self.uncheckedElement(at: index)) + } + } else { + result.hashTable.copyContents(of: hashTable) + result._storage._count = self.count + for index in hashTable { + let element = uncheckedElement(at: index) + result.uncheckedInitialize(at: index, to: element) + } + } + } + _storage = result._storage + return rehash + } + + /// Ensure storage of self is uniquely held and can hold at least `capacity` + /// elements. Returns true iff contents were rehashed. + @inlinable + @inline(__always) + internal mutating func ensureUnique(isUnique: Bool, capacity: Int) -> Bool { + if _fastPath(capacity <= self.capacity && isUnique) { + return false + } + guard isUnique else { + return copy(capacity: capacity) + } + resize(capacity: capacity) + return true + } + + @inlinable + internal mutating func reserveCapacity(_ capacity: Int, isUnique: Bool) { + _ = ensureUnique(isUnique: isUnique, capacity: capacity) + } +} + +extension _NativeSet: _SetBuffer { + @usableFromInline + internal typealias Index = _HashTable.Index + + @inlinable + internal var startIndex: Index { + return hashTable.startIndex + } + + @inlinable + internal var endIndex: Index { + return hashTable.endIndex + } + + @inlinable + internal func index(after index: Index) -> Index { + return hashTable.index(after: index) + } + + @inlinable + @inline(__always) + internal func index(for element: Element) -> Index? { + if count == 0 { + // Fast path that avoids computing the hash of the key. + return nil + } + let (index, found) = find(element) + return found ? index : nil + } + + @inlinable + internal var count: Int { + @inline(__always) get { + return _assumeNonNegative(_storage._count) + } + } + + @inlinable + @inline(__always) + internal func contains(_ member: Element) -> Bool { + // Fast path: Don't calculate the hash if the set has no elements. + if count == 0 { return false } + return find(member).found + } + + @inlinable + @inline(__always) + internal func element(at index: Index) -> Element { + hashTable.checkOccupied(index) + return _elements[index.bucket] + } +} + +// This function has a highly visible name to make it stand out in stack traces. +@usableFromInline +@inline(never) +internal func ELEMENT_TYPE_OF_SET_VIOLATES_HASHABLE_REQUIREMENTS( + _ elementType: Any.Type +) -> Never { + _assertionFailure( + "Fatal error", + """ + Duplicate elements of type '\(elementType)' were found in a Set. + This usually means either that the type violates Hashable's requirements, or + that members of such a set were mutated after insertion. + """, + flags: _fatalErrorFlags()) +} + +extension _NativeSet { // Insertions + /// Insert a new element into uniquely held storage. + /// Storage must be uniquely referenced with adequate capacity. + /// The `element` must not be already present in the Set. + @inlinable + internal func _unsafeInsertNew(_ element: Element) { + _sanityCheck(count + 1 <= capacity) + let hashValue = self.hashValue(for: element) + if _isDebugAssertConfiguration() { + // In debug builds, perform a full lookup and trap if we detect duplicate + // elements -- these imply that the Element type violates Hashable + // requirements. This is generally more costly than a direct insertion, + // because we'll need to compare elements in case of hash collisions. + let (index, found) = find(element, hashValue: hashValue) + guard !found else { + ELEMENT_TYPE_OF_SET_VIOLATES_HASHABLE_REQUIREMENTS(Element.self) + } + hashTable.insert(index) + uncheckedInitialize(at: index, to: element) + } else { + let index = hashTable.insertNew(hashValue: hashValue) + uncheckedInitialize(at: index, to: element) + } + _storage._count += 1 + } + + /// Insert a new element into uniquely held storage. + /// Storage must be uniquely referenced. + /// The `element` must not be already present in the Set. + @inlinable + internal mutating func insertNew(_ element: Element, isUnique: Bool) { + _ = ensureUnique(isUnique: isUnique, capacity: count + 1) + _unsafeInsertNew(element) + } + + @inlinable + internal func _unsafeInsertNew(_ element: Element, at index: Index) { + hashTable.insert(index) + uncheckedInitialize(at: index, to: element) + _storage._count += 1 + } + + @inlinable + internal mutating func insertNew( + _ element: Element, + at index: Index, + isUnique: Bool + ) { + _sanityCheck(!hashTable.isOccupied(index)) + var index = index + if ensureUnique(isUnique: isUnique, capacity: count + 1) { + let (i, f) = find(element) + if f { + ELEMENT_TYPE_OF_SET_VIOLATES_HASHABLE_REQUIREMENTS(Element.self) + } + index = i + } + _unsafeInsertNew(element, at: index) + } + + @inlinable + internal mutating func update( + with element: Element, + isUnique: Bool + ) -> Element? { + var (index, found) = find(element) + let rehashed = ensureUnique( + isUnique: isUnique, + capacity: count + (found ? 0 : 1)) + if rehashed { + let (i, f) = find(element) + if f != found { + ELEMENT_TYPE_OF_SET_VIOLATES_HASHABLE_REQUIREMENTS(Element.self) + } + index = i + } + if found { + let old = (_elements + index.bucket).move() + uncheckedInitialize(at: index, to: element) + return old + } + _unsafeInsertNew(element, at: index) + return nil + } +} + +extension _NativeSet: _HashTableDelegate { + @inlinable + @inline(__always) + internal func hashValue(at index: Index) -> Int { + return hashValue(for: uncheckedElement(at: index)) + } + + @inlinable + @inline(__always) + internal func moveEntry(from source: Index, to target: Index) { + (_elements + target.bucket) + .moveInitialize(from: _elements + source.bucket, count: 1) + } +} + +extension _NativeSet { // Deletion + @inlinable + internal mutating func _delete(at index: Index) { + hashTable.delete(at: index, with: self) + _storage._count -= 1 + } + + @inlinable + @inline(__always) + internal mutating func uncheckedRemove( + at index: Index, + isUnique: Bool) -> Element { + _sanityCheck(hashTable.isOccupied(index)) + let rehashed = ensureUnique(isUnique: isUnique, capacity: capacity) + _sanityCheck(!rehashed) + let old = (_elements + index.bucket).move() + _delete(at: index) + return old + } + + @inlinable + @inline(__always) + internal mutating func remove(at index: Index, isUnique: Bool) -> Element { + _precondition(hashTable.isOccupied(index), "Invalid index") + return uncheckedRemove(at: index, isUnique: isUnique) + } + + @usableFromInline + internal mutating func removeAll(isUnique: Bool) { + guard isUnique else { + let scale = self._storage._scale + _storage = _SetStorage.allocate(scale: scale) + return + } + for index in hashTable { + (_elements + index.bucket).deinitialize(count: 1) + } + hashTable.clear() + _storage._count = 0 + } +} + +extension _NativeSet: Sequence { + @usableFromInline + @_fixed_layout + internal struct Iterator { + // The iterator is iterating over a frozen view of the collection state, so + // it keeps its own reference to the set. + @usableFromInline + internal let base: _NativeSet + @usableFromInline + internal var iterator: _HashTable.Iterator + + @inlinable + init(_ base: _NativeSet) { + self.base = base + self.iterator = base.hashTable.makeIterator() + } + } + + @inlinable + __consuming internal func makeIterator() -> Iterator { + return Iterator(self) + } +} + +extension _NativeSet.Iterator: IteratorProtocol { + @inlinable + internal mutating func next() -> Element? { + guard let index = iterator.next() else { return nil } + return base.uncheckedElement(at: index) + } +} diff --git a/stdlib/public/core/Set.swift b/stdlib/public/core/Set.swift index 238efc57e4941..4e9a02683a58d 100644 --- a/stdlib/public/core/Set.swift +++ b/stdlib/public/core/Set.swift @@ -10,8 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftShims - /// An unordered collection of unique elements. /// /// You use a set instead of an array when you need to test efficiently for @@ -1062,110 +1060,6 @@ extension Set: CustomStringConvertible, CustomDebugStringConvertible { } } -#if _runtime(_ObjC) -@_silgen_name("swift_stdlib_CFSetGetValues") -@usableFromInline -internal -func _stdlib_CFSetGetValues(_ nss: _NSSet, _: UnsafeMutablePointer) - -/// Equivalent to `NSSet.allObjects`, but does not leave objects on the -/// autorelease pool. -@inlinable -internal func _stdlib_NSSet_allObjects( - _ nss: _NSSet -) -> _HeapBuffer { - let count = nss.count - let storage = _HeapBuffer( - _HeapBufferStorage.self, count, count) - _stdlib_CFSetGetValues(nss, storage.baseAddress) - return storage -} -#endif - -//===--- Compiler conversion/casting entry points for Set --------===// - -/// Perform a non-bridged upcast that always succeeds. -/// -/// - Precondition: `BaseValue` is a base class or base `@objc` -/// protocol (such as `AnyObject`) of `DerivedValue`. -@inlinable -public func _setUpCast(_ source: Set) - -> Set { - var builder = _SetBuilder(count: source.count) - for x in source { - builder.add(member: x as! BaseValue) - } - return builder.take() -} - -/// Called by the casting machinery. -@_silgen_name("_swift_setDownCastIndirect") -internal func _setDownCastIndirect( - _ source: UnsafePointer>, - _ target: UnsafeMutablePointer>) { - target.initialize(to: _setDownCast(source.pointee)) -} - -/// Implements a forced downcast. This operation should have O(1) complexity. -/// -/// The cast can fail if bridging fails. The actual checks and bridging can be -/// deferred. -/// -/// - Precondition: `DerivedValue` is a subtype of `BaseValue` and both -/// are reference types. -@inlinable -public func _setDownCast(_ source: Set) - -> Set { - -#if _runtime(_ObjC) - if _isClassOrObjCExistential(BaseValue.self) - && _isClassOrObjCExistential(DerivedValue.self) { - switch source._variant { - case .native(let nativeSet): - return Set(_immutableCocoaSet: nativeSet.bridged()) - case .cocoa(let cocoaSet): - return Set(_immutableCocoaSet: cocoaSet.object) - } - } -#endif - return _setDownCastConditional(source)! -} - -/// Called by the casting machinery. -@_silgen_name("_swift_setDownCastConditionalIndirect") -internal func _setDownCastConditionalIndirect( - _ source: UnsafePointer>, - _ target: UnsafeMutablePointer> -) -> Bool { - if let result: Set = _setDownCastConditional(source.pointee) { - target.initialize(to: result) - return true - } - return false -} - -/// Implements a conditional downcast. -/// -/// If the cast fails, the function returns `nil`. All checks should be -/// performed eagerly. -/// -/// - Precondition: `DerivedValue` is a subtype of `BaseValue` and both -/// are reference types. -@inlinable -public func _setDownCastConditional( - _ source: Set -) -> Set? { - var result = Set(minimumCapacity: source.count) - for member in source { - if let derivedMember = member as? DerivedValue { - result.insert(derivedMember) - continue - } - return nil - } - return result -} - extension Set { /// Removes the elements of the given set from this set. /// @@ -1373,1863 +1267,218 @@ extension Set { } } -/// This protocol is only used for compile-time checks that -/// every buffer type implements all required operations. -internal protocol _SetBuffer { - associatedtype Element - associatedtype Index - - var startIndex: Index { get } - var endIndex: Index { get } - func index(after i: Index) -> Index - func index(for element: Element) -> Index? - var count: Int { get } - - func contains(_ member: Element) -> Bool - func element(at i: Index) -> Element -} - -/// An instance of this class has all `Set` data tail-allocated. -/// Enough bytes are allocated to hold the bitmap for marking valid entries, -/// keys, and values. The data layout starts with the bitmap, followed by the -/// keys, followed by the values. -// -// See the docs at the top of the file for more details on this type -// -// NOTE: The precise layout of this type is relied on in the runtime -// to provide a statically allocated empty singleton. -// See stdlib/public/stubs/GlobalObjects.cpp for details. -@_fixed_layout // FIXME(sil-serialize-all) -@usableFromInline -@_objc_non_lazy_realization -internal class _RawSetStorage: _SwiftNativeNSSet { - /// The current number of occupied entries in this set. - @usableFromInline - @nonobjc - internal final var _count: Int - - /// The maximum number of elements that can be inserted into this set without - /// exceeding the hash table's maximum load factor. - @usableFromInline - @nonobjc - internal final var _capacity: Int - - /// The scale of this set. The number of buckets is 2 raised to the - /// power of `scale`. - @usableFromInline - @nonobjc - internal final var _scale: Int - - @usableFromInline - internal final var _seed: Hasher._Seed - - @usableFromInline - @nonobjc - internal final var _rawElements: UnsafeMutableRawPointer +extension Set { + /// The position of an element in a set. + @_fixed_layout + public struct Index { + // Index for native buffer is efficient. Index for bridged NSSet is + // not, because neither NSEnumerator nor fast enumeration support moving + // backwards. Even if they did, there is another issue: NSEnumerator does + // not support NSCopying, and fast enumeration does not document that it is + // safe to copy the state. So, we cannot implement Index that is a value + // type for bridged NSSet in terms of Cocoa enumeration facilities. - // This type is made with allocWithTailElems, so no init is ever called. - // But we still need to have an init to satisfy the compiler. - @nonobjc - internal init(_doNotCallMe: ()) { - _sanityCheckFailure("This class cannot be directly initialized") - } + @_frozen + @usableFromInline + internal enum _Variant { + case native(_NativeSet.Index) +#if _runtime(_ObjC) + case cocoa(_CocoaSet.Index) +#endif + } - @inlinable - @nonobjc - internal final var _bucketCount: Int { - @inline(__always) get { return 1 &<< _scale } - } + @usableFromInline + internal var _variant: _Variant - @inlinable - @nonobjc - internal final var _metadata: UnsafeMutablePointer<_HashTable.Word> { - @inline(__always) get { - let address = Builtin.projectTailElems(self, _HashTable.Word.self) - return UnsafeMutablePointer(address) + @inlinable + @inline(__always) + internal init(_variant: _Variant) { + self._variant = _variant } - } - // The _HashTable struct contains pointers into tail-allocated storage, so - // this is unsafe and needs `_fixLifetime` calls in the caller. - @inlinable - @nonobjc - internal final var _hashTable: _HashTable { - @inline(__always) get { - return _HashTable(words: _metadata, bucketCount: _bucketCount) + @inlinable + @inline(__always) + internal init(_native index: _NativeSet.Index) { + self.init(_variant: .native(index)) } - } -} - -/// The storage class for the singleton empty set. -/// The single instance of this class is created by the runtime. -@_fixed_layout -@usableFromInline -internal class _EmptySetSingleton: _RawSetStorage { - @nonobjc - override internal init(_doNotCallMe: ()) { - _sanityCheckFailure("This class cannot be directly initialized") - } #if _runtime(_ObjC) - @objc - internal required init(objects: UnsafePointer, count: Int) { - _sanityCheckFailure("This class cannot be directly initialized") - } + @inlinable + @inline(__always) + internal init(_cocoa index: _CocoaSet.Index) { + self.init(_variant: .cocoa(index)) + } #endif -} - -extension _RawSetStorage { - /// The empty singleton that is used for every single Set that is created - /// without any elements. The contents of the storage must never be mutated. - @inlinable - @nonobjc - internal static var empty: _EmptySetSingleton { - return Builtin.bridgeFromRawPointer( - Builtin.addressof(&_swiftEmptySetSingleton)) } } -extension _EmptySetSingleton: _NSSetCore { +extension Set.Index { #if _runtime(_ObjC) - // - // NSSet implementation, assuming Self is the empty singleton - // - @objc(copyWithZone:) - internal func copy(with zone: _SwiftNSZone?) -> AnyObject { - return self - } - - @objc - internal var count: Int { - return 0 + @usableFromInline @_transparent + internal var _guaranteedNative: Bool { + return _canBeClass(Element.self) == 0 } - @objc(member:) - internal func member(_ object: AnyObject) -> AnyObject? { - return nil + /// Allow the optimizer to consider the surrounding code unreachable if + /// Set is guaranteed to be native. + @usableFromInline + @_transparent + internal func _cocoaPath() { + if _guaranteedNative { + _conditionallyUnreachable() + } } +#endif - @objc - internal func objectEnumerator() -> _NSEnumerator { - return _SwiftEmptyNSEnumerator() + @usableFromInline @_transparent + internal var _asNative: _NativeSet.Index { + switch _variant { + case .native(let nativeIndex): + return nativeIndex +#if _runtime(_ObjC) + case .cocoa: + _sanityCheckFailure("internal error: does not contain a native index") +#endif + } } - @objc(countByEnumeratingWithState:objects:count:) - internal func countByEnumerating( - with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>, - objects: UnsafeMutablePointer?, count: Int - ) -> Int { - // Even though we never do anything in here, we need to update the - // state so that callers know we actually ran. - var theState = state.pointee - if theState.state == 0 { - theState.state = 1 // Arbitrary non-zero value. - theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects) - theState.mutationsPtr = _fastEnumerationStorageMutationsPtr +#if _runtime(_ObjC) + @usableFromInline @_transparent + internal var _asCocoa: _CocoaSet.Index { + switch _variant { + case .native: + _sanityCheckFailure("internal error: does not contain a Cocoa index") + case .cocoa(let cocoaIndex): + return cocoaIndex } - state.pointee = theState - return 0 } #endif } -// See the docs at the top of this file for a description of this type -@_fixed_layout // FIXME(sil-serialize-all) -@usableFromInline -final internal class _SetStorage - : _RawSetStorage, _NSSetCore { - // This type is made with allocWithTailElems, so no init is ever called. - // But we still need to have an init to satisfy the compiler. - @nonobjc - override internal init(_doNotCallMe: ()) { - _sanityCheckFailure("This class cannot be directly initialized") - } - - deinit { - guard _count > 0 else { return } - if !_isPOD(Element.self) { - let elements = _elements - for index in _hashTable { - (elements + index.bucket).deinitialize(count: 1) - } +extension Set.Index: Equatable { + @inlinable + public static func == ( + lhs: Set.Index, + rhs: Set.Index + ) -> Bool { + switch (lhs._variant, rhs._variant) { + case (.native(let lhsNative), .native(let rhsNative)): + return lhsNative == rhsNative + #if _runtime(_ObjC) + case (.cocoa(let lhsCocoa), .cocoa(let rhsCocoa)): + lhs._cocoaPath() + return lhsCocoa == rhsCocoa + default: + _preconditionFailure("Comparing indexes from different sets") + #endif } - _fixLifetime(self) } +} +extension Set.Index: Comparable { @inlinable - final internal var _elements: UnsafeMutablePointer { - @inline(__always) - get { - return self._rawElements.assumingMemoryBound(to: Element.self) + public static func < ( + lhs: Set.Index, + rhs: Set.Index + ) -> Bool { + switch (lhs._variant, rhs._variant) { + case (.native(let lhsNative), .native(let rhsNative)): + return lhsNative < rhsNative + #if _runtime(_ObjC) + case (.cocoa(let lhsCocoa), .cocoa(let rhsCocoa)): + lhs._cocoaPath() + return lhsCocoa < rhsCocoa + default: + _preconditionFailure("Comparing indexes from different sets") + #endif } } +} - internal var asNative: _NativeSet { - return _NativeSet(self) - } - -#if _runtime(_ObjC) - @objc - internal required init(objects: UnsafePointer, count: Int) { - _sanityCheckFailure("don't call this designated initializer") +extension Set.Index: Hashable { + /// Hashes the essential components of this value by feeding them into the + /// given hasher. + /// + /// - Parameter hasher: The hasher to use when combining the components + /// of this instance. + @inlinable + public func hash(into hasher: inout Hasher) { + #if _runtime(_ObjC) + switch _variant { + case .native(let nativeIndex): + hasher.combine(0 as UInt8) + hasher.combine(nativeIndex.bucket) + case .cocoa(let cocoaIndex): + _cocoaPath() + hasher.combine(1 as UInt8) + hasher.combine(cocoaIndex.currentKeyIndex) + } + #else + hasher.combine(_asNative.bucket) + #endif } +} - @objc(copyWithZone:) - internal func copy(with zone: _SwiftNSZone?) -> AnyObject { - return self - } +extension Set { + /// An iterator over the members of a `Set`. + @_fixed_layout + public struct Iterator { + // Set has a separate IteratorProtocol and Index because of efficiency + // and implementability reasons. + // + // Native sets have efficient indices. Bridged NSSet instances don't. + // + // Even though fast enumeration is not suitable for implementing + // Index, which is multi-pass, it is suitable for implementing a + // IteratorProtocol, which is being consumed as iteration proceeds. - @objc - internal var count: Int { - return _count - } + @usableFromInline + @_frozen + internal enum _Variant { + case native(_NativeSet.Iterator) +#if _runtime(_ObjC) + case cocoa(_CocoaSet.Iterator) +#endif + } - @objc - internal func objectEnumerator() -> _NSEnumerator { - return _SwiftSetNSEnumerator(asNative) - } + @usableFromInline + internal var _variant: _Variant - @objc(countByEnumeratingWithState:objects:count:) - internal func countByEnumerating( - with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>, - objects: UnsafeMutablePointer?, count: Int - ) -> Int { - var theState = state.pointee - if theState.state == 0 { - theState.state = 1 // Arbitrary non-zero value. - theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects) - theState.mutationsPtr = _fastEnumerationStorageMutationsPtr - theState.extra.0 = CUnsignedLong(asNative.startIndex.bucket) + @inlinable + internal init(_variant: _Variant) { + self._variant = _variant } - // Test 'objects' rather than 'count' because (a) this is very rare anyway, - // and (b) the optimizer should then be able to optimize away the - // unwrapping check below. - if _slowPath(objects == nil) { - return 0 + @inlinable + internal init(_native: _NativeSet.Iterator) { + self.init(_variant: .native(_native)) } - let unmanagedObjects = _UnmanagedAnyObjectArray(objects!) - var index = _HashTable.Index(bucket: Int(theState.extra.0)) - let endIndex = asNative.endIndex - _precondition(index == endIndex || _hashTable.isValid(index)) - var stored = 0 - for i in 0.. AnyObject? { - guard let native = _conditionallyBridgeFromObjectiveC(object, Element.self) - else { return nil } - - let (index, found) = asNative.find(native) - guard found else { return nil } - return _bridgeAnythingToObjectiveC(_elements[index.bucket]) - } #endif -} - -extension _SetStorage { - @usableFromInline - @_effects(releasenone) - internal static func reallocate( - original: _RawSetStorage, - capacity: Int - ) -> (storage: _SetStorage, rehash: Bool) { - _sanityCheck(capacity >= original._count) - let scale = _HashTable.scale(forCapacity: capacity) - let rehash = (scale != original._scale) - let newStorage = _SetStorage.allocate(scale: scale) - return (newStorage, rehash) - } - - @usableFromInline - @_effects(releasenone) - static internal func allocate(capacity: Int) -> _SetStorage { - let scale = _HashTable.scale(forCapacity: capacity) - return allocate(scale: scale) - } - - static internal func allocate(scale: Int) -> _SetStorage { - // The entry count must be representable by an Int value; hence the scale's - // peculiar upper bound. - _sanityCheck(scale >= 0 && scale < Int.bitWidth - 1) - - let bucketCount = 1 &<< scale - let wordCount = _UnsafeBitset.wordCount(forCapacity: bucketCount) - let storage = Builtin.allocWithTailElems_2( - _SetStorage.self, - wordCount._builtinWordValue, _HashTable.Word.self, - bucketCount._builtinWordValue, Element.self) - - let metadataAddr = Builtin.projectTailElems(storage, _HashTable.Word.self) - let elementsAddr = Builtin.getTailAddr_Word( - metadataAddr, wordCount._builtinWordValue, _HashTable.Word.self, - Element.self) - storage._count = 0 - storage._capacity = _HashTable.capacity(forScale: scale) - storage._scale = scale - storage._rawElements = UnsafeMutableRawPointer(elementsAddr) - - // We use a slightly different hash seed whenever we change the size of the - // hash table, so that we avoid certain copy operations becoming quadratic, - // without breaking value semantics. (For background details, see - // https://bugs.swift.org/browse/SR-3268) - - // FIXME: Use true per-instance seeding instead. Per-capacity seeding still - // leaves hash values the same in same-sized tables, which may affect - // operations on two tables at once. (E.g., union.) - storage._seed = ( - Hasher._seed.0 ^ UInt64(truncatingIfNeeded: scale), - Hasher._seed.1) - - // Initialize hash table metadata. - storage._hashTable.clear() - return storage } } -/// A wrapper around _RawSetStorage that provides most of the -/// implementation of Set. -@usableFromInline -@_fixed_layout -internal struct _NativeSet { - /// See the comments on _RawSetStorage and its subclasses to understand why we - /// store an untyped storage here. - @usableFromInline - internal var _storage: _RawSetStorage - - /// Constructs an instance from the empty singleton. - @inlinable - @inline(__always) - internal init() { - self._storage = _RawSetStorage.empty +extension Set.Iterator { +#if _runtime(_ObjC) + @usableFromInline @_transparent + internal var _guaranteedNative: Bool { + return _canBeClass(Element.self) == 0 } - /// Constructs a native set adopting the given storage. - @inlinable - @inline(__always) - internal init(_ storage: _RawSetStorage) { - self._storage = storage + /// Allow the optimizer to consider the surrounding code unreachable if + /// Set is guaranteed to be native. + @usableFromInline @_transparent + internal func _cocoaPath() { + if _guaranteedNative { + _conditionallyUnreachable() + } } - - @usableFromInline - @_effects(releasenone) - internal init(capacity: Int) { - let scale = _HashTable.scale(forCapacity: capacity) - self._storage = _SetStorage.allocate(scale: scale) - } - -#if _runtime(_ObjC) - @inlinable - internal init(_ cocoa: _CocoaSet) { - self.init(cocoa, capacity: cocoa.count) - } - - @inlinable - internal init(_ cocoa: _CocoaSet, capacity: Int) { - _sanityCheck(cocoa.count <= capacity) - self.init(capacity: capacity) - for element in cocoa { - let nativeElement = _forceBridgeFromObjectiveC(element, Element.self) - insertNew(nativeElement, isUnique: true) - } - } -#endif -} - -extension _NativeSet { // Primitive fields - @inlinable - internal var capacity: Int { - @inline(__always) - get { - return _assumeNonNegative(_storage._capacity) - } - } - - @inlinable - internal var hashTable: _HashTable { - @inline(__always) get { - return _storage._hashTable - } - } - - // This API is unsafe and needs a `_fixLifetime` in the caller. - @inlinable - internal var _elements: UnsafeMutablePointer { - return _storage._rawElements.assumingMemoryBound(to: Element.self) - } -} - -extension _NativeSet { // Low-level unchecked operations - @inlinable - @inline(__always) - internal func uncheckedElement(at index: Index) -> Element { - defer { _fixLifetime(self) } - _sanityCheck(hashTable.isOccupied(index)) - return _elements[index.bucket] - } - - @inlinable - @inline(__always) - internal func uncheckedInitialize(at index: Index, to element: Element) { - _sanityCheck(hashTable.isValid(index)) - (_elements + index.bucket).initialize(to: element) - } -} - -extension _NativeSet { // Low-level lookup operations - @inlinable - @inline(__always) - internal func hashValue(for element: Element) -> Int { - return element._rawHashValue(seed: _storage._seed) - } - - @inlinable - @inline(__always) - internal func find(_ element: Element) -> (index: Index, found: Bool) { - return find(element, hashValue: self.hashValue(for: element)) - } - - /// Search for a given element, assuming it has the specified hash value. - /// - /// If the element is not present in this set, return the position where it - /// could be inserted. - @inlinable - @inline(__always) - internal func find( - _ element: Element, - hashValue: Int - ) -> (index: Index, found: Bool) { - let hashTable = self.hashTable - var index = hashTable.idealIndex(forHashValue: hashValue) - while hashTable._isOccupied(index) { - if uncheckedElement(at: index) == element { - return (index, true) - } - index = hashTable.index(wrappedAfter: index) - } - return (index, false) - } -} - -extension _NativeSet { // ensureUnique - @inlinable - internal mutating func resize(capacity: Int) { - let capacity = Swift.max(capacity, self.capacity) - let result = _NativeSet(_SetStorage.allocate(capacity: capacity)) - if count > 0 { - for index in hashTable { - let element = (self._elements + index.bucket).move() - result._unsafeInsertNew(element) - } - // Clear out old storage, ensuring that its deinit won't overrelease the - // elements we've just moved out. - _storage._hashTable.clear() - _storage._count = 0 - } - _storage = result._storage - } - - @inlinable - internal mutating func copy(capacity: Int) -> Bool { - let capacity = Swift.max(capacity, self.capacity) - let (newStorage, rehash) = _SetStorage.reallocate( - original: _storage, - capacity: capacity) - let result = _NativeSet(newStorage) - if count > 0 { - if rehash { - for index in hashTable { - result._unsafeInsertNew(self.uncheckedElement(at: index)) - } - } else { - result.hashTable.copyContents(of: hashTable) - result._storage._count = self.count - for index in hashTable { - let element = uncheckedElement(at: index) - result.uncheckedInitialize(at: index, to: element) - } - } - } - _storage = result._storage - return rehash - } - - /// Ensure storage of self is uniquely held and can hold at least `capacity` - /// elements. Returns true iff contents were rehashed. - @inlinable - @inline(__always) - internal mutating func ensureUnique(isUnique: Bool, capacity: Int) -> Bool { - if _fastPath(capacity <= self.capacity && isUnique) { - return false - } - guard isUnique else { - return copy(capacity: capacity) - } - resize(capacity: capacity) - return true - } - - @inlinable - internal mutating func reserveCapacity(_ capacity: Int, isUnique: Bool) { - _ = ensureUnique(isUnique: isUnique, capacity: capacity) - } -} - -extension _NativeSet: _SetBuffer { - @usableFromInline - internal typealias Index = _HashTable.Index - - @inlinable - internal var startIndex: Index { - return hashTable.startIndex - } - - @inlinable - internal var endIndex: Index { - return hashTable.endIndex - } - - @inlinable - internal func index(after index: Index) -> Index { - return hashTable.index(after: index) - } - - @inlinable - @inline(__always) - internal func index(for element: Element) -> Index? { - if count == 0 { - // Fast path that avoids computing the hash of the key. - return nil - } - let (index, found) = find(element) - return found ? index : nil - } - - @inlinable - internal var count: Int { - @inline(__always) get { - return _assumeNonNegative(_storage._count) - } - } - - @inlinable - @inline(__always) - internal func contains(_ member: Element) -> Bool { - // Fast path: Don't calculate the hash if the set has no elements. - if count == 0 { return false } - return find(member).found - } - - @inlinable - @inline(__always) - internal func element(at index: Index) -> Element { - hashTable.checkOccupied(index) - return _elements[index.bucket] - } -} - -#if _runtime(_ObjC) -extension _NativeSet { // Bridging - @usableFromInline - internal func bridged() -> _NSSet { - // We can zero-cost bridge if our keys are verbatim - // or if we're the empty singleton. - - // Temporary var for SOME type safety before a cast. - let nsSet: _NSSetCore - - if _storage === _RawSetStorage.empty || count == 0 { - nsSet = _RawSetStorage.empty - } else if _isBridgedVerbatimToObjectiveC(Element.self) { - nsSet = unsafeDowncast(_storage, to: _SetStorage.self) - } else { - nsSet = _SwiftDeferredNSSet(self) - } - - // Cast from "minimal NSSet" to "NSSet" - // Note that if you actually ask Swift for this cast, it will fail. - // Never trust a shadow protocol! - return unsafeBitCast(nsSet, to: _NSSet.self) - } -} -#endif - -// This function has a highly visible name to make it stand out in stack traces. -@usableFromInline -@inline(never) -internal func ELEMENT_TYPE_OF_SET_VIOLATES_HASHABLE_REQUIREMENTS( - _ elementType: Any.Type -) -> Never { - _assertionFailure( - "Fatal error", - """ - Duplicate elements of type '\(elementType)' were found in a Set. - This usually means either that the type violates Hashable's requirements, or - that members of such a set were mutated after insertion. - """, - flags: _fatalErrorFlags()) -} - -extension _NativeSet { // Insertions - /// Insert a new element into uniquely held storage. - /// Storage must be uniquely referenced with adequate capacity. - /// The `element` must not be already present in the Set. - @inlinable - internal func _unsafeInsertNew(_ element: Element) { - _sanityCheck(count + 1 <= capacity) - let hashValue = self.hashValue(for: element) - if _isDebugAssertConfiguration() { - // In debug builds, perform a full lookup and trap if we detect duplicate - // elements -- these imply that the Element type violates Hashable - // requirements. This is generally more costly than a direct insertion, - // because we'll need to compare elements in case of hash collisions. - let (index, found) = find(element, hashValue: hashValue) - guard !found else { - ELEMENT_TYPE_OF_SET_VIOLATES_HASHABLE_REQUIREMENTS(Element.self) - } - hashTable.insert(index) - uncheckedInitialize(at: index, to: element) - } else { - let index = hashTable.insertNew(hashValue: hashValue) - uncheckedInitialize(at: index, to: element) - } - _storage._count += 1 - } - - /// Insert a new element into uniquely held storage. - /// Storage must be uniquely referenced. - /// The `element` must not be already present in the Set. - @inlinable - internal mutating func insertNew(_ element: Element, isUnique: Bool) { - _ = ensureUnique(isUnique: isUnique, capacity: count + 1) - _unsafeInsertNew(element) - } - - @inlinable - internal func _unsafeInsertNew(_ element: Element, at index: Index) { - hashTable.insert(index) - uncheckedInitialize(at: index, to: element) - _storage._count += 1 - } - - @inlinable - internal mutating func insertNew( - _ element: Element, - at index: Index, - isUnique: Bool - ) { - _sanityCheck(!hashTable.isOccupied(index)) - var index = index - if ensureUnique(isUnique: isUnique, capacity: count + 1) { - let (i, f) = find(element) - if f { - ELEMENT_TYPE_OF_SET_VIOLATES_HASHABLE_REQUIREMENTS(Element.self) - } - index = i - } - _unsafeInsertNew(element, at: index) - } - - @inlinable - internal mutating func update( - with element: Element, - isUnique: Bool - ) -> Element? { - var (index, found) = find(element) - let rehashed = ensureUnique( - isUnique: isUnique, - capacity: count + (found ? 0 : 1)) - if rehashed { - let (i, f) = find(element) - if f != found { - ELEMENT_TYPE_OF_SET_VIOLATES_HASHABLE_REQUIREMENTS(Element.self) - } - index = i - } - if found { - let old = (_elements + index.bucket).move() - uncheckedInitialize(at: index, to: element) - return old - } - _unsafeInsertNew(element, at: index) - return nil - } -} - -extension _NativeSet: _HashTableDelegate { - @inlinable - @inline(__always) - internal func hashValue(at index: Index) -> Int { - return hashValue(for: uncheckedElement(at: index)) - } - - @inlinable - @inline(__always) - internal func moveEntry(from source: Index, to target: Index) { - (_elements + target.bucket) - .moveInitialize(from: _elements + source.bucket, count: 1) - } -} - -extension _NativeSet { // Deletion - @inlinable - internal mutating func _delete(at index: Index) { - hashTable.delete(at: index, with: self) - _storage._count -= 1 - } - - @inlinable - @inline(__always) - internal mutating func uncheckedRemove( - at index: Index, - isUnique: Bool) -> Element { - _sanityCheck(hashTable.isOccupied(index)) - let rehashed = ensureUnique(isUnique: isUnique, capacity: capacity) - _sanityCheck(!rehashed) - let old = (_elements + index.bucket).move() - _delete(at: index) - return old - } - - @inlinable - @inline(__always) - internal mutating func remove(at index: Index, isUnique: Bool) -> Element { - _precondition(hashTable.isOccupied(index), "Invalid index") - return uncheckedRemove(at: index, isUnique: isUnique) - } - - @usableFromInline - internal mutating func removeAll(isUnique: Bool) { - guard isUnique else { - let scale = self._storage._scale - _storage = _SetStorage.allocate(scale: scale) - return - } - for index in hashTable { - (_elements + index.bucket).deinitialize(count: 1) - } - hashTable.clear() - _storage._count = 0 - } -} - -#if _runtime(_ObjC) -/// An NSEnumerator that works with any _NativeSet of verbatim bridgeable -/// elements. Used by the various NSSet impls. -final internal class _SwiftSetNSEnumerator - : _SwiftNativeNSEnumerator, _NSEnumerator { - - @nonobjc internal var base: _NativeSet - @nonobjc internal var bridgedElements: _BridgingHashBuffer? - @nonobjc internal var nextIndex: _NativeSet.Index - @nonobjc internal var endIndex: _NativeSet.Index - - @objc - internal override required init() { - _sanityCheckFailure("don't call this designated initializer") - } - - internal init(_ base: _NativeSet) { - _sanityCheck(_isBridgedVerbatimToObjectiveC(Element.self)) - self.base = base - self.bridgedElements = nil - self.nextIndex = base.startIndex - self.endIndex = base.endIndex - } - - @nonobjc - internal init(_ deferred: _SwiftDeferredNSSet) { - _sanityCheck(!_isBridgedVerbatimToObjectiveC(Element.self)) - self.base = deferred.native - self.bridgedElements = deferred.bridgeElements() - self.nextIndex = base.startIndex - self.endIndex = base.endIndex - } - - private func bridgedElement(at index: _HashTable.Index) -> AnyObject { - _sanityCheck(base.hashTable.isOccupied(index)) - if let bridgedElements = self.bridgedElements { - return bridgedElements[index] - } - return _bridgeAnythingToObjectiveC(base.element(at: index)) - } - - // - // NSEnumerator implementation. - // - // Do not call any of these methods from the standard library! - // - - @objc - internal func nextObject() -> AnyObject? { - if nextIndex == endIndex { - return nil - } - let index = nextIndex - nextIndex = base.index(after: nextIndex) - return self.bridgedElement(at: index) - } - - @objc(countByEnumeratingWithState:objects:count:) - internal func countByEnumerating( - with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>, - objects: UnsafeMutablePointer, - count: Int - ) -> Int { - var theState = state.pointee - if theState.state == 0 { - theState.state = 1 // Arbitrary non-zero value. - theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects) - theState.mutationsPtr = _fastEnumerationStorageMutationsPtr - } - - if nextIndex == endIndex { - state.pointee = theState - return 0 - } - - // Return only a single element so that code can start iterating via fast - // enumeration, terminate it, and continue via NSEnumerator. - let unmanagedObjects = _UnmanagedAnyObjectArray(objects) - unmanagedObjects[0] = self.bridgedElement(at: nextIndex) - nextIndex = base.index(after: nextIndex) - state.pointee = theState - return 1 - } -} -#endif - -#if _runtime(_ObjC) -/// This class exists for Objective-C bridging. It holds a reference to a -/// _NativeSet, and can be upcast to NSSelf when bridging is necessary. This is -/// the fallback implementation for situations where toll-free bridging isn't -/// possible. On first access, a _NativeSet of AnyObject will be constructed -/// containing all the bridged elements. -final internal class _SwiftDeferredNSSet - : _SwiftNativeNSSet, _NSSetCore { - - // This stored property must be stored at offset zero. We perform atomic - // operations on it. - // - // Do not access this property directly. - @nonobjc - private var _bridgedElements_DoNotUse: AnyObject? - - /// The unbridged elements. - internal var native: _NativeSet - - internal init(_ native: _NativeSet) { - _sanityCheck(native.count > 0) - _sanityCheck(!_isBridgedVerbatimToObjectiveC(Element.self)) - self.native = native - super.init() - } - - /// Returns the pointer to the stored property, which contains bridged - /// Set elements. - @nonobjc - private var _bridgedElementsPtr: UnsafeMutablePointer { - return _getUnsafePointerToStoredProperties(self) - .assumingMemoryBound(to: Optional.self) - } - - /// The buffer for bridged Set elements, if present. - @nonobjc - private var _bridgedElements: _BridgingHashBuffer? { - guard let ref = _stdlib_atomicLoadARCRef(object: _bridgedElementsPtr) else { - return nil - } - return unsafeDowncast(ref, to: _BridgingHashBuffer.self) - } - - /// Attach a buffer for bridged Set elements. - @nonobjc - private func _initializeBridgedElements(_ storage: _BridgingHashBuffer) { - _stdlib_atomicInitializeARCRef( - object: _bridgedElementsPtr, - desired: storage) - } - - @nonobjc - internal func bridgeElements() -> _BridgingHashBuffer { - if let bridgedElements = _bridgedElements { return bridgedElements } - - // Allocate and initialize heap storage for bridged objects. - let bridged = _BridgingHashBuffer.allocate( - owner: native._storage, - hashTable: native.hashTable) - for index in native.hashTable { - let object = _bridgeAnythingToObjectiveC(native.element(at: index)) - bridged.initialize(at: index, to: object) - } - - // Atomically put the bridged elements in place. - _initializeBridgedElements(bridged) - return _bridgedElements! - } - - @objc - internal required init(objects: UnsafePointer, count: Int) { - _sanityCheckFailure("don't call this designated initializer") - } - - @objc(copyWithZone:) - internal func copy(with zone: _SwiftNSZone?) -> AnyObject { - // Instances of this class should be visible outside of standard library as - // having `NSSet` type, which is immutable. - return self - } - - @objc(member:) - internal func member(_ object: AnyObject) -> AnyObject? { - guard let element = _conditionallyBridgeFromObjectiveC(object, Element.self) - else { return nil } - - let (index, found) = native.find(element) - guard found else { return nil } - let bridged = bridgeElements() - return bridged[index] - } - - @objc - internal func objectEnumerator() -> _NSEnumerator { - return _SwiftSetNSEnumerator(self) - } - - @objc - internal var count: Int { - return native.count - } - - @objc(countByEnumeratingWithState:objects:count:) - internal func countByEnumerating( - with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>, - objects: UnsafeMutablePointer?, - count: Int - ) -> Int { - var theState = state.pointee - if theState.state == 0 { - theState.state = 1 // Arbitrary non-zero value. - theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects) - theState.mutationsPtr = _fastEnumerationStorageMutationsPtr - theState.extra.0 = CUnsignedLong(native.startIndex.bucket) - } - - // Test 'objects' rather than 'count' because (a) this is very rare anyway, - // and (b) the optimizer should then be able to optimize away the - // unwrapping check below. - if _slowPath(objects == nil) { - return 0 - } - - let unmanagedObjects = _UnmanagedAnyObjectArray(objects!) - var index = _NativeSet.Index(bucket: Int(theState.extra.0)) - let endIndex = native.endIndex - _precondition(index == endIndex || native.hashTable.isValid(index)) - - // Only need to bridge once, so we can hoist it out of the loop. - let bridgedElements = bridgeElements() - - var stored = 0 - for i in 0.. { } -#endif - -#if _runtime(_ObjC) -@usableFromInline -@_fixed_layout -internal struct _CocoaSet { - @usableFromInline - internal let object: _NSSet - - @inlinable - internal init(_ object: _NSSet) { - self.object = object - } -} - -extension _CocoaSet { - @usableFromInline - @_effects(releasenone) - internal func member(for index: Index) -> AnyObject { - return index.allKeys[index.currentKeyIndex] - } - - @inlinable - internal func member(for element: AnyObject) -> AnyObject? { - return object.member(element) - } -} - -extension _CocoaSet: Equatable { - @usableFromInline - internal static func ==(lhs: _CocoaSet, rhs: _CocoaSet) -> Bool { - return _stdlib_NSObject_isEqual(lhs.object, rhs.object) - } -} - -extension _CocoaSet: _SetBuffer { - @usableFromInline - internal typealias Element = AnyObject - - @inlinable - internal var startIndex: Index { - return Index(self, startIndex: ()) - } - - @inlinable - internal var endIndex: Index { - return Index(self, endIndex: ()) - } - - @inlinable - internal func index(after i: Index) -> Index { - var i = i - formIndex(after: &i) - return i - } - - @usableFromInline - @_effects(releasenone) - internal func formIndex(after i: inout Index) { - _precondition(i.base.object === self.object, "Invalid index") - _precondition(i.currentKeyIndex < i.allKeys.value, - "Cannot increment endIndex") - i.currentKeyIndex += 1 - } - - @usableFromInline - internal func index(for element: AnyObject) -> Index? { - // Fast path that does not involve creating an array of all keys. In case - // the key is present, this lookup is a penalty for the slow path, but the - // potential savings are significant: we could skip a memory allocation and - // a linear search. - if !contains(element) { - return nil - } - - let allKeys = _stdlib_NSSet_allObjects(object) - var keyIndex = -1 - for i in 0..= 0, - "Key was found in fast path, but not found later?") - return Index(self, allKeys, keyIndex) - } - - @inlinable - internal var count: Int { - return object.count - } - - @inlinable - internal func contains(_ element: AnyObject) -> Bool { - return object.member(element) != nil - } - - @usableFromInline - internal func element(at i: Index) -> AnyObject { - let value: AnyObject? = i.allKeys[i.currentKeyIndex] - _sanityCheck(value != nil, "Item not found in underlying NSSet") - return value! - } -} -#endif - -extension Set { - @usableFromInline - @_frozen - internal enum _Variant { - case native(_NativeSet) -#if _runtime(_ObjC) - case cocoa(_CocoaSet) -#endif - } -} - -extension Set._Variant { -#if _runtime(_ObjC) - @usableFromInline - @_transparent - internal var guaranteedNative: Bool { - return _canBeClass(Element.self) == 0 - } - - /// Allow the optimizer to consider the surrounding code unreachable if - /// Set is guaranteed to be native. - @usableFromInline - @_transparent - internal func cocoaPath() { - if guaranteedNative { - _conditionallyUnreachable() - } - } -#endif - - @inlinable - internal mutating func isUniquelyReferenced() -> Bool { - // Note that &self drills down through .native(_NativeSet) to the first - // property in _NativeSet, which is the reference to the storage. - switch self { - case .native: - return _isUnique_native(&self) -#if _runtime(_ObjC) - case .cocoa: - cocoaPath() - // Don't consider Cocoa buffer mutable, even if it is mutable and is - // uniquely referenced. - return false -#endif - } - } - - @usableFromInline @_transparent - internal var asNative: _NativeSet { - get { - switch self { - case .native(let nativeSet): - return nativeSet -#if _runtime(_ObjC) - case .cocoa: - _sanityCheckFailure("internal error: not backed by native buffer") -#endif - } - } - set { - self = .native(newValue) - } - } - -#if _runtime(_ObjC) - @inlinable - internal var asCocoa: _CocoaSet { - switch self { - case .native: - _sanityCheckFailure("internal error: not backed by NSSet") - case .cocoa(let cocoa): - return cocoa - } - } -#endif - - /// Reserves enough space for the specified number of elements to be stored - /// without reallocating additional storage. - @inlinable - internal mutating func reserveCapacity(_ capacity: Int) { - switch self { - case .native: - let isUnique = isUniquelyReferenced() - asNative.reserveCapacity(capacity, isUnique: isUnique) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - let capacity = Swift.max(cocoa.count, capacity) - self = .native(_NativeSet(cocoa, capacity: capacity)) -#endif - } - } - - /// The number of elements that can be stored without expanding the current - /// storage. - /// - /// For bridged storage, this is equal to the current count of the - /// collection, since any addition will trigger a copy of the elements into - /// newly allocated storage. For native storage, this is the element count - /// at which adding any more elements will exceed the load factor. - @inlinable - internal var capacity: Int { - switch self { - case .native: - return asNative.capacity -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return cocoa.count -#endif - } - } -} - -extension Set._Variant: _SetBuffer { - @usableFromInline - internal typealias Index = Set.Index - - @inlinable - internal var startIndex: Index { - switch self { - case .native: - return Index(_native: asNative.startIndex) -#if _runtime(_ObjC) - case .cocoa(let cocoaSet): - cocoaPath() - return Index(_cocoa: cocoaSet.startIndex) -#endif - } - } - - @inlinable - internal var endIndex: Index { - switch self { - case .native: - return Index(_native: asNative.endIndex) -#if _runtime(_ObjC) - case .cocoa(let cocoaSet): - cocoaPath() - return Index(_cocoa: cocoaSet.endIndex) -#endif - } - } - - @inlinable - internal func index(after i: Index) -> Index { - switch self { - case .native: - return Index(_native: asNative.index(after: i._asNative)) -#if _runtime(_ObjC) - case .cocoa(let cocoaSet): - cocoaPath() - return Index(_cocoa: cocoaSet.index(after: i._asCocoa)) -#endif - } - } - - @inlinable - @inline(__always) - internal func index(for element: Element) -> Index? { - switch self { - case .native: - guard let index = asNative.index(for: element) else { return nil } - return Index(_native: index) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - let cocoaElement = _bridgeAnythingToObjectiveC(element) - guard let index = cocoa.index(for: cocoaElement) else { return nil } - return Index(_cocoa: index) -#endif - } - } - - @inlinable - internal var count: Int { - @inline(__always) - get { - switch self { - case .native: - return asNative.count -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return cocoa.count -#endif - } - } - } - - @inlinable - @inline(__always) - internal func contains(_ member: Element) -> Bool { - switch self { - case .native: - return asNative.contains(member) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return cocoa.contains(_bridgeAnythingToObjectiveC(member)) -#endif - } - } - - @inlinable - @inline(__always) - internal func element(at i: Index) -> Element { - switch self { - case .native: - return asNative.element(at: i._asNative) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - let cocoaMember = cocoa.element(at: i._asCocoa) - return _forceBridgeFromObjectiveC(cocoaMember, Element.self) -#endif - } - } -} - -extension Set._Variant { - @inlinable - internal mutating func update(with value: Element) -> Element? { - switch self { - case .native: - let isUnique = self.isUniquelyReferenced() - return asNative.update(with: value, isUnique: isUnique) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - // Make sure we have space for an extra element. - var native = _NativeSet(cocoa, capacity: cocoa.count + 1) - let old = native.update(with: value, isUnique: true) - self = .native(native) - return old -#endif - } - } - - @inlinable - internal mutating func insert( - _ element: Element - ) -> (inserted: Bool, memberAfterInsert: Element) { - switch self { - case .native: - let (index, found) = asNative.find(element) - if found { - return (false, asNative.uncheckedElement(at: index)) - } - let isUnique = self.isUniquelyReferenced() - asNative.insertNew(element, at: index, isUnique: isUnique) - return (true, element) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - // Make sure we have space for an extra element. - let cocoaMember = _bridgeAnythingToObjectiveC(element) - if let m = cocoa.member(for: cocoaMember) { - return (false, _forceBridgeFromObjectiveC(m, Element.self)) - } - var native = _NativeSet(cocoa, capacity: cocoa.count + 1) - native.insertNew(element, isUnique: true) - self = .native(native) - return (true, element) -#endif - } - } - - @inlinable - @discardableResult - internal mutating func remove(at index: Index) -> Element { - switch self { - case .native: - let isUnique = isUniquelyReferenced() - return asNative.remove(at: index._asNative, isUnique: isUnique) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - // We have to migrate the data first. But after we do so, the Cocoa - // index becomes useless, so get the element first. - let cocoaMember = cocoa.member(for: index._asCocoa) - let nativeMember = _forceBridgeFromObjectiveC(cocoaMember, Element.self) - return _migrateToNative(cocoa, removing: nativeMember) -#endif - } - } - - @inlinable - @discardableResult - internal mutating func remove(_ member: Element) -> Element? { - switch self { - case .native: - let (index, found) = asNative.find(member) - guard found else { return nil } - let isUnique = isUniquelyReferenced() - return asNative.uncheckedRemove(at: index, isUnique: isUnique) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - let cocoaMember = _bridgeAnythingToObjectiveC(member) - guard cocoa.contains(cocoaMember) else { return nil } - return _migrateToNative(cocoa, removing: member) -#endif - } - } - -#if _runtime(_ObjC) - @inlinable - internal mutating func _migrateToNative( - _ cocoa: _CocoaSet, - removing member: Element - ) -> Element { - // FIXME(performance): fuse data migration and element deletion into one - // operation. - var native = _NativeSet(cocoa) - let (index, found) = native.find(member) - _precondition(found, "Bridging did not preserve equality") - let old = native.remove(at: index, isUnique: true) - _precondition(member == old, "Bridging did not preserve equality") - self = .native(native) - return old - } -#endif - - @inlinable - internal mutating func removeAll(keepingCapacity keepCapacity: Bool) { - if !keepCapacity { - self = .native(_NativeSet()) - return - } - guard count > 0 else { return } - - switch self { - case .native: - let isUnique = isUniquelyReferenced() - asNative.removeAll(isUnique: isUnique) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - self = .native(_NativeSet(capacity: cocoa.count)) -#endif - } - } -} - -extension Set._Variant { - /// Returns an iterator over the elements. - /// - /// - Complexity: O(1). - @inlinable - @inline(__always) - __consuming internal func makeIterator() -> Set.Iterator { - switch self { - case .native(let native): - return Set.Iterator(_native: native.makeIterator()) -#if _runtime(_ObjC) - case .cocoa(let cocoa): - cocoaPath() - return Set.Iterator(_cocoa: cocoa.makeIterator()) -#endif - } - } -} - -#if _runtime(_ObjC) -extension _CocoaSet { - @_fixed_layout // FIXME(sil-serialize-all) - @usableFromInline - internal struct Index { - // Assumption: we rely on NSDictionary.getObjects when being - // repeatedly called on the same NSDictionary, returning items in the same - // order every time. - // Similarly, the same assumption holds for NSSet.allObjects. - - /// A reference to the NSSet, which owns members in `allObjects`, - /// or `allKeys`, for NSSet and NSDictionary respectively. - @usableFromInline // FIXME(sil-serialize-all) - internal let base: _CocoaSet - // FIXME: swift-3-indexing-model: try to remove the cocoa reference, but - // make sure that we have a safety check for accessing `allKeys`. Maybe - // move both into the dictionary/set itself. - - /// An unowned array of keys. - @usableFromInline // FIXME(sil-serialize-all) - internal var allKeys: _HeapBuffer - - /// Index into `allKeys` - @usableFromInline // FIXME(sil-serialize-all) - internal var currentKeyIndex: Int - - @inlinable // FIXME(sil-serialize-all) - internal init(_ base: _CocoaSet, startIndex: ()) { - self.base = base - self.allKeys = _stdlib_NSSet_allObjects(base.object) - self.currentKeyIndex = 0 - } - - @inlinable // FIXME(sil-serialize-all) - internal init(_ base: _CocoaSet, endIndex: ()) { - self.base = base - self.allKeys = _stdlib_NSSet_allObjects(base.object) - self.currentKeyIndex = allKeys.value - } - - @inlinable // FIXME(sil-serialize-all) - internal init( - _ base: _CocoaSet, - _ allKeys: _HeapBuffer, - _ currentKeyIndex: Int - ) { - self.base = base - self.allKeys = allKeys - self.currentKeyIndex = currentKeyIndex - } - } -} - -extension _CocoaSet.Index: Equatable { - @inlinable - internal static func == (lhs: _CocoaSet.Index, rhs: _CocoaSet.Index) -> Bool { - _precondition(lhs.base.object === rhs.base.object, - "Comparing indexes from different sets") - return lhs.currentKeyIndex == rhs.currentKeyIndex - } -} - -extension _CocoaSet.Index: Comparable { - @inlinable - internal static func < (lhs: _CocoaSet.Index, rhs: _CocoaSet.Index) -> Bool { - _precondition(lhs.base.object === rhs.base.object, - "Comparing indexes from different sets") - return lhs.currentKeyIndex < rhs.currentKeyIndex - } -} -#endif - -extension Set { - /// The position of an element in a set. - @_fixed_layout - public struct Index { - // Index for native buffer is efficient. Index for bridged NSSet is - // not, because neither NSEnumerator nor fast enumeration support moving - // backwards. Even if they did, there is another issue: NSEnumerator does - // not support NSCopying, and fast enumeration does not document that it is - // safe to copy the state. So, we cannot implement Index that is a value - // type for bridged NSSet in terms of Cocoa enumeration facilities. - - @_frozen - @usableFromInline - internal enum _Variant { - case native(_NativeSet.Index) -#if _runtime(_ObjC) - case cocoa(_CocoaSet.Index) -#endif - } - - @usableFromInline - internal var _variant: _Variant - - @inlinable - @inline(__always) - internal init(_variant: _Variant) { - self._variant = _variant - } - - @inlinable - @inline(__always) - internal init(_native index: _NativeSet.Index) { - self.init(_variant: .native(index)) - } - -#if _runtime(_ObjC) - @inlinable - @inline(__always) - internal init(_cocoa index: _CocoaSet.Index) { - self.init(_variant: .cocoa(index)) - } -#endif - } -} - -extension Set.Index { -#if _runtime(_ObjC) - @usableFromInline @_transparent - internal var _guaranteedNative: Bool { - return _canBeClass(Element.self) == 0 - } - - /// Allow the optimizer to consider the surrounding code unreachable if - /// Set is guaranteed to be native. - @usableFromInline - @_transparent - internal func _cocoaPath() { - if _guaranteedNative { - _conditionallyUnreachable() - } - } -#endif - - @usableFromInline @_transparent - internal var _asNative: _NativeSet.Index { - switch _variant { - case .native(let nativeIndex): - return nativeIndex -#if _runtime(_ObjC) - case .cocoa: - _sanityCheckFailure("internal error: does not contain a native index") -#endif - } - } - -#if _runtime(_ObjC) - @usableFromInline @_transparent - internal var _asCocoa: _CocoaSet.Index { - switch _variant { - case .native: - _sanityCheckFailure("internal error: does not contain a Cocoa index") - case .cocoa(let cocoaIndex): - return cocoaIndex - } - } -#endif -} - -extension Set.Index: Equatable { - @inlinable - public static func == ( - lhs: Set.Index, - rhs: Set.Index - ) -> Bool { - switch (lhs._variant, rhs._variant) { - case (.native(let lhsNative), .native(let rhsNative)): - return lhsNative == rhsNative - #if _runtime(_ObjC) - case (.cocoa(let lhsCocoa), .cocoa(let rhsCocoa)): - lhs._cocoaPath() - return lhsCocoa == rhsCocoa - default: - _preconditionFailure("Comparing indexes from different sets") - #endif - } - } -} - -extension Set.Index: Comparable { - @inlinable - public static func < ( - lhs: Set.Index, - rhs: Set.Index - ) -> Bool { - switch (lhs._variant, rhs._variant) { - case (.native(let lhsNative), .native(let rhsNative)): - return lhsNative < rhsNative - #if _runtime(_ObjC) - case (.cocoa(let lhsCocoa), .cocoa(let rhsCocoa)): - lhs._cocoaPath() - return lhsCocoa < rhsCocoa - default: - _preconditionFailure("Comparing indexes from different sets") - #endif - } - } -} - -extension Set.Index: Hashable { - /// Hashes the essential components of this value by feeding them into the - /// given hasher. - /// - /// - Parameter hasher: The hasher to use when combining the components - /// of this instance. - @inlinable - public func hash(into hasher: inout Hasher) { - #if _runtime(_ObjC) - switch _variant { - case .native(let nativeIndex): - hasher.combine(0 as UInt8) - hasher.combine(nativeIndex.bucket) - case .cocoa(let cocoaIndex): - _cocoaPath() - hasher.combine(1 as UInt8) - hasher.combine(cocoaIndex.currentKeyIndex) - } - #else - hasher.combine(_asNative.bucket) - #endif - } -} - -extension _NativeSet: Sequence { - @usableFromInline - @_fixed_layout - internal struct Iterator { - // The iterator is iterating over a frozen view of the collection state, so - // it keeps its own reference to the set. - @usableFromInline - internal let base: _NativeSet - @usableFromInline - internal var iterator: _HashTable.Iterator - - @inlinable - init(_ base: _NativeSet) { - self.base = base - self.iterator = base.hashTable.makeIterator() - } - } - - @inlinable - __consuming internal func makeIterator() -> Iterator { - return Iterator(self) - } -} - -extension _NativeSet.Iterator: IteratorProtocol { - @inlinable - internal mutating func next() -> Element? { - guard let index = iterator.next() else { return nil } - return base.uncheckedElement(at: index) - } -} - -#if _runtime(_ObjC) -extension _CocoaSet: Sequence { - @usableFromInline - final internal class Iterator { - // Cocoa Set iterator has to be a class, otherwise we cannot - // guarantee that the fast enumeration struct is pinned to a certain memory - // location. - - // This stored property should be stored at offset zero. There's code below - // relying on this. - internal var _fastEnumerationState: _SwiftNSFastEnumerationState = - _makeSwiftNSFastEnumerationState() - - // This stored property should be stored right after - // `_fastEnumerationState`. There's code below relying on this. - internal var _fastEnumerationStackBuf = _CocoaFastEnumerationStackBuf() - - internal let base: _CocoaSet - - internal var _fastEnumerationStatePtr: - UnsafeMutablePointer<_SwiftNSFastEnumerationState> { - return _getUnsafePointerToStoredProperties(self).assumingMemoryBound( - to: _SwiftNSFastEnumerationState.self) - } - - internal var _fastEnumerationStackBufPtr: - UnsafeMutablePointer<_CocoaFastEnumerationStackBuf> { - return UnsafeMutableRawPointer(_fastEnumerationStatePtr + 1) - .assumingMemoryBound(to: _CocoaFastEnumerationStackBuf.self) - } - - // These members have to be word-sized integers, they cannot be limited to - // Int8 just because our storage holds 16 elements: fast enumeration is - // allowed to return inner pointers to the container, which can be much - // larger. - internal var itemIndex: Int = 0 - internal var itemCount: Int = 0 - - internal init(_ base: _CocoaSet) { - self.base = base - } - } - - @usableFromInline - __consuming internal func makeIterator() -> Iterator { - return Iterator(self) - } -} - -extension _CocoaSet.Iterator: IteratorProtocol { - @usableFromInline - internal typealias Element = AnyObject - - @usableFromInline - internal func next() -> Element? { - if itemIndex < 0 { - return nil - } - let base = self.base - if itemIndex == itemCount { - let stackBufCount = _fastEnumerationStackBuf.count - // We can't use `withUnsafeMutablePointer` here to get pointers to - // properties, because doing so might introduce a writeback storage, but - // fast enumeration relies on the pointer identity of the enumeration - // state struct. - itemCount = base.object.countByEnumerating( - with: _fastEnumerationStatePtr, - objects: UnsafeMutableRawPointer(_fastEnumerationStackBufPtr) - .assumingMemoryBound(to: AnyObject.self), - count: stackBufCount) - if itemCount == 0 { - itemIndex = -1 - return nil - } - itemIndex = 0 - } - let itemsPtrUP = - UnsafeMutableRawPointer(_fastEnumerationState.itemsPtr!) - .assumingMemoryBound(to: AnyObject.self) - let itemsPtr = _UnmanagedAnyObjectArray(itemsPtrUP) - let key: AnyObject = itemsPtr[itemIndex] - itemIndex += 1 - return key - } -} -#endif - -extension Set { - /// An iterator over the members of a `Set`. - @_fixed_layout - public struct Iterator { - // Set has a separate IteratorProtocol and Index because of efficiency - // and implementability reasons. - // - // Native sets have efficient indices. Bridged NSSet instances don't. - // - // Even though fast enumeration is not suitable for implementing - // Index, which is multi-pass, it is suitable for implementing a - // IteratorProtocol, which is being consumed as iteration proceeds. - - @usableFromInline - @_frozen - internal enum _Variant { - case native(_NativeSet.Iterator) -#if _runtime(_ObjC) - case cocoa(_CocoaSet.Iterator) -#endif - } - - @usableFromInline - internal var _variant: _Variant - - @inlinable - internal init(_variant: _Variant) { - self._variant = _variant - } - - @inlinable - internal init(_native: _NativeSet.Iterator) { - self.init(_variant: .native(_native)) - } - -#if _runtime(_ObjC) - @usableFromInline - internal init(_cocoa: _CocoaSet.Iterator) { - self.init(_variant: .cocoa(_cocoa)) - } -#endif - } -} - -extension Set.Iterator { -#if _runtime(_ObjC) - @usableFromInline @_transparent - internal var _guaranteedNative: Bool { - return _canBeClass(Element.self) == 0 - } - - /// Allow the optimizer to consider the surrounding code unreachable if - /// Set is guaranteed to be native. - @usableFromInline @_transparent - internal func _cocoaPath() { - if _guaranteedNative { - _conditionallyUnreachable() - } - } -#endif +#endif @usableFromInline @_transparent internal var _asNative: _NativeSet.Iterator { @@ -3291,45 +1540,6 @@ extension Set: CustomReflectable { } } -/// Initializes a `Set` from unique members. -/// -/// Using a builder can be faster than inserting members into an empty -/// `Set`. -@_fixed_layout -public // SPI(Foundation) -struct _SetBuilder { - @usableFromInline - internal var _target: _NativeSet - @usableFromInline - internal let _requestedCount: Int - - @inlinable - public init(count: Int) { - _target = _NativeSet(capacity: count) - _requestedCount = count - } - - @inlinable - public mutating func add(member: Element) { - _precondition(_target.count < _requestedCount, - "Can't add more members than promised") - _target.insertNew(member, isUnique: true) - } - - @inlinable - public mutating func take() -> Set { - _precondition(_target.capacity > 0 || _requestedCount == 0, - "Cannot take the result twice") - _precondition(_target.count == _requestedCount, - "The number of members added does not match the promised count") - - // Prevent taking the result twice. - var result = _NativeSet() - swap(&result, &_target) - return Set(_native: result) - } -} - extension Set { /// Removes and returns the first element of the set. /// @@ -3370,45 +1580,5 @@ extension Set { } } -//===--- Bridging ---------------------------------------------------------===// - -#if _runtime(_ObjC) -extension Set { - @inlinable - public func _bridgeToObjectiveCImpl() -> _NSSetCore { - switch _variant { - case .native(let nativeSet): - return nativeSet.bridged() - case .cocoa(let cocoaSet): - return cocoaSet.object - } - } - - /// Returns the native Dictionary hidden inside this NSDictionary; - /// returns nil otherwise. - public static func _bridgeFromObjectiveCAdoptingNativeStorageOf( - _ s: AnyObject - ) -> Set? { - - // Try all three NSSet impls that we currently provide. - - if let deferred = s as? _SwiftDeferredNSSet { - return Set(_native: deferred.native) - } - - if let nativeStorage = s as? _SetStorage { - return Set(_native: _NativeSet(nativeStorage)) - } - - if s === _RawSetStorage.empty { - return Set() - } - - // FIXME: what if `s` is native storage, but for different key/value type? - return nil - } -} -#endif - public typealias SetIndex = Set.Index public typealias SetIterator = Set.Iterator diff --git a/stdlib/public/core/HashedCollectionsAnyHashableExtensions.swift b/stdlib/public/core/SetAnyHashableExtensions.swift similarity index 100% rename from stdlib/public/core/HashedCollectionsAnyHashableExtensions.swift rename to stdlib/public/core/SetAnyHashableExtensions.swift diff --git a/stdlib/public/core/SetBridging.swift b/stdlib/public/core/SetBridging.swift new file mode 100644 index 0000000000000..41783ae1678cc --- /dev/null +++ b/stdlib/public/core/SetBridging.swift @@ -0,0 +1,582 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#if _runtime(_ObjC) + +import SwiftShims + +@_silgen_name("swift_stdlib_CFSetGetValues") +@usableFromInline +internal +func _stdlib_CFSetGetValues(_ nss: _NSSet, _: UnsafeMutablePointer) + +/// Equivalent to `NSSet.allObjects`, but does not leave objects on the +/// autorelease pool. +@inlinable +internal func _stdlib_NSSet_allObjects( + _ nss: _NSSet +) -> _HeapBuffer { + let count = nss.count + let storage = _HeapBuffer( + _HeapBufferStorage.self, count, count) + _stdlib_CFSetGetValues(nss, storage.baseAddress) + return storage +} + +extension _NativeSet { // Bridging + @usableFromInline + internal func bridged() -> _NSSet { + // We can zero-cost bridge if our keys are verbatim + // or if we're the empty singleton. + + // Temporary var for SOME type safety before a cast. + let nsSet: _NSSetCore + + if _storage === _RawSetStorage.empty || count == 0 { + nsSet = _RawSetStorage.empty + } else if _isBridgedVerbatimToObjectiveC(Element.self) { + nsSet = unsafeDowncast(_storage, to: _SetStorage.self) + } else { + nsSet = _SwiftDeferredNSSet(self) + } + + // Cast from "minimal NSSet" to "NSSet" + // Note that if you actually ask Swift for this cast, it will fail. + // Never trust a shadow protocol! + return unsafeBitCast(nsSet, to: _NSSet.self) + } +} + +/// An NSEnumerator that works with any _NativeSet of verbatim bridgeable +/// elements. Used by the various NSSet impls. +final internal class _SwiftSetNSEnumerator + : _SwiftNativeNSEnumerator, _NSEnumerator { + + @nonobjc internal var base: _NativeSet + @nonobjc internal var bridgedElements: _BridgingHashBuffer? + @nonobjc internal var nextIndex: _NativeSet.Index + @nonobjc internal var endIndex: _NativeSet.Index + + @objc + internal override required init() { + _sanityCheckFailure("don't call this designated initializer") + } + + internal init(_ base: _NativeSet) { + _sanityCheck(_isBridgedVerbatimToObjectiveC(Element.self)) + self.base = base + self.bridgedElements = nil + self.nextIndex = base.startIndex + self.endIndex = base.endIndex + } + + @nonobjc + internal init(_ deferred: _SwiftDeferredNSSet) { + _sanityCheck(!_isBridgedVerbatimToObjectiveC(Element.self)) + self.base = deferred.native + self.bridgedElements = deferred.bridgeElements() + self.nextIndex = base.startIndex + self.endIndex = base.endIndex + } + + private func bridgedElement(at index: _HashTable.Index) -> AnyObject { + _sanityCheck(base.hashTable.isOccupied(index)) + if let bridgedElements = self.bridgedElements { + return bridgedElements[index] + } + return _bridgeAnythingToObjectiveC(base.element(at: index)) + } + + // + // NSEnumerator implementation. + // + // Do not call any of these methods from the standard library! + // + + @objc + internal func nextObject() -> AnyObject? { + if nextIndex == endIndex { + return nil + } + let index = nextIndex + nextIndex = base.index(after: nextIndex) + return self.bridgedElement(at: index) + } + + @objc(countByEnumeratingWithState:objects:count:) + internal func countByEnumerating( + with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>, + objects: UnsafeMutablePointer, + count: Int + ) -> Int { + var theState = state.pointee + if theState.state == 0 { + theState.state = 1 // Arbitrary non-zero value. + theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects) + theState.mutationsPtr = _fastEnumerationStorageMutationsPtr + } + + if nextIndex == endIndex { + state.pointee = theState + return 0 + } + + // Return only a single element so that code can start iterating via fast + // enumeration, terminate it, and continue via NSEnumerator. + let unmanagedObjects = _UnmanagedAnyObjectArray(objects) + unmanagedObjects[0] = self.bridgedElement(at: nextIndex) + nextIndex = base.index(after: nextIndex) + state.pointee = theState + return 1 + } +} + +/// This class exists for Objective-C bridging. It holds a reference to a +/// _NativeSet, and can be upcast to NSSelf when bridging is necessary. This is +/// the fallback implementation for situations where toll-free bridging isn't +/// possible. On first access, a _NativeSet of AnyObject will be constructed +/// containing all the bridged elements. +final internal class _SwiftDeferredNSSet + : _SwiftNativeNSSet, _NSSetCore { + + // This stored property must be stored at offset zero. We perform atomic + // operations on it. + // + // Do not access this property directly. + @nonobjc + private var _bridgedElements_DoNotUse: AnyObject? + + /// The unbridged elements. + internal var native: _NativeSet + + internal init(_ native: _NativeSet) { + _sanityCheck(native.count > 0) + _sanityCheck(!_isBridgedVerbatimToObjectiveC(Element.self)) + self.native = native + super.init() + } + + /// Returns the pointer to the stored property, which contains bridged + /// Set elements. + @nonobjc + private var _bridgedElementsPtr: UnsafeMutablePointer { + return _getUnsafePointerToStoredProperties(self) + .assumingMemoryBound(to: Optional.self) + } + + /// The buffer for bridged Set elements, if present. + @nonobjc + private var _bridgedElements: _BridgingHashBuffer? { + guard let ref = _stdlib_atomicLoadARCRef(object: _bridgedElementsPtr) else { + return nil + } + return unsafeDowncast(ref, to: _BridgingHashBuffer.self) + } + + /// Attach a buffer for bridged Set elements. + @nonobjc + private func _initializeBridgedElements(_ storage: _BridgingHashBuffer) { + _stdlib_atomicInitializeARCRef( + object: _bridgedElementsPtr, + desired: storage) + } + + @nonobjc + internal func bridgeElements() -> _BridgingHashBuffer { + if let bridgedElements = _bridgedElements { return bridgedElements } + + // Allocate and initialize heap storage for bridged objects. + let bridged = _BridgingHashBuffer.allocate( + owner: native._storage, + hashTable: native.hashTable) + for index in native.hashTable { + let object = _bridgeAnythingToObjectiveC(native.element(at: index)) + bridged.initialize(at: index, to: object) + } + + // Atomically put the bridged elements in place. + _initializeBridgedElements(bridged) + return _bridgedElements! + } + + @objc + internal required init(objects: UnsafePointer, count: Int) { + _sanityCheckFailure("don't call this designated initializer") + } + + @objc(copyWithZone:) + internal func copy(with zone: _SwiftNSZone?) -> AnyObject { + // Instances of this class should be visible outside of standard library as + // having `NSSet` type, which is immutable. + return self + } + + @objc(member:) + internal func member(_ object: AnyObject) -> AnyObject? { + guard let element = _conditionallyBridgeFromObjectiveC(object, Element.self) + else { return nil } + + let (index, found) = native.find(element) + guard found else { return nil } + let bridged = bridgeElements() + return bridged[index] + } + + @objc + internal func objectEnumerator() -> _NSEnumerator { + return _SwiftSetNSEnumerator(self) + } + + @objc + internal var count: Int { + return native.count + } + + @objc(countByEnumeratingWithState:objects:count:) + internal func countByEnumerating( + with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>, + objects: UnsafeMutablePointer?, + count: Int + ) -> Int { + var theState = state.pointee + if theState.state == 0 { + theState.state = 1 // Arbitrary non-zero value. + theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects) + theState.mutationsPtr = _fastEnumerationStorageMutationsPtr + theState.extra.0 = CUnsignedLong(native.startIndex.bucket) + } + + // Test 'objects' rather than 'count' because (a) this is very rare anyway, + // and (b) the optimizer should then be able to optimize away the + // unwrapping check below. + if _slowPath(objects == nil) { + return 0 + } + + let unmanagedObjects = _UnmanagedAnyObjectArray(objects!) + var index = _NativeSet.Index(bucket: Int(theState.extra.0)) + let endIndex = native.endIndex + _precondition(index == endIndex || native.hashTable.isValid(index)) + + // Only need to bridge once, so we can hoist it out of the loop. + let bridgedElements = bridgeElements() + + var stored = 0 + for i in 0.. AnyObject { + return index.allKeys[index.currentKeyIndex] + } + + @inlinable + internal func member(for element: AnyObject) -> AnyObject? { + return object.member(element) + } +} + +extension _CocoaSet: Equatable { + @usableFromInline + internal static func ==(lhs: _CocoaSet, rhs: _CocoaSet) -> Bool { + return _stdlib_NSObject_isEqual(lhs.object, rhs.object) + } +} + +extension _CocoaSet: _SetBuffer { + @usableFromInline + internal typealias Element = AnyObject + + @inlinable + internal var startIndex: Index { + return Index(self, startIndex: ()) + } + + @inlinable + internal var endIndex: Index { + return Index(self, endIndex: ()) + } + + @inlinable + internal func index(after i: Index) -> Index { + var i = i + formIndex(after: &i) + return i + } + + @usableFromInline + @_effects(releasenone) + internal func formIndex(after i: inout Index) { + _precondition(i.base.object === self.object, "Invalid index") + _precondition(i.currentKeyIndex < i.allKeys.value, + "Cannot increment endIndex") + i.currentKeyIndex += 1 + } + + @usableFromInline + internal func index(for element: AnyObject) -> Index? { + // Fast path that does not involve creating an array of all keys. In case + // the key is present, this lookup is a penalty for the slow path, but the + // potential savings are significant: we could skip a memory allocation and + // a linear search. + if !contains(element) { + return nil + } + + let allKeys = _stdlib_NSSet_allObjects(object) + var keyIndex = -1 + for i in 0..= 0, + "Key was found in fast path, but not found later?") + return Index(self, allKeys, keyIndex) + } + + @inlinable + internal var count: Int { + return object.count + } + + @inlinable + internal func contains(_ element: AnyObject) -> Bool { + return object.member(element) != nil + } + + @usableFromInline + internal func element(at i: Index) -> AnyObject { + let value: AnyObject? = i.allKeys[i.currentKeyIndex] + _sanityCheck(value != nil, "Item not found in underlying NSSet") + return value! + } +} + +extension _CocoaSet { + @_fixed_layout // FIXME(sil-serialize-all) + @usableFromInline + internal struct Index { + // Assumption: we rely on NSDictionary.getObjects when being + // repeatedly called on the same NSDictionary, returning items in the same + // order every time. + // Similarly, the same assumption holds for NSSet.allObjects. + + /// A reference to the NSSet, which owns members in `allObjects`, + /// or `allKeys`, for NSSet and NSDictionary respectively. + @usableFromInline // FIXME(sil-serialize-all) + internal let base: _CocoaSet + // FIXME: swift-3-indexing-model: try to remove the cocoa reference, but + // make sure that we have a safety check for accessing `allKeys`. Maybe + // move both into the dictionary/set itself. + + /// An unowned array of keys. + @usableFromInline // FIXME(sil-serialize-all) + internal var allKeys: _HeapBuffer + + /// Index into `allKeys` + @usableFromInline // FIXME(sil-serialize-all) + internal var currentKeyIndex: Int + + @inlinable // FIXME(sil-serialize-all) + internal init(_ base: _CocoaSet, startIndex: ()) { + self.base = base + self.allKeys = _stdlib_NSSet_allObjects(base.object) + self.currentKeyIndex = 0 + } + + @inlinable // FIXME(sil-serialize-all) + internal init(_ base: _CocoaSet, endIndex: ()) { + self.base = base + self.allKeys = _stdlib_NSSet_allObjects(base.object) + self.currentKeyIndex = allKeys.value + } + + @inlinable // FIXME(sil-serialize-all) + internal init( + _ base: _CocoaSet, + _ allKeys: _HeapBuffer, + _ currentKeyIndex: Int + ) { + self.base = base + self.allKeys = allKeys + self.currentKeyIndex = currentKeyIndex + } + } +} + +extension _CocoaSet.Index: Equatable { + @inlinable + internal static func == (lhs: _CocoaSet.Index, rhs: _CocoaSet.Index) -> Bool { + _precondition(lhs.base.object === rhs.base.object, + "Comparing indexes from different sets") + return lhs.currentKeyIndex == rhs.currentKeyIndex + } +} + +extension _CocoaSet.Index: Comparable { + @inlinable + internal static func < (lhs: _CocoaSet.Index, rhs: _CocoaSet.Index) -> Bool { + _precondition(lhs.base.object === rhs.base.object, + "Comparing indexes from different sets") + return lhs.currentKeyIndex < rhs.currentKeyIndex + } +} + +extension _CocoaSet: Sequence { + @usableFromInline + final internal class Iterator { + // Cocoa Set iterator has to be a class, otherwise we cannot + // guarantee that the fast enumeration struct is pinned to a certain memory + // location. + + // This stored property should be stored at offset zero. There's code below + // relying on this. + internal var _fastEnumerationState: _SwiftNSFastEnumerationState = + _makeSwiftNSFastEnumerationState() + + // This stored property should be stored right after + // `_fastEnumerationState`. There's code below relying on this. + internal var _fastEnumerationStackBuf = _CocoaFastEnumerationStackBuf() + + internal let base: _CocoaSet + + internal var _fastEnumerationStatePtr: + UnsafeMutablePointer<_SwiftNSFastEnumerationState> { + return _getUnsafePointerToStoredProperties(self).assumingMemoryBound( + to: _SwiftNSFastEnumerationState.self) + } + + internal var _fastEnumerationStackBufPtr: + UnsafeMutablePointer<_CocoaFastEnumerationStackBuf> { + return UnsafeMutableRawPointer(_fastEnumerationStatePtr + 1) + .assumingMemoryBound(to: _CocoaFastEnumerationStackBuf.self) + } + + // These members have to be word-sized integers, they cannot be limited to + // Int8 just because our storage holds 16 elements: fast enumeration is + // allowed to return inner pointers to the container, which can be much + // larger. + internal var itemIndex: Int = 0 + internal var itemCount: Int = 0 + + internal init(_ base: _CocoaSet) { + self.base = base + } + } + + @usableFromInline + __consuming internal func makeIterator() -> Iterator { + return Iterator(self) + } +} + +extension _CocoaSet.Iterator: IteratorProtocol { + @usableFromInline + internal typealias Element = AnyObject + + @usableFromInline + internal func next() -> Element? { + if itemIndex < 0 { + return nil + } + let base = self.base + if itemIndex == itemCount { + let stackBufCount = _fastEnumerationStackBuf.count + // We can't use `withUnsafeMutablePointer` here to get pointers to + // properties, because doing so might introduce a writeback storage, but + // fast enumeration relies on the pointer identity of the enumeration + // state struct. + itemCount = base.object.countByEnumerating( + with: _fastEnumerationStatePtr, + objects: UnsafeMutableRawPointer(_fastEnumerationStackBufPtr) + .assumingMemoryBound(to: AnyObject.self), + count: stackBufCount) + if itemCount == 0 { + itemIndex = -1 + return nil + } + itemIndex = 0 + } + let itemsPtrUP = + UnsafeMutableRawPointer(_fastEnumerationState.itemsPtr!) + .assumingMemoryBound(to: AnyObject.self) + let itemsPtr = _UnmanagedAnyObjectArray(itemsPtrUP) + let key: AnyObject = itemsPtr[itemIndex] + itemIndex += 1 + return key + } +} + +//===--- Bridging ---------------------------------------------------------===// + +extension Set { + @inlinable + public func _bridgeToObjectiveCImpl() -> _NSSetCore { + switch _variant { + case .native(let nativeSet): + return nativeSet.bridged() + case .cocoa(let cocoaSet): + return cocoaSet.object + } + } + + /// Returns the native Dictionary hidden inside this NSDictionary; + /// returns nil otherwise. + public static func _bridgeFromObjectiveCAdoptingNativeStorageOf( + _ s: AnyObject + ) -> Set? { + + // Try all three NSSet impls that we currently provide. + + if let deferred = s as? _SwiftDeferredNSSet { + return Set(_native: deferred.native) + } + + if let nativeStorage = s as? _SetStorage { + return Set(_native: _NativeSet(nativeStorage)) + } + + if s === _RawSetStorage.empty { + return Set() + } + + // FIXME: what if `s` is native storage, but for different key/value type? + return nil + } +} + +#endif // _runtime(_ObjC) diff --git a/stdlib/public/core/SetBuilder.swift b/stdlib/public/core/SetBuilder.swift new file mode 100644 index 0000000000000..3205a3824f6a5 --- /dev/null +++ b/stdlib/public/core/SetBuilder.swift @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// Initializes a `Set` from unique members. +/// +/// Using a builder can be faster than inserting members into an empty +/// `Set`. +@_fixed_layout +public // SPI(Foundation) +struct _SetBuilder { + @usableFromInline + internal var _target: _NativeSet + @usableFromInline + internal let _requestedCount: Int + + @inlinable + public init(count: Int) { + _target = _NativeSet(capacity: count) + _requestedCount = count + } + + @inlinable + public mutating func add(member: Element) { + _precondition(_target.count < _requestedCount, + "Can't add more members than promised") + _target.insertNew(member, isUnique: true) + } + + @inlinable + public mutating func take() -> Set { + _precondition(_target.capacity > 0 || _requestedCount == 0, + "Cannot take the result twice") + _precondition(_target.count == _requestedCount, + "The number of members added does not match the promised count") + + // Prevent taking the result twice. + var result = _NativeSet() + swap(&result, &_target) + return Set(_native: result) + } +} diff --git a/stdlib/public/core/SetCasting.swift b/stdlib/public/core/SetCasting.swift new file mode 100644 index 0000000000000..787c51a420717 --- /dev/null +++ b/stdlib/public/core/SetCasting.swift @@ -0,0 +1,95 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +//===--- Compiler conversion/casting entry points for Set --------===// + +/// Perform a non-bridged upcast that always succeeds. +/// +/// - Precondition: `BaseValue` is a base class or base `@objc` +/// protocol (such as `AnyObject`) of `DerivedValue`. +@inlinable +public func _setUpCast(_ source: Set) + -> Set { + var builder = _SetBuilder(count: source.count) + for x in source { + builder.add(member: x as! BaseValue) + } + return builder.take() +} + +/// Called by the casting machinery. +@_silgen_name("_swift_setDownCastIndirect") +internal func _setDownCastIndirect( + _ source: UnsafePointer>, + _ target: UnsafeMutablePointer>) { + target.initialize(to: _setDownCast(source.pointee)) +} + +/// Implements a forced downcast. This operation should have O(1) complexity. +/// +/// The cast can fail if bridging fails. The actual checks and bridging can be +/// deferred. +/// +/// - Precondition: `DerivedValue` is a subtype of `BaseValue` and both +/// are reference types. +@inlinable +public func _setDownCast(_ source: Set) + -> Set { + +#if _runtime(_ObjC) + if _isClassOrObjCExistential(BaseValue.self) + && _isClassOrObjCExistential(DerivedValue.self) { + switch source._variant { + case .native(let nativeSet): + return Set(_immutableCocoaSet: nativeSet.bridged()) + case .cocoa(let cocoaSet): + return Set(_immutableCocoaSet: cocoaSet.object) + } + } +#endif + return _setDownCastConditional(source)! +} + +/// Called by the casting machinery. +@_silgen_name("_swift_setDownCastConditionalIndirect") +internal func _setDownCastConditionalIndirect( + _ source: UnsafePointer>, + _ target: UnsafeMutablePointer> +) -> Bool { + if let result: Set = _setDownCastConditional(source.pointee) { + target.initialize(to: result) + return true + } + return false +} + +/// Implements a conditional downcast. +/// +/// If the cast fails, the function returns `nil`. All checks should be +/// performed eagerly. +/// +/// - Precondition: `DerivedValue` is a subtype of `BaseValue` and both +/// are reference types. +@inlinable +public func _setDownCastConditional( + _ source: Set +) -> Set? { + var result = Set(minimumCapacity: source.count) + for member in source { + if let derivedMember = member as? DerivedValue { + result.insert(derivedMember) + continue + } + return nil + } + return result +} diff --git a/stdlib/public/core/SetStorage.swift b/stdlib/public/core/SetStorage.swift new file mode 100644 index 0000000000000..d0d97408bd267 --- /dev/null +++ b/stdlib/public/core/SetStorage.swift @@ -0,0 +1,322 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftShims + +/// An instance of this class has all `Set` data tail-allocated. +/// Enough bytes are allocated to hold the bitmap for marking valid entries, +/// keys, and values. The data layout starts with the bitmap, followed by the +/// keys, followed by the values. +// +// See the docs at the top of the file for more details on this type +// +// NOTE: The precise layout of this type is relied on in the runtime +// to provide a statically allocated empty singleton. +// See stdlib/public/stubs/GlobalObjects.cpp for details. +@_fixed_layout // FIXME(sil-serialize-all) +@usableFromInline +@_objc_non_lazy_realization +internal class _RawSetStorage: _SwiftNativeNSSet { + /// The current number of occupied entries in this set. + @usableFromInline + @nonobjc + internal final var _count: Int + + /// The maximum number of elements that can be inserted into this set without + /// exceeding the hash table's maximum load factor. + @usableFromInline + @nonobjc + internal final var _capacity: Int + + /// The scale of this set. The number of buckets is 2 raised to the + /// power of `scale`. + @usableFromInline + @nonobjc + internal final var _scale: Int + + @usableFromInline + internal final var _seed: Hasher._Seed + + @usableFromInline + @nonobjc + internal final var _rawElements: UnsafeMutableRawPointer + + // This type is made with allocWithTailElems, so no init is ever called. + // But we still need to have an init to satisfy the compiler. + @nonobjc + internal init(_doNotCallMe: ()) { + _sanityCheckFailure("This class cannot be directly initialized") + } + + @inlinable + @nonobjc + internal final var _bucketCount: Int { + @inline(__always) get { return 1 &<< _scale } + } + + @inlinable + @nonobjc + internal final var _metadata: UnsafeMutablePointer<_HashTable.Word> { + @inline(__always) get { + let address = Builtin.projectTailElems(self, _HashTable.Word.self) + return UnsafeMutablePointer(address) + } + } + + // The _HashTable struct contains pointers into tail-allocated storage, so + // this is unsafe and needs `_fixLifetime` calls in the caller. + @inlinable + @nonobjc + internal final var _hashTable: _HashTable { + @inline(__always) get { + return _HashTable(words: _metadata, bucketCount: _bucketCount) + } + } +} + +/// The storage class for the singleton empty set. +/// The single instance of this class is created by the runtime. +@_fixed_layout +@usableFromInline +internal class _EmptySetSingleton: _RawSetStorage { + @nonobjc + override internal init(_doNotCallMe: ()) { + _sanityCheckFailure("This class cannot be directly initialized") + } + +#if _runtime(_ObjC) + @objc + internal required init(objects: UnsafePointer, count: Int) { + _sanityCheckFailure("This class cannot be directly initialized") + } +#endif +} + +extension _RawSetStorage { + /// The empty singleton that is used for every single Set that is created + /// without any elements. The contents of the storage must never be mutated. + @inlinable + @nonobjc + internal static var empty: _EmptySetSingleton { + return Builtin.bridgeFromRawPointer( + Builtin.addressof(&_swiftEmptySetSingleton)) + } +} + +extension _EmptySetSingleton: _NSSetCore { +#if _runtime(_ObjC) + // + // NSSet implementation, assuming Self is the empty singleton + // + @objc(copyWithZone:) + internal func copy(with zone: _SwiftNSZone?) -> AnyObject { + return self + } + + @objc + internal var count: Int { + return 0 + } + + @objc(member:) + internal func member(_ object: AnyObject) -> AnyObject? { + return nil + } + + @objc + internal func objectEnumerator() -> _NSEnumerator { + return _SwiftEmptyNSEnumerator() + } + + @objc(countByEnumeratingWithState:objects:count:) + internal func countByEnumerating( + with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>, + objects: UnsafeMutablePointer?, count: Int + ) -> Int { + // Even though we never do anything in here, we need to update the + // state so that callers know we actually ran. + var theState = state.pointee + if theState.state == 0 { + theState.state = 1 // Arbitrary non-zero value. + theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects) + theState.mutationsPtr = _fastEnumerationStorageMutationsPtr + } + state.pointee = theState + return 0 + } +#endif +} + +// See the docs at the top of this file for a description of this type +@_fixed_layout // FIXME(sil-serialize-all) +@usableFromInline +final internal class _SetStorage + : _RawSetStorage, _NSSetCore { + // This type is made with allocWithTailElems, so no init is ever called. + // But we still need to have an init to satisfy the compiler. + @nonobjc + override internal init(_doNotCallMe: ()) { + _sanityCheckFailure("This class cannot be directly initialized") + } + + deinit { + guard _count > 0 else { return } + if !_isPOD(Element.self) { + let elements = _elements + for index in _hashTable { + (elements + index.bucket).deinitialize(count: 1) + } + } + _fixLifetime(self) + } + + @inlinable + final internal var _elements: UnsafeMutablePointer { + @inline(__always) + get { + return self._rawElements.assumingMemoryBound(to: Element.self) + } + } + + internal var asNative: _NativeSet { + return _NativeSet(self) + } + +#if _runtime(_ObjC) + @objc + internal required init(objects: UnsafePointer, count: Int) { + _sanityCheckFailure("don't call this designated initializer") + } + + @objc(copyWithZone:) + internal func copy(with zone: _SwiftNSZone?) -> AnyObject { + return self + } + + @objc + internal var count: Int { + return _count + } + + @objc + internal func objectEnumerator() -> _NSEnumerator { + return _SwiftSetNSEnumerator(asNative) + } + + @objc(countByEnumeratingWithState:objects:count:) + internal func countByEnumerating( + with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>, + objects: UnsafeMutablePointer?, count: Int + ) -> Int { + var theState = state.pointee + if theState.state == 0 { + theState.state = 1 // Arbitrary non-zero value. + theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects) + theState.mutationsPtr = _fastEnumerationStorageMutationsPtr + theState.extra.0 = CUnsignedLong(asNative.startIndex.bucket) + } + + // Test 'objects' rather than 'count' because (a) this is very rare anyway, + // and (b) the optimizer should then be able to optimize away the + // unwrapping check below. + if _slowPath(objects == nil) { + return 0 + } + + let unmanagedObjects = _UnmanagedAnyObjectArray(objects!) + var index = _HashTable.Index(bucket: Int(theState.extra.0)) + let endIndex = asNative.endIndex + _precondition(index == endIndex || _hashTable.isValid(index)) + var stored = 0 + for i in 0.. AnyObject? { + guard let native = _conditionallyBridgeFromObjectiveC(object, Element.self) + else { return nil } + + let (index, found) = asNative.find(native) + guard found else { return nil } + return _bridgeAnythingToObjectiveC(_elements[index.bucket]) + } +#endif +} + +extension _SetStorage { + @usableFromInline + @_effects(releasenone) + internal static func reallocate( + original: _RawSetStorage, + capacity: Int + ) -> (storage: _SetStorage, rehash: Bool) { + _sanityCheck(capacity >= original._count) + let scale = _HashTable.scale(forCapacity: capacity) + let rehash = (scale != original._scale) + let newStorage = _SetStorage.allocate(scale: scale) + return (newStorage, rehash) + } + + @usableFromInline + @_effects(releasenone) + static internal func allocate(capacity: Int) -> _SetStorage { + let scale = _HashTable.scale(forCapacity: capacity) + return allocate(scale: scale) + } + + static internal func allocate(scale: Int) -> _SetStorage { + // The entry count must be representable by an Int value; hence the scale's + // peculiar upper bound. + _sanityCheck(scale >= 0 && scale < Int.bitWidth - 1) + + let bucketCount = 1 &<< scale + let wordCount = _UnsafeBitset.wordCount(forCapacity: bucketCount) + let storage = Builtin.allocWithTailElems_2( + _SetStorage.self, + wordCount._builtinWordValue, _HashTable.Word.self, + bucketCount._builtinWordValue, Element.self) + + let metadataAddr = Builtin.projectTailElems(storage, _HashTable.Word.self) + let elementsAddr = Builtin.getTailAddr_Word( + metadataAddr, wordCount._builtinWordValue, _HashTable.Word.self, + Element.self) + storage._count = 0 + storage._capacity = _HashTable.capacity(forScale: scale) + storage._scale = scale + storage._rawElements = UnsafeMutableRawPointer(elementsAddr) + + // We use a slightly different hash seed whenever we change the size of the + // hash table, so that we avoid certain copy operations becoming quadratic, + // without breaking value semantics. (For background details, see + // https://bugs.swift.org/browse/SR-3268) + + // FIXME: Use true per-instance seeding instead. Per-capacity seeding still + // leaves hash values the same in same-sized tables, which may affect + // operations on two tables at once. (E.g., union.) + storage._seed = ( + Hasher._seed.0 ^ UInt64(truncatingIfNeeded: scale), + Hasher._seed.1) + + // Initialize hash table metadata. + storage._hashTable.clear() + return storage + } +} diff --git a/stdlib/public/core/SetVariant.swift b/stdlib/public/core/SetVariant.swift new file mode 100644 index 0000000000000..c2312fd670106 --- /dev/null +++ b/stdlib/public/core/SetVariant.swift @@ -0,0 +1,392 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// This protocol is only used for compile-time checks that +/// every buffer type implements all required operations. +internal protocol _SetBuffer { + associatedtype Element + associatedtype Index + + var startIndex: Index { get } + var endIndex: Index { get } + func index(after i: Index) -> Index + func index(for element: Element) -> Index? + var count: Int { get } + + func contains(_ member: Element) -> Bool + func element(at i: Index) -> Element +} + +extension Set { + @usableFromInline + @_frozen + internal enum _Variant { + case native(_NativeSet) +#if _runtime(_ObjC) + case cocoa(_CocoaSet) +#endif + } +} + +extension Set._Variant { +#if _runtime(_ObjC) + @usableFromInline + @_transparent + internal var guaranteedNative: Bool { + return _canBeClass(Element.self) == 0 + } + + /// Allow the optimizer to consider the surrounding code unreachable if + /// Set is guaranteed to be native. + @usableFromInline + @_transparent + internal func cocoaPath() { + if guaranteedNative { + _conditionallyUnreachable() + } + } +#endif + + @inlinable + internal mutating func isUniquelyReferenced() -> Bool { + // Note that &self drills down through .native(_NativeSet) to the first + // property in _NativeSet, which is the reference to the storage. + switch self { + case .native: + return _isUnique_native(&self) +#if _runtime(_ObjC) + case .cocoa: + cocoaPath() + // Don't consider Cocoa buffer mutable, even if it is mutable and is + // uniquely referenced. + return false +#endif + } + } + + @usableFromInline @_transparent + internal var asNative: _NativeSet { + get { + switch self { + case .native(let nativeSet): + return nativeSet +#if _runtime(_ObjC) + case .cocoa: + _sanityCheckFailure("internal error: not backed by native buffer") +#endif + } + } + set { + self = .native(newValue) + } + } + +#if _runtime(_ObjC) + @inlinable + internal var asCocoa: _CocoaSet { + switch self { + case .native: + _sanityCheckFailure("internal error: not backed by NSSet") + case .cocoa(let cocoa): + return cocoa + } + } +#endif + + /// Reserves enough space for the specified number of elements to be stored + /// without reallocating additional storage. + @inlinable + internal mutating func reserveCapacity(_ capacity: Int) { + switch self { + case .native: + let isUnique = isUniquelyReferenced() + asNative.reserveCapacity(capacity, isUnique: isUnique) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + let capacity = Swift.max(cocoa.count, capacity) + self = .native(_NativeSet(cocoa, capacity: capacity)) +#endif + } + } + + /// The number of elements that can be stored without expanding the current + /// storage. + /// + /// For bridged storage, this is equal to the current count of the + /// collection, since any addition will trigger a copy of the elements into + /// newly allocated storage. For native storage, this is the element count + /// at which adding any more elements will exceed the load factor. + @inlinable + internal var capacity: Int { + switch self { + case .native: + return asNative.capacity +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + return cocoa.count +#endif + } + } +} + +extension Set._Variant: _SetBuffer { + @usableFromInline + internal typealias Index = Set.Index + + @inlinable + internal var startIndex: Index { + switch self { + case .native: + return Index(_native: asNative.startIndex) +#if _runtime(_ObjC) + case .cocoa(let cocoaSet): + cocoaPath() + return Index(_cocoa: cocoaSet.startIndex) +#endif + } + } + + @inlinable + internal var endIndex: Index { + switch self { + case .native: + return Index(_native: asNative.endIndex) +#if _runtime(_ObjC) + case .cocoa(let cocoaSet): + cocoaPath() + return Index(_cocoa: cocoaSet.endIndex) +#endif + } + } + + @inlinable + internal func index(after i: Index) -> Index { + switch self { + case .native: + return Index(_native: asNative.index(after: i._asNative)) +#if _runtime(_ObjC) + case .cocoa(let cocoaSet): + cocoaPath() + return Index(_cocoa: cocoaSet.index(after: i._asCocoa)) +#endif + } + } + + @inlinable + @inline(__always) + internal func index(for element: Element) -> Index? { + switch self { + case .native: + guard let index = asNative.index(for: element) else { return nil } + return Index(_native: index) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + let cocoaElement = _bridgeAnythingToObjectiveC(element) + guard let index = cocoa.index(for: cocoaElement) else { return nil } + return Index(_cocoa: index) +#endif + } + } + + @inlinable + internal var count: Int { + @inline(__always) + get { + switch self { + case .native: + return asNative.count +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + return cocoa.count +#endif + } + } + } + + @inlinable + @inline(__always) + internal func contains(_ member: Element) -> Bool { + switch self { + case .native: + return asNative.contains(member) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + return cocoa.contains(_bridgeAnythingToObjectiveC(member)) +#endif + } + } + + @inlinable + @inline(__always) + internal func element(at i: Index) -> Element { + switch self { + case .native: + return asNative.element(at: i._asNative) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + let cocoaMember = cocoa.element(at: i._asCocoa) + return _forceBridgeFromObjectiveC(cocoaMember, Element.self) +#endif + } + } +} + +extension Set._Variant { + @inlinable + internal mutating func update(with value: Element) -> Element? { + switch self { + case .native: + let isUnique = self.isUniquelyReferenced() + return asNative.update(with: value, isUnique: isUnique) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + // Make sure we have space for an extra element. + var native = _NativeSet(cocoa, capacity: cocoa.count + 1) + let old = native.update(with: value, isUnique: true) + self = .native(native) + return old +#endif + } + } + + @inlinable + internal mutating func insert( + _ element: Element + ) -> (inserted: Bool, memberAfterInsert: Element) { + switch self { + case .native: + let (index, found) = asNative.find(element) + if found { + return (false, asNative.uncheckedElement(at: index)) + } + let isUnique = self.isUniquelyReferenced() + asNative.insertNew(element, at: index, isUnique: isUnique) + return (true, element) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + // Make sure we have space for an extra element. + let cocoaMember = _bridgeAnythingToObjectiveC(element) + if let m = cocoa.member(for: cocoaMember) { + return (false, _forceBridgeFromObjectiveC(m, Element.self)) + } + var native = _NativeSet(cocoa, capacity: cocoa.count + 1) + native.insertNew(element, isUnique: true) + self = .native(native) + return (true, element) +#endif + } + } + + @inlinable + @discardableResult + internal mutating func remove(at index: Index) -> Element { + switch self { + case .native: + let isUnique = isUniquelyReferenced() + return asNative.remove(at: index._asNative, isUnique: isUnique) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + // We have to migrate the data first. But after we do so, the Cocoa + // index becomes useless, so get the element first. + let cocoaMember = cocoa.member(for: index._asCocoa) + let nativeMember = _forceBridgeFromObjectiveC(cocoaMember, Element.self) + return _migrateToNative(cocoa, removing: nativeMember) +#endif + } + } + + @inlinable + @discardableResult + internal mutating func remove(_ member: Element) -> Element? { + switch self { + case .native: + let (index, found) = asNative.find(member) + guard found else { return nil } + let isUnique = isUniquelyReferenced() + return asNative.uncheckedRemove(at: index, isUnique: isUnique) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + let cocoaMember = _bridgeAnythingToObjectiveC(member) + guard cocoa.contains(cocoaMember) else { return nil } + return _migrateToNative(cocoa, removing: member) +#endif + } + } + +#if _runtime(_ObjC) + @inlinable + internal mutating func _migrateToNative( + _ cocoa: _CocoaSet, + removing member: Element + ) -> Element { + // FIXME(performance): fuse data migration and element deletion into one + // operation. + var native = _NativeSet(cocoa) + let (index, found) = native.find(member) + _precondition(found, "Bridging did not preserve equality") + let old = native.remove(at: index, isUnique: true) + _precondition(member == old, "Bridging did not preserve equality") + self = .native(native) + return old + } +#endif + + @inlinable + internal mutating func removeAll(keepingCapacity keepCapacity: Bool) { + if !keepCapacity { + self = .native(_NativeSet()) + return + } + guard count > 0 else { return } + + switch self { + case .native: + let isUnique = isUniquelyReferenced() + asNative.removeAll(isUnique: isUnique) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + self = .native(_NativeSet(capacity: cocoa.count)) +#endif + } + } +} + +extension Set._Variant { + /// Returns an iterator over the elements. + /// + /// - Complexity: O(1). + @inlinable + @inline(__always) + __consuming internal func makeIterator() -> Set.Iterator { + switch self { + case .native(let native): + return Set.Iterator(_native: native.makeIterator()) +#if _runtime(_ObjC) + case .cocoa(let cocoa): + cocoaPath() + return Set.Iterator(_cocoa: cocoa.makeIterator()) +#endif + } + } +} +