Skip to content

Commit c5fc715

Browse files
committed
Reimplement the CF stub system using ObjC. The primary effect of this is to break the link-time dependency on the CF symbols, but it also improves performance a bit.
One additional tweak (setting the scalar-aligned bit on foreign indices) had to be made to avoid a performance regression for long non-ASCII foreign strings.
1 parent d9fd3d3 commit c5fc715

File tree

6 files changed

+172
-189
lines changed

6 files changed

+172
-189
lines changed

stdlib/public/SwiftShims/CoreFoundationShims.h

Lines changed: 1 addition & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -55,44 +55,6 @@ typedef unsigned char _swift_shims_Boolean;
5555
typedef __swift_uint16_t _swift_shims_UniChar;
5656
typedef __swift_uint8_t _swift_shims_UInt8;
5757

58-
// Buffer is nullable in case the string is zero-length.
59-
SWIFT_RUNTIME_STDLIB_API
60-
void _swift_stdlib_CFStringGetCharacters(
61-
_swift_shims_CFStringRef _Nonnull theString, _swift_shims_CFRange range,
62-
_swift_shims_UniChar *_Nullable buffer);
63-
64-
SWIFT_RUNTIME_STDLIB_API
65-
_swift_shims_CFIndex _swift_stdlib_CFStringGetBytes(
66-
_swift_shims_CFStringRef _Nonnull theString, _swift_shims_CFRange range,
67-
_swift_shims_CFStringEncoding encoding, _swift_shims_UInt8 lossByte,
68-
_swift_shims_Boolean isExternalRepresentation,
69-
_swift_shims_UInt8 *_Nonnull buffer, _swift_shims_CFIndex maxBufLen,
70-
_swift_shims_CFIndex *_Nullable usedBufLen);
71-
72-
SWIFT_RUNTIME_STDLIB_API
73-
const _swift_shims_UniChar *_Nullable _swift_stdlib_CFStringGetCharactersPtr(
74-
_swift_shims_CFStringRef _Nonnull theString);
75-
76-
SWIFT_RUNTIME_STDLIB_API
77-
_swift_shims_CFIndex _swift_stdlib_CFStringGetLength(
78-
_swift_shims_CFStringRef _Nonnull theString);
79-
80-
SWIFT_RUNTIME_STDLIB_API
81-
__attribute__((ns_returns_retained))
82-
_swift_shims_CFStringRef _Nonnull _swift_stdlib_CFStringCreateWithSubstring(
83-
const void * _Nullable unused,
84-
_swift_shims_CFStringRef _Nonnull str, _swift_shims_CFRange range);
85-
86-
SWIFT_RUNTIME_STDLIB_API
87-
_swift_shims_UniChar _swift_stdlib_CFStringGetCharacterAtIndex(
88-
_swift_shims_CFStringRef _Nonnull theString, _swift_shims_CFIndex idx);
89-
90-
SWIFT_RUNTIME_STDLIB_API
91-
__attribute__((ns_returns_retained))
92-
_swift_shims_CFStringRef _Nonnull _swift_stdlib_CFStringCreateCopy(
93-
const void * _Nullable unused,
94-
_swift_shims_CFStringRef _Nonnull theString);
95-
9658
SWIFT_RUNTIME_STDLIB_API
9759
__attribute__((ns_returns_retained))
9860
_swift_shims_CFStringRef _Nonnull _swift_stdlib_CFStringCreateWithBytes(
@@ -109,12 +71,7 @@ const char *_Nullable _swift_stdlib_CFStringGetCStringPtr(
10971
SWIFT_RUNTIME_STDLIB_API
11072
_swift_shims_CFStringRef _Nonnull _swift_stdlib_objcDebugDescription(
11173
id _Nonnull nsObject);
112-
113-
SWIFT_RUNTIME_STDLIB_API
114-
_swift_shims_CFComparisonResult _swift_stdlib_CFStringCompare(
115-
_swift_shims_CFStringRef _Nonnull string,
116-
_swift_shims_CFStringRef _Nonnull string2);
117-
74+
11875
SWIFT_RUNTIME_STDLIB_API
11976
__swift_uint8_t _swift_stdlib_isNSString(id _Nonnull obj);
12077

@@ -137,10 +94,6 @@ _swift_stdlib_NSStringGetCStringTrampoline(id _Nonnull obj,
13794
_swift_shims_UInt8 *_Nonnull buffer,
13895
_swift_shims_CFIndex maxLength,
13996
unsigned long encoding);
140-
141-
SWIFT_RUNTIME_STDLIB_API
142-
__swift_uintptr_t
143-
_swift_stdlib_unsafeAddressOfClass(id _Nonnull obj);
14497

14598
#endif // __OBJC2__
14699

stdlib/public/core/StringBridge.swift

Lines changed: 166 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,113 @@ import SwiftShims
1717
internal typealias _CocoaString = AnyObject
1818

1919
#if _runtime(_ObjC)
20+
2021
// Swift's String bridges NSString via this protocol and these
2122
// variables, allowing the core stdlib to remain decoupled from
2223
// Foundation.
2324

25+
@objc internal protocol _StringSelectorHolder : _NSCopying {
26+
27+
@objc var length: Int { get }
28+
29+
@objc var hash: UInt { get }
30+
31+
@objc(characterAtIndex:)
32+
func character(at offset: Int) -> UInt16
33+
34+
@objc(getCharacters:range:)
35+
func getCharacters(
36+
_ buffer: UnsafeMutablePointer<UInt16>, range aRange: _SwiftNSRange
37+
)
38+
39+
@objc(_fastCStringContents:)
40+
func _fastCStringContents(
41+
_ requiresNulTermination: Int8
42+
) -> UnsafePointer<CChar>?
43+
44+
@objc(_fastCharacterContents)
45+
func _fastCharacterContents() -> UnsafePointer<UInt16>?
46+
47+
@objc(getBytes:maxLength:usedLength:encoding:options:range:remainingRange:)
48+
func getBytes(_ buffer: UnsafeMutableRawPointer?,
49+
maxLength maxBufferCount: Int,
50+
usedLength usedBufferCount: UnsafeMutablePointer<Int>?,
51+
encoding: UInt,
52+
options: UInt,
53+
range: _SwiftNSRange,
54+
remaining leftover: UnsafeMutablePointer<_SwiftNSRange>?) -> Int8
55+
56+
@objc(compare:options:range:locale:)
57+
func compare(_ string: _CocoaString,
58+
options: UInt,
59+
range: _SwiftNSRange,
60+
locale: AnyObject?) -> Int
61+
}
62+
63+
/*
64+
Passing a _CocoaString through _objc() lets you call ObjC methods that the
65+
compiler doesn't know about, via the protocol above. In order to get good
66+
performance, you need a double indirection like this:
67+
68+
func a -> _objc -> func a'
69+
70+
because any refcounting @_effects on 'a' will be lost when _objc breaks ARC's
71+
knowledge that the _CocoaString and _StringSelectorHolder are the same object.
72+
*/
73+
@inline(__always)
74+
internal func _objc(_ str: _CocoaString) -> _StringSelectorHolder {
75+
return unsafeBitCast(str, to: _StringSelectorHolder.self)
76+
}
77+
78+
@_effects(releasenone)
79+
private func _copyNSString(_ str: _StringSelectorHolder) -> _CocoaString {
80+
return str.copy(with: nil)
81+
}
82+
2483
@usableFromInline // @testable
2584
@_effects(releasenone)
2685
internal func _stdlib_binary_CFStringCreateCopy(
2786
_ source: _CocoaString
2887
) -> _CocoaString {
29-
let result = _swift_stdlib_CFStringCreateCopy(nil, source) as AnyObject
30-
return result
88+
return _copyNSString(_objc(source))
89+
}
90+
91+
@_effects(readonly)
92+
private func _NSStringLen(_ str: _StringSelectorHolder) -> Int {
93+
return str.length
3194
}
3295

3396
@usableFromInline // @testable
3497
@_effects(readonly)
3598
internal func _stdlib_binary_CFStringGetLength(
3699
_ source: _CocoaString
37100
) -> Int {
38-
return _swift_stdlib_CFStringGetLength(source)
101+
return _NSStringLen(_objc(source))
102+
}
103+
104+
@_effects(readonly)
105+
private func _NSStringCharactersPtr(_ str: _StringSelectorHolder) -> UnsafeMutablePointer<UTF16.CodeUnit>? {
106+
return UnsafeMutablePointer(mutating: str._fastCharacterContents())
39107
}
40108

41109
@usableFromInline // @testable
42110
@_effects(readonly)
43111
internal func _stdlib_binary_CFStringGetCharactersPtr(
44112
_ source: _CocoaString
45113
) -> UnsafeMutablePointer<UTF16.CodeUnit>? {
46-
return UnsafeMutablePointer(
47-
mutating: _swift_stdlib_CFStringGetCharactersPtr(source))
114+
return _NSStringCharactersPtr(_objc(source))
115+
}
116+
117+
@_effects(releasenone)
118+
private func _NSStringGetCharacters(
119+
from source: _StringSelectorHolder,
120+
range: Range<Int>,
121+
into destination: UnsafeMutablePointer<UTF16.CodeUnit>
122+
) {
123+
source.getCharacters(destination, range: _SwiftNSRange(
124+
location: range.startIndex,
125+
length: range.count)
126+
)
48127
}
49128

50129
/// Copies a slice of a _CocoaString into contiguous storage of sufficient
@@ -55,68 +134,99 @@ internal func _cocoaStringCopyCharacters(
55134
range: Range<Int>,
56135
into destination: UnsafeMutablePointer<UTF16.CodeUnit>
57136
) {
58-
_swift_stdlib_CFStringGetCharacters(
59-
source,
60-
_swift_shims_CFRange(location: range.lowerBound, length: range.count),
61-
destination)
137+
_NSStringGetCharacters(from: _objc(source), range: range, into: destination)
138+
}
139+
140+
@_effects(readonly)
141+
private func _NSStringGetCharacter(
142+
_ target: _StringSelectorHolder, _ position: Int
143+
) -> UTF16.CodeUnit {
144+
return target.character(at: position)
62145
}
63146

64147
@_effects(readonly)
65148
internal func _cocoaStringSubscript(
66149
_ target: _CocoaString, _ position: Int
67150
) -> UTF16.CodeUnit {
68-
let cfSelf: _swift_shims_CFStringRef = target
69-
return _swift_stdlib_CFStringGetCharacterAtIndex(cfSelf, position)
151+
return _NSStringGetCharacter(_objc(target), position)
70152
}
71153

72154
@_effects(releasenone)
73-
internal func _cocoaStringCopyUTF8(
74-
_ target: _CocoaString,
155+
private func _NSStringCopyUTF8(
156+
_ o: _StringSelectorHolder,
75157
into bufPtr: UnsafeMutableBufferPointer<UInt8>
76158
) -> Int? {
77159
let ptr = bufPtr.baseAddress._unsafelyUnwrappedUnchecked
78-
let len = _stdlib_binary_CFStringGetLength(target)
79-
var count = 0
80-
let converted = _swift_stdlib_CFStringGetBytes(
81-
target,
82-
_swift_shims_CFRange(location: 0, length: len),
83-
kCFStringEncodingUTF8,
84-
0,
85-
0,
160+
let len = o.length
161+
var remainingRange = _SwiftNSRange(location: 0, length: 0)
162+
var usedLen = 0
163+
let success = 0 != o.getBytes(
86164
ptr,
87-
bufPtr.count,
88-
&count
165+
maxLength: bufPtr.count,
166+
usedLength: &usedLen,
167+
encoding: _cocoaUTF8Encoding,
168+
options: 0,
169+
range: _SwiftNSRange(location: 0, length: len),
170+
remaining: &remainingRange
89171
)
90-
return len == converted ? count : nil
172+
if success && remainingRange.length == 0 {
173+
return usedLen
174+
}
175+
return nil
91176
}
92177

93-
@_effects(readonly)
94-
internal func _cocoaStringUTF8Count(
178+
@_effects(releasenone)
179+
internal func _cocoaStringCopyUTF8(
95180
_ target: _CocoaString,
181+
into bufPtr: UnsafeMutableBufferPointer<UInt8>
182+
) -> Int? {
183+
return _NSStringCopyUTF8(_objc(target), into: bufPtr)
184+
}
185+
186+
@_effects(readonly)
187+
private func _NSStringUTF8Count(
188+
_ o: _StringSelectorHolder,
96189
range: Range<Int>
97190
) -> Int? {
98-
var count = 0
99-
let len = _stdlib_binary_CFStringGetLength(target)
100-
let converted = _swift_stdlib_CFStringGetBytes(
101-
target,
102-
_swift_shims_CFRange(location: range.startIndex, length: range.count),
103-
kCFStringEncodingUTF8,
104-
0,
105-
0,
191+
var remainingRange = _SwiftNSRange(location: 0, length: 0)
192+
var usedLen = 0
193+
let success = 0 != o.getBytes(
106194
UnsafeMutablePointer<UInt8>(Builtin.inttoptr_Word(0._builtinWordValue)),
107-
0,
108-
&count
195+
maxLength: 0,
196+
usedLength: &usedLen,
197+
encoding: _cocoaUTF8Encoding,
198+
options: 0,
199+
range: _SwiftNSRange(location: range.startIndex, length: range.count),
200+
remaining: &remainingRange
109201
)
110-
return converted == len ? count : nil
202+
if success && remainingRange.length == 0 {
203+
return usedLen
204+
}
205+
return nil
206+
}
207+
208+
@_effects(readonly)
209+
internal func _cocoaStringUTF8Count(
210+
_ target: _CocoaString,
211+
range: Range<Int>
212+
) -> Int? {
213+
return _NSStringUTF8Count(_objc(target), range: range)
214+
}
215+
216+
@_effects(readonly)
217+
private func _NSStringCompare(
218+
_ o: _StringSelectorHolder, _ other: _CocoaString
219+
) -> Int {
220+
let range = _SwiftNSRange(location: 0, length: o.length)
221+
let options = UInt(2) /* NSLiteralSearch*/
222+
return o.compare(other, options: options, range: range, locale: nil)
111223
}
112224

113225
@_effects(readonly)
114226
internal func _cocoaStringCompare(
115227
_ string: _CocoaString, _ other: _CocoaString
116228
) -> Int {
117-
let cfSelf: _swift_shims_CFStringRef = string
118-
let cfOther: _swift_shims_CFStringRef = other
119-
return _swift_stdlib_CFStringCompare(cfSelf, cfOther)
229+
return _NSStringCompare(_objc(string), other)
120230
}
121231

122232
@_effects(readonly)
@@ -196,33 +306,30 @@ internal enum _KnownCocoaString {
196306
}
197307

198308
#if !(arch(i386) || arch(arm))
309+
199310
// Resiliently write a tagged _CocoaString's contents into a buffer.
311+
// TODO: move this to the Foundation overlay and reimplement it with
312+
// _NSTaggedPointerStringGetBytes
200313
@_effects(releasenone) // @opaque
201314
internal func _bridgeTagged(
202315
_ cocoa: _CocoaString,
203316
intoUTF8 bufPtr: UnsafeMutableBufferPointer<UInt8>
204317
) -> Int? {
205318
_internalInvariant(_isObjCTaggedPointer(cocoa))
206-
let ptr = bufPtr.baseAddress._unsafelyUnwrappedUnchecked
207-
let length = _stdlib_binary_CFStringGetLength(cocoa)
208-
_internalInvariant(length <= _SmallString.capacity)
209-
var count = 0
210-
let numCharWritten = _swift_stdlib_CFStringGetBytes(
211-
cocoa, _swift_shims_CFRange(location: 0, length: length),
212-
kCFStringEncodingUTF8, 0, 0, ptr, bufPtr.count, &count)
213-
return length == numCharWritten ? count : nil
319+
return _cocoaStringCopyUTF8(cocoa, into: bufPtr)
214320
}
215321
#endif
216322

217-
@_effects(releasenone) // @opaque
218-
internal func _cocoaUTF8Pointer(_ str: _CocoaString) -> UnsafePointer<UInt8>? {
219-
// TODO(String bridging): Is there a better interface here? This requires nul
220-
// termination and may assume ASCII.
221-
guard let ptr = _swift_stdlib_CFStringGetCStringPtr(
222-
str, kCFStringEncodingUTF8
223-
) else { return nil }
323+
@_effects(readonly)
324+
private func _NSStringASCIIPointer(_ str: _StringSelectorHolder) -> UnsafePointer<UInt8>? {
325+
// TODO(String bridging): Is there a better interface here? Ideally we'd be
326+
// able to ask for UTF8 rather than just ASCII
327+
return str._fastCStringContents(0)?._asUInt8
328+
}
224329

225-
return ptr._asUInt8
330+
@_effects(readonly) // @opaque
331+
internal func _cocoaASCIIPointer(_ str: _CocoaString) -> UnsafePointer<UInt8>? {
332+
return _NSStringASCIIPointer(_objc(str))
226333
}
227334

228335
private enum CocoaStringPointer {
@@ -236,11 +343,11 @@ private enum CocoaStringPointer {
236343
private func _getCocoaStringPointer(
237344
_ cfImmutableValue: _CocoaString
238345
) -> CocoaStringPointer {
239-
if let utf8Ptr = _cocoaUTF8Pointer(cfImmutableValue) {
346+
if let asciiPtr = _cocoaASCIIPointer(cfImmutableValue) {
240347
// NOTE: CFStringGetCStringPointer means ASCII
241-
return .ascii(utf8Ptr)
348+
return .ascii(asciiPtr)
242349
}
243-
if let utf16Ptr = _swift_stdlib_CFStringGetCharactersPtr(cfImmutableValue) {
350+
if let utf16Ptr = _stdlib_binary_CFStringGetCharactersPtr(cfImmutableValue) {
244351
return .utf16(utf16Ptr)
245352
}
246353
return .none

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 _cocoaUTF8Pointer(cocoaObject)._unsafelyUnwrappedUnchecked
804+
return _cocoaASCIIPointer(cocoaObject)._unsafelyUnwrappedUnchecked
805805
}
806806
#endif
807807

0 commit comments

Comments
 (0)