Skip to content

Commit 77d25d8

Browse files
authored
Merge pull request #18974 from lorentey/dictionary-generalized-accessors
2 parents 44b1270 + 653b68c commit 77d25d8

File tree

1 file changed

+106
-3
lines changed

1 file changed

+106
-3
lines changed

stdlib/public/core/Dictionary.swift

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,61 @@ extension Dictionary {
785785
removeValue(forKey: key)
786786
}
787787
}
788+
_modify {
789+
// FIXME: This code should be moved to _variant, with Dictionary.subscript
790+
// yielding `&_variant[key]`.
791+
792+
// Look up (empty or occupied) bucket corresponding to the given key.
793+
let isUnique = _variant.isUniquelyReferenced()
794+
var idealBucket = _variant.asNative._bucket(key)
795+
var (pos, found) = _variant.asNative._find(key, startBucket: idealBucket)
796+
797+
// Prepare storage.
798+
// If `key` isn't in the dictionary yet, assume that this access will end
799+
// up inserting it. (If we guess wrong, we might needlessly expand
800+
// storage; that's fine.) Otherwise this can only be a removal or an
801+
// in-place mutation.
802+
let (_, rehashed) = _variant.ensureUniqueNative(
803+
withCapacity: self.count + (found ? 0 : 1),
804+
isUnique: isUnique)
805+
// FIXME: we should be able to make this a let; however, some of the
806+
// low-level operations below are (incorrectly) marked as mutating.
807+
var native = _variant.asNative
808+
if rehashed {
809+
// Key needs to be hashed again if storage has been resized.
810+
_sanityCheck(!found)
811+
idealBucket = native._bucket(key)
812+
(pos, found) = native._find(key, startBucket: idealBucket)
813+
_sanityCheck(!found)
814+
}
815+
// FIXME: Mark this entry as being modified in hash table metadata
816+
// so that lldb can recognize it's not valid.
817+
818+
// Move the old value (if any) out of storage, wrapping it into an
819+
// optional before yielding it.
820+
var value: Value? = found ? native.moveValue(at: pos.bucket) : nil
821+
yield &value
822+
823+
// Value is now potentially different. Check which one of the four
824+
// possible cases apply.
825+
switch (found, value != nil) {
826+
case (true, true): // Mutation
827+
// Initialize storage to new value.
828+
(native.values + pos.bucket).initialize(to: value!)
829+
case (true, false): // Removal
830+
// We've already removed the value; deinitialize and remove the key too.
831+
native.destroyHalfEntry(at: pos.bucket)
832+
native._deleteDestroyed(idealBucket: idealBucket, bucket: pos.bucket)
833+
case (false, true): // Insertion
834+
// Insert the new entry at the correct place.
835+
// We've already ensured we have enough capacity.
836+
native.initializeKey(key, value: value!, at: pos.bucket)
837+
native.count += 1
838+
case (false, false): // Noop
839+
// Easy!
840+
break
841+
}
842+
}
788843
}
789844
}
790845

@@ -2224,6 +2279,21 @@ internal struct _NativeDictionary<Key, Value> {
22242279
_storage.initializedEntries[i] = false
22252280
}
22262281

2282+
@inlinable
2283+
internal func moveValue(at bucket: Int) -> Value {
2284+
defer { _fixLifetime(self) }
2285+
return (values + bucket).move()
2286+
}
2287+
2288+
// This assumes the value is already deinitialized.
2289+
@inlinable
2290+
internal func destroyHalfEntry(at bucket: Int) {
2291+
_sanityCheck(isInitializedEntry(at: bucket))
2292+
defer { _fixLifetime(self) }
2293+
(keys + bucket).deinitialize(count: 1)
2294+
_storage.initializedEntries[bucket] = false
2295+
}
2296+
22272297
@usableFromInline @_transparent
22282298
internal func initializeKey(_ k: Key, value v: Value, at i: Int) {
22292299
_sanityCheck(!isInitializedEntry(at: i))
@@ -2488,6 +2558,13 @@ extension _NativeDictionary where Key: Hashable {
24882558

24892559
// remove the element
24902560
destroyEntry(at: bucket)
2561+
_deleteDestroyed(idealBucket: idealBucket, bucket: bucket)
2562+
}
2563+
2564+
@inlinable // FIXME(sil-serialize-all)
2565+
internal mutating func _deleteDestroyed(idealBucket: Int, bucket: Int) {
2566+
_sanityCheck(!isInitializedEntry(at: bucket), "expected initialized entry")
2567+
24912568
self.count -= 1
24922569

24932570
// If we've put a hole in a chain of contiguous elements, some
@@ -3138,14 +3215,26 @@ extension Dictionary._Variant: _DictionaryBuffer {
31383215
internal mutating func _ensureUniqueNative(
31393216
withBucketCount desiredBucketCount: Int
31403217
) -> (reallocated: Bool, capacityChanged: Bool) {
3141-
let oldBucketCount = asNative.bucketCount
31423218
let isUnique = isUniquelyReferenced()
3219+
return _ensureUniqueNative(
3220+
withBucketCount: desiredBucketCount,
3221+
isUnique: isUnique)
3222+
}
3223+
3224+
@inline(__always)
3225+
@inlinable // FIXME(sil-serialize-all)
3226+
internal mutating func _ensureUniqueNative(
3227+
withBucketCount desiredBucketCount: Int,
3228+
isUnique: Bool
3229+
) -> (reallocated: Bool, capacityChanged: Bool) {
3230+
let oldBucketCount = asNative.bucketCount
31433231
if oldBucketCount >= desiredBucketCount && isUnique {
31443232
return (reallocated: false, capacityChanged: false)
31453233
}
31463234

31473235
let oldDictionary = asNative
3148-
var newDictionary = _NativeDictionary<Key, Value>(bucketCount: desiredBucketCount)
3236+
var newDictionary = _NativeDictionary<Key, Value>(
3237+
bucketCount: desiredBucketCount)
31493238
let newBucketCount = newDictionary.bucketCount
31503239
for i in 0..<oldBucketCount {
31513240
if oldDictionary.isInitializedEntry(at: i) {
@@ -3164,7 +3253,9 @@ extension Dictionary._Variant: _DictionaryBuffer {
31643253
newDictionary.count = oldDictionary.count
31653254

31663255
self = .native(newDictionary)
3167-
return (reallocated: true, capacityChanged: oldBucketCount != newBucketCount)
3256+
return (
3257+
reallocated: true,
3258+
capacityChanged: oldBucketCount != newBucketCount)
31683259
}
31693260

31703261
@inline(__always)
@@ -3178,6 +3269,18 @@ extension Dictionary._Variant: _DictionaryBuffer {
31783269
return ensureUniqueNative(withBucketCount: bucketCount)
31793270
}
31803271

3272+
@inline(__always)
3273+
@inlinable // FIXME(sil-serialize-all)
3274+
internal mutating func ensureUniqueNative(
3275+
withCapacity minimumCapacity: Int,
3276+
isUnique: Bool
3277+
) -> (reallocated: Bool, capacityChanged: Bool) {
3278+
let bucketCount = _NativeDictionary<Key, Value>.bucketCount(
3279+
forCapacity: minimumCapacity,
3280+
maxLoadFactorInverse: _hashContainerDefaultMaxLoadFactorInverse)
3281+
return _ensureUniqueNative(withBucketCount: bucketCount, isUnique: isUnique)
3282+
}
3283+
31813284
/// Ensure this we hold a unique reference to a native dictionary
31823285
/// having at least `minimumCapacity` elements.
31833286
@inlinable // FIXME(sil-serialize-all)

0 commit comments

Comments
 (0)