Skip to content

[WIP] Dictionary/Set cumulative test PR #19586

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6358294
[stdlib] Set, Dictionary: Add a mutation count to storage ivars
lorentey Sep 26, 2018
e608901
[stdlib] Fill out the 24 bits currently left as padding in Set/Dictio…
lorentey Sep 26, 2018
c3caa64
[stdlib] _NativeSet._delete: check that count doesn’t go below 0
lorentey Sep 26, 2018
911d4ab
[runtime] Update empty singletons for Set/Dictionary
lorentey Sep 26, 2018
f600426
[stdlib][NFC] Update comments
lorentey Sep 26, 2018
5705582
[stdlib] _HashTable.Index: New struct
lorentey Sep 27, 2018
c765b59
[stdlib] Set: Embed & check the mutation count in native indices
lorentey Sep 27, 2018
e5d976d
[stdlib] Dictionary: Embed & check the mutation count in native indices
lorentey Sep 27, 2018
863e8b6
[stdlib] Set, Dictionary: Add a message to fast enumeration state checks
lorentey Sep 27, 2018
720f3a0
[stdlib] Set, Dictionary: Validate index received on native index(aft…
lorentey Sep 27, 2018
be68ede
[stdlib] Dictionary.Values.subscript: Validate index in _modify accessor
lorentey Sep 27, 2018
6033454
[stdlib] Dictionary.Values: Specialize swapAt
lorentey Sep 27, 2018
1c69181
[temp] Don’t update keys on updateValue or subscript
lorentey Sep 27, 2018
b1a3ece
[temp] Per-instance hash seeds
lorentey Sep 27, 2018
40f1fad
[stdlib] _CocoaSet.element, _CocoaDictionary.key: New properties
lorentey Sep 27, 2018
3a62545
[stdlib] Native Set, Dictionary: Add index validation support for top…
lorentey Sep 27, 2018
60ef0be
f
lorentey Sep 27, 2018
3fc410d
[stdlib] Set, Dictionary: Use lowercase index in validation error mes…
lorentey Sep 27, 2018
c9875d0
[stdlib] Dictionary: Update remove(at:) and Values to allow Cocoa ind…
lorentey Sep 27, 2018
016373d
[stdlib] validate(_:) ⟹ validatedBucket(for:)
lorentey Sep 27, 2018
a74dd91
f
lorentey Sep 27, 2018
74c3d28
[stdlib] Tighten up Cocoa index validation for native sets/dictionaries
lorentey Sep 27, 2018
20eec80
[stdlib] Set: Allow limited use of Cocoa indices in native sets
lorentey Sep 27, 2018
9196fc0
[stdlib] Dictionary: Allow limited use of Cocoa indices in native dic…
lorentey Sep 27, 2018
37b7bd9
[stdlib] Supplying a native index to a bridged set/dictionary is not …
lorentey Sep 27, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions stdlib/public/SwiftShims/GlobalObjects.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ struct _SwiftEmptyArrayStorage _swiftEmptyArrayStorage;
struct _SwiftDictionaryBodyStorage {
__swift_intptr_t count;
__swift_intptr_t capacity;
__swift_intptr_t scale;
__swift_int8_t scale;
__swift_int8_t reservedScale;
__swift_int16_t extra;
__swift_int32_t age;
__swift_intptr_t seed;
void *rawKeys;
void *rawValues;
Expand All @@ -52,7 +55,10 @@ struct _SwiftDictionaryBodyStorage {
struct _SwiftSetBodyStorage {
__swift_intptr_t count;
__swift_intptr_t capacity;
__swift_intptr_t scale;
__swift_int8_t scale;
__swift_int8_t reservedScale;
__swift_int16_t extra;
__swift_int32_t age;
__swift_intptr_t seed;
void *rawElements;
};
Expand Down Expand Up @@ -86,6 +92,16 @@ struct _SwiftHashingParameters _swift_stdlib_Hashing_parameters;

#ifdef __cplusplus

static_assert(
sizeof(_SwiftDictionaryBodyStorage) ==
5 * sizeof(__swift_intptr_t) + sizeof(__swift_int64_t),
"_SwiftDictionaryBodyStorage has unexpected size");

static_assert(
sizeof(_SwiftSetBodyStorage) ==
4 * sizeof(__swift_intptr_t) + sizeof(__swift_int64_t),
"_SwiftSetBodyStorage has unexpected size");

static_assert(std::is_pod<_SwiftEmptyArrayStorage>::value,
"empty array type should be POD");
static_assert(std::is_pod<_SwiftEmptyDictionarySingleton>::value,
Expand Down
50 changes: 41 additions & 9 deletions stdlib/public/core/Dictionary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
// | _count |
// | _capacity |
// | _scale |
// | _age |
// | _seed |
// | _rawKeys |
// | _rawValue |
Expand Down Expand Up @@ -149,6 +150,13 @@
// dictionary storage are bounds-checked, this scheme never compromises memory
// safety.
//
// As a safeguard against using invalid indices, Set and Dictionary maintain a
// mutation counter in their storage header (`_age`). This counter gets bumped
// every time an element is removed and whenever the contents are
// rehashed. Native indices include a copy of this counter so that index
// validation can verify it matches with current storage. This can't catch all
// misuse, because counters may match by accident; but it does make indexing a
// lot more reliable.
//
// Bridging
// ========
Expand Down Expand Up @@ -1455,8 +1463,9 @@ extension Dictionary {
return _variant.value(at: position)
}
_modify {
let index = _variant.ensureUniqueNative(preserving: position)
let address = _variant.asNative._values + index.offset
let native = _variant.ensureUniqueNative()
let bucket = native.validatedBucket(for: position)
let address = native._values + bucket.offset
yield &address.pointee
_fixLifetime(self)
}
Expand Down Expand Up @@ -1485,6 +1494,27 @@ extension Dictionary {
public var debugDescription: String {
return _makeCollectionDescription(withTypeName: "Dictionary.Values")
}

@inlinable
public mutating func swapAt(_ i: Index, _ j: Index) {
guard i != j else { return }
let (a, b): (_HashTable.Bucket, _HashTable.Bucket)
switch _variant {
case .native(let native):
a = native.validatedBucket(for: i)
b = native.validatedBucket(for: j)
#if _runtime(_ObjC)
case .cocoa(let cocoa):
_variant.cocoaPath()
let native = _NativeDictionary<Key, Value>(cocoa)
a = native.validatedBucket(for: i)
b = native.validatedBucket(for: j)
_variant = .native(native)
#endif
}
let isUnique = _variant.isUniquelyReferenced()
_variant.asNative.swapValuesAt(a, b, isUnique: isUnique)
}
}
}

Expand Down Expand Up @@ -1691,7 +1721,7 @@ extension Dictionary {
@_frozen
@usableFromInline
internal enum _Variant {
case native(_NativeDictionary<Key, Value>.Index)
case native(_HashTable.Index)
#if _runtime(_ObjC)
case cocoa(_CocoaDictionary.Index)
#endif
Expand All @@ -1708,7 +1738,7 @@ extension Dictionary {

@inlinable
@inline(__always)
internal init(_native index: _NativeDictionary<Key, Value>.Index) {
internal init(_native index: _HashTable.Index) {
self.init(_variant: .native(index))
}

Expand Down Expand Up @@ -1740,13 +1770,14 @@ extension Dictionary.Index {
#endif

@usableFromInline @_transparent
internal var _asNative: _NativeDictionary<Key, Value>.Index {
internal var _asNative: _HashTable.Index {
switch _variant {
case .native(let nativeIndex):
return nativeIndex
#if _runtime(_ObjC)
case .cocoa:
_sanityCheckFailure("internal error: does not contain a native index")
_preconditionFailure(
"Attempting to access Dictionary elements using an invalid index")
#endif
}
}
Expand All @@ -1756,7 +1787,8 @@ extension Dictionary.Index {
internal var _asCocoa: _CocoaDictionary.Index {
switch _variant {
case .native:
_sanityCheckFailure("internal error: does not contain a Cocoa index")
_preconditionFailure(
"Attempting to access Dictionary elements using an invalid index")
case .cocoa(let cocoaIndex):
return cocoaIndex
}
Expand Down Expand Up @@ -1811,14 +1843,14 @@ extension Dictionary.Index: Hashable {
switch _variant {
case .native(let nativeIndex):
hasher.combine(0 as UInt8)
hasher.combine(nativeIndex.offset)
hasher.combine(nativeIndex.bucket.offset)
case .cocoa(let cocoaIndex):
_cocoaPath()
hasher.combine(1 as UInt8)
hasher.combine(cocoaIndex.currentKeyIndex)
}
#else
hasher.combine(_asNative.offset)
hasher.combine(_asNative.bucket.offset)
#endif
}
}
Expand Down
24 changes: 22 additions & 2 deletions stdlib/public/core/DictionaryBridging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,8 @@ final internal class _SwiftDeferredNSDictionary<Key: Hashable, Value>
let unmanagedObjects = _UnmanagedAnyObjectArray(objects!)
var bucket = _HashTable.Bucket(offset: Int(theState.extra.0))
let endBucket = hashTable.endBucket
_precondition(bucket == endBucket || hashTable.isOccupied(bucket))
_precondition(bucket == endBucket || hashTable.isOccupied(bucket),
"Invalid fast enumeration state")
var stored = 0

// Only need to bridge once, so we can hoist it out of the loop.
Expand Down Expand Up @@ -528,7 +529,7 @@ extension _CocoaDictionary: _DictionaryBuffer {
@inline(__always)
func key(at index: Index) -> Key {
_precondition(index.base.object === self.object, "Invalid index")
return index.allKeys[index.currentKeyIndex]
return index.key
}

@inlinable
Expand Down Expand Up @@ -607,6 +608,25 @@ extension _CocoaDictionary {
}
}

extension _CocoaDictionary.Index {
@inlinable
@nonobjc
internal var key: AnyObject {
_precondition(currentKeyIndex < allKeys.value,
"Attempting to access Dictionary elements using an invalid index")
return allKeys[currentKeyIndex]
}

@usableFromInline
@nonobjc
internal var age: Int32 {
@_effects(releasenone)
get {
return _HashTable.age(for: base.object)
}
}
}

extension _CocoaDictionary.Index: Equatable {
@inlinable
internal static func == (
Expand Down
92 changes: 70 additions & 22 deletions stdlib/public/core/DictionaryStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,14 @@ import SwiftShims
/// 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 {
// 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
Expand All @@ -41,15 +39,37 @@ internal class _RawDictionaryStorage: __SwiftNativeNSDictionary {
/// power of `scale`.
@usableFromInline
@nonobjc
internal final var _scale: Int
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
Expand Down Expand Up @@ -272,7 +292,8 @@ final internal class _DictionaryStorage<Key: Hashable, Value>
let unmanagedObjects = _UnmanagedAnyObjectArray(objects!)
var bucket = _HashTable.Bucket(offset: Int(theState.extra.0))
let endBucket = hashTable.endBucket
_precondition(bucket == endBucket || _hashTable.isOccupied(bucket))
_precondition(bucket == endBucket || hashTable.isOccupied(bucket),
"Invalid fast enumeration state")
var stored = 0
for i in 0..<count {
if bucket == endBucket { break }
Expand Down Expand Up @@ -345,23 +366,47 @@ extension _DictionaryStorage {
_sanityCheck(capacity >= original._count)
let scale = _HashTable.scale(forCapacity: capacity)
let rehash = (scale != original._scale)
let newStorage = _DictionaryStorage<Key, Value>.allocate(scale: scale)
let newStorage: _DictionaryStorage<Key, Value>
if rehash {
// Invalidate indices and generate a new seed.
newStorage = .allocate(scale: scale, age: nil, seed: nil)
} else {
newStorage = .allocate(
scale: scale,
age: original._age,
seed: original._seed)
}
return (newStorage, rehash)
}

@usableFromInline
@_effects(releasenone)
static internal func allocate(capacity: Int) -> _DictionaryStorage {
let scale = _HashTable.scale(forCapacity: capacity)
return allocate(scale: scale)
return allocate(scale: scale, age: nil, seed: nil)
}

static internal func allocate(scale: Int) -> _DictionaryStorage {
@usableFromInline
@_effects(releasenone)
static internal func convert(
_ cocoa: _CocoaDictionary,
capacity: Int
) -> _DictionaryStorage {
let scale = _HashTable.scale(forCapacity: capacity)
let age = _HashTable.age(for: cocoa.object)
return allocate(scale: scale, age: age, seed: nil)
}

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.
_sanityCheck(scale >= 0 && scale < Int.bitWidth - 1)

let bucketCount = 1 &<< scale
let bucketCount = (1 as Int) &<< scale
let wordCount = _UnsafeBitset.wordCount(forCapacity: bucketCount)
let storage = Builtin.allocWithTailElems_3(
_DictionaryStorage<Key, Value>.self,
Expand All @@ -379,19 +424,22 @@ extension _DictionaryStorage {
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._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 = scale

// Initialize hash table metadata.
storage._hashTable.clear()
return storage
Expand Down
Loading