Skip to content

Commit eb69682

Browse files
committed
[stdlib] Turn _CocoaSet.Index into a COW value type with opaque storage
This enables making the struct @_fixed_layout, which should win us back some of the performance lost by hiding Cocoa indexing internals.
1 parent cceb389 commit eb69682

File tree

2 files changed

+59
-38
lines changed

2 files changed

+59
-38
lines changed

stdlib/public/core/Set.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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)

stdlib/public/core/SetBridging.swift

Lines changed: 58 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -328,33 +328,43 @@ extension _CocoaSet: _SetBuffer {
328328
internal var startIndex: Index {
329329
@_effects(releasenone)
330330
get {
331-
return Index(self, startIndex: ())
331+
let allKeys = _stdlib_NSSet_allObjects(self.object)
332+
return Index(Index.Storage(self, allKeys, 0))
332333
}
333334
}
334335

335336
@usableFromInline // FIXME(cocoa-index): Should be inlinable
336337
internal var endIndex: Index {
337338
@_effects(releasenone)
338339
get {
339-
return Index(self, endIndex: ())
340+
let allKeys = _stdlib_NSSet_allObjects(self.object)
341+
return Index(Index.Storage(self, allKeys, allKeys.value))
340342
}
341343
}
342344

343345
@usableFromInline // FIXME(cocoa-index): Should be inlinable
344346
@_effects(releasenone)
345-
internal func index(after i: Index) -> Index {
346-
var i = i
347-
formIndex(after: &i)
348-
return i
347+
internal func index(after index: Index) -> Index {
348+
var result = index
349+
formIndex(after: &result)
350+
return result
351+
}
352+
353+
internal func validate(_ index: Index) {
354+
_precondition(index.storage.base.object === self.object,
355+
"Invalid index")
356+
_precondition(index.storage.currentKeyIndex < index.storage.allKeys.value,
357+
"Attempt to access endIndex")
349358
}
350359

351360
@usableFromInline // FIXME(cocoa-index): Should be inlinable
352361
@_effects(releasenone)
353-
internal func formIndex(after i: inout Index) {
354-
_precondition(i.base.object === self.object, "Invalid index")
355-
_precondition(i.currentKeyIndex < i.allKeys.value,
356-
"Cannot increment endIndex")
357-
i.currentKeyIndex += 1
362+
internal func formIndex(after index: inout Index) {
363+
validate(index)
364+
let isUnique = index.isUniquelyReferenced()
365+
if !isUnique { index.storage = index.copy() }
366+
let storage = index.storage // FIXME: rdar://problem/44863751
367+
storage.currentKeyIndex += 1
358368
}
359369

360370
@usableFromInline // FIXME(cocoa-index): Should be inlinable
@@ -369,16 +379,13 @@ extension _CocoaSet: _SetBuffer {
369379
}
370380

371381
let allKeys = _stdlib_NSSet_allObjects(object)
372-
var keyIndex = -1
373382
for i in 0..<allKeys.value {
374383
if _stdlib_NSObject_isEqual(element, allKeys[i]) {
375-
keyIndex = i
376-
break
384+
return Index(Index.Storage(self, allKeys, i))
377385
}
378386
}
379-
_sanityCheck(keyIndex >= 0,
380-
"Key was found in fast path, but not found later?")
381-
return Index(self, allKeys, keyIndex)
387+
_sanityCheckFailure(
388+
"An NSSet member wasn't listed amongst its enumerated contents")
382389
}
383390

384391
@inlinable
@@ -401,9 +408,22 @@ extension _CocoaSet: _SetBuffer {
401408
}
402409

403410
extension _CocoaSet {
404-
// FIXME(cocoa-index): Overhaul and make @_fixed_layout
411+
@_fixed_layout
405412
@usableFromInline
406413
internal struct Index {
414+
@usableFromInline
415+
internal var storage: Storage
416+
417+
internal init(_ storage: __owned Storage) {
418+
self.storage = storage
419+
}
420+
}
421+
}
422+
423+
extension _CocoaSet.Index {
424+
// FIXME(cocoa-index): Try using an NSEnumerator to speed this up.
425+
@usableFromInline
426+
internal class Storage {
407427
// Assumption: we rely on NSDictionary.getObjects when being
408428
// repeatedly called on the same NSDictionary, returning items in the same
409429
// order every time.
@@ -422,18 +442,6 @@ extension _CocoaSet {
422442
/// Index into `allKeys`
423443
internal var currentKeyIndex: Int
424444

425-
internal init(_ base: __owned _CocoaSet, startIndex: ()) {
426-
self.base = base
427-
self.allKeys = _stdlib_NSSet_allObjects(base.object)
428-
self.currentKeyIndex = 0
429-
}
430-
431-
internal init(_ base: __owned _CocoaSet, endIndex: ()) {
432-
self.base = base
433-
self.allKeys = _stdlib_NSSet_allObjects(base.object)
434-
self.currentKeyIndex = allKeys.value
435-
}
436-
437445
internal init(
438446
_ base: __owned _CocoaSet,
439447
_ allKeys: __owned _HeapBuffer<Int, AnyObject>,
@@ -446,15 +454,28 @@ extension _CocoaSet {
446454
}
447455
}
448456

457+
extension _CocoaSet.Index {
458+
@inlinable
459+
internal mutating func isUniquelyReferenced() -> Bool {
460+
return _isUnique_native(&storage)
461+
}
462+
463+
@usableFromInline
464+
internal mutating func copy() -> Storage {
465+
let storage = self.storage
466+
return Storage(storage.base, storage.allKeys, storage.currentKeyIndex)
467+
}
468+
}
469+
449470
extension _CocoaSet.Index {
450471
@usableFromInline // FIXME(cocoa-index): Make inlinable
451472
@nonobjc
452473
internal var element: AnyObject {
453474
@_effects(readonly)
454475
get {
455-
_precondition(currentKeyIndex < allKeys.value,
476+
_precondition(storage.currentKeyIndex < storage.allKeys.value,
456477
"Attempting to access Set elements using an invalid index")
457-
return allKeys[currentKeyIndex]
478+
return storage.allKeys[storage.currentKeyIndex]
458479
}
459480
}
460481

@@ -463,7 +484,7 @@ extension _CocoaSet.Index {
463484
internal var age: Int32 {
464485
@_effects(releasenone)
465486
get {
466-
return _HashTable.age(for: base.object)
487+
return _HashTable.age(for: storage.base.object)
467488
}
468489
}
469490
}
@@ -472,19 +493,19 @@ extension _CocoaSet.Index: Equatable {
472493
@usableFromInline // FIXME(cocoa-index): Make inlinable
473494
@_effects(readonly)
474495
internal static func == (lhs: _CocoaSet.Index, rhs: _CocoaSet.Index) -> Bool {
475-
_precondition(lhs.base.object === rhs.base.object,
496+
_precondition(lhs.storage.base.object === rhs.storage.base.object,
476497
"Comparing indexes from different sets")
477-
return lhs.currentKeyIndex == rhs.currentKeyIndex
498+
return lhs.storage.currentKeyIndex == rhs.storage.currentKeyIndex
478499
}
479500
}
480501

481502
extension _CocoaSet.Index: Comparable {
482503
@usableFromInline // FIXME(cocoa-index): Make inlinable
483504
@_effects(readonly)
484505
internal static func < (lhs: _CocoaSet.Index, rhs: _CocoaSet.Index) -> Bool {
485-
_precondition(lhs.base.object === rhs.base.object,
506+
_precondition(lhs.storage.base.object === rhs.storage.base.object,
486507
"Comparing indexes from different sets")
487-
return lhs.currentKeyIndex < rhs.currentKeyIndex
508+
return lhs.storage.currentKeyIndex < rhs.storage.currentKeyIndex
488509
}
489510
}
490511

0 commit comments

Comments
 (0)