mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
510 lines
16 KiB
Swift
510 lines
16 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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.
|
|
// NOTE: older runtimes called this class _RawDictionaryStorage. The two
|
|
// must coexist without a conflicting ObjC class name, so it was
|
|
// renamed. The old name must not be used in the new runtime.
|
|
@_fixed_layout
|
|
@usableFromInline
|
|
@_objc_non_lazy_realization
|
|
@unsafe
|
|
internal class __RawDictionaryStorage: __SwiftNativeNSDictionary {
|
|
// 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.
|
|
|
|
/// 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: Int8
|
|
|
|
/// The scale corresponding to the highest `reserveCapacity(_:)` call so far,
|
|
/// or 0 if there were none. This may be used later to allow removals to
|
|
/// resize storage.
|
|
///
|
|
/// FIXME: <rdar://problem/18114559> Shrink storage on deletion
|
|
@usableFromInline
|
|
@nonobjc
|
|
internal final var _reservedScale: Int8
|
|
|
|
// Currently unused, set to zero.
|
|
@nonobjc
|
|
internal final var _extra: Int16
|
|
|
|
/// A mutation count, enabling stricter index validation.
|
|
@usableFromInline
|
|
@nonobjc
|
|
internal final var _age: Int32
|
|
|
|
/// The hash seed used to hash elements in this dictionary instance.
|
|
@usableFromInline
|
|
internal final var _seed: Int
|
|
|
|
/// A raw pointer to the start of the tail-allocated hash buffer holding keys.
|
|
@usableFromInline
|
|
@nonobjc
|
|
internal final var _rawKeys: UnsafeMutableRawPointer
|
|
|
|
/// A raw pointer to the start of the tail-allocated hash buffer holding
|
|
/// values.
|
|
@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: ()) {
|
|
_internalInvariantFailure("This class cannot be directly initialized")
|
|
}
|
|
|
|
@inlinable
|
|
@nonobjc
|
|
internal final var _bucketCount: Int {
|
|
@inline(__always) get { return unsafe 1 &<< _scale }
|
|
}
|
|
|
|
@inlinable
|
|
@nonobjc
|
|
internal final var _metadata: UnsafeMutablePointer<_HashTable.Word> {
|
|
@inline(__always) get {
|
|
let address = unsafe Builtin.projectTailElems(self, _HashTable.Word.self)
|
|
return unsafe 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 unsafe _HashTable(words: _metadata, bucketCount: _bucketCount)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The storage class for the singleton empty set.
|
|
/// The single instance of this class is created by the runtime.
|
|
// NOTE: older runtimes called this class _EmptyDictionarySingleton.
|
|
// The two must coexist without a conflicting ObjC class name, so it was
|
|
// renamed. The old name must not be used in the new runtime.
|
|
@unsafe @_fixed_layout
|
|
@usableFromInline
|
|
@_objc_non_lazy_realization
|
|
internal class __EmptyDictionarySingleton: __RawDictionaryStorage {
|
|
@nonobjc
|
|
internal override init(_doNotCallMe: ()) {
|
|
_internalInvariantFailure("This class cannot be directly initialized")
|
|
}
|
|
|
|
#if _runtime(_ObjC)
|
|
@objc
|
|
internal required init(
|
|
objects: UnsafePointer<AnyObject?>,
|
|
forKeys: UnsafeRawPointer,
|
|
count: Int
|
|
) {
|
|
_internalInvariantFailure("This class cannot be directly initialized")
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if _runtime(_ObjC)
|
|
extension __EmptyDictionarySingleton: @unsafe _NSDictionaryCore {
|
|
@objc(copyWithZone:)
|
|
internal func copy(with zone: _SwiftNSZone?) -> AnyObject {
|
|
return unsafe self
|
|
}
|
|
|
|
@objc
|
|
internal var count: Int {
|
|
return 0
|
|
}
|
|
|
|
@objc(countByEnumeratingWithState:objects:count:)
|
|
internal func countByEnumerating(
|
|
with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>,
|
|
objects: UnsafeMutablePointer<AnyObject>?, 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 = unsafe state.pointee
|
|
if unsafe theState.state == 0 {
|
|
unsafe theState.state = 1 // Arbitrary non-zero value.
|
|
unsafe theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects)
|
|
unsafe theState.mutationsPtr = _fastEnumerationStorageMutationsPtr
|
|
}
|
|
unsafe 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<AnyObject>?,
|
|
andKeys keys: UnsafeMutablePointer<AnyObject>?,
|
|
count: Int) {
|
|
// Do nothing, we're empty
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if $Embedded
|
|
// In embedded Swift, the stdlib is a .swiftmodule only without any .o/.a files,
|
|
// to allow consuming it by clients with different LLVM codegen setting (-mcpu
|
|
// flags, etc.), which means we cannot declare the singleton in a C/C++ file.
|
|
//
|
|
// TODO: We should figure out how to make this a constant so that it's placed in
|
|
// non-writable memory (can't be a let, Builtin.addressof below requires a var).
|
|
@unsafe
|
|
public var _swiftEmptyDictionarySingleton: (Int, Int, Int, Int, UInt8, UInt8, UInt16, UInt32, Int, Int, Int, Int) =
|
|
(
|
|
/*isa*/0, /*refcount*/-1, // HeapObject header
|
|
/*count*/0,
|
|
/*capacity*/0,
|
|
/*scale*/0,
|
|
/*reservedScale*/0,
|
|
/*extra*/0,
|
|
/*age*/0,
|
|
/*seed*/0,
|
|
/*rawKeys*/1,
|
|
/*rawValues*/1,
|
|
/*metadata*/~1
|
|
)
|
|
#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 unsafe Builtin.bridgeFromRawPointer(
|
|
Builtin.addressof(&_swiftEmptyDictionarySingleton))
|
|
}
|
|
|
|
@_alwaysEmitIntoClient
|
|
@inline(__always)
|
|
internal final func uncheckedKey<Key: Hashable>(at bucket: _HashTable.Bucket) -> Key {
|
|
defer { unsafe _fixLifetime(self) }
|
|
unsafe _internalInvariant(_hashTable.isOccupied(bucket))
|
|
let keys = unsafe _rawKeys.assumingMemoryBound(to: Key.self)
|
|
return unsafe keys[bucket.offset]
|
|
}
|
|
|
|
@_alwaysEmitIntoClient
|
|
@inline(never)
|
|
internal final func find<Key: Hashable>(_ key: Key) -> (bucket: _HashTable.Bucket, found: Bool) {
|
|
return unsafe find(key, hashValue: key._rawHashValue(seed: _seed))
|
|
}
|
|
|
|
@_alwaysEmitIntoClient
|
|
@inline(never)
|
|
internal final func find<Key: Hashable>(_ key: Key, hashValue: Int) -> (bucket: _HashTable.Bucket, found: Bool) {
|
|
let hashTable = unsafe _hashTable
|
|
var bucket = unsafe hashTable.idealBucket(forHashValue: hashValue)
|
|
while unsafe hashTable._isOccupied(bucket) {
|
|
if unsafe uncheckedKey(at: bucket) == key {
|
|
return unsafe (bucket, true)
|
|
}
|
|
unsafe bucket = unsafe hashTable.bucket(wrappedAfter: bucket)
|
|
}
|
|
return unsafe (bucket, false)
|
|
}
|
|
}
|
|
|
|
@unsafe @usableFromInline
|
|
final internal class _DictionaryStorage<Key: Hashable, Value>
|
|
: __RawDictionaryStorage, @unsafe _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: ()) {
|
|
_internalInvariantFailure("This class cannot be directly initialized")
|
|
}
|
|
|
|
deinit {
|
|
guard unsafe _count > 0 else { return }
|
|
if !_isPOD(Key.self) {
|
|
let keys = unsafe self._keys
|
|
for unsafe bucket in unsafe _hashTable {
|
|
unsafe (keys + bucket.offset).deinitialize(count: 1)
|
|
}
|
|
}
|
|
if !_isPOD(Value.self) {
|
|
let values = unsafe self._values
|
|
for unsafe bucket in unsafe _hashTable {
|
|
unsafe (values + bucket.offset).deinitialize(count: 1)
|
|
}
|
|
}
|
|
unsafe _count = 0
|
|
unsafe _fixLifetime(self)
|
|
}
|
|
|
|
@inlinable
|
|
final internal var _keys: UnsafeMutablePointer<Key> {
|
|
@inline(__always)
|
|
get {
|
|
return unsafe self._rawKeys.assumingMemoryBound(to: Key.self)
|
|
}
|
|
}
|
|
|
|
@inlinable
|
|
final internal var _values: UnsafeMutablePointer<Value> {
|
|
@inline(__always)
|
|
get {
|
|
return unsafe self._rawValues.assumingMemoryBound(to: Value.self)
|
|
}
|
|
}
|
|
|
|
internal var asNative: _NativeDictionary<Key, Value> {
|
|
return _NativeDictionary(self)
|
|
}
|
|
|
|
#if _runtime(_ObjC)
|
|
@objc
|
|
internal required init(
|
|
objects: UnsafePointer<AnyObject?>,
|
|
forKeys: UnsafeRawPointer,
|
|
count: Int
|
|
) {
|
|
_internalInvariantFailure("This class cannot be directly initialized")
|
|
}
|
|
|
|
@objc(copyWithZone:)
|
|
internal func copy(with zone: _SwiftNSZone?) -> AnyObject {
|
|
return unsafe self
|
|
}
|
|
|
|
@objc
|
|
internal var count: Int {
|
|
return unsafe _count
|
|
}
|
|
|
|
@objc(keyEnumerator)
|
|
internal func keyEnumerator() -> _NSEnumerator {
|
|
return unsafe _SwiftDictionaryNSEnumerator<Key, Value>(asNative)
|
|
}
|
|
|
|
@objc(countByEnumeratingWithState:objects:count:)
|
|
internal func countByEnumerating(
|
|
with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>,
|
|
objects: UnsafeMutablePointer<AnyObject>?, count: Int
|
|
) -> Int {
|
|
defer { unsafe _fixLifetime(self) }
|
|
let hashTable = unsafe _hashTable
|
|
|
|
var theState = unsafe state.pointee
|
|
if unsafe theState.state == 0 {
|
|
unsafe theState.state = 1 // Arbitrary non-zero value.
|
|
unsafe theState.itemsPtr = AutoreleasingUnsafeMutablePointer(objects)
|
|
unsafe theState.mutationsPtr = _fastEnumerationStorageMutationsPtr
|
|
unsafe theState.extra.0 = CUnsignedLong(hashTable.startBucket.offset)
|
|
}
|
|
|
|
// 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 unsafe _slowPath(objects == nil) {
|
|
return 0
|
|
}
|
|
|
|
let unmanagedObjects = unsafe _UnmanagedAnyObjectArray(objects!)
|
|
var bucket = unsafe _HashTable.Bucket(offset: Int(theState.extra.0))
|
|
let endBucket = unsafe hashTable.endBucket
|
|
unsafe _precondition(bucket == endBucket || hashTable.isOccupied(bucket),
|
|
"Invalid fast enumeration state")
|
|
var stored = 0
|
|
for i in 0..<count {
|
|
if unsafe bucket == endBucket { break }
|
|
|
|
let key = unsafe _keys[bucket.offset]
|
|
unsafe unmanagedObjects[i] = _bridgeAnythingToObjectiveC(key)
|
|
|
|
stored += 1
|
|
unsafe bucket = unsafe hashTable.occupiedBucket(after: bucket)
|
|
}
|
|
unsafe theState.extra.0 = CUnsignedLong(bucket.offset)
|
|
unsafe state.pointee = theState
|
|
return stored
|
|
}
|
|
|
|
@objc(objectForKey:)
|
|
internal func object(forKey aKey: AnyObject) -> AnyObject? {
|
|
guard let nativeKey = _conditionallyBridgeFromObjectiveC(aKey, Key.self)
|
|
else { return nil }
|
|
|
|
let (bucket, found) = unsafe asNative.find(nativeKey)
|
|
guard found else { return nil }
|
|
let value = unsafe asNative.uncheckedValue(at: bucket)
|
|
return _bridgeAnythingToObjectiveC(value)
|
|
}
|
|
|
|
@objc(getObjects:andKeys:count:)
|
|
internal func getObjects(
|
|
_ objects: UnsafeMutablePointer<AnyObject>?,
|
|
andKeys keys: UnsafeMutablePointer<AnyObject>?,
|
|
count: Int) {
|
|
_precondition(count >= 0, "Invalid count")
|
|
guard count > 0 else { return }
|
|
var i = 0 // Current position in the output buffers
|
|
switch unsafe (_UnmanagedAnyObjectArray(keys), _UnmanagedAnyObjectArray(objects)) {
|
|
case (let unmanagedKeys?, let unmanagedObjects?):
|
|
for (key, value) in unsafe asNative {
|
|
unsafe unmanagedObjects[i] = _bridgeAnythingToObjectiveC(value)
|
|
unsafe unmanagedKeys[i] = _bridgeAnythingToObjectiveC(key)
|
|
i += 1
|
|
guard i < count else { break }
|
|
}
|
|
case (let unmanagedKeys?, nil):
|
|
for (key, _) in unsafe asNative {
|
|
unsafe unmanagedKeys[i] = _bridgeAnythingToObjectiveC(key)
|
|
i += 1
|
|
guard i < count else { break }
|
|
}
|
|
case (nil, let unmanagedObjects?):
|
|
for (_, value) in unsafe asNative {
|
|
unsafe unmanagedObjects[i] = _bridgeAnythingToObjectiveC(value)
|
|
i += 1
|
|
guard i < count else { break }
|
|
}
|
|
case (nil, nil):
|
|
// Do nothing.
|
|
break
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
extension _DictionaryStorage {
|
|
@usableFromInline
|
|
@_effects(releasenone)
|
|
internal static func copy(
|
|
original: __RawDictionaryStorage
|
|
) -> _DictionaryStorage {
|
|
return unsafe allocate(
|
|
scale: original._scale,
|
|
age: original._age,
|
|
seed: original._seed)
|
|
}
|
|
|
|
@usableFromInline
|
|
@_effects(releasenone)
|
|
static internal func resize(
|
|
original: __RawDictionaryStorage,
|
|
capacity: Int,
|
|
move: Bool
|
|
) -> _DictionaryStorage {
|
|
let scale = unsafe _HashTable.scale(forCapacity: capacity)
|
|
return unsafe allocate(scale: scale, age: nil, seed: nil)
|
|
}
|
|
|
|
@usableFromInline
|
|
@_effects(releasenone)
|
|
static internal func allocate(capacity: Int) -> _DictionaryStorage {
|
|
let scale = unsafe _HashTable.scale(forCapacity: capacity)
|
|
return unsafe allocate(scale: scale, age: nil, seed: nil)
|
|
}
|
|
|
|
#if _runtime(_ObjC)
|
|
@usableFromInline
|
|
@_effects(releasenone)
|
|
static internal func convert(
|
|
_ cocoa: __CocoaDictionary,
|
|
capacity: Int
|
|
) -> _DictionaryStorage {
|
|
let scale = unsafe _HashTable.scale(forCapacity: capacity)
|
|
let age = unsafe _HashTable.age(for: cocoa.object)
|
|
return unsafe allocate(scale: scale, age: age, seed: nil)
|
|
}
|
|
#endif
|
|
|
|
static internal func allocate(
|
|
scale: Int8,
|
|
age: Int32?,
|
|
seed: Int?
|
|
) -> _DictionaryStorage {
|
|
// The entry count must be representable by an Int value; hence the scale's
|
|
// peculiar upper bound.
|
|
_internalInvariant(scale >= 0 && scale < Int.bitWidth - 1)
|
|
|
|
let bucketCount = (1 as Int) &<< scale
|
|
let wordCount = unsafe _UnsafeBitset.wordCount(forCapacity: bucketCount)
|
|
let storage = unsafe Builtin.allocWithTailElems_3(
|
|
_DictionaryStorage<Key, Value>.self,
|
|
wordCount._builtinWordValue, _HashTable.Word.self,
|
|
bucketCount._builtinWordValue, Key.self,
|
|
bucketCount._builtinWordValue, Value.self)
|
|
|
|
let metadataAddr = unsafe Builtin.projectTailElems(storage, _HashTable.Word.self)
|
|
let keysAddr = unsafe Builtin.getTailAddr_Word(
|
|
metadataAddr, wordCount._builtinWordValue, _HashTable.Word.self,
|
|
Key.self)
|
|
let valuesAddr = Builtin.getTailAddr_Word(
|
|
keysAddr, bucketCount._builtinWordValue, Key.self,
|
|
Value.self)
|
|
unsafe storage._count = 0
|
|
unsafe storage._capacity = unsafe _HashTable.capacity(forScale: scale)
|
|
unsafe storage._scale = scale
|
|
unsafe storage._reservedScale = 0
|
|
unsafe storage._extra = 0
|
|
|
|
if let age = age {
|
|
unsafe storage._age = age
|
|
} else {
|
|
// The default mutation count is simply a scrambled version of the storage
|
|
// address.
|
|
unsafe storage._age = Int32(
|
|
truncatingIfNeeded: ObjectIdentifier(storage).hashValue)
|
|
}
|
|
|
|
unsafe storage._seed = unsafe seed ?? _HashTable.hashSeed(for: Builtin.castToNativeObject(storage), scale: scale)
|
|
unsafe storage._rawKeys = UnsafeMutableRawPointer(keysAddr)
|
|
unsafe storage._rawValues = UnsafeMutableRawPointer(valuesAddr)
|
|
|
|
// Initialize hash table metadata.
|
|
unsafe storage._hashTable.clear()
|
|
return unsafe storage
|
|
}
|
|
}
|