Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,12 @@ _swift_stdlib_NSStringGetCStringTrampoline(id _Nonnull obj,
SWIFT_RUNTIME_STDLIB_API
__swift_uint8_t
_swift_stdlib_dyld_is_objc_constant_string(const void * _Nonnull addr);


SWIFT_RUNTIME_STDLIB_API
const void * _Nullable
_swift_stdlib_CreateIndirectTaggedPointerString(const __swift_uint8_t * _Nonnull bytes,
_swift_shims_CFIndex len);

#endif // __OBJC2__

#ifdef __cplusplus
Expand Down
18 changes: 16 additions & 2 deletions stdlib/public/core/StringBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ private func _NSStringCharactersPtr(_ str: _StringSelectorHolder) -> UnsafeMutab
return unsafe UnsafeMutablePointer(mutating: str._fastCharacterContents())
}

private func _stdlib_binary_createIndirectTaggedPointerNSString(
ptr: UnsafePointer<UInt8>,
count: Int
) -> UnsafeRawPointer? {
return _swift_stdlib_CreateIndirectTaggedPointerString(ptr, count);
}

@usableFromInline // @testable
@_effects(readonly)
internal func _stdlib_binary_CFStringGetCharactersPtr(
Expand Down Expand Up @@ -623,8 +630,15 @@ extension String {
return copy._bridgeToObjectiveCImpl()
}
if _guts._object.isImmortal && !_guts._object.largeFastIsConstantCocoa {
// TODO: We'd rather emit a valid ObjC object statically than create a
// shared string class instance.
if _guts.isASCII && _guts._object.isFastZeroTerminated {
let ptr = _guts._object.fastUTF8.baseAddress!
let count = _guts.count
if let indirect = _stdlib_binary_createIndirectTaggedPointerNSString(
ptr: ptr, count: count
) {
return unsafeBitCast(indirect, to: AnyObject.self)
}
}
let gutsCountAndFlags = _guts._object._countAndFlags
return unsafe __SharedStringStorage(
immortal: _guts._object.fastUTF8.baseAddress!,
Expand Down
33 changes: 33 additions & 0 deletions stdlib/public/stubs/FoundationHelpers.mm
Original file line number Diff line number Diff line change
Expand Up @@ -112,5 +112,38 @@ typedef __swift_uint8_t (*getCStringImplPtr)(id,
&& SWIFT_RUNTIME_WEAK_USE(_dyld_is_objc_constant(dyld_objc_string_kind, addr))) ? 1 : 0;
}

typedef const void * _Nullable (*createIndirectTaggedImplPtr)(id,
SEL,
const _swift_shims_UInt8 * _Nonnull,
_swift_shims_CFIndex);
static swift_once_t lookUpIndirectTaggedStringCreationOnce;
static createIndirectTaggedImplPtr createIndirectTaggedString;
static Class indirectTaggedStringClass;

static void lookUpIndirectTaggedStringCreationOnceImpl(void *ctxt) {
Class cls = objc_lookUpClass("NSIndirectTaggedPointerString");
if (!cls) return;
SEL sel = @selector(newIndirectTaggedNSStringWithConstantNullTerminatedASCIIBytes_:length_:);
Method m = class_getClassMethod(cls, sel);
if (!m) return;
createIndirectTaggedString = (createIndirectTaggedImplPtr)method_getImplementation(m);
indirectTaggedStringClass = cls;
}

SWIFT_RUNTIME_STDLIB_API
const void *
_swift_stdlib_CreateIndirectTaggedPointerString(const __swift_uint8_t *bytes,
_swift_shims_CFIndex len) {
swift_once(&lookUpIndirectTaggedStringCreationOnce,
lookUpIndirectTaggedStringCreationOnceImpl,
nullptr);

if (indirectTaggedStringClass) {
SEL sel = @selector(newIndirectTaggedNSStringWithConstantNullTerminatedASCIIBytes_:length_:);
return createIndirectTaggedString(indirectTaggedStringClass, sel, bytes, len);
}
return NULL;
}

#endif

3 changes: 3 additions & 0 deletions test/abi/macOS/arm64/stdlib.swift
Original file line number Diff line number Diff line change
Expand Up @@ -959,3 +959,6 @@ Added: _$ss18EnumeratedSequenceVsSlRzrlE7isEmptySbvpMV
Added: _$ss18EnumeratedSequenceVsSlRzrlE8endIndexABsSlRzrlE0D0Vyx_GvpMV
Added: _$ss18EnumeratedSequenceVsSlRzrlEySi6offset_7ElementQz7elementtABsSlRzrlE5IndexVyx_GcipMV
Added: _$ss18EnumeratedSequenceVyxGSKsSkRzrlMc

// Indirect tagged string creation
Added: __swift_stdlib_CreateIndirectTaggedPointerString
3 changes: 3 additions & 0 deletions test/abi/macOS/x86_64/stdlib.swift
Original file line number Diff line number Diff line change
Expand Up @@ -959,3 +959,6 @@ Added: _$ss18EnumeratedSequenceVsSlRzrlE7isEmptySbvpMV
Added: _$ss18EnumeratedSequenceVsSlRzrlE8endIndexABsSlRzrlE0D0Vyx_GvpMV
Added: _$ss18EnumeratedSequenceVsSlRzrlEySi6offset_7ElementQz7elementtABsSlRzrlE5IndexVyx_GcipMV
Added: _$ss18EnumeratedSequenceVyxGSKsSkRzrlMc

// Indirect tagged string creation
Added: __swift_stdlib_CreateIndirectTaggedPointerString
28 changes: 28 additions & 0 deletions test/stdlib/StringBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,34 @@ StringBridgeTests.test("Constant NSString New SPI") {
}
}

StringBridgeTests.test("Shared String SPI")
.require(.stdlib_6_2)
.code {
guard #available(SwiftStdlib 6.2, *) else { return }
func test(literal: String, isASCII: Bool) {
let baseCount = literal.utf8.count
literal.withCString { intptr in
intptr.withMemoryRebound(to: UInt8.self, capacity: baseCount) { ptr in
let fullBuffer = UnsafeBufferPointer(start: ptr, count: baseCount)
let fullString = _SwiftCreateImmortalString_ForFoundation(
buffer: fullBuffer,
isASCII: isASCII
)
expectNotNil(fullString)
let bridgedFullString = (fullString! as NSString)
let fullCString = bridgedFullString.utf8String!
expectEqual(baseCount, strlen(fullCString))
expectEqual(strcmp(ptr, fullCString), 0)
let fullCString2 = bridgedFullString.utf8String!
expectEqual(fullCString, fullCString2) //if we're already terminated, we can return the contents pointer as-is
withExtendedLifetime(fullString) {}
}
}
}
test(literal: "abcdefghijklmnopqrstuvwxyz", isASCII: true)
test(literal: "abcdëfghijklmnopqrstuvwxyz", isASCII: false)
}

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