Skip to content

Commit bb36ddf

Browse files
authored
Merge pull request #19599 from lorentey/opaque-cocoa-indices
[stdlib] Set, Dictionary: Make Cocoa indices resilient for now
2 parents 2b67b0c + 900c930 commit bb36ddf

File tree

4 files changed

+180
-124
lines changed

4 files changed

+180
-124
lines changed

stdlib/public/core/Dictionary.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1897,7 +1897,7 @@ extension Dictionary.Index: Comparable {
18971897
}
18981898

18991899
extension Dictionary.Index: Hashable {
1900-
@inlinable
1900+
@_effects(readonly) // FIXME(cocoa-index): Make inlinable
19011901
public func hash(into hasher: inout Hasher) {
19021902
#if _runtime(_ObjC)
19031903
switch _variant {
@@ -1907,7 +1907,7 @@ extension Dictionary.Index: Hashable {
19071907
case .cocoa(let cocoaIndex):
19081908
_cocoaPath()
19091909
hasher.combine(1 as UInt8)
1910-
hasher.combine(cocoaIndex.currentKeyIndex)
1910+
hasher.combine(cocoaIndex.storage.currentKeyIndex)
19111911
}
19121912
#else
19131913
hasher.combine(_asNative.bucket.offset)

stdlib/public/core/DictionaryBridging.swift

Lines changed: 94 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -450,33 +450,50 @@ extension _CocoaDictionary: _DictionaryBuffer {
450450
@usableFromInline
451451
internal typealias Value = AnyObject
452452

453-
@inlinable
453+
@usableFromInline // FIXME(cocoa-index): Should be inlinable
454454
internal var startIndex: Index {
455-
return Index(self, startIndex: ())
455+
@_effects(releasenone)
456+
get {
457+
let allKeys = _stdlib_NSDictionary_allKeys(self.object)
458+
return Index(Index.Storage(self, allKeys, 0))
459+
}
456460
}
457461

458-
@inlinable
462+
@usableFromInline // FIXME(cocoa-index): Should be inlinable
459463
internal var endIndex: Index {
460-
return Index(self, endIndex: ())
464+
@_effects(releasenone)
465+
get {
466+
let allKeys = _stdlib_NSDictionary_allKeys(self.object)
467+
return Index(Index.Storage(self, allKeys, allKeys.value))
468+
}
461469
}
462470

463-
@inlinable
464-
internal func index(after i: Index) -> Index {
465-
var i = i
466-
formIndex(after: &i)
467-
return i
471+
@usableFromInline // FIXME(cocoa-index): Should be inlinable
472+
@_effects(releasenone)
473+
internal func index(after index: Index) -> Index {
474+
var result = index
475+
formIndex(after: &result)
476+
return result
468477
}
469478

470-
@usableFromInline
479+
internal func validate(_ index: Index) {
480+
_precondition(index.storage.base.object === self.object, "Invalid index")
481+
_precondition(index.storage.currentKeyIndex < index.storage.allKeys.value,
482+
"Attempt to access endIndex")
483+
}
484+
485+
@usableFromInline // FIXME(cocoa-index): Should be inlinable
471486
@_effects(releasenone)
472-
internal func formIndex(after i: inout Index) {
473-
_precondition(i.base.object === self.object, "Invalid index")
474-
_precondition(i.currentKeyIndex < i.allKeys.value,
475-
"Cannot increment endIndex")
476-
i.currentKeyIndex += 1
487+
internal func formIndex(after index: inout Index) {
488+
validate(index)
489+
let isUnique = index.isUniquelyReferenced()
490+
if !isUnique { index.storage = index.copy() }
491+
let storage = index.storage // FIXME: rdar://problem/44863751
492+
storage.currentKeyIndex += 1
477493
}
478494

479-
@usableFromInline
495+
@usableFromInline // FIXME(cocoa-index): Should be inlinable
496+
@_effects(releasenone)
480497
internal func index(forKey key: Key) -> Index? {
481498
// Fast path that does not involve creating an array of all keys. In case
482499
// the key is present, this lookup is a penalty for the slow path, but the
@@ -487,16 +504,13 @@ extension _CocoaDictionary: _DictionaryBuffer {
487504
}
488505

489506
let allKeys = _stdlib_NSDictionary_allKeys(object)
490-
var keyIndex = -1
491507
for i in 0..<allKeys.value {
492508
if _stdlib_NSObject_isEqual(key, allKeys[i]) {
493-
keyIndex = i
494-
break
509+
return Index(Index.Storage(self, allKeys, i))
495510
}
496511
}
497-
_sanityCheck(keyIndex >= 0,
498-
"Key was found in fast path, but not found later?")
499-
return Index(self, allKeys, keyIndex)
512+
_sanityCheckFailure(
513+
"An NSDictionary key wassn't listed amongst its enumerated contents")
500514
}
501515

502516
@inlinable
@@ -516,28 +530,28 @@ extension _CocoaDictionary: _DictionaryBuffer {
516530
return object.object(forKey: key)
517531
}
518532

519-
@inlinable
520-
@inline(__always)
533+
@usableFromInline // FIXME(cocoa-index): Should be inlinable
534+
@_effects(releasenone)
521535
internal func lookup(_ index: Index) -> (key: Key, value: Value) {
522-
_precondition(index.base.object === self.object, "Invalid index")
523-
let key: Key = index.allKeys[index.currentKeyIndex]
524-
let value: Value = index.base.object.object(forKey: key)!
536+
_precondition(index.storage.base.object === self.object, "Invalid index")
537+
let key: Key = index.storage.allKeys[index.storage.currentKeyIndex]
538+
let value: Value = index.storage.base.object.object(forKey: key)!
525539
return (key, value)
526540
}
527541

528-
@inlinable
529-
@inline(__always)
542+
@usableFromInline // FIXME(cocoa-index): Make inlinable
543+
@_effects(releasenone)
530544
func key(at index: Index) -> Key {
531-
_precondition(index.base.object === self.object, "Invalid index")
545+
_precondition(index.storage.base.object === self.object, "Invalid index")
532546
return index.key
533547
}
534548

535-
@inlinable
536-
@inline(__always)
549+
@usableFromInline // FIXME(cocoa-index): Make inlinable
550+
@_effects(releasenone)
537551
func value(at index: Index) -> Value {
538-
_precondition(index.base.object === self.object, "Invalid index")
539-
let key = index.allKeys[index.currentKeyIndex]
540-
return index.base.object.object(forKey: key)!
552+
_precondition(index.storage.base.object === self.object, "Invalid index")
553+
let key = index.storage.allKeys[index.storage.currentKeyIndex]
554+
return index.storage.base.object.object(forKey: key)!
541555
}
542556
}
543557

@@ -557,45 +571,40 @@ extension _CocoaDictionary {
557571
}
558572

559573
extension _CocoaDictionary {
560-
@_fixed_layout // FIXME(sil-serialize-all)
574+
@_fixed_layout
561575
@usableFromInline
562576
internal struct Index {
563-
// Assumption: we rely on NSDictionary.getObjects when being
577+
@usableFromInline
578+
internal var storage: Storage
579+
580+
internal init(_ storage: Storage) {
581+
self.storage = storage
582+
}
583+
}
584+
}
585+
586+
extension _CocoaDictionary.Index {
587+
// FIXME(cocoa-index): Try using an NSEnumerator to speed this up.
588+
@usableFromInline
589+
internal class Storage {
590+
// Assumption: we rely on NSDictionary.getObjects when being
564591
// repeatedly called on the same NSDictionary, returning items in the same
565592
// order every time.
566593
// Similarly, the same assumption holds for NSSet.allObjects.
567594

568595
/// A reference to the NSDictionary, which owns members in `allObjects`,
569596
/// or `allKeys`, for NSSet and NSDictionary respectively.
570-
@usableFromInline // FIXME(sil-serialize-all)
571597
internal let base: _CocoaDictionary
572598
// FIXME: swift-3-indexing-model: try to remove the cocoa reference, but
573599
// make sure that we have a safety check for accessing `allKeys`. Maybe
574600
// move both into the dictionary/set itself.
575601

576602
/// An unowned array of keys.
577-
@usableFromInline // FIXME(sil-serialize-all)
578603
internal var allKeys: _HeapBuffer<Int, AnyObject>
579604

580605
/// Index into `allKeys`
581-
@usableFromInline // FIXME(sil-serialize-all)
582606
internal var currentKeyIndex: Int
583607

584-
@inlinable // FIXME(sil-serialize-all)
585-
internal init(_ base: __owned _CocoaDictionary, startIndex: ()) {
586-
self.base = base
587-
self.allKeys = _stdlib_NSDictionary_allKeys(base.object)
588-
self.currentKeyIndex = 0
589-
}
590-
591-
@inlinable // FIXME(sil-serialize-all)
592-
internal init(_ base: __owned _CocoaDictionary, endIndex: ()) {
593-
self.base = base
594-
self.allKeys = _stdlib_NSDictionary_allKeys(base.object)
595-
self.currentKeyIndex = allKeys.value
596-
}
597-
598-
@inlinable // FIXME(sil-serialize-all)
599608
internal init(
600609
_ base: __owned _CocoaDictionary,
601610
_ allKeys: __owned _HeapBuffer<Int, AnyObject>,
@@ -610,44 +619,62 @@ extension _CocoaDictionary {
610619

611620
extension _CocoaDictionary.Index {
612621
@inlinable
622+
internal mutating func isUniquelyReferenced() -> Bool {
623+
return _isUnique_native(&storage)
624+
}
625+
626+
@usableFromInline
627+
internal mutating func copy() -> Storage {
628+
let storage = self.storage
629+
return Storage(storage.base, storage.allKeys, storage.currentKeyIndex)
630+
}
631+
}
632+
633+
extension _CocoaDictionary.Index {
634+
@usableFromInline // FIXME(cocoa-index): Make inlinable
613635
@nonobjc
614636
internal var key: AnyObject {
615-
_precondition(currentKeyIndex < allKeys.value,
616-
"Attempting to access Dictionary elements using an invalid index")
617-
return allKeys[currentKeyIndex]
637+
@_effects(readonly)
638+
get {
639+
_precondition(storage.currentKeyIndex < storage.allKeys.value,
640+
"Attempting to access Dictionary elements using an invalid index")
641+
return storage.allKeys[storage.currentKeyIndex]
642+
}
618643
}
619644

620-
@usableFromInline
645+
@usableFromInline // FIXME(cocoa-index): Make inlinable
621646
@nonobjc
622647
internal var age: Int32 {
623-
@_effects(releasenone)
648+
@_effects(readonly)
624649
get {
625-
return _HashTable.age(for: base.object)
650+
return _HashTable.age(for: storage.base.object)
626651
}
627652
}
628653
}
629654

630655
extension _CocoaDictionary.Index: Equatable {
631-
@inlinable
656+
@usableFromInline // FIXME(cocoa-index): Make inlinable
657+
@_effects(readonly)
632658
internal static func == (
633659
lhs: _CocoaDictionary.Index,
634660
rhs: _CocoaDictionary.Index
635661
) -> Bool {
636-
_precondition(lhs.base.object === rhs.base.object,
662+
_precondition(lhs.storage.base.object === rhs.storage.base.object,
637663
"Comparing indexes from different dictionaries")
638-
return lhs.currentKeyIndex == rhs.currentKeyIndex
664+
return lhs.storage.currentKeyIndex == rhs.storage.currentKeyIndex
639665
}
640666
}
641667

642668
extension _CocoaDictionary.Index: Comparable {
643-
@inlinable
669+
@usableFromInline // FIXME(cocoa-index): Make inlinable
670+
@_effects(readonly)
644671
internal static func < (
645672
lhs: _CocoaDictionary.Index,
646673
rhs: _CocoaDictionary.Index
647674
) -> Bool {
648-
_precondition(lhs.base.object === rhs.base.object,
675+
_precondition(lhs.storage.base.object === rhs.storage.base.object,
649676
"Comparing indexes from different dictionaries")
650-
return lhs.currentKeyIndex < rhs.currentKeyIndex
677+
return lhs.storage.currentKeyIndex < rhs.storage.currentKeyIndex
651678
}
652679
}
653680

stdlib/public/core/Set.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,7 +1418,7 @@ extension Set.Index: Hashable {
14181418
///
14191419
/// - Parameter hasher: The hasher to use when combining the components
14201420
/// of this instance.
1421-
@inlinable
1421+
@_effects(readonly) // FIXME(cocoa-index): Make inlinable
14221422
public func hash(into hasher: inout Hasher) {
14231423
#if _runtime(_ObjC)
14241424
switch _variant {
@@ -1428,7 +1428,7 @@ extension Set.Index: Hashable {
14281428
case .cocoa(let cocoaIndex):
14291429
_cocoaPath()
14301430
hasher.combine(1 as UInt8)
1431-
hasher.combine(cocoaIndex.currentKeyIndex)
1431+
hasher.combine(cocoaIndex.storage.currentKeyIndex)
14321432
}
14331433
#else
14341434
hasher.combine(_asNative.bucket.offset)

0 commit comments

Comments
 (0)