Skip to content

Commit f800fdf

Browse files
authored
Adopt indirect tagged strings (#80425)
Fixes rdar://142991821
1 parent 4bddb3a commit f800fdf

File tree

6 files changed

+89
-3
lines changed

6 files changed

+89
-3
lines changed

stdlib/public/SwiftShims/swift/shims/CoreFoundationShims.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,12 @@ _swift_stdlib_NSStringGetCStringTrampoline(id _Nonnull obj,
7474
SWIFT_RUNTIME_STDLIB_API
7575
__swift_uint8_t
7676
_swift_stdlib_dyld_is_objc_constant_string(const void * _Nonnull addr);
77-
77+
78+
SWIFT_RUNTIME_STDLIB_API
79+
const void * _Nullable
80+
_swift_stdlib_CreateIndirectTaggedPointerString(const __swift_uint8_t * _Nonnull bytes,
81+
_swift_shims_CFIndex len);
82+
7883
#endif // __OBJC2__
7984

8085
#ifdef __cplusplus

stdlib/public/core/StringBridge.swift

+16-2
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ private func _NSStringCharactersPtr(_ str: _StringSelectorHolder) -> UnsafeMutab
115115
return unsafe UnsafeMutablePointer(mutating: str._fastCharacterContents())
116116
}
117117

118+
private func _stdlib_binary_createIndirectTaggedPointerNSString(
119+
ptr: UnsafePointer<UInt8>,
120+
count: Int
121+
) -> UnsafeRawPointer? {
122+
return _swift_stdlib_CreateIndirectTaggedPointerString(ptr, count);
123+
}
124+
118125
@usableFromInline // @testable
119126
@_effects(readonly)
120127
internal func _stdlib_binary_CFStringGetCharactersPtr(
@@ -623,8 +630,15 @@ extension String {
623630
return copy._bridgeToObjectiveCImpl()
624631
}
625632
if _guts._object.isImmortal && !_guts._object.largeFastIsConstantCocoa {
626-
// TODO: We'd rather emit a valid ObjC object statically than create a
627-
// shared string class instance.
633+
if _guts.isASCII && _guts._object.isFastZeroTerminated {
634+
let ptr = _guts._object.fastUTF8.baseAddress!
635+
let count = _guts.count
636+
if let indirect = _stdlib_binary_createIndirectTaggedPointerNSString(
637+
ptr: ptr, count: count
638+
) {
639+
return unsafeBitCast(indirect, to: AnyObject.self)
640+
}
641+
}
628642
let gutsCountAndFlags = _guts._object._countAndFlags
629643
return unsafe __SharedStringStorage(
630644
immortal: _guts._object.fastUTF8.baseAddress!,

stdlib/public/stubs/FoundationHelpers.mm

+33
Original file line numberDiff line numberDiff line change
@@ -112,5 +112,38 @@ typedef __swift_uint8_t (*getCStringImplPtr)(id,
112112
&& SWIFT_RUNTIME_WEAK_USE(_dyld_is_objc_constant(dyld_objc_string_kind, addr))) ? 1 : 0;
113113
}
114114

115+
typedef const void * _Nullable (*createIndirectTaggedImplPtr)(id,
116+
SEL,
117+
const _swift_shims_UInt8 * _Nonnull,
118+
_swift_shims_CFIndex);
119+
static swift_once_t lookUpIndirectTaggedStringCreationOnce;
120+
static createIndirectTaggedImplPtr createIndirectTaggedString;
121+
static Class indirectTaggedStringClass;
122+
123+
static void lookUpIndirectTaggedStringCreationOnceImpl(void *ctxt) {
124+
Class cls = objc_lookUpClass("NSIndirectTaggedPointerString");
125+
if (!cls) return;
126+
SEL sel = @selector(newIndirectTaggedNSStringWithConstantNullTerminatedASCIIBytes_:length_:);
127+
Method m = class_getClassMethod(cls, sel);
128+
if (!m) return;
129+
createIndirectTaggedString = (createIndirectTaggedImplPtr)method_getImplementation(m);
130+
indirectTaggedStringClass = cls;
131+
}
132+
133+
SWIFT_RUNTIME_STDLIB_API
134+
const void *
135+
_swift_stdlib_CreateIndirectTaggedPointerString(const __swift_uint8_t *bytes,
136+
_swift_shims_CFIndex len) {
137+
swift_once(&lookUpIndirectTaggedStringCreationOnce,
138+
lookUpIndirectTaggedStringCreationOnceImpl,
139+
nullptr);
140+
141+
if (indirectTaggedStringClass) {
142+
SEL sel = @selector(newIndirectTaggedNSStringWithConstantNullTerminatedASCIIBytes_:length_:);
143+
return createIndirectTaggedString(indirectTaggedStringClass, sel, bytes, len);
144+
}
145+
return NULL;
146+
}
147+
115148
#endif
116149

test/abi/macOS/arm64/stdlib.swift

+3
Original file line numberDiff line numberDiff line change
@@ -959,3 +959,6 @@ Added: _$ss18EnumeratedSequenceVsSlRzrlE7isEmptySbvpMV
959959
Added: _$ss18EnumeratedSequenceVsSlRzrlE8endIndexABsSlRzrlE0D0Vyx_GvpMV
960960
Added: _$ss18EnumeratedSequenceVsSlRzrlEySi6offset_7ElementQz7elementtABsSlRzrlE5IndexVyx_GcipMV
961961
Added: _$ss18EnumeratedSequenceVyxGSKsSkRzrlMc
962+
963+
// Indirect tagged string creation
964+
Added: __swift_stdlib_CreateIndirectTaggedPointerString

test/abi/macOS/x86_64/stdlib.swift

+3
Original file line numberDiff line numberDiff line change
@@ -959,3 +959,6 @@ Added: _$ss18EnumeratedSequenceVsSlRzrlE7isEmptySbvpMV
959959
Added: _$ss18EnumeratedSequenceVsSlRzrlE8endIndexABsSlRzrlE0D0Vyx_GvpMV
960960
Added: _$ss18EnumeratedSequenceVsSlRzrlEySi6offset_7ElementQz7elementtABsSlRzrlE5IndexVyx_GcipMV
961961
Added: _$ss18EnumeratedSequenceVyxGSKsSkRzrlMc
962+
963+
// Indirect tagged string creation
964+
Added: __swift_stdlib_CreateIndirectTaggedPointerString

test/stdlib/StringBridge.swift

+28
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,34 @@ StringBridgeTests.test("Constant NSString New SPI") {
7777
}
7878
}
7979

80+
StringBridgeTests.test("Shared String SPI")
81+
.require(.stdlib_6_2)
82+
.code {
83+
guard #available(SwiftStdlib 6.2, *) else { return }
84+
func test(literal: String, isASCII: Bool) {
85+
let baseCount = literal.utf8.count
86+
literal.withCString { intptr in
87+
intptr.withMemoryRebound(to: UInt8.self, capacity: baseCount) { ptr in
88+
let fullBuffer = UnsafeBufferPointer(start: ptr, count: baseCount)
89+
let fullString = _SwiftCreateImmortalString_ForFoundation(
90+
buffer: fullBuffer,
91+
isASCII: isASCII
92+
)
93+
expectNotNil(fullString)
94+
let bridgedFullString = (fullString! as NSString)
95+
let fullCString = bridgedFullString.utf8String!
96+
expectEqual(baseCount, strlen(fullCString))
97+
expectEqual(strcmp(ptr, fullCString), 0)
98+
let fullCString2 = bridgedFullString.utf8String!
99+
expectEqual(fullCString, fullCString2) //if we're already terminated, we can return the contents pointer as-is
100+
withExtendedLifetime(fullString) {}
101+
}
102+
}
103+
}
104+
test(literal: "abcdefghijklmnopqrstuvwxyz", isASCII: true)
105+
test(literal: "abcdëfghijklmnopqrstuvwxyz", isASCII: false)
106+
}
107+
80108
StringBridgeTests.test("Bridging") {
81109
// Test bridging retains small string form
82110
func bridge(_ small: _SmallString) -> String {

0 commit comments

Comments
 (0)