@@ -785,6 +785,60 @@ 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. Otherwise this can only be a removal or an in-place
800+ // mutation. (If we guess wrong, we might needlessly rehash; that's fine.)
801+ let ( _, rehashed) = _variant. ensureUniqueNative (
802+ withCapacity: self . capacity + ( found ? 0 : 1 ) ,
803+ isUnique: isUnique)
804+ // FIXME: we should be able to make this a let; however, some of the
805+ // low-level operations below are (incorrectly) marked as mutating.
806+ var native = _variant. asNative
807+ if rehashed {
808+ // Key needs to be hashed again if storage has been resized.
809+ _sanityCheck ( !found)
810+ idealBucket = native. _bucket ( key)
811+ ( pos, found) = native. _find ( key, startBucket: idealBucket)
812+ _sanityCheck ( !found)
813+ }
814+ // FIXME: Mark this entry as being modified in hash table metadata
815+ // so that lldb can recognize it's not valid.
816+
817+ // Move the old value (if any) out of storage, wrapping it into an
818+ // optional before yielding it.
819+ var value : Value ? = found ? native. moveValue ( at: pos. bucket) : nil
820+ yield & value
821+
822+ // Value is now potentially different. Check which one of the four
823+ // possible cases apply.
824+ switch ( found, value != nil ) {
825+ case ( true , true ) : // Mutation
826+ // Initialize storage to new value.
827+ ( native. values + pos. bucket) . initialize ( to: value!)
828+ case ( true , false ) : // Removal
829+ // We've already removed the value; deinitialize and remove the key too.
830+ native. destroyHalfEntry ( at: pos. bucket)
831+ native. _deleteDestroyed ( idealBucket: idealBucket, bucket: pos. bucket)
832+ case ( false , true ) : // Insertion
833+ // Insert the new entry at the correct place.
834+ // We've already ensured we have enough capacity.
835+ native. initializeKey ( key, value: value!, at: pos. bucket)
836+ native. count += 1
837+ case ( false , false ) : // Noop
838+ // Easy!
839+ break
840+ }
841+ }
788842 }
789843}
790844
@@ -2369,6 +2423,21 @@ internal struct _NativeDictionary<Key, Value> {
23692423 _storage. initializedEntries [ i] = false
23702424 }
23712425
2426+ @inlinable
2427+ internal func moveValue( at bucket: Int ) -> Value {
2428+ defer { _fixLifetime ( self ) }
2429+ return ( values + bucket) . move ( )
2430+ }
2431+
2432+ // This assumes the value is already deinitialized.
2433+ @inlinable
2434+ internal func destroyHalfEntry( at bucket: Int ) {
2435+ _sanityCheck ( isInitializedEntry ( at: bucket) )
2436+ defer { _fixLifetime ( self ) }
2437+ ( keys + bucket) . deinitialize ( count: 1 )
2438+ _storage. initializedEntries [ bucket] = false
2439+ }
2440+
23722441 @usableFromInline @_transparent
23732442 internal func initializeKey( _ k: Key , value v: Value , at i: Int ) {
23742443 _sanityCheck ( !isInitializedEntry( at: i) )
@@ -2633,6 +2702,13 @@ extension _NativeDictionary where Key: Hashable {
26332702
26342703 // remove the element
26352704 destroyEntry ( at: bucket)
2705+ _deleteDestroyed ( idealBucket: idealBucket, bucket: bucket)
2706+ }
2707+
2708+ @inlinable // FIXME(sil-serialize-all)
2709+ internal mutating func _deleteDestroyed( idealBucket: Int , bucket: Int ) {
2710+ _sanityCheck ( !isInitializedEntry( at: bucket) , " expected initialized entry " )
2711+
26362712 self . count -= 1
26372713
26382714 // If we've put a hole in a chain of contiguous elements, some
@@ -3283,14 +3359,26 @@ extension Dictionary._Variant: _DictionaryBuffer {
32833359 internal mutating func _ensureUniqueNative(
32843360 withBucketCount desiredBucketCount: Int
32853361 ) -> ( reallocated: Bool , capacityChanged: Bool ) {
3286- let oldBucketCount = asNative. bucketCount
32873362 let isUnique = isUniquelyReferenced ( )
3363+ return _ensureUniqueNative (
3364+ withBucketCount: desiredBucketCount,
3365+ isUnique: isUnique)
3366+ }
3367+
3368+ @inline ( __always)
3369+ @inlinable // FIXME(sil-serialize-all)
3370+ internal mutating func _ensureUniqueNative(
3371+ withBucketCount desiredBucketCount: Int ,
3372+ isUnique: Bool
3373+ ) -> ( reallocated: Bool , capacityChanged: Bool ) {
3374+ let oldBucketCount = asNative. bucketCount
32883375 if oldBucketCount >= desiredBucketCount && isUnique {
32893376 return ( reallocated: false , capacityChanged: false )
32903377 }
32913378
32923379 let oldDictionary = asNative
3293- var newDictionary = _NativeDictionary < Key , Value > ( bucketCount: desiredBucketCount)
3380+ var newDictionary = _NativeDictionary < Key , Value > (
3381+ bucketCount: desiredBucketCount)
32943382 let newBucketCount = newDictionary. bucketCount
32953383 for i in 0 ..< oldBucketCount {
32963384 if oldDictionary. isInitializedEntry ( at: i) {
@@ -3309,7 +3397,9 @@ extension Dictionary._Variant: _DictionaryBuffer {
33093397 newDictionary. count = oldDictionary. count
33103398
33113399 self = . native( newDictionary)
3312- return ( reallocated: true , capacityChanged: oldBucketCount != newBucketCount)
3400+ return (
3401+ reallocated: true ,
3402+ capacityChanged: oldBucketCount != newBucketCount)
33133403 }
33143404
33153405 @inline ( __always)
@@ -3323,6 +3413,18 @@ extension Dictionary._Variant: _DictionaryBuffer {
33233413 return ensureUniqueNative ( withBucketCount: bucketCount)
33243414 }
33253415
3416+ @inline ( __always)
3417+ @inlinable // FIXME(sil-serialize-all)
3418+ internal mutating func ensureUniqueNative(
3419+ withCapacity minimumCapacity: Int ,
3420+ isUnique: Bool
3421+ ) -> ( reallocated: Bool , capacityChanged: Bool ) {
3422+ let bucketCount = _NativeDictionary < Key , Value > . bucketCount (
3423+ forCapacity: minimumCapacity,
3424+ maxLoadFactorInverse: _hashContainerDefaultMaxLoadFactorInverse)
3425+ return _ensureUniqueNative ( withBucketCount: bucketCount, isUnique: isUnique)
3426+ }
3427+
33263428 /// Ensure this we hold a unique reference to a native dictionary
33273429 /// having at least `minimumCapacity` elements.
33283430 @inlinable // FIXME(sil-serialize-all)
0 commit comments