Skip to content

Commit 5ad4b15

Browse files
committed
Avoid attempting to create SmallStrings for constant tagged CFStrings
1 parent afc963a commit 5ad4b15

File tree

5 files changed

+161
-40
lines changed

5 files changed

+161
-40
lines changed

stdlib/public/SwiftShims/CoreFoundationShims.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ typedef unsigned char _swift_shims_Boolean;
4040
typedef __swift_uint8_t _swift_shims_UInt8;
4141
typedef __swift_uint32_t _swift_shims_CFStringEncoding;
4242

43+
/* This is layout-compatible with constant CFStringRefs on Darwin */
44+
typedef struct __swift_shims_builtin_CFString {
45+
const void * _Nonnull isa; // point to __CFConstantStringClassReference
46+
unsigned long flags;
47+
const __swift_uint8_t * _Nonnull str;
48+
unsigned long length;
49+
} _swift_shims_builtin_CFString;
50+
4351
SWIFT_RUNTIME_STDLIB_API
4452
__swift_uint8_t _swift_stdlib_isNSString(id _Nonnull obj);
4553

@@ -62,6 +70,10 @@ _swift_stdlib_NSStringGetCStringTrampoline(id _Nonnull obj,
6270
_swift_shims_UInt8 *_Nonnull buffer,
6371
_swift_shims_CFIndex maxLength,
6472
unsigned long encoding);
73+
74+
SWIFT_RUNTIME_STDLIB_API
75+
__swift_uint8_t
76+
_swift_stdlib_dyld_is_objc_constant_string(const void * _Nonnull addr);
6577

6678
#endif // __OBJC2__
6779

stdlib/public/core/StringBridge.swift

Lines changed: 116 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,20 @@ private func _NSStringLen(_ str: _StringSelectorHolder) -> Int {
102102
internal func _stdlib_binary_CFStringGetLength(
103103
_ source: _CocoaString
104104
) -> Int {
105+
if let len = getConstantTaggedCocoaContents(source)?.utf16Length {
106+
return len
107+
}
105108
return _NSStringLen(_objc(source))
106109
}
107110

111+
@_effects(readonly)
112+
internal func _isNSString(_ str:AnyObject) -> Bool {
113+
if getConstantTaggedCocoaContents(str) != nil {
114+
return true
115+
}
116+
return _swift_stdlib_isNSString(str) != 0
117+
}
118+
108119
@_effects(readonly)
109120
private func _NSStringCharactersPtr(_ str: _StringSelectorHolder) -> UnsafeMutablePointer<UTF16.CodeUnit>? {
110121
return UnsafeMutablePointer(mutating: str._fastCharacterContents())
@@ -288,13 +299,24 @@ internal enum _KnownCocoaString {
288299
#if !(arch(i386) || arch(arm) || arch(wasm32))
289300
case tagged
290301
#endif
302+
#if arch(arm64)
303+
case constantTagged
304+
#endif
291305

292306
@inline(__always)
293307
init(_ str: _CocoaString) {
294308

295309
#if !(arch(i386) || arch(arm))
296310
if _isObjCTaggedPointer(str) {
311+
#if arch(arm64)
312+
if let _ = getConstantTaggedCocoaContents(str) {
313+
self = .constantTagged
314+
} else {
315+
self = .tagged
316+
}
317+
#else
297318
self = .tagged
319+
#endif
298320
return
299321
}
300322
#endif
@@ -333,8 +355,42 @@ private func _NSStringASCIIPointer(_ str: _StringSelectorHolder) -> UnsafePointe
333355
}
334356

335357
@_effects(readonly) // @opaque
336-
internal func _cocoaASCIIPointer(_ str: _CocoaString) -> UnsafePointer<UInt8>? {
337-
return _NSStringASCIIPointer(_objc(str))
358+
private func _withCocoaASCIIPointer<R>(
359+
_ str: _CocoaString,
360+
requireStableAddress: Bool,
361+
work: (UnsafePointer<UInt8>) -> R?
362+
) -> R? {
363+
#if !(arch(i386) || arch(arm))
364+
if _isObjCTaggedPointer(str) {
365+
if let ptr = getConstantTaggedCocoaContents(str)?.asciiContentsPointer {
366+
return work(ptr)
367+
}
368+
if requireStableAddress {
369+
return nil // tagged pointer strings don't support _fastCStringContents
370+
}
371+
let tmp = _StringGuts(_SmallString(taggedCocoa: str))
372+
return tmp.withFastUTF8 { work($0.baseAddress._unsafelyUnwrappedUnchecked) }
373+
}
374+
#endif
375+
defer { _fixLifetime(str) }
376+
if let ptr = _NSStringASCIIPointer(_objc(str)) {
377+
return work(ptr)
378+
}
379+
return nil
380+
}
381+
382+
@_effects(readonly) // @opaque
383+
internal func withCocoaASCIIPointer<R>(
384+
_ str: _CocoaString,
385+
work: (UnsafePointer<UInt8>) -> R?
386+
) -> R? {
387+
return _withCocoaASCIIPointer(str, requireStableAddress: false, work: work)
388+
}
389+
390+
@_effects(readonly)
391+
internal func stableCocoaASCIIPointer(_ str: _CocoaString)
392+
-> UnsafePointer<UInt8>? {
393+
return _withCocoaASCIIPointer(str, requireStableAddress: true, work: { $0 })
338394
}
339395

340396
private enum CocoaStringPointer {
@@ -348,16 +404,61 @@ private enum CocoaStringPointer {
348404
private func _getCocoaStringPointer(
349405
_ cfImmutableValue: _CocoaString
350406
) -> CocoaStringPointer {
351-
if let asciiPtr = _cocoaASCIIPointer(cfImmutableValue) {
352-
// NOTE: CFStringGetCStringPointer means ASCII
353-
return .ascii(asciiPtr)
407+
if let ascii = stableCocoaASCIIPointer(cfImmutableValue) {
408+
return .ascii(ascii)
354409
}
355410
if let utf16Ptr = _stdlib_binary_CFStringGetCharactersPtr(cfImmutableValue) {
356411
return .utf16(utf16Ptr)
357412
}
358413
return .none
359414
}
360415

416+
417+
@inline(__always)
418+
private func getConstantTaggedCocoaContents(_ cocoaString: _CocoaString) ->
419+
(utf16Length: Int, asciiContentsPointer: UnsafePointer<UInt8>?)? {
420+
#if !arch(arm64)
421+
return nil
422+
#else
423+
424+
guard _isObjCTaggedPointer(cocoaString) else {
425+
return nil
426+
}
427+
428+
let taggedValue = unsafeBitCast(cocoaString, to: UInt.self)
429+
430+
//11000000..payload..111
431+
let tagMask:UInt =
432+
0b1111_1111_1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0111
433+
let expectedValue:UInt =
434+
0b1100_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0111
435+
436+
guard taggedValue & tagMask == expectedValue else {
437+
return nil
438+
}
439+
440+
guard _swift_stdlib_dyld_is_objc_constant_string(
441+
cocoaString as! UnsafeRawPointer
442+
) == 1 else {
443+
return nil
444+
}
445+
446+
let payloadMask = ~tagMask
447+
let payload = taggedValue & payloadMask
448+
let ivarPointer = UnsafePointer<_swift_shims_builtin_CFString>(
449+
bitPattern: payload
450+
)!
451+
let length = ivarPointer.pointee.length
452+
let isUTF16Mask:UInt = 0x0000_0000_0000_0004 //CFStringFlags bit 4: isUnicode
453+
let isASCII = ivarPointer.pointee.flags & isUTF16Mask == 0
454+
let contentsPtr = ivarPointer.pointee.str
455+
return (
456+
utf16Length: Int(length),
457+
asciiContentsPointer: isASCII ? contentsPtr : nil
458+
)
459+
#endif
460+
}
461+
361462
@usableFromInline
362463
@_effects(releasenone) // @opaque
363464
internal func _bridgeCocoaString(_ cocoaString: _CocoaString) -> _StringGuts {
@@ -371,6 +472,16 @@ internal func _bridgeCocoaString(_ cocoaString: _CocoaString) -> _StringGuts {
371472
#if !(arch(i386) || arch(arm))
372473
case .tagged:
373474
return _StringGuts(_SmallString(taggedCocoa: cocoaString))
475+
#if arch(arm64)
476+
case .constantTagged:
477+
let taggedContents = getConstantTaggedCocoaContents(cocoaString)!
478+
return _StringGuts(
479+
cocoa: cocoaString,
480+
providesFastUTF8: false, //TODO: if contentsPtr is UTF8 compatible, use it
481+
isASCII: taggedContents.asciiContentsPointer != nil,
482+
length: taggedContents.utf16Length
483+
)
484+
#endif
374485
#endif
375486
case .cocoa:
376487
// "Copy" it into a value to be sure nobody will modify behind

stdlib/public/core/StringObject.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ extension _StringObject {
801801
_internalInvariant(largeFastIsShared)
802802
#if _runtime(_ObjC)
803803
if largeIsCocoa {
804-
return _cocoaASCIIPointer(cocoaObject)._unsafelyUnwrappedUnchecked
804+
return stableCocoaASCIIPointer(cocoaObject)._unsafelyUnwrappedUnchecked
805805
}
806806
#endif
807807

stdlib/public/core/StringStorageBridge.swift

Lines changed: 15 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,6 @@ import SwiftShims
1414

1515
#if _runtime(_ObjC)
1616

17-
@_effects(readonly)
18-
private func _isNSString(_ str:AnyObject) -> UInt8 {
19-
return _swift_stdlib_isNSString(str)
20-
}
21-
2217
internal let _cocoaASCIIEncoding:UInt = 1 /* NSASCIIStringEncoding */
2318
internal let _cocoaUTF8Encoding:UInt = 4 /* NSUTF8StringEncoding */
2419

@@ -96,57 +91,43 @@ extension _AbstractStringStorage {
9691
// Handle the case where both strings were bridged from Swift.
9792
// We can't use String.== because it doesn't match NSString semantics.
9893
let knownOther = _KnownCocoaString(other)
99-
var otherIsTagged = false
10094
switch knownOther {
10195
case .storage:
10296
return _nativeIsEqual(
10397
_unsafeUncheckedDowncast(other, to: __StringStorage.self))
10498
case .shared:
10599
return _nativeIsEqual(
106100
_unsafeUncheckedDowncast(other, to: __SharedStringStorage.self))
107-
#if !(arch(i386) || arch(arm))
108-
case .tagged:
109-
// Tagged means ASCII. If we're equal, our UTF-8 length is the same as our
110-
// UTF-16 length, so just compare the UTF-8 length (which is faster).
111-
otherIsTagged = true
112-
fallthrough
113-
#endif
114-
case .cocoa:
115-
// We're allowed to crash, but for compatibility reasons NSCFString allows
101+
default:
102+
// We're allowed to crash, but for compatibility reasons NSCFString allows
116103
// non-strings here.
117-
if _isNSString(other) != 1 {
104+
if !_isNSString(other) {
118105
return 0
119106
}
120-
// At this point we've proven that it is an NSString of some sort, but not
121-
// one of ours.
122-
123-
defer { _fixLifetime(other) }
124-
125107

108+
// At this point we've proven that it is a non-Swift NSString
126109
let otherUTF16Length = _stdlib_binary_CFStringGetLength(other)
127-
if otherIsTagged && self.count != otherUTF16Length {
128-
return 0
129-
}
130110

131111
// CFString will only give us ASCII bytes here, but that's fine.
132112
// We already handled non-ASCII UTF8 strings earlier since they're Swift.
133-
if let otherStart = _cocoaASCIIPointer(other) {
134-
//We know that otherUTF16Length is also its byte count at this point
135-
if count != otherUTF16Length {
136-
return 0
113+
if let asciiEqual = withCocoaASCIIPointer(other, work: { (ascii) -> Bool in
114+
// UTF16 length == UTF8 length iff ASCII
115+
if otherUTF16Length == self.count {
116+
return (start == ascii || (memcmp(start, ascii, self.count) == 0))
137117
}
138-
return (start == otherStart ||
139-
(memcmp(start, otherStart, count) == 0)) ? 1 : 0
118+
return false
119+
}) {
120+
return asciiEqual ? 1 : 0
140121
}
141122

142123
if self.UTF16Length != otherUTF16Length {
143124
return 0
144125
}
145126

146127
/*
147-
The abstract implementation of -isEqualToString: falls back to -compare:
148-
immediately, so when we run out of fast options to try, do the same.
149-
We can likely be more clever here if need be
128+
The abstract implementation of -isEqualToString: falls back to -compare:
129+
immediately, so when we run out of fast options to try, do the same.
130+
We can likely be more clever here if need be
150131
*/
151132
return _cocoaStringCompare(self, other) == 0 ? 1 : 0
152133
}
@@ -345,4 +326,4 @@ extension __SharedStringStorage {
345326
}
346327
}
347328

348-
#endif // _runtime(_ObjC)
329+
#endif // _runtime(_ObjC)

stdlib/public/stubs/FoundationHelpers.mm

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,18 @@
2525
#include "swift/Runtime/Once.h"
2626
#include <dlfcn.h>
2727

28+
typedef enum {
29+
dyld_objc_string_kind
30+
} DyldObjCConstantKind;
31+
2832
using namespace swift;
2933

3034
static CFHashCode(*_CFStringHashCString)(const uint8_t *bytes, CFIndex len);
3135
static CFHashCode(*_CFStringHashNSString)(id str);
3236
static CFTypeID(*_CFGetTypeID)(CFTypeRef obj);
3337
static CFTypeID _CFStringTypeID = 0;
38+
static bool(*dyld_is_objc_constant)(DyldObjCConstantKind kind,
39+
const void *addr);
3440
static swift_once_t initializeBridgingFuncsOnce;
3541

3642
static void _initializeBridgingFunctionsImpl(void *ctxt) {
@@ -46,6 +52,11 @@ static void _initializeBridgingFunctionsImpl(void *ctxt) {
4652
_CFStringHashCString = (CFHashCode(*)(const uint8_t *, CFIndex))dlsym(
4753
RTLD_DEFAULT,
4854
"CFStringHashCString");
55+
if (dlsym(RTLD_NEXT, "objc_debug_tag60_permutations") /* tagged constant strings available */) {
56+
dyld_is_objc_constant = (bool(*)(DyldObjCConstantKind, const void *))dlsym(
57+
RTLD_NEXT,
58+
"_dyld_is_objc_constant");
59+
}
4960
}
5061

5162
static inline void initializeBridgingFunctions() {
@@ -99,5 +110,11 @@ typedef __swift_uint8_t (*getCStringImplPtr)(id,
99110

100111
}
101112

113+
__swift_uint8_t
114+
swift::_swift_stdlib_dyld_is_objc_constant_string(const void *addr) {
115+
if (!dyld_is_objc_constant) return false;
116+
return dyld_is_objc_constant(dyld_objc_string_kind, addr) ? 1 : 0;
117+
}
118+
102119
#endif
103120

0 commit comments

Comments
 (0)