Skip to content

Commit 3d7d3bd

Browse files
lorenteyairspeedswift
authored andcommitted
[5.0][stdlib] Support throwing yields in all _modify accessors (#21171)
* [stdlib] Refactor Dictionary.subscript._modify for better layering This may also reduce ARC traffic by a little bit. (cherry picked from commit d32fd28) * [stdlib] _modify: Use defer to ensure invariants are restored when yield throws _modify mustn’t leave Collection storage in an inconsistent state when the code to which we’re yielding happens to throw. In practice this means that any cleanup code must happen in defer blocks before the yield. Fix Dictionary to do just that and add tests to cover this functionality (as well as some other aspects of _modify). (cherry picked from commit ce96f1e) # Conflicts: # validation-test/stdlib/Dictionary.swift
1 parent 8be4950 commit 3d7d3bd

File tree

6 files changed

+510
-62
lines changed

6 files changed

+510
-62
lines changed

stdlib/public/core/Dictionary.swift

Lines changed: 8 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -794,45 +794,8 @@ extension Dictionary {
794794
}
795795
}
796796
_modify {
797-
// FIXME: This code should be moved to _variant, with Dictionary.subscript
798-
// yielding `&_variant[key]`.
799-
800-
let (bucket, found) = _variant.mutatingFind(key)
801-
802-
// FIXME: Mark this entry as being modified in hash table metadata
803-
// so that lldb can recognize it's not valid.
804-
805-
// Move the old value (if any) out of storage, wrapping it into an
806-
// optional before yielding it.
807-
let native = _variant.asNative
808-
if found {
809-
var value: Value? = (native._values + bucket.offset).move()
810-
yield &value
811-
if let value = value {
812-
// **Mutation**
813-
//
814-
// Initialize storage to new value.
815-
(native._values + bucket.offset).initialize(to: value)
816-
} else {
817-
// **Removal**
818-
//
819-
// We've already deinitialized the value; deinitialize the key too and
820-
// register the removal.
821-
(native._keys + bucket.offset).deinitialize(count: 1)
822-
native._delete(at: bucket)
823-
}
824-
} else {
825-
var value: Value? = nil
826-
yield &value
827-
if let value = value {
828-
// **Insertion**
829-
//
830-
// Insert the new entry at the correct place. Note that
831-
// `mutatingFind` already ensured that we have enough capacity.
832-
native._insert(at: bucket, key: key, value: value)
833-
}
834-
}
835-
_fixLifetime(self)
797+
defer { _fixLifetime(self) }
798+
yield &_variant[key]
836799
}
837800
}
838801
}
@@ -935,8 +898,8 @@ extension Dictionary {
935898
native._insert(at: bucket, key: key, value: value)
936899
}
937900
let address = native._values + bucket.offset
901+
defer { _fixLifetime(self) }
938902
yield &address.pointee
939-
_fixLifetime(self)
940903
}
941904
}
942905

@@ -1309,14 +1272,10 @@ extension Dictionary {
13091272
return Values(_dictionary: self)
13101273
}
13111274
_modify {
1312-
var values: Values
1313-
do {
1314-
var temp = _Variant(dummy: ())
1315-
swap(&temp, &_variant)
1316-
values = Values(_variant: temp)
1317-
}
1275+
var values = Values(_variant: _Variant(dummy: ()))
1276+
swap(&values._variant, &_variant)
1277+
defer { self._variant = values._variant }
13181278
yield &values
1319-
self._variant = values._variant
13201279
}
13211280
}
13221281

@@ -1499,8 +1458,8 @@ extension Dictionary {
14991458
let native = _variant.ensureUniqueNative()
15001459
let bucket = native.validatedBucket(for: position)
15011460
let address = native._values + bucket.offset
1461+
defer { _fixLifetime(self) }
15021462
yield &address.pointee
1503-
_fixLifetime(self)
15041463
}
15051464
}
15061465

@@ -1883,8 +1842,8 @@ extension Dictionary.Index {
18831842
}
18841843
let dummy = _HashTable.Index(bucket: _HashTable.Bucket(offset: 0), age: 0)
18851844
_variant = .native(dummy)
1845+
defer { _variant = .cocoa(cocoa) }
18861846
yield &cocoa
1887-
_variant = .cocoa(cocoa)
18881847
}
18891848
}
18901849
#endif

stdlib/public/core/DictionaryVariant.swift

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ extension Dictionary._Variant {
9595
_modify {
9696
var native = _NativeDictionary<Key, Value>(object.unflaggedNativeInstance)
9797
self = .init(dummy: ())
98+
defer { object = .init(native: native._storage) }
9899
yield &native
99-
object = .init(native: native._storage)
100100
}
101101
}
102102

@@ -278,6 +278,31 @@ extension Dictionary._Variant: _DictionaryBuffer {
278278
}
279279
}
280280

281+
extension Dictionary._Variant {
282+
@inlinable
283+
internal subscript(key: Key) -> Value? {
284+
@inline(__always)
285+
get {
286+
return lookup(key)
287+
}
288+
@inline(__always)
289+
_modify {
290+
#if _runtime(_ObjC)
291+
guard isNative else {
292+
let cocoa = asCocoa
293+
var native = _NativeDictionary<Key, Value>(
294+
cocoa, capacity: cocoa.count + 1)
295+
self = .init(native: native)
296+
yield &native[key, isUnique: true]
297+
return
298+
}
299+
#endif
300+
let isUnique = isUniquelyReferenced()
301+
yield &asNative[key, isUnique: isUnique]
302+
}
303+
}
304+
}
305+
281306
extension Dictionary._Variant {
282307
/// Same as find(_:), except assume a corresponding key/value pair will be
283308
/// inserted if it doesn't already exist, and mutated if it does exist. When

stdlib/public/core/NativeDictionary.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,52 @@ extension _NativeDictionary: _DictionaryBuffer {
387387
}
388388
}
389389

390+
extension _NativeDictionary {
391+
@inlinable
392+
subscript(key: Key, isUnique isUnique: Bool) -> Value? {
393+
@inline(__always)
394+
get {
395+
// Dummy definition; don't use.
396+
return lookup(key)
397+
}
398+
@inline(__always)
399+
_modify {
400+
let (bucket, found) = mutatingFind(key, isUnique: isUnique)
401+
if found {
402+
// Move the old value out of storage, wrapping it into an optional
403+
// before yielding it.
404+
var value: Value? = (_values + bucket.offset).move()
405+
defer {
406+
// This is in a defer block because yield might throw, and we need to
407+
// preserve Dictionary's storage invariants when that happens.
408+
if let value = value {
409+
// **Mutation.** Initialize storage to new value.
410+
(_values + bucket.offset).initialize(to: value)
411+
} else {
412+
// **Removal.** We've already deinitialized the value; deinitialize
413+
// the key too and register the removal.
414+
(_keys + bucket.offset).deinitialize(count: 1)
415+
_delete(at: bucket)
416+
}
417+
}
418+
yield &value
419+
} else {
420+
var value: Value? = nil
421+
defer {
422+
// This is in a defer block because yield might throw, and we need to
423+
// preserve Dictionary invariants when that happens.
424+
if let value = value {
425+
// **Insertion.** Insert the new entry at the correct place. Note
426+
// that `mutatingFind` already ensured that we have enough capacity.
427+
_insert(at: bucket, key: key, value: value)
428+
}
429+
}
430+
yield &value
431+
}
432+
}
433+
}
434+
}
435+
390436
// This function has a highly visible name to make it stand out in stack traces.
391437
@usableFromInline
392438
@inline(never)

stdlib/public/core/Set.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1367,8 +1367,8 @@ extension Set.Index {
13671367
}
13681368
let dummy = _HashTable.Index(bucket: _HashTable.Bucket(offset: 0), age: 0)
13691369
_variant = .native(dummy)
1370+
defer { _variant = .cocoa(cocoa) }
13701371
yield &cocoa
1371-
_variant = .cocoa(cocoa)
13721372
}
13731373
}
13741374
#endif

stdlib/public/core/SetVariant.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,12 @@ extension Set._Variant {
9292
_modify {
9393
var native = _NativeSet<Element>(object.unflaggedNativeInstance)
9494
self = .init(dummy: ())
95+
defer {
96+
// This is in a defer block because yield might throw, and we need to
97+
// preserve Set's storage invariants when that happens.
98+
object = .init(native: native._storage)
99+
}
95100
yield &native
96-
object = .init(native: native._storage)
97101
}
98102
}
99103

0 commit comments

Comments
 (0)