Skip to content

Commit e69d4fe

Browse files
GankraAlexis Beingessner
authored andcommitted
make String bridging eager
1 parent 7d9cd11 commit e69d4fe

File tree

4 files changed

+59
-163
lines changed

4 files changed

+59
-163
lines changed

stdlib/public/core/ObjCMirrors.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func _getObjCChild<T>(_: Int, _: _MagicMirrorData) -> (T, _Mirror)
2020

2121
func _getObjCSummary(_ data: _MagicMirrorData) -> String {
2222
let theDescription = _swift_stdlib_objcDebugDescription(data._loadValue(ofType: AnyObject.self)) as AnyObject
23-
return _cocoaStringToSwiftString_NonASCII(theDescription)
23+
return String(_cocoaString: theDescription)
2424
}
2525

2626
public // SPI(runtime)

stdlib/public/core/ShadowProtocols.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,36 @@ public protocol _NSNumber {
182182
var objCType: UnsafePointer<Int8> { get }
183183
}
184184

185+
/// A shadow for the API of NSString we will use in the core stdlib.
186+
@objc
187+
public protocol _NSStringCore {
188+
func length() -> Int
189+
func characterAtIndex(_ index: Int) -> UInt16
190+
191+
func getCharacters(
192+
_ buffer: UnsafeMutablePointer<UInt16>,
193+
range aRange: _SwiftNSRange)
194+
195+
// SPI APIs
196+
func _fastCharacterContents() -> UnsafeMutablePointer<UInt16>?
197+
}
198+
199+
// See _NSSet above for why this exists
200+
@unsafe_no_objc_tagged_pointer @objc
201+
public protocol _NSString : _NSStringCore {
202+
func getCharacters(_ buffer: UnsafeMutablePointer<UInt16>)
203+
204+
// SPI APIs
205+
// TODO: undo Uint8 being used as a proxy for bool
206+
func _fastCStringContents(_ nullTerminationRequired: UInt8)
207+
-> UnsafeMutablePointer<Int8>?
208+
}
209+
185210
#else
186211

187212
public protocol _NSArrayCore {}
188213
public protocol _NSDictionaryCore {}
189214
public protocol _NSSetCore {}
215+
public protocol _NSStringCore {}
190216

191217
#endif

stdlib/public/core/String.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -624,10 +624,10 @@ extension Sequence where Iterator.Element == String {
624624

625625
#if _runtime(_ObjC)
626626
@_silgen_name("swift_stdlib_NSStringLowercaseString")
627-
func _stdlib_NSStringLowercaseString(_ str: AnyObject) -> _CocoaString
627+
func _stdlib_NSStringLowercaseString(_ str: AnyObject) -> _NSString
628628

629629
@_silgen_name("swift_stdlib_NSStringUppercaseString")
630-
func _stdlib_NSStringUppercaseString(_ str: AnyObject) -> _CocoaString
630+
func _stdlib_NSStringUppercaseString(_ str: AnyObject) -> _NSString
631631
#else
632632
internal func _nativeUnicodeLowercaseString(_ str: String) -> String {
633633
var buffer = _StringBuffer(

stdlib/public/core/StringBridge.swift

Lines changed: 30 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -20,164 +20,44 @@ import SwiftShims
2020
/// Effectively an untyped NSString that doesn't require foundation.
2121
public typealias _CocoaString = AnyObject
2222

23-
public // @testable
24-
func _stdlib_binary_CFStringCreateCopy(
25-
_ source: _CocoaString
26-
) -> _CocoaString {
27-
let result = _swift_stdlib_CFStringCreateCopy(nil, source) as AnyObject
28-
Builtin.release(result)
29-
return result
30-
}
31-
32-
public // @testable
33-
func _stdlib_binary_CFStringGetLength(
34-
_ source: _CocoaString
35-
) -> Int {
36-
return _swift_stdlib_CFStringGetLength(source)
37-
}
38-
39-
public // @testable
40-
func _stdlib_binary_CFStringGetCharactersPtr(
41-
_ source: _CocoaString
42-
) -> UnsafeMutablePointer<UTF16.CodeUnit>? {
43-
return UnsafeMutablePointer(mutating: _swift_stdlib_CFStringGetCharactersPtr(source))
44-
}
45-
46-
/// Bridges `source` to `Swift.String`, assuming that `source` has non-ASCII
47-
/// characters (does not apply ASCII optimizations).
48-
@inline(never) @_semantics("stdlib_binary_only") // Hide the CF dependency
49-
func _cocoaStringToSwiftString_NonASCII(
50-
_ source: _CocoaString
51-
) -> String {
52-
let cfImmutableValue = _stdlib_binary_CFStringCreateCopy(source)
53-
let length = _stdlib_binary_CFStringGetLength(cfImmutableValue)
54-
let start = _stdlib_binary_CFStringGetCharactersPtr(cfImmutableValue)
55-
56-
return String(_StringCore(
57-
baseAddress: start,
58-
count: length,
59-
elementShift: 1,
60-
hasCocoaBuffer: true,
61-
owner: unsafeBitCast(cfImmutableValue, to: Optional<AnyObject>.self)))
62-
}
63-
64-
/// Loading Foundation initializes these function variables
65-
/// with useful values
66-
67-
/// Produces a `_StringBuffer` from a given subrange of a source
68-
/// `_CocoaString`, having the given minimum capacity.
69-
@inline(never) @_semantics("stdlib_binary_only") // Hide the CF dependency
70-
internal func _cocoaStringToContiguous(
71-
source: _CocoaString, range: Range<Int>, minimumCapacity: Int
72-
) -> _StringBuffer {
73-
_sanityCheck(_swift_stdlib_CFStringGetCharactersPtr(source) == nil,
74-
"Known contiguously stored strings should already be converted to Swift")
75-
76-
let startIndex = range.lowerBound
77-
let count = range.upperBound - startIndex
78-
79-
let buffer = _StringBuffer(capacity: max(count, minimumCapacity),
80-
initialSize: count, elementWidth: 2)
81-
82-
_swift_stdlib_CFStringGetCharacters(
83-
source, _swift_shims_CFRange(location: startIndex, length: count),
84-
buffer.start.assumingMemoryBound(to: _swift_shims_UniChar.self))
85-
86-
return buffer
87-
}
88-
89-
/// Reads the entire contents of a _CocoaString into contiguous
90-
/// storage of sufficient capacity.
91-
@inline(never) @_semantics("stdlib_binary_only") // Hide the CF dependency
92-
internal func _cocoaStringReadAll(
93-
_ source: _CocoaString, _ destination: UnsafeMutablePointer<UTF16.CodeUnit>
94-
) {
95-
_swift_stdlib_CFStringGetCharacters(
96-
source, _swift_shims_CFRange(
97-
location: 0, length: _swift_stdlib_CFStringGetLength(source)), destination)
98-
}
99-
100-
@inline(never) @_semantics("stdlib_binary_only") // Hide the CF dependency
101-
internal func _cocoaStringSlice(
102-
_ target: _StringCore, _ bounds: Range<Int>
103-
) -> _StringCore {
104-
_sanityCheck(target.hasCocoaBuffer)
105-
106-
let cfSelf: _swift_shims_CFStringRef = target.cocoaBuffer.unsafelyUnwrapped
107-
108-
_sanityCheck(
109-
_swift_stdlib_CFStringGetCharactersPtr(cfSelf) == nil,
110-
"Known contiguously stored strings should already be converted to Swift")
111-
112-
let cfResult = _swift_stdlib_CFStringCreateWithSubstring(
113-
nil, cfSelf, _swift_shims_CFRange(
114-
location: bounds.lowerBound, length: bounds.count)) as AnyObject
115-
116-
return String(_cocoaString: cfResult)._core
117-
}
118-
119-
@_versioned
120-
@inline(never) @_semantics("stdlib_binary_only") // Hide the CF dependency
121-
internal func _cocoaStringSubscript(
122-
_ target: _StringCore, _ position: Int
123-
) -> UTF16.CodeUnit {
124-
let cfSelf: _swift_shims_CFStringRef = target.cocoaBuffer.unsafelyUnwrapped
125-
126-
_sanityCheck(_swift_stdlib_CFStringGetCharactersPtr(cfSelf) == nil,
127-
"Known contiguously stored strings should already be converted to Swift")
128-
129-
return _swift_stdlib_CFStringGetCharacterAtIndex(cfSelf, position)
130-
}
131-
132-
//
133-
// Conversion from NSString to Swift's native representation
134-
//
135-
136-
internal var kCFStringEncodingASCII : _swift_shims_CFStringEncoding {
137-
return 0x0600
138-
}
139-
14023
extension String {
14124
@inline(never) @_semantics("stdlib_binary_only") // Hide the CF dependency
14225
public // SPI(Foundation)
14326
init(_cocoaString: AnyObject) {
27+
// If the NSString is actually a Swift String in disguise,
28+
// we can just copy out the internal repr.
14429
if let wrapped = _cocoaString as? _NSContiguousString {
14530
self._core = wrapped._core
14631
return
14732
}
14833

149-
// "copy" it into a value to be sure nobody will modify behind
150-
// our backs. In practice, when value is already immutable, this
151-
// just does a retain.
152-
let cfImmutableValue
153-
= _stdlib_binary_CFStringCreateCopy(_cocoaString) as AnyObject
34+
let cocoaString = unsafeBitCast(_cocoaString, to: _NSString.self)
15435

155-
let length = _swift_stdlib_CFStringGetLength(cfImmutableValue)
36+
let length = cocoaString.length()
15637

157-
// Look first for null-terminated ASCII
158-
// Note: the code in clownfish appears to guarantee
159-
// nul-termination, but I'm waiting for an answer from Chris Kane
160-
// about whether we can count on it for all time or not.
161-
let nulTerminatedASCII = _swift_stdlib_CFStringGetCStringPtr(
162-
cfImmutableValue, kCFStringEncodingASCII)
38+
// The 0 here is `nullRequired: false` (bad hack)
39+
let nulTerminatedASCII = cocoaString._fastCStringContents(0)
16340

164-
// start will hold the base pointer of contiguous storage, if it
165-
// is found.
166-
var start: UnsafeMutableRawPointer?
16741
let isUTF16 = (nulTerminatedASCII == nil)
42+
let buffer = _StringBuffer(capacity: length,
43+
initialSize: length,
44+
elementWidth: isUTF16 ? 2 : 1)
45+
46+
// We try our darndest to just get a pointer and memcpy, falling back
47+
// to having the string do it for us with getCharacters.
16848
if isUTF16 {
169-
let utf16Buf = _swift_stdlib_CFStringGetCharactersPtr(cfImmutableValue)
170-
start = UnsafeMutableRawPointer(mutating: utf16Buf)
49+
let bufPtr = buffer.start.assumingMemoryBound(to: UInt16.self)
50+
if let utf16Ptr = cocoaString._fastCharacterContents() {
51+
bufPtr.assign(from: utf16Ptr, count: length)
52+
} else {
53+
cocoaString.getCharacters(bufPtr)
54+
}
55+
17156
} else {
172-
start = UnsafeMutableRawPointer(mutating: nulTerminatedASCII)
57+
buffer.start.copyBytes(from: nulTerminatedASCII!, count: length)
17358
}
17459

175-
self._core = _StringCore(
176-
baseAddress: start,
177-
count: length,
178-
elementShift: isUTF16 ? 1 : 0,
179-
hasCocoaBuffer: true,
180-
owner: cfImmutableValue)
60+
self._core = _StringCore(buffer)
18161
}
18262
}
18363

@@ -187,25 +67,15 @@ extension String {
18767
// The @_swift_native_objc_runtime_base attribute
18868
// This allows us to subclass an Objective-C class and use the fast Swift
18969
// memory allocator.
70+
//
71+
// Subclasses should provide:
72+
// * func length() -> Int
73+
// * func characterAtIndex(_ index: Int) -> UInt16
19074
@objc @_swift_native_objc_runtime_base(_SwiftNativeNSStringBase)
19175
public class _SwiftNativeNSString {}
19276

193-
@objc
194-
public protocol _NSStringCore :
195-
_NSCopying, _NSFastEnumeration {
196-
197-
// The following methods should be overridden when implementing an
198-
// NSString subclass.
199-
200-
func length() -> Int
201-
202-
func characterAtIndex(_ index: Int) -> UInt16
203-
204-
// We also override the following methods for efficiency.
205-
}
206-
20777
/// An `NSString` built around a slice of contiguous Swift `String` storage.
208-
public final class _NSContiguousString : _SwiftNativeNSString {
78+
public final class _NSContiguousString : _SwiftNativeNSString, _NSStringCore {
20979
public init(_ _core: _StringCore) {
21080
_sanityCheck(
21181
_core.hasContiguousStorage,
@@ -218,16 +88,16 @@ public final class _NSContiguousString : _SwiftNativeNSString {
21888
_sanityCheckFailure("init(coder:) not implemented for _NSContiguousString")
21989
}
22090

221-
func length() -> Int {
91+
public func length() -> Int {
22292
return _core.count
22393
}
22494

225-
func characterAtIndex(_ index: Int) -> UInt16 {
95+
public func characterAtIndex(_ index: Int) -> UInt16 {
22696
return _core[index]
22797
}
22898

22999
@inline(__always) // Performance: To save on reference count operations.
230-
func getCharacters(
100+
public func getCharacters(
231101
_ buffer: UnsafeMutablePointer<UInt16>,
232102
range aRange: _SwiftNSRange) {
233103

@@ -248,7 +118,7 @@ public final class _NSContiguousString : _SwiftNativeNSString {
248118
}
249119

250120
@objc
251-
func _fastCharacterContents() -> UnsafeMutablePointer<UInt16>? {
121+
public func _fastCharacterContents() -> UnsafeMutablePointer<UInt16>? {
252122
return _core.elementWidth == 2 ? _core.startUTF16 : nil
253123
}
254124

0 commit comments

Comments
 (0)