Skip to content

Commit 900c930

Browse files
committed
[stdlib] Turn _CocoaDictionary.Index into a COW value type with opaque storage
1 parent eb69682 commit 900c930

File tree

2 files changed

+66
-46
lines changed

2 files changed

+66
-46
lines changed

stdlib/public/core/Dictionary.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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: 65 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -454,33 +454,42 @@ extension _CocoaDictionary: _DictionaryBuffer {
454454
internal var startIndex: Index {
455455
@_effects(releasenone)
456456
get {
457-
return Index(self, startIndex: ())
457+
let allKeys = _stdlib_NSDictionary_allKeys(self.object)
458+
return Index(Index.Storage(self, allKeys, 0))
458459
}
459460
}
460461

461462
@usableFromInline // FIXME(cocoa-index): Should be inlinable
462463
internal var endIndex: Index {
463464
@_effects(releasenone)
464465
get {
465-
return Index(self, endIndex: ())
466+
let allKeys = _stdlib_NSDictionary_allKeys(self.object)
467+
return Index(Index.Storage(self, allKeys, allKeys.value))
466468
}
467469
}
468470

469471
@usableFromInline // FIXME(cocoa-index): Should be inlinable
470472
@_effects(releasenone)
471-
internal func index(after i: Index) -> Index {
472-
var i = i
473-
formIndex(after: &i)
474-
return i
473+
internal func index(after index: Index) -> Index {
474+
var result = index
475+
formIndex(after: &result)
476+
return result
477+
}
478+
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")
475483
}
476484

477485
@usableFromInline // FIXME(cocoa-index): Should be inlinable
478486
@_effects(releasenone)
479-
internal func formIndex(after i: inout Index) {
480-
_precondition(i.base.object === self.object, "Invalid index")
481-
_precondition(i.currentKeyIndex < i.allKeys.value,
482-
"Cannot increment endIndex")
483-
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
484493
}
485494

486495
@usableFromInline // FIXME(cocoa-index): Should be inlinable
@@ -495,16 +504,13 @@ extension _CocoaDictionary: _DictionaryBuffer {
495504
}
496505

497506
let allKeys = _stdlib_NSDictionary_allKeys(object)
498-
var keyIndex = -1
499507
for i in 0..<allKeys.value {
500508
if _stdlib_NSObject_isEqual(key, allKeys[i]) {
501-
keyIndex = i
502-
break
509+
return Index(Index.Storage(self, allKeys, i))
503510
}
504511
}
505-
_sanityCheck(keyIndex >= 0,
506-
"Key was found in fast path, but not found later?")
507-
return Index(self, allKeys, keyIndex)
512+
_sanityCheckFailure(
513+
"An NSDictionary key wassn't listed amongst its enumerated contents")
508514
}
509515

510516
@inlinable
@@ -527,25 +533,25 @@ extension _CocoaDictionary: _DictionaryBuffer {
527533
@usableFromInline // FIXME(cocoa-index): Should be inlinable
528534
@_effects(releasenone)
529535
internal func lookup(_ index: Index) -> (key: Key, value: Value) {
530-
_precondition(index.base.object === self.object, "Invalid index")
531-
let key: Key = index.allKeys[index.currentKeyIndex]
532-
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)!
533539
return (key, value)
534540
}
535541

536542
@usableFromInline // FIXME(cocoa-index): Make inlinable
537543
@_effects(releasenone)
538544
func key(at index: Index) -> Key {
539-
_precondition(index.base.object === self.object, "Invalid index")
545+
_precondition(index.storage.base.object === self.object, "Invalid index")
540546
return index.key
541547
}
542548

543549
@usableFromInline // FIXME(cocoa-index): Make inlinable
544550
@_effects(releasenone)
545551
func value(at index: Index) -> Value {
546-
_precondition(index.base.object === self.object, "Invalid index")
547-
let key = index.allKeys[index.currentKeyIndex]
548-
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)!
549555
}
550556
}
551557

@@ -565,10 +571,23 @@ extension _CocoaDictionary {
565571
}
566572

567573
extension _CocoaDictionary {
568-
// FIXME(cocoa-index): Overhaul and make @_fixed_layout
574+
@_fixed_layout
569575
@usableFromInline
570576
internal struct Index {
571-
// 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
572591
// repeatedly called on the same NSDictionary, returning items in the same
573592
// order every time.
574593
// Similarly, the same assumption holds for NSSet.allObjects.
@@ -586,18 +605,6 @@ extension _CocoaDictionary {
586605
/// Index into `allKeys`
587606
internal var currentKeyIndex: Int
588607

589-
internal init(_ base: __owned _CocoaDictionary, startIndex: ()) {
590-
self.base = base
591-
self.allKeys = _stdlib_NSDictionary_allKeys(base.object)
592-
self.currentKeyIndex = 0
593-
}
594-
595-
internal init(_ base: __owned _CocoaDictionary, endIndex: ()) {
596-
self.base = base
597-
self.allKeys = _stdlib_NSDictionary_allKeys(base.object)
598-
self.currentKeyIndex = allKeys.value
599-
}
600-
601608
internal init(
602609
_ base: __owned _CocoaDictionary,
603610
_ allKeys: __owned _HeapBuffer<Int, AnyObject>,
@@ -610,15 +617,28 @@ extension _CocoaDictionary {
610617
}
611618
}
612619

620+
extension _CocoaDictionary.Index {
621+
@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+
613633
extension _CocoaDictionary.Index {
614634
@usableFromInline // FIXME(cocoa-index): Make inlinable
615635
@nonobjc
616636
internal var key: AnyObject {
617637
@_effects(readonly)
618638
get {
619-
_precondition(currentKeyIndex < allKeys.value,
639+
_precondition(storage.currentKeyIndex < storage.allKeys.value,
620640
"Attempting to access Dictionary elements using an invalid index")
621-
return allKeys[currentKeyIndex]
641+
return storage.allKeys[storage.currentKeyIndex]
622642
}
623643
}
624644

@@ -627,7 +647,7 @@ extension _CocoaDictionary.Index {
627647
internal var age: Int32 {
628648
@_effects(readonly)
629649
get {
630-
return _HashTable.age(for: base.object)
650+
return _HashTable.age(for: storage.base.object)
631651
}
632652
}
633653
}
@@ -639,9 +659,9 @@ extension _CocoaDictionary.Index: Equatable {
639659
lhs: _CocoaDictionary.Index,
640660
rhs: _CocoaDictionary.Index
641661
) -> Bool {
642-
_precondition(lhs.base.object === rhs.base.object,
662+
_precondition(lhs.storage.base.object === rhs.storage.base.object,
643663
"Comparing indexes from different dictionaries")
644-
return lhs.currentKeyIndex == rhs.currentKeyIndex
664+
return lhs.storage.currentKeyIndex == rhs.storage.currentKeyIndex
645665
}
646666
}
647667

@@ -652,9 +672,9 @@ extension _CocoaDictionary.Index: Comparable {
652672
lhs: _CocoaDictionary.Index,
653673
rhs: _CocoaDictionary.Index
654674
) -> Bool {
655-
_precondition(lhs.base.object === rhs.base.object,
675+
_precondition(lhs.storage.base.object === rhs.storage.base.object,
656676
"Comparing indexes from different dictionaries")
657-
return lhs.currentKeyIndex < rhs.currentKeyIndex
677+
return lhs.storage.currentKeyIndex < rhs.storage.currentKeyIndex
658678
}
659679
}
660680

0 commit comments

Comments
 (0)