blob: 44936c806e1c2be496110c743016abfb25366b8a [file] [log] [blame]
//===----------------------------------------------------------------------===//
//
// 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.
// NOTE: older runtimes called this class _RawSetStorage. 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
internal class __RawSetStorage: __SwiftNativeNSSet {
// 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 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: 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 set instance.
@usableFromInline
internal final var _seed: Int
/// A raw pointer to the start of the tail-allocated hash buffer holding set
/// members.
@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: ()) {
_internalInvariantFailure("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.
// NOTE: older runtimes called this class _EmptySetSingleton. The two
// must coexist without conflicting ObjC class names, so it was renamed.
// The old names must not be used in the new runtime.
@_fixed_layout
@usableFromInline
internal class __EmptySetSingleton: __RawSetStorage {
@nonobjc
override internal init(_doNotCallMe: ()) {
_internalInvariantFailure("This class cannot be directly initialized")
}
#if _runtime(_ObjC)
@objc
internal required init(objects: UnsafePointer<AnyObject?>, count: Int) {
_internalInvariantFailure("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<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 = 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
}
@usableFromInline
final internal class _SetStorage<Element: Hashable>
: __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: ()) {
_internalInvariantFailure("This class cannot be directly initialized")
}
deinit {
guard _count > 0 else { return }
if !_isPOD(Element.self) {
let elements = _elements
for bucket in _hashTable {
(elements + bucket.offset).deinitialize(count: 1)
}
}
_fixLifetime(self)
}
@inlinable
final internal var _elements: UnsafeMutablePointer<Element> {
@inline(__always)
get {
return self._rawElements.assumingMemoryBound(to: Element.self)
}
}
internal var asNative: _NativeSet<Element> {
return _NativeSet(self)
}
#if _runtime(_ObjC)
@objc
internal required init(objects: UnsafePointer<AnyObject?>, count: Int) {
_internalInvariantFailure("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<Element>(asNative)
}
@objc(countByEnumeratingWithState:objects:count:)
internal func countByEnumerating(
with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>,
objects: UnsafeMutablePointer<AnyObject>?, count: Int
) -> Int {
defer { _fixLifetime(self) }
let hashTable = _hashTable
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(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 _slowPath(objects == nil) {
return 0
}
let unmanagedObjects = _UnmanagedAnyObjectArray(objects!)
var bucket = _HashTable.Bucket(offset: Int(theState.extra.0))
let endBucket = hashTable.endBucket
_precondition(bucket == endBucket || hashTable.isOccupied(bucket),
"Invalid fast enumeration state")
var stored = 0
for i in 0..<count {
if bucket == endBucket { break }
let element = _elements[bucket.offset]
unmanagedObjects[i] = _bridgeAnythingToObjectiveC(element)
stored += 1
bucket = hashTable.occupiedBucket(after: bucket)
}
theState.extra.0 = CUnsignedLong(bucket.offset)
state.pointee = theState
return stored
}
@objc(member:)
internal func member(_ object: AnyObject) -> AnyObject? {
guard let native = _conditionallyBridgeFromObjectiveC(object, Element.self)
else { return nil }
let (bucket, found) = asNative.find(native)
guard found else { return nil }
return _bridgeAnythingToObjectiveC(_elements[bucket.offset])
}
#endif
}
extension _SetStorage {
@usableFromInline
@_effects(releasenone)
internal static func copy(original: __RawSetStorage) -> _SetStorage {
return .allocate(
scale: original._scale,
age: original._age,
seed: original._seed)
}
@usableFromInline
@_effects(releasenone)
static internal func resize(
original: __RawSetStorage,
capacity: Int,
move: Bool
) -> _SetStorage {
let scale = _HashTable.scale(forCapacity: capacity)
return allocate(scale: scale, age: nil, seed: nil)
}
@usableFromInline
@_effects(releasenone)
static internal func allocate(capacity: Int) -> _SetStorage {
let scale = _HashTable.scale(forCapacity: capacity)
return allocate(scale: scale, age: nil, seed: nil)
}
#if _runtime(_ObjC)
@usableFromInline
@_effects(releasenone)
static internal func convert(
_ cocoa: __CocoaSet,
capacity: Int
) -> _SetStorage {
let scale = _HashTable.scale(forCapacity: capacity)
let age = _HashTable.age(for: cocoa.object)
return allocate(scale: scale, age: age, seed: nil)
}
#endif
static internal func allocate(
scale: Int8,
age: Int32?,
seed: Int?
) -> _SetStorage {
// 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 = _UnsafeBitset.wordCount(forCapacity: bucketCount)
let storage = Builtin.allocWithTailElems_2(
_SetStorage<Element>.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._reservedScale = 0
storage._extra = 0
if let age = age {
storage._age = age
} else {
// The default mutation count is simply a scrambled version of the storage
// address.
storage._age = Int32(
truncatingIfNeeded: ObjectIdentifier(storage).hashValue)
}
storage._seed = seed ?? _HashTable.hashSeed(for: storage, scale: scale)
storage._rawElements = UnsafeMutableRawPointer(elementsAddr)
// Initialize hash table metadata.
storage._hashTable.clear()
return storage
}
}