Skip to content

Commit 4e4325d

Browse files
authored
Merge pull request #21235 from Catfish-Man/cheaper-by-the-dozen
Adopt bulk Dictionary creation in bridging
2 parents 83950ba + 016ced2 commit 4e4325d

File tree

5 files changed

+438
-36
lines changed

5 files changed

+438
-36
lines changed

stdlib/public/Darwin/Foundation/NSDictionary.swift

Lines changed: 232 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,140 @@ extension Dictionary : _ObjectiveCBridgeable {
6161
to: NSDictionary.self)
6262
}
6363

64+
/***
65+
Precondition: `buffer` points to a region of memory bound to `AnyObject`,
66+
with a capacity large enough to fit at least `index`+1 elements of type `T`
67+
68+
_bridgeInitialize rebinds the `index`th `T` of `buffer` to `T`,
69+
and initializes it to `value`
70+
71+
Note: *not* the `index`th element of `buffer`, since T and AnyObject may be
72+
different sizes. e.g. if T is String (2 words) then given a buffer like so:
73+
74+
[object:AnyObject, object:AnyObject, uninitialized, uninitialized]
75+
76+
`_bridgeInitialize(1, of: buffer, to: buffer[1] as! T)` will leave it as:
77+
78+
[object:AnyObject, object:AnyObject, string:String]
79+
80+
and `_bridgeInitialize(0, of: buffer, to: buffer[0] as! T)` will then leave:
81+
82+
[string:String, string:String]
83+
84+
Doing this in reverse order as shown above is required if T and AnyObject are
85+
different sizes. Here's what we get if instead of 1, 0 we did 0, 1:
86+
87+
[object:AnyObject, object:AnyObject, uninitialized, uninitialized]
88+
[string:String, uninitialized, uninitialized]
89+
<segfault trying to treat the second word of 'string' as an AnyObject>
90+
91+
Note: if you have retained any of the objects in `buffer`, you must release
92+
them separately, _bridgeInitialize will overwrite them without releasing them
93+
*/
94+
@inline(__always)
95+
private static func _bridgeInitialize<T>(index:Int,
96+
of buffer: UnsafePointer<AnyObject>, to value: T) {
97+
let typedBase = UnsafeMutableRawPointer(mutating:
98+
buffer).assumingMemoryBound(to: T.self)
99+
let rawTarget = UnsafeMutableRawPointer(mutating: typedBase + index)
100+
rawTarget.initializeMemory(as: T.self, repeating: value, count: 1)
101+
}
102+
103+
@inline(__always)
104+
private static func _verbatimForceBridge<T>(
105+
_ buffer: UnsafeMutablePointer<AnyObject>,
106+
count: Int,
107+
to: T.Type
108+
) {
109+
//doesn't have to iterate in reverse because sizeof(T) == sizeof(AnyObject)
110+
for i in 0..<count {
111+
_bridgeInitialize(index: i, of: buffer, to: buffer[i] as! T)
112+
}
113+
}
114+
115+
@inline(__always)
116+
private static func _verbatimBridge<T>(
117+
_ buffer: UnsafeMutablePointer<AnyObject>,
118+
count: Int,
119+
to type: T.Type
120+
) -> Int {
121+
var numUninitialized = count
122+
while numUninitialized > 0 {
123+
guard let bridged = buffer[numUninitialized - 1] as? T else {
124+
return numUninitialized
125+
}
126+
numUninitialized -= 1
127+
_bridgeInitialize(index: numUninitialized, of: buffer, to: bridged)
128+
}
129+
return numUninitialized
130+
}
131+
132+
@inline(__always)
133+
private static func _nonVerbatimForceBridge<T>(
134+
_ buffer: UnsafeMutablePointer<AnyObject>,
135+
count: Int,
136+
to: T.Type
137+
) {
138+
for i in (0..<count).reversed() {
139+
let bridged = Swift._forceBridgeFromObjectiveC(buffer[i], T.self)
140+
_bridgeInitialize(index: i, of: buffer, to: bridged)
141+
}
142+
}
143+
144+
@inline(__always)
145+
private static func _nonVerbatimBridge<T>(
146+
_ buffer: UnsafeMutablePointer<AnyObject>,
147+
count: Int,
148+
to: T.Type
149+
) -> Int {
150+
var numUninitialized = count
151+
while numUninitialized > 0 {
152+
guard let bridged = Swift._conditionallyBridgeFromObjectiveC(
153+
buffer[numUninitialized - 1], T.self)
154+
else {
155+
return numUninitialized
156+
}
157+
numUninitialized -= 1
158+
_bridgeInitialize(index: numUninitialized, of: buffer, to: bridged)
159+
}
160+
return numUninitialized
161+
}
162+
163+
@inline(__always)
164+
private static func _forceBridge<T>(
165+
_ buffer: UnsafeMutablePointer<AnyObject>,
166+
count: Int,
167+
to: T.Type
168+
) {
169+
if _isBridgedVerbatimToObjectiveC(T.self) {
170+
_verbatimForceBridge(buffer, count: count, to: T.self)
171+
} else {
172+
_nonVerbatimForceBridge(buffer, count: count, to: T.self)
173+
}
174+
}
175+
176+
@inline(__always)
177+
private static func _conditionallyBridge<T>(
178+
_ buffer: UnsafeMutablePointer<AnyObject>,
179+
count: Int,
180+
to: T.Type
181+
) -> Bool {
182+
let numUninitialized:Int
183+
if _isBridgedVerbatimToObjectiveC(T.self) {
184+
numUninitialized = _verbatimBridge(buffer, count: count, to: T.self)
185+
} else {
186+
numUninitialized = _nonVerbatimBridge(buffer, count: count, to: T.self)
187+
}
188+
if numUninitialized == 0 {
189+
return true
190+
}
191+
let numInitialized = count - numUninitialized
192+
(UnsafeMutableRawPointer(mutating: buffer).assumingMemoryBound(to:
193+
T.self) + numUninitialized).deinitialize(count: numInitialized)
194+
return false
195+
}
196+
197+
@_specialize(where Key == String, Value == Any)
64198
public static func _forceBridgeFromObjectiveC(
65199
_ d: NSDictionary,
66200
result: inout Dictionary?
@@ -73,53 +207,120 @@ extension Dictionary : _ObjectiveCBridgeable {
73207

74208
if _isBridgedVerbatimToObjectiveC(Key.self) &&
75209
_isBridgedVerbatimToObjectiveC(Value.self) {
210+
//Lazily type-checked on access
76211
result = [Key : Value](_cocoaDictionary: d)
77212
return
78213
}
79214

80-
if Key.self == String.self {
215+
let keyStride = MemoryLayout<Key>.stride
216+
let valueStride = MemoryLayout<Value>.stride
217+
let objectStride = MemoryLayout<AnyObject>.stride
218+
219+
//If Key or Value are smaller than AnyObject, a Dictionary with N elements
220+
//doesn't have large enough backing stores to hold the objects to be bridged
221+
//For now we just handle that case the slow way.
222+
if keyStride < objectStride || valueStride < objectStride {
223+
var builder = _DictionaryBuilder<Key, Value>(count: d.count)
224+
d.enumerateKeysAndObjects({ (anyKey: Any, anyValue: Any, _) in
225+
let anyObjectKey = anyKey as AnyObject
226+
let anyObjectValue = anyValue as AnyObject
227+
builder.add(
228+
key: Swift._forceBridgeFromObjectiveC(anyObjectKey, Key.self),
229+
value: Swift._forceBridgeFromObjectiveC(anyObjectValue, Value.self))
230+
})
231+
result = builder.take()
232+
} else {
233+
defer { _fixLifetime(d) }
234+
235+
let numElems = d.count
236+
81237
// String and NSString have different concepts of equality, so
82238
// string-keyed NSDictionaries may generate key collisions when bridged
83239
// over to Swift. See rdar://problem/35995647
84-
var dict = Dictionary(minimumCapacity: d.count)
85-
d.enumerateKeysAndObjects({ (anyKey: Any, anyValue: Any, _) in
86-
let key = Swift._forceBridgeFromObjectiveC(
87-
anyKey as AnyObject, Key.self)
88-
let value = Swift._forceBridgeFromObjectiveC(
89-
anyValue as AnyObject, Value.self)
90-
// FIXME: Log a warning if `dict` already had a value for `key`
91-
dict[key] = value
92-
})
93-
result = dict
94-
return
95-
}
240+
let handleDuplicates = (Key.self == String.self)
241+
242+
result = Dictionary(_unsafeUninitializedCapacity: numElems,
243+
allowingDuplicates: handleDuplicates) { (keys, vals, outCount) in
244+
245+
let objectKeys = UnsafeMutableRawPointer(mutating:
246+
keys.baseAddress!).assumingMemoryBound(to: AnyObject.self)
247+
let objectVals = UnsafeMutableRawPointer(mutating:
248+
vals.baseAddress!).assumingMemoryBound(to: AnyObject.self)
96249

97-
// `Dictionary<Key, Value>` where either `Key` or `Value` is a value type
98-
// may not be backed by an NSDictionary.
99-
var builder = _DictionaryBuilder<Key, Value>(count: d.count)
100-
d.enumerateKeysAndObjects({ (anyKey: Any, anyValue: Any, _) in
101-
let anyObjectKey = anyKey as AnyObject
102-
let anyObjectValue = anyValue as AnyObject
103-
builder.add(
104-
key: Swift._forceBridgeFromObjectiveC(anyObjectKey, Key.self),
105-
value: Swift._forceBridgeFromObjectiveC(anyObjectValue, Value.self))
106-
})
107-
result = builder.take()
108-
}
250+
//This initializes the first N AnyObjects of the Dictionary buffers.
251+
//Any unused buffer space is left uninitialized
252+
//This is fixed up in-place as we bridge elements, by _bridgeInitialize
253+
__NSDictionaryGetObjects(d, objectVals, objectKeys, numElems)
109254

255+
_forceBridge(objectKeys, count: numElems, to: Key.self)
256+
_forceBridge(objectVals, count: numElems, to: Value.self)
257+
258+
outCount = numElems
259+
}
260+
}
261+
}
262+
263+
@_specialize(where Key == String, Value == Any)
110264
public static func _conditionallyBridgeFromObjectiveC(
111265
_ x: NSDictionary,
112266
result: inout Dictionary?
113267
) -> Bool {
114-
let anyDict = x as [NSObject : AnyObject]
115-
if _isBridgedVerbatimToObjectiveC(Key.self) &&
116-
_isBridgedVerbatimToObjectiveC(Value.self) {
117-
result = Swift._dictionaryDownCastConditional(anyDict)
268+
269+
if let native = [Key : Value]._bridgeFromObjectiveCAdoptingNativeStorageOf(
270+
x as AnyObject) {
271+
result = native
272+
return true
273+
}
274+
275+
let keyStride = MemoryLayout<Key>.stride
276+
let valueStride = MemoryLayout<Value>.stride
277+
let objectStride = MemoryLayout<AnyObject>.stride
278+
279+
//If Key or Value are smaller than AnyObject, a Dictionary with N elements
280+
//doesn't have large enough backing stores to hold the objects to be bridged
281+
//For now we just handle that case the slow way.
282+
if keyStride < objectStride || valueStride < objectStride {
283+
result = x as [NSObject : AnyObject] as? Dictionary
118284
return result != nil
119285
}
120286

121-
result = anyDict as? Dictionary
122-
return result != nil
287+
defer { _fixLifetime(x) }
288+
289+
let numElems = x.count
290+
var success = true
291+
292+
// String and NSString have different concepts of equality, so
293+
// string-keyed NSDictionaries may generate key collisions when bridged
294+
// over to Swift. See rdar://problem/35995647
295+
let handleDuplicates = (Key.self == String.self)
296+
297+
let tmpResult = Dictionary(_unsafeUninitializedCapacity: numElems,
298+
allowingDuplicates: handleDuplicates) { (keys, vals, outCount) in
299+
300+
let objectKeys = UnsafeMutableRawPointer(mutating:
301+
keys.baseAddress!).assumingMemoryBound(to: AnyObject.self)
302+
let objectVals = UnsafeMutableRawPointer(mutating:
303+
vals.baseAddress!).assumingMemoryBound(to: AnyObject.self)
304+
305+
//This initializes the first N AnyObjects of the Dictionary buffers.
306+
//Any unused buffer space is left uninitialized
307+
//This is fixed up in-place as we bridge elements, by _bridgeInitialize
308+
__NSDictionaryGetObjects(x, objectVals, objectKeys, numElems)
309+
310+
success = _conditionallyBridge(objectKeys, count: numElems, to: Key.self)
311+
if success {
312+
success = _conditionallyBridge(objectVals,
313+
count: numElems, to: Value.self)
314+
if !success {
315+
(UnsafeMutableRawPointer(mutating: objectKeys).assumingMemoryBound(to:
316+
Key.self)).deinitialize(count: numElems)
317+
}
318+
}
319+
outCount = success ? numElems : 0
320+
}
321+
322+
result = success ? tmpResult : nil
323+
return success
123324
}
124325

125326
@_effects(readonly)

0 commit comments

Comments
 (0)