Skip to content

A new way to bridge constant NSStrings #74881

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
merged 11 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion stdlib/public/core/StringBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,16 @@ private func _NSStringUTF8Pointer(_ str: _StringSelectorHolder) -> UnsafePointer
return _NSStringASCIIPointer(str)
}

@_effects(readonly)
internal func _getNSCFConstantStringContentsPointer(
_ cocoa: AnyObject
) -> UnsafePointer<UInt8> {
return unsafeBitCast(
cocoa,
to: UnsafePointer<_swift_shims_builtin_CFString>.self
).pointee.str
}

@_effects(readonly) // @opaque
private func _withCocoaASCIIPointer<R>(
_ str: _CocoaString,
Expand Down Expand Up @@ -635,6 +645,31 @@ internal func _bridgeCocoaString(_ cocoaString: _CocoaString) -> _StringGuts {
}

extension String {
@available(SwiftStdlib 6.1, *)
@_spi(Foundation)
public init<Encoding: Unicode.Encoding>(
_immortalCocoaString: AnyObject,
count: Int,
encoding: Encoding.Type
) {
if encoding == Unicode.ASCII.self || encoding == Unicode.UTF8.self {
self._guts = _StringGuts(
constantCocoa: _immortalCocoaString,
providesFastUTF8: true,
isASCII: encoding == Unicode.ASCII.self,
length: count)
} else {
_precondition(encoding == Unicode.UTF16.self)
// Only need the very last bit of _bridgeCocoaString here,
// since we know the fast paths don't apply
self._guts = _StringGuts(
cocoa: _immortalCocoaString,
providesFastUTF8: false,
isASCII: false,
length: count)
}
}

@_spi(Foundation)
public init(_cocoaString: AnyObject) {
self._guts = _bridgeCocoaString(_cocoaString)
Expand Down Expand Up @@ -696,7 +731,7 @@ extension String {
_internalInvariant(!copy._guts.isSmall)
return copy._bridgeToObjectiveCImpl()
}
if _guts._object.isImmortal {
if _guts._object.isImmortal && !_guts._object.largeFastIsConstantCocoa {
// TODO: We'd rather emit a valid ObjC object statically than create a
// shared string class instance.
let gutsCountAndFlags = _guts._object._countAndFlags
Expand Down
15 changes: 15 additions & 0 deletions stdlib/public/core/StringGuts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ extension _StringGuts {
internal init(_ storage: __SharedStringStorage) {
self.init(_StringObject(storage))
}

#if !$Embedded
internal init(
constantCocoa cocoa: AnyObject,
providesFastUTF8: Bool,
isASCII: Bool,
length: Int
) {
self.init(_StringObject(
constantCocoa: cocoa,
providesFastUTF8: providesFastUTF8,
isASCII: isASCII,
length: length))
}
#endif

#if !$Embedded
internal init(
Expand Down
55 changes: 52 additions & 3 deletions stdlib/public/core/StringObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
│ Immortal, Small ║ 1 │ASCII│ 1 │ 0 │
├─────────────────────╫─────┼─────┼─────┼─────┤
│ Immortal, Large ║ 1 │ 0 │ 0 │ 0 │
├─────────────────────╫─────┼─────┼─────┼─────┤
│ Immortal, Bridged ║ 1 │ 1 │ 0 │ 0 │
╞═════════════════════╬═════╪═════╪═════╪═════╡
│ Native ║ 0 │ 0 │ 0 │ 0 │
├─────────────────────╫─────┼─────┼─────┼─────┤
Expand Down Expand Up @@ -436,6 +438,10 @@ extension _StringObject.Nibbles {
internal static func largeCocoa(providesFastUTF8: Bool) -> UInt64 {
return providesFastUTF8 ? 0x4000_0000_0000_0000 : 0x5000_0000_0000_0000
}

internal static func largeFastImmortalCocoa() -> UInt64 {
0xC000_0000_0000_0000
}
}

extension _StringObject {
Expand Down Expand Up @@ -549,6 +555,15 @@ extension _StringObject {
return (discriminatedObjectRawBits & 0x4000_0000_0000_0000) != 0
#endif
}

@inline(__always)
internal var largeFastIsConstantCocoa: Bool {
#if os(Android) && arch(arm64)
false
#else
(discriminatedObjectRawBits & 0xF000_0000_0000_0000) == 0xC000_0000_0000_0000
#endif
}

// Whether this string is in one of our fastest representations:
// small or tail-allocated (i.e. mortal/immortal native)
Expand Down Expand Up @@ -952,6 +967,11 @@ extension _StringObject {
internal func getSharedUTF8Start() -> UnsafePointer<UInt8> {
_internalInvariant(largeFastIsShared)
#if _runtime(_ObjC)
if largeFastIsConstantCocoa {
return withCocoaObject {
_getNSCFConstantStringContentsPointer($0)
}
}
if largeIsCocoa {
return withCocoaObject {
stableCocoaUTF8Pointer($0)._unsafelyUnwrappedUnchecked
Expand Down Expand Up @@ -1075,7 +1095,9 @@ extension _StringObject {
#if $Embedded
fatalError("unreachable in embedded Swift")
#elseif _pointerBitWidth(_64)
_internalInvariant(largeIsCocoa && !isImmortal)
_internalInvariant(
(largeIsCocoa && !isImmortal) || largeFastIsConstantCocoa
)
let unmanaged = Unmanaged<AnyObject>.fromOpaque(largeAddress)
return unmanaged._withUnsafeGuaranteedRef { body($0) }
#elseif _pointerBitWidth(_32)
Expand Down Expand Up @@ -1168,7 +1190,7 @@ extension _StringObject {
internal var hasObjCBridgeableObject: Bool {
@_effects(releasenone) get {
// Currently, all mortal objects can zero-cost bridge
return !self.isImmortal
return !self.isImmortal || self.largeFastIsConstantCocoa
}
}

Expand Down Expand Up @@ -1291,6 +1313,33 @@ extension _StringObject {
countAndFlags: countAndFlags)
#else
#error("Unknown platform")
#endif
}

@_unavailableInEmbedded
internal init(
constantCocoa cocoa: AnyObject,
providesFastUTF8: Bool,
isASCII: Bool,
length: Int
) {
let countAndFlags = CountAndFlags(sharedCount: length, isASCII: isASCII)
let discriminator = Nibbles.largeFastImmortalCocoa()
#if $Embedded
fatalError("unreachable in embedded Swift")
#elseif _pointerBitWidth(_64)
self.init(
object: cocoa, discriminator: discriminator, countAndFlags: countAndFlags)
_internalInvariant(self.largeAddressBits == Builtin.reinterpretCast(cocoa))
_internalInvariant(self.providesFastUTF8 == providesFastUTF8)
_internalInvariant(self.largeCount == length)
#elseif _pointerBitWidth(_32)
self.init(
variant: .bridged(cocoa),
discriminator: discriminator,
countAndFlags: countAndFlags)
#else
#error("Unknown platform")
#endif
}
}
Expand Down Expand Up @@ -1357,7 +1406,7 @@ extension _StringObject {
_internalInvariant(nativeStorage.count == self.count)
}
}
if largeIsCocoa {
if largeFastIsConstantCocoa || largeIsCocoa {
_internalInvariant(hasObjCBridgeableObject)
_internalInvariant(!isSmall)
_internalInvariant(!_countAndFlags.isNativelyStored)
Expand Down
5 changes: 4 additions & 1 deletion test/abi/macOS/arm64/stdlib.swift
Original file line number Diff line number Diff line change
Expand Up @@ -573,4 +573,7 @@ Added: _swift_updatePureObjCClassMetadata
Added: _swift_bincompat_useLegacyNonCrashingExecutorChecks

// Add add SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE
Added: _concurrencyIsCurrentExecutorLegacyModeOverride
Added: _concurrencyIsCurrentExecutorLegacyModeOverride

//String.init<Encoding: Unicode.Encoding>(_immortalCocoaString: AnyObject, count: Int, encoding: Encoding.Type)
Added: _$sSS20_immortalCocoaString5count8encodingSSyXl_Sixmtcs16_UnicodeEncodingRzlufC
5 changes: 4 additions & 1 deletion test/abi/macOS/x86_64/stdlib.swift
Original file line number Diff line number Diff line change
Expand Up @@ -573,4 +573,7 @@ Added: _swift_updatePureObjCClassMetadata
Added: _swift_bincompat_useLegacyNonCrashingExecutorChecks

// Add add SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE
Added: _concurrencyIsCurrentExecutorLegacyModeOverride
Added: _concurrencyIsCurrentExecutorLegacyModeOverride

//String.init<Encoding: Unicode.Encoding>(_immortalCocoaString: AnyObject, count: Int, encoding: Encoding.Type)
Added: _$sSS20_immortalCocoaString5count8encodingSSyXl_Sixmtcs16_UnicodeEncodingRzlufC
21 changes: 21 additions & 0 deletions test/stdlib/StringBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,27 @@ StringBridgeTests.test("Tagged NSString") {
#endif // not 32bit
}

StringBridgeTests.test("Constant NSString New SPI") {
if #available(SwiftStdlib 6.1, *) {
//21 characters long so avoids _SmallString
let constantString:NSString = CFRunLoopMode.commonModes.rawValue as NSString
let regularBridged = constantString as String
let count = regularBridged.count
let bridged = String(
_immortalCocoaString: constantString,
count: count,
encoding: Unicode.ASCII.self
)
let reverseBridged = bridged as NSString
expectEqual(constantString, reverseBridged)
expectEqual(
ObjectIdentifier(constantString),
ObjectIdentifier(reverseBridged)
)
expectEqual(bridged, regularBridged)
}
}

StringBridgeTests.test("Bridging") {
// Test bridging retains small string form
func bridge(_ small: _SmallString) -> String {
Expand Down