-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Adopt bulk Dictionary creation in bridging #21235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Catfish-Man
merged 6 commits into
swiftlang:master
from
Catfish-Man:cheaper-by-the-dozen
Dec 21, 2018
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
f1f5e0f
[stdlib] Dictionary: Add unsafe bulk-loading initializer
lorentey 039891c
[stdlib] Dictionary: Add support for non-unique keys in bulk loading …
lorentey f0e04f7
[stdlib] Process elements from back to front
lorentey f862ad3
[stdlib] Doc updates
lorentey 9d61bad
[stdlib] Keep the first duplicate key instead of the last
lorentey 016ced2
Adopt the new bulk Dictionary initializer in bridging
Catfish-Man File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,6 +61,140 @@ extension Dictionary : _ObjectiveCBridgeable { | |
to: NSDictionary.self) | ||
} | ||
|
||
/*** | ||
Precondition: `buffer` points to a region of memory bound to `AnyObject`, | ||
with a capacity large enough to fit at least `index`+1 elements of type `T` | ||
|
||
_bridgeInitialize rebinds the `index`th `T` of `buffer` to `T`, | ||
and initializes it to `value` | ||
|
||
Note: *not* the `index`th element of `buffer`, since T and AnyObject may be | ||
different sizes. e.g. if T is String (2 words) then given a buffer like so: | ||
|
||
[object:AnyObject, object:AnyObject, uninitialized, uninitialized] | ||
|
||
`_bridgeInitialize(1, of: buffer, to: buffer[1] as! T)` will leave it as: | ||
|
||
[object:AnyObject, object:AnyObject, string:String] | ||
|
||
and `_bridgeInitialize(0, of: buffer, to: buffer[0] as! T)` will then leave: | ||
|
||
[string:String, string:String] | ||
|
||
Doing this in reverse order as shown above is required if T and AnyObject are | ||
different sizes. Here's what we get if instead of 1, 0 we did 0, 1: | ||
|
||
[object:AnyObject, object:AnyObject, uninitialized, uninitialized] | ||
[string:String, uninitialized, uninitialized] | ||
<segfault trying to treat the second word of 'string' as an AnyObject> | ||
|
||
Note: if you have retained any of the objects in `buffer`, you must release | ||
them separately, _bridgeInitialize will overwrite them without releasing them | ||
*/ | ||
@inline(__always) | ||
private static func _bridgeInitialize<T>(index:Int, | ||
of buffer: UnsafePointer<AnyObject>, to value: T) { | ||
let typedBase = UnsafeMutableRawPointer(mutating: | ||
buffer).assumingMemoryBound(to: T.self) | ||
let rawTarget = UnsafeMutableRawPointer(mutating: typedBase + index) | ||
rawTarget.initializeMemory(as: T.self, repeating: value, count: 1) | ||
} | ||
|
||
@inline(__always) | ||
private static func _verbatimForceBridge<T>( | ||
_ buffer: UnsafeMutablePointer<AnyObject>, | ||
count: Int, | ||
to: T.Type | ||
) { | ||
//doesn't have to iterate in reverse because sizeof(T) == sizeof(AnyObject) | ||
for i in 0..<count { | ||
_bridgeInitialize(index: i, of: buffer, to: buffer[i] as! T) | ||
} | ||
} | ||
|
||
@inline(__always) | ||
private static func _verbatimBridge<T>( | ||
_ buffer: UnsafeMutablePointer<AnyObject>, | ||
count: Int, | ||
to type: T.Type | ||
) -> Int { | ||
var numUninitialized = count | ||
while numUninitialized > 0 { | ||
guard let bridged = buffer[numUninitialized - 1] as? T else { | ||
return numUninitialized | ||
} | ||
numUninitialized -= 1 | ||
_bridgeInitialize(index: numUninitialized, of: buffer, to: bridged) | ||
} | ||
return numUninitialized | ||
} | ||
|
||
@inline(__always) | ||
private static func _nonVerbatimForceBridge<T>( | ||
_ buffer: UnsafeMutablePointer<AnyObject>, | ||
count: Int, | ||
to: T.Type | ||
) { | ||
for i in (0..<count).reversed() { | ||
let bridged = Swift._forceBridgeFromObjectiveC(buffer[i], T.self) | ||
_bridgeInitialize(index: i, of: buffer, to: bridged) | ||
} | ||
} | ||
|
||
@inline(__always) | ||
private static func _nonVerbatimBridge<T>( | ||
_ buffer: UnsafeMutablePointer<AnyObject>, | ||
count: Int, | ||
to: T.Type | ||
) -> Int { | ||
var numUninitialized = count | ||
while numUninitialized > 0 { | ||
guard let bridged = Swift._conditionallyBridgeFromObjectiveC( | ||
buffer[numUninitialized - 1], T.self) | ||
else { | ||
return numUninitialized | ||
} | ||
numUninitialized -= 1 | ||
_bridgeInitialize(index: numUninitialized, of: buffer, to: bridged) | ||
} | ||
return numUninitialized | ||
} | ||
|
||
@inline(__always) | ||
private static func _forceBridge<T>( | ||
_ buffer: UnsafeMutablePointer<AnyObject>, | ||
count: Int, | ||
to: T.Type | ||
) { | ||
if _isBridgedVerbatimToObjectiveC(T.self) { | ||
_verbatimForceBridge(buffer, count: count, to: T.self) | ||
} else { | ||
_nonVerbatimForceBridge(buffer, count: count, to: T.self) | ||
} | ||
} | ||
|
||
@inline(__always) | ||
private static func _conditionallyBridge<T>( | ||
_ buffer: UnsafeMutablePointer<AnyObject>, | ||
count: Int, | ||
to: T.Type | ||
) -> Bool { | ||
let numUninitialized:Int | ||
if _isBridgedVerbatimToObjectiveC(T.self) { | ||
numUninitialized = _verbatimBridge(buffer, count: count, to: T.self) | ||
} else { | ||
numUninitialized = _nonVerbatimBridge(buffer, count: count, to: T.self) | ||
} | ||
if numUninitialized == 0 { | ||
return true | ||
} | ||
let numInitialized = count - numUninitialized | ||
(UnsafeMutableRawPointer(mutating: buffer).assumingMemoryBound(to: | ||
T.self) + numUninitialized).deinitialize(count: numInitialized) | ||
return false | ||
} | ||
|
||
@_specialize(where Key == String, Value == Any) | ||
public static func _forceBridgeFromObjectiveC( | ||
_ d: NSDictionary, | ||
result: inout Dictionary? | ||
|
@@ -73,53 +207,120 @@ extension Dictionary : _ObjectiveCBridgeable { | |
|
||
if _isBridgedVerbatimToObjectiveC(Key.self) && | ||
_isBridgedVerbatimToObjectiveC(Value.self) { | ||
//Lazily type-checked on access | ||
result = [Key : Value](_cocoaDictionary: d) | ||
return | ||
} | ||
|
||
if Key.self == String.self { | ||
let keyStride = MemoryLayout<Key>.stride | ||
let valueStride = MemoryLayout<Value>.stride | ||
let objectStride = MemoryLayout<AnyObject>.stride | ||
|
||
//If Key or Value are smaller than AnyObject, a Dictionary with N elements | ||
//doesn't have large enough backing stores to hold the objects to be bridged | ||
//For now we just handle that case the slow way. | ||
if keyStride < objectStride || valueStride < objectStride { | ||
var builder = _DictionaryBuilder<Key, Value>(count: d.count) | ||
d.enumerateKeysAndObjects({ (anyKey: Any, anyValue: Any, _) in | ||
let anyObjectKey = anyKey as AnyObject | ||
let anyObjectValue = anyValue as AnyObject | ||
builder.add( | ||
key: Swift._forceBridgeFromObjectiveC(anyObjectKey, Key.self), | ||
value: Swift._forceBridgeFromObjectiveC(anyObjectValue, Value.self)) | ||
}) | ||
result = builder.take() | ||
} else { | ||
defer { _fixLifetime(d) } | ||
|
||
let numElems = d.count | ||
|
||
// String and NSString have different concepts of equality, so | ||
// string-keyed NSDictionaries may generate key collisions when bridged | ||
// over to Swift. See rdar://problem/35995647 | ||
var dict = Dictionary(minimumCapacity: d.count) | ||
d.enumerateKeysAndObjects({ (anyKey: Any, anyValue: Any, _) in | ||
let key = Swift._forceBridgeFromObjectiveC( | ||
anyKey as AnyObject, Key.self) | ||
let value = Swift._forceBridgeFromObjectiveC( | ||
anyValue as AnyObject, Value.self) | ||
// FIXME: Log a warning if `dict` already had a value for `key` | ||
dict[key] = value | ||
}) | ||
result = dict | ||
return | ||
} | ||
let handleDuplicates = (Key.self == String.self) | ||
|
||
result = Dictionary(_unsafeUninitializedCapacity: numElems, | ||
allowingDuplicates: handleDuplicates) { (keys, vals, outCount) in | ||
|
||
let objectKeys = UnsafeMutableRawPointer(mutating: | ||
keys.baseAddress!).assumingMemoryBound(to: AnyObject.self) | ||
let objectVals = UnsafeMutableRawPointer(mutating: | ||
vals.baseAddress!).assumingMemoryBound(to: AnyObject.self) | ||
|
||
// `Dictionary<Key, Value>` where either `Key` or `Value` is a value type | ||
// may not be backed by an NSDictionary. | ||
var builder = _DictionaryBuilder<Key, Value>(count: d.count) | ||
d.enumerateKeysAndObjects({ (anyKey: Any, anyValue: Any, _) in | ||
let anyObjectKey = anyKey as AnyObject | ||
let anyObjectValue = anyValue as AnyObject | ||
builder.add( | ||
key: Swift._forceBridgeFromObjectiveC(anyObjectKey, Key.self), | ||
value: Swift._forceBridgeFromObjectiveC(anyObjectValue, Value.self)) | ||
}) | ||
result = builder.take() | ||
} | ||
//This initializes the first N AnyObjects of the Dictionary buffers. | ||
//Any unused buffer space is left uninitialized | ||
//This is fixed up in-place as we bridge elements, by _bridgeInitialize | ||
__NSDictionaryGetObjects(d, objectVals, objectKeys, numElems) | ||
|
||
_forceBridge(objectKeys, count: numElems, to: Key.self) | ||
_forceBridge(objectVals, count: numElems, to: Value.self) | ||
|
||
outCount = numElems | ||
} | ||
} | ||
} | ||
|
||
@_specialize(where Key == String, Value == Any) | ||
public static func _conditionallyBridgeFromObjectiveC( | ||
_ x: NSDictionary, | ||
result: inout Dictionary? | ||
) -> Bool { | ||
let anyDict = x as [NSObject : AnyObject] | ||
if _isBridgedVerbatimToObjectiveC(Key.self) && | ||
_isBridgedVerbatimToObjectiveC(Value.self) { | ||
result = Swift._dictionaryDownCastConditional(anyDict) | ||
|
||
if let native = [Key : Value]._bridgeFromObjectiveCAdoptingNativeStorageOf( | ||
x as AnyObject) { | ||
result = native | ||
return true | ||
} | ||
|
||
let keyStride = MemoryLayout<Key>.stride | ||
let valueStride = MemoryLayout<Value>.stride | ||
let objectStride = MemoryLayout<AnyObject>.stride | ||
|
||
//If Key or Value are smaller than AnyObject, a Dictionary with N elements | ||
//doesn't have large enough backing stores to hold the objects to be bridged | ||
//For now we just handle that case the slow way. | ||
if keyStride < objectStride || valueStride < objectStride { | ||
result = x as [NSObject : AnyObject] as? Dictionary | ||
return result != nil | ||
} | ||
|
||
result = anyDict as? Dictionary | ||
return result != nil | ||
defer { _fixLifetime(x) } | ||
|
||
let numElems = x.count | ||
var success = true | ||
|
||
// String and NSString have different concepts of equality, so | ||
// string-keyed NSDictionaries may generate key collisions when bridged | ||
// over to Swift. See rdar://problem/35995647 | ||
let handleDuplicates = (Key.self == String.self) | ||
|
||
let tmpResult = Dictionary(_unsafeUninitializedCapacity: numElems, | ||
allowingDuplicates: handleDuplicates) { (keys, vals, outCount) in | ||
|
||
let objectKeys = UnsafeMutableRawPointer(mutating: | ||
keys.baseAddress!).assumingMemoryBound(to: AnyObject.self) | ||
let objectVals = UnsafeMutableRawPointer(mutating: | ||
vals.baseAddress!).assumingMemoryBound(to: AnyObject.self) | ||
|
||
//This initializes the first N AnyObjects of the Dictionary buffers. | ||
//Any unused buffer space is left uninitialized | ||
//This is fixed up in-place as we bridge elements, by _bridgeInitialize | ||
__NSDictionaryGetObjects(x, objectVals, objectKeys, numElems) | ||
|
||
success = _conditionallyBridge(objectKeys, count: numElems, to: Key.self) | ||
if success { | ||
success = _conditionallyBridge(objectVals, | ||
count: numElems, to: Value.self) | ||
if !success { | ||
(UnsafeMutableRawPointer(mutating: objectKeys).assumingMemoryBound(to: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For reference, this was the fix for the memory leak >.< cleaning up the buffer that failed worked fine, but if the second buffer failed we wouldn't clean up the first one. |
||
Key.self)).deinitialize(count: numElems) | ||
} | ||
} | ||
outCount = success ? numElems : 0 | ||
} | ||
|
||
result = success ? tmpResult : nil | ||
return success | ||
} | ||
|
||
@_effects(readonly) | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't look right -- verbatim bridging should still perform a checked downcast, or we would allow casting an
NSDictionary
ofNSString
keys to[NSNumber: Any]
.(Maybe there is room to add an unchecked downcasting variant for use in case we're totally sure.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, it looks like we don't perform this check for verbatim-bridged forced downcasts on master, either -- we only check conditional downcasts. That seems like a bug!
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Except it isn't -- the check is of course still performed when we're downcasting individual items. This is by design; bridging is done lazily in these cases. Sorry for the brain hiccup!
Despite my confusion about forced casts, conditional casts are always expected to eagerly check that all items can be bridged/downcasted to the target type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I have this right in the most recent update I pushed. The verbatim ones use ? and !, and the non verbatim ones use Swift._forceBridgeFromObjectiveC and Swift._conditionallyBridgeFromObjectiveC. It's possible we could merge some of the cases now, I'm not entirely sure.