Skip to content

Commit 3589044

Browse files
authored
A new way to bridge constant NSStrings (#74881)
1 parent ed0413d commit 3589044

File tree

6 files changed

+132
-6
lines changed

6 files changed

+132
-6
lines changed

stdlib/public/core/StringBridge.swift

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,16 @@ private func _NSStringUTF8Pointer(_ str: _StringSelectorHolder) -> UnsafePointer
386386
return _NSStringASCIIPointer(str)
387387
}
388388

389+
@_effects(readonly)
390+
internal func _getNSCFConstantStringContentsPointer(
391+
_ cocoa: AnyObject
392+
) -> UnsafePointer<UInt8> {
393+
return unsafeBitCast(
394+
cocoa,
395+
to: UnsafePointer<_swift_shims_builtin_CFString>.self
396+
).pointee.str
397+
}
398+
389399
@_effects(readonly) // @opaque
390400
private func _withCocoaASCIIPointer<R>(
391401
_ str: _CocoaString,
@@ -635,6 +645,31 @@ internal func _bridgeCocoaString(_ cocoaString: _CocoaString) -> _StringGuts {
635645
}
636646

637647
extension String {
648+
@available(SwiftStdlib 6.1, *)
649+
@_spi(Foundation)
650+
public init<Encoding: Unicode.Encoding>(
651+
_immortalCocoaString: AnyObject,
652+
count: Int,
653+
encoding: Encoding.Type
654+
) {
655+
if encoding == Unicode.ASCII.self || encoding == Unicode.UTF8.self {
656+
self._guts = _StringGuts(
657+
constantCocoa: _immortalCocoaString,
658+
providesFastUTF8: true,
659+
isASCII: encoding == Unicode.ASCII.self,
660+
length: count)
661+
} else {
662+
_precondition(encoding == Unicode.UTF16.self)
663+
// Only need the very last bit of _bridgeCocoaString here,
664+
// since we know the fast paths don't apply
665+
self._guts = _StringGuts(
666+
cocoa: _immortalCocoaString,
667+
providesFastUTF8: false,
668+
isASCII: false,
669+
length: count)
670+
}
671+
}
672+
638673
@_spi(Foundation)
639674
public init(_cocoaString: AnyObject) {
640675
self._guts = _bridgeCocoaString(_cocoaString)
@@ -696,7 +731,7 @@ extension String {
696731
_internalInvariant(!copy._guts.isSmall)
697732
return copy._bridgeToObjectiveCImpl()
698733
}
699-
if _guts._object.isImmortal {
734+
if _guts._object.isImmortal && !_guts._object.largeFastIsConstantCocoa {
700735
// TODO: We'd rather emit a valid ObjC object statically than create a
701736
// shared string class instance.
702737
let gutsCountAndFlags = _guts._object._countAndFlags

stdlib/public/core/StringGuts.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,21 @@ extension _StringGuts {
6363
internal init(_ storage: __SharedStringStorage) {
6464
self.init(_StringObject(storage))
6565
}
66+
67+
#if !$Embedded
68+
internal init(
69+
constantCocoa cocoa: AnyObject,
70+
providesFastUTF8: Bool,
71+
isASCII: Bool,
72+
length: Int
73+
) {
74+
self.init(_StringObject(
75+
constantCocoa: cocoa,
76+
providesFastUTF8: providesFastUTF8,
77+
isASCII: isASCII,
78+
length: length))
79+
}
80+
#endif
6681

6782
#if !$Embedded
6883
internal init(

stdlib/public/core/StringObject.swift

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
│ Immortal, Small ║ 1 │ASCII│ 1 │ 0 │
2929
├─────────────────────╫─────┼─────┼─────┼─────┤
3030
│ Immortal, Large ║ 1 │ 0 │ 0 │ 0 │
31+
├─────────────────────╫─────┼─────┼─────┼─────┤
32+
│ Immortal, Bridged ║ 1 │ 1 │ 0 │ 0 │
3133
╞═════════════════════╬═════╪═════╪═════╪═════╡
3234
│ Native ║ 0 │ 0 │ 0 │ 0 │
3335
├─────────────────────╫─────┼─────┼─────┼─────┤
@@ -436,6 +438,10 @@ extension _StringObject.Nibbles {
436438
internal static func largeCocoa(providesFastUTF8: Bool) -> UInt64 {
437439
return providesFastUTF8 ? 0x4000_0000_0000_0000 : 0x5000_0000_0000_0000
438440
}
441+
442+
internal static func largeFastImmortalCocoa() -> UInt64 {
443+
0xC000_0000_0000_0000
444+
}
439445
}
440446

441447
extension _StringObject {
@@ -549,6 +555,15 @@ extension _StringObject {
549555
return (discriminatedObjectRawBits & 0x4000_0000_0000_0000) != 0
550556
#endif
551557
}
558+
559+
@inline(__always)
560+
internal var largeFastIsConstantCocoa: Bool {
561+
#if os(Android) && arch(arm64)
562+
false
563+
#else
564+
(discriminatedObjectRawBits & 0xF000_0000_0000_0000) == 0xC000_0000_0000_0000
565+
#endif
566+
}
552567

553568
// Whether this string is in one of our fastest representations:
554569
// small or tail-allocated (i.e. mortal/immortal native)
@@ -952,6 +967,11 @@ extension _StringObject {
952967
internal func getSharedUTF8Start() -> UnsafePointer<UInt8> {
953968
_internalInvariant(largeFastIsShared)
954969
#if _runtime(_ObjC)
970+
if largeFastIsConstantCocoa {
971+
return withCocoaObject {
972+
_getNSCFConstantStringContentsPointer($0)
973+
}
974+
}
955975
if largeIsCocoa {
956976
return withCocoaObject {
957977
stableCocoaUTF8Pointer($0)._unsafelyUnwrappedUnchecked
@@ -1075,7 +1095,9 @@ extension _StringObject {
10751095
#if $Embedded
10761096
fatalError("unreachable in embedded Swift")
10771097
#elseif _pointerBitWidth(_64)
1078-
_internalInvariant(largeIsCocoa && !isImmortal)
1098+
_internalInvariant(
1099+
(largeIsCocoa && !isImmortal) || largeFastIsConstantCocoa
1100+
)
10791101
let unmanaged = Unmanaged<AnyObject>.fromOpaque(largeAddress)
10801102
return unmanaged._withUnsafeGuaranteedRef { body($0) }
10811103
#elseif _pointerBitWidth(_32)
@@ -1168,7 +1190,7 @@ extension _StringObject {
11681190
internal var hasObjCBridgeableObject: Bool {
11691191
@_effects(releasenone) get {
11701192
// Currently, all mortal objects can zero-cost bridge
1171-
return !self.isImmortal
1193+
return !self.isImmortal || self.largeFastIsConstantCocoa
11721194
}
11731195
}
11741196

@@ -1291,6 +1313,33 @@ extension _StringObject {
12911313
countAndFlags: countAndFlags)
12921314
#else
12931315
#error("Unknown platform")
1316+
#endif
1317+
}
1318+
1319+
@_unavailableInEmbedded
1320+
internal init(
1321+
constantCocoa cocoa: AnyObject,
1322+
providesFastUTF8: Bool,
1323+
isASCII: Bool,
1324+
length: Int
1325+
) {
1326+
let countAndFlags = CountAndFlags(sharedCount: length, isASCII: isASCII)
1327+
let discriminator = Nibbles.largeFastImmortalCocoa()
1328+
#if $Embedded
1329+
fatalError("unreachable in embedded Swift")
1330+
#elseif _pointerBitWidth(_64)
1331+
self.init(
1332+
object: cocoa, discriminator: discriminator, countAndFlags: countAndFlags)
1333+
_internalInvariant(self.largeAddressBits == Builtin.reinterpretCast(cocoa))
1334+
_internalInvariant(self.providesFastUTF8 == providesFastUTF8)
1335+
_internalInvariant(self.largeCount == length)
1336+
#elseif _pointerBitWidth(_32)
1337+
self.init(
1338+
variant: .bridged(cocoa),
1339+
discriminator: discriminator,
1340+
countAndFlags: countAndFlags)
1341+
#else
1342+
#error("Unknown platform")
12941343
#endif
12951344
}
12961345
}
@@ -1357,7 +1406,7 @@ extension _StringObject {
13571406
_internalInvariant(nativeStorage.count == self.count)
13581407
}
13591408
}
1360-
if largeIsCocoa {
1409+
if largeFastIsConstantCocoa || largeIsCocoa {
13611410
_internalInvariant(hasObjCBridgeableObject)
13621411
_internalInvariant(!isSmall)
13631412
_internalInvariant(!_countAndFlags.isNativelyStored)

test/abi/macOS/arm64/stdlib.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,4 +573,7 @@ Added: _swift_updatePureObjCClassMetadata
573573
Added: _swift_bincompat_useLegacyNonCrashingExecutorChecks
574574

575575
// Add add SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE
576-
Added: _concurrencyIsCurrentExecutorLegacyModeOverride
576+
Added: _concurrencyIsCurrentExecutorLegacyModeOverride
577+
578+
//String.init<Encoding: Unicode.Encoding>(_immortalCocoaString: AnyObject, count: Int, encoding: Encoding.Type)
579+
Added: _$sSS20_immortalCocoaString5count8encodingSSyXl_Sixmtcs16_UnicodeEncodingRzlufC

test/abi/macOS/x86_64/stdlib.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,4 +573,7 @@ Added: _swift_updatePureObjCClassMetadata
573573
Added: _swift_bincompat_useLegacyNonCrashingExecutorChecks
574574

575575
// Add add SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE
576-
Added: _concurrencyIsCurrentExecutorLegacyModeOverride
576+
Added: _concurrencyIsCurrentExecutorLegacyModeOverride
577+
578+
//String.init<Encoding: Unicode.Encoding>(_immortalCocoaString: AnyObject, count: Int, encoding: Encoding.Type)
579+
Added: _$sSS20_immortalCocoaString5count8encodingSSyXl_Sixmtcs16_UnicodeEncodingRzlufC

test/stdlib/StringBridge.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,27 @@ StringBridgeTests.test("Tagged NSString") {
5656
#endif // not 32bit
5757
}
5858

59+
StringBridgeTests.test("Constant NSString New SPI") {
60+
if #available(SwiftStdlib 6.1, *) {
61+
//21 characters long so avoids _SmallString
62+
let constantString:NSString = CFRunLoopMode.commonModes.rawValue as NSString
63+
let regularBridged = constantString as String
64+
let count = regularBridged.count
65+
let bridged = String(
66+
_immortalCocoaString: constantString,
67+
count: count,
68+
encoding: Unicode.ASCII.self
69+
)
70+
let reverseBridged = bridged as NSString
71+
expectEqual(constantString, reverseBridged)
72+
expectEqual(
73+
ObjectIdentifier(constantString),
74+
ObjectIdentifier(reverseBridged)
75+
)
76+
expectEqual(bridged, regularBridged)
77+
}
78+
}
79+
5980
StringBridgeTests.test("Bridging") {
6081
// Test bridging retains small string form
6182
func bridge(_ small: _SmallString) -> String {

0 commit comments

Comments
 (0)