<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class="">I’ve been looking a lot into how Swift and Objective C collections inter-convert because a bunch of necessary cleanup for ABI stability interacts with it. Unfortunately I’ve run into some conflicting information with respect to how stuff <i class="">should</i> work, and how it seems to <i class="">actually </i>work. Hoping y'all could help me clear this up.<div class=""><br class=""></div><div class="">Here’s the literal state of the Swift codebase, as I currently understand it:</div><div class=""><br class=""></div><div class="">* String, Array, Dictionary, and Set are all effectively tagged unions of "Native || Objc”. </div><div class="">* The ObjC case is the result of lazy bridging, and is basically just storing an NSWhatever pointer.</div><div class="">* If any of these collections are in the ObjC state, then bridging to ObjC is obvious and trivial. (yay!)</div><div class="">* If any of these collections are in the Native state:</div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><div class=""><b class="">Array</b>: If the storage is verbatim bridgeable to ObjC (~it’s a class or objc existential), just return the buffer (toll free). Otherwise, wrap the storage in a _SwiftDeferredNSArray (<b class="">not toll free</b>). The first time someone tries to access the contents of the _SwiftDeferredNSArray, a CAS-loop race occurs to create a new buffer containing all the bridged values. The only alternative to this would be bridging each element as it’s requested, but that’s ultimately just a trade-off (and has issues if people are relying on pointer equality). There has to be some toll here.</div></div><div class=""><br class=""></div><div class="">However the construction of the _SwiftDeferredNSArray is hypothetically unnecessary, as alluded to in the comments on _SDNSArray. The class and its CAS pointer could be embedded in the native array buffer. This would presumably incur some bloat on all native Arrays, but it might not be too bad (and platforms which don’t support _runtime(ObjC) presumably can omit it)? That said, I’m not 100% clear if we have the machinery to accomplish this yet or not. If anyone knows this, it would help a lot!</div><div class=""><br class=""></div><div class="">(there’s also some special case singleton for empty arrays, that all seems fine)</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span></div><div class="">See: </div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>ContiguousArrayBuffer.swift: _asCocoaArray</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>ContiguousArrayBuffer.swift: _getNonVerbatimBridgedHeapBuffer</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>SwiftNativeNSArray: _SwiftDeferredNSArray (the class that wraps the storage)</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span></div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><b class="">Dictionary/Set</b>: Looks to be pretty much the same thing as Array, <i class="">except</i> that the old indexing model led to a double-indirection being baked into the design, so a wrapper class doesn’t need to be constructed in the non-verbatim case.</div><div class="">(_NativeDictionaryStorageOwner contains the CAS-pointer). (<b class="">toll free as can be!</b>)</div><div class=""><br class=""></div><div class=""><b class="">But </b>this means that cleaning up all the gunk from the old indexing model and removing this indirection will lead to a regression in bridging performance *unless* Dictionary/Set gets the kind of optimizations discussed for Array. The class inlining optimization also seem more acceptable for Dictionary, since it’s necessarily a more bloated allocation than Array (less overhead by %).</div><div class=""><br class=""></div><div class="">See: </div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>HashedCollections.swift.gyb: _bridgeToObjectiveCImpl</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>HashedCollections.swift.gyb: _Native${Self}StorageOwner (the “outer” class)</div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><b class="">String</b>: Unconditionally construct a class that wraps the String’s storage. (<b class="">not toll free</b>)</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span></div><div class=""><div class="">This just seems bad, and as far as I can tell isn’t expected. It seems to be the result of _StringBuffer (the lowest-level type in the String abstraction stack that still actually knows it’s a string) being a struct, and not a class that inherits from _HeapBuffer due to some problems with deriving from generic classes. I’m not 100% sure what the “fix” for this is supposed to be.</div></div><div class=""><br class=""></div><div class="">I think any fix will necessarily lead to String becoming pointer-sized, which appears to be a desirable ABI feature anyway.</div><div class="">However this has tricky consequences for Strings which are actually sub-slices of other Strings. At the limit, this will definitely require some slices which don’t allocate (because they just create a new String pointing at the old buffer with different start/length values) to start requiring an allocation (because those fields will be in a class, and not a struct). <i class="">Maybe</i> stack promotion and careful pointer-tagging can eliminate most allocations in practice.</div><div class=""><br class=""></div><div class="">See: </div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>StringBridge.swift: _stdlib_binary_bridgeToObjectiveCImpl</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>StringBridge.swift: _NSContiguousString (the class that wraps the storage)</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>StringBuffer.swift: _StringBuffer (the type that wants to subclass _HeapBuffer)</div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><br class=""></div><div class="">So that’s the situation as I understand it. Did I get anything wrong? Are there any details I’m missing?</div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><br class=""></div></body></html>