@@ -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