Skip to content

Commit 637af2f

Browse files
authored
Merge pull request #27709 from Catfish-Man/bridge-to-the-future
Optimize accessing Swift arrays via ObjC
2 parents bfd9e0a + d9cef0a commit 637af2f

File tree

3 files changed

+106
-22
lines changed

3 files changed

+106
-22
lines changed

stdlib/public/core/ContiguousArrayBuffer.swift

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,97 @@ internal final class _ContiguousArrayStorage<
7979
}
8080

8181
#if _runtime(_ObjC)
82+
83+
internal final override func withUnsafeBufferOfObjects<R>(
84+
_ body: (UnsafeBufferPointer<AnyObject>) throws -> R
85+
) rethrows -> R {
86+
_internalInvariant(_isBridgedVerbatimToObjectiveC(Element.self))
87+
let count = countAndCapacity.count
88+
let elements = UnsafeRawPointer(_elementPointer)
89+
.assumingMemoryBound(to: AnyObject.self)
90+
defer { _fixLifetime(self) }
91+
return try body(UnsafeBufferPointer(start: elements, count: count))
92+
}
93+
94+
@objc(countByEnumeratingWithState:objects:count:)
95+
@_effects(releasenone)
96+
internal final override func countByEnumerating(
97+
with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>,
98+
objects: UnsafeMutablePointer<AnyObject>?, count: Int
99+
) -> Int {
100+
var enumerationState = state.pointee
101+
102+
if enumerationState.state != 0 {
103+
return 0
104+
}
105+
106+
return withUnsafeBufferOfObjects {
107+
objects in
108+
enumerationState.mutationsPtr = _fastEnumerationStorageMutationsPtr
109+
enumerationState.itemsPtr =
110+
AutoreleasingUnsafeMutablePointer(objects.baseAddress)
111+
enumerationState.state = 1
112+
state.pointee = enumerationState
113+
return objects.count
114+
}
115+
}
116+
117+
@inline(__always)
118+
@_effects(readonly)
119+
@nonobjc private func _objectAt(_ index: Int) -> Unmanaged<AnyObject> {
120+
return withUnsafeBufferOfObjects {
121+
objects in
122+
_precondition(
123+
_isValidArraySubscript(index, count: objects.count),
124+
"Array index out of range")
125+
return Unmanaged.passUnretained(objects[index])
126+
}
127+
}
128+
129+
@objc(objectAtIndexedSubscript:)
130+
@_effects(readonly)
131+
final override internal func objectAtSubscript(_ index: Int) -> Unmanaged<AnyObject> {
132+
return _objectAt(index)
133+
}
134+
135+
@objc(objectAtIndex:)
136+
@_effects(readonly)
137+
final override internal func objectAt(_ index: Int) -> Unmanaged<AnyObject> {
138+
return _objectAt(index)
139+
}
140+
141+
@objc internal override final var count: Int {
142+
@_effects(readonly) get {
143+
return withUnsafeBufferOfObjects { $0.count }
144+
}
145+
}
146+
147+
@_effects(releasenone)
148+
@objc internal override final func getObjects(
149+
_ aBuffer: UnsafeMutablePointer<AnyObject>, range: _SwiftNSRange
150+
) {
151+
return withUnsafeBufferOfObjects {
152+
objects in
153+
_precondition(
154+
_isValidArrayIndex(range.location, count: objects.count),
155+
"Array index out of range")
156+
157+
_precondition(
158+
_isValidArrayIndex(
159+
range.location + range.length, count: objects.count),
160+
"Array index out of range")
161+
162+
if objects.isEmpty { return }
163+
164+
// These objects are "returned" at +0, so treat them as pointer values to
165+
// avoid retains. Copy bytes via a raw pointer to circumvent reference
166+
// counting while correctly aliasing with all other pointer types.
167+
UnsafeMutableRawPointer(aBuffer).copyMemory(
168+
from: objects.baseAddress! + range.location,
169+
byteCount: range.length * MemoryLayout<AnyObject>.stride)
170+
}
171+
}
172+
82173
/// If the `Element` is bridged verbatim, invoke `body` on an
83174
/// `UnsafeBufferPointer` to the elements and return the result.
84175
/// Otherwise, return `nil`.

stdlib/public/core/SwiftNativeNSArray.swift

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,29 +64,32 @@ internal class __SwiftNativeNSArrayWithContiguousStorage
6464
private let NSNotFound: Int = .max
6565

6666
// Implement the APIs required by NSArray
67-
extension __SwiftNativeNSArrayWithContiguousStorage: _NSArrayCore {
67+
extension __SwiftNativeNSArrayWithContiguousStorage {
6868
@objc internal var count: Int {
6969
return withUnsafeBufferOfObjects { $0.count }
7070
}
7171

7272
@inline(__always)
73-
@nonobjc private func _objectAt(_ index: Int) -> AnyObject {
73+
@_effects(readonly)
74+
@nonobjc private func _objectAt(_ index: Int) -> Unmanaged<AnyObject> {
7475
return withUnsafeBufferOfObjects {
7576
objects in
7677
_precondition(
7778
_isValidArraySubscript(index, count: objects.count),
7879
"Array index out of range")
79-
return objects[index]
80+
return Unmanaged.passUnretained(objects[index])
8081
}
8182
}
8283

8384
@objc(objectAtIndexedSubscript:)
84-
dynamic internal func objectAtSubscript(_ index: Int) -> AnyObject {
85+
@_effects(readonly)
86+
dynamic internal func objectAtSubscript(_ index: Int) -> Unmanaged<AnyObject> {
8587
return _objectAt(index)
8688
}
8789

8890
@objc(objectAtIndex:)
89-
dynamic internal func objectAt(_ index: Int) -> AnyObject {
91+
@_effects(readonly)
92+
dynamic internal func objectAt(_ index: Int) -> Unmanaged<AnyObject> {
9093
return _objectAt(index)
9194
}
9295

@@ -146,7 +149,7 @@ extension __SwiftNativeNSArrayWithContiguousStorage: _NSArrayCore {
146149
@_fixed_layout
147150
@usableFromInline
148151
@objc internal final class _SwiftNSMutableArray :
149-
_SwiftNativeNSMutableArray, _NSArrayCore
152+
_SwiftNativeNSMutableArray
150153
{
151154
internal var contents: [AnyObject]
152155

@@ -160,15 +163,17 @@ extension __SwiftNativeNSArrayWithContiguousStorage: _NSArrayCore {
160163
}
161164

162165
@objc(objectAtIndexedSubscript:)
163-
dynamic internal func objectAtSubscript(_ index: Int) -> AnyObject {
166+
@_effects(readonly)
167+
dynamic internal func objectAtSubscript(_ index: Int) -> Unmanaged<AnyObject> {
164168
//TODO: exception instead of precondition, once that's possible
165-
return contents[index]
169+
return Unmanaged.passUnretained(contents[index])
166170
}
167171

168172
@objc(objectAtIndex:)
169-
dynamic internal func objectAt(_ index: Int) -> AnyObject {
173+
@_effects(readonly)
174+
dynamic internal func objectAt(_ index: Int) -> Unmanaged<AnyObject> {
170175
//TODO: exception instead of precondition, once that's possible
171-
return contents[index]
176+
return Unmanaged.passUnretained(contents[index])
172177
}
173178

174179
@objc internal func getObjects(

validation-test/stdlib/ArrayNew.swift.gyb

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -544,8 +544,6 @@ ArrayTestSuite.test("BridgedToObjC/Verbatim/objectAtIndex") {
544544
expectEqual(idValue0, unsafeBitCast(a.object(at: 0) as AnyObject, to: UInt.self))
545545
expectEqual(idValue1, unsafeBitCast(a.object(at: 1) as AnyObject, to: UInt.self))
546546
expectEqual(idValue2, unsafeBitCast(a.object(at: 2) as AnyObject, to: UInt.self))
547-
548-
expectAutoreleasedKeysAndValues(unopt: (0, 3))
549547
}
550548

551549
for indexRange in [
@@ -605,8 +603,6 @@ ArrayTestSuite.test("BridgedToObjC/Verbatim/getObjects") {
605603

606604
buffer.deallocate()
607605
_fixLifetime(a)
608-
609-
expectAutoreleasedKeysAndValues(unopt: (0, 3))
610606
}
611607

612608
ArrayTestSuite.test("BridgedToObjC/Verbatim/copyWithZone") {
@@ -724,8 +720,6 @@ ArrayTestSuite.test("BridgedToObjC/Verbatim/BridgeBack/Reallocate") {
724720
expectEqual(idValue0, unsafeBitCast(a.object(at: 0) as AnyObject, to: UInt.self))
725721
expectEqual(idValue1, unsafeBitCast(a.object(at: 1) as AnyObject, to: UInt.self))
726722
expectEqual(idValue2, unsafeBitCast(a.object(at: 2) as AnyObject, to: UInt.self))
727-
728-
expectAutoreleasedKeysAndValues(unopt: (0, 3))
729723
}
730724

731725
ArrayTestSuite.test("BridgedToObjC/Verbatim/BridgeBack/Adopt") {
@@ -815,7 +809,6 @@ ArrayTestSuite.test("BridgedToObjC/Custom/objectAtIndex") {
815809
expectEqual(idValue2, unsafeBitCast(a.object(at: 2) as AnyObject, to: UInt.self))
816810

817811
expectEqual(3, TestBridgedValueTy.bridgeOperations)
818-
expectAutoreleasedKeysAndValues(unopt: (0, 3))
819812
}
820813

821814
for indexRange in [
@@ -877,7 +870,6 @@ ArrayTestSuite.test("BridgedToObjC/Custom/getObjects") {
877870
_fixLifetime(a)
878871

879872
expectEqual(3, TestBridgedValueTy.bridgeOperations)
880-
expectAutoreleasedKeysAndValues(unopt: (0, 3))
881873
}
882874

883875
ArrayTestSuite.test("BridgedToObjC/Custom/copyWithZone") {
@@ -1014,8 +1006,6 @@ ArrayTestSuite.test("BridgedToObjC/Custom/BridgeBack/Cast") {
10141006
expectEqual(idValue0, unsafeBitCast(a.object(at: 0) as AnyObject, to: UInt.self))
10151007
expectEqual(idValue1, unsafeBitCast(a.object(at: 1) as AnyObject, to: UInt.self))
10161008
expectEqual(idValue2, unsafeBitCast(a.object(at: 2) as AnyObject, to: UInt.self))
1017-
1018-
expectAutoreleasedKeysAndValues(unopt: (0, 3))
10191009
}
10201010

10211011
ArrayTestSuite.test("BridgedToObjC/Custom/BridgeBack/Reallocate") {
@@ -1048,8 +1038,6 @@ ArrayTestSuite.test("BridgedToObjC/Custom/BridgeBack/Reallocate") {
10481038
expectEqual(idValue0, unsafeBitCast(a.object(at: 0) as AnyObject, to: UInt.self))
10491039
expectEqual(idValue1, unsafeBitCast(a.object(at: 1) as AnyObject, to: UInt.self))
10501040
expectEqual(idValue2, unsafeBitCast(a.object(at: 2) as AnyObject, to: UInt.self))
1051-
1052-
expectAutoreleasedKeysAndValues(unopt: (0, 3))
10531041
}
10541042

10551043
ArrayTestSuite.test("BridgedToObjC/Custom/BridgeBack/Adopt") {

0 commit comments

Comments
 (0)