[5.0] [String] ASCII fast-path for UTF16View (#20900)
* Assorted bridging changes:
• Convert _AbstractStringStorage to a protocol, and the free functions used to deduplicate implementations to extensions on that protocol.
• Move 'start' into the abstract type and use that to simplify some code
• Move the ASCII fast path for length into UTF16View.
• Add a weirder but faster way to check which (if any) of our NSString subclasses a given object is, and adopt it
(cherry picked from commit 8b57921905ed89b921aeaf68f72b73d3dfb67054)
* [String] ASCII fast-path for UTF16View (#20848)
Add an isASCII fast-path for many UTF16View operations. These are
heavily utilized in random-access scenarios, allowing us to both be
more efficient and skip generating breadcrumbs for ASCII strings.
diff --git a/stdlib/public/SwiftShims/CoreFoundationShims.h b/stdlib/public/SwiftShims/CoreFoundationShims.h
index 57b6c53..03eabe5 100644
--- a/stdlib/public/SwiftShims/CoreFoundationShims.h
+++ b/stdlib/public/SwiftShims/CoreFoundationShims.h
@@ -137,6 +137,10 @@
_swift_shims_UInt8 *buffer,
_swift_shims_CFIndex maxLength,
unsigned long encoding);
+
+SWIFT_RUNTIME_STDLIB_API
+__swift_uintptr_t
+_swift_stdlib_unsafeAddressOfClass(id _Nonnull obj);
#endif // __OBJC2__
diff --git a/stdlib/public/core/StringBridge.swift b/stdlib/public/core/StringBridge.swift
index 776187b..4950f5f 100644
--- a/stdlib/public/core/StringBridge.swift
+++ b/stdlib/public/core/StringBridge.swift
@@ -107,6 +107,8 @@
}
+
+
@_effects(releasenone)
internal func _cocoaGetCStringTrampoline(
_ string: _CocoaString,
@@ -131,6 +133,40 @@
@inline(__always) get { return 0x8000100 }
}
+@_effects(readonly)
+private func _unsafeAddressOfCocoaStringClass(_ str: _CocoaString) -> UInt {
+ return _swift_stdlib_unsafeAddressOfClass(str)
+}
+
+internal enum _KnownCocoaString {
+ case storage
+ case shared
+ case cocoa
+#if !(arch(i386) || arch(arm))
+ case tagged
+#endif
+
+ @inline(__always)
+ init(_ str: _CocoaString) {
+
+ #if !(arch(i386) || arch(arm))
+ if _isObjCTaggedPointer(str) {
+ self = .tagged
+ return
+ }
+ #endif
+
+ switch _unsafeAddressOfCocoaStringClass(str) {
+ case unsafeBitCast(_StringStorage.self, to: UInt.self):
+ self = .storage
+ case unsafeBitCast(_SharedStringStorage.self, to: UInt.self):
+ self = .shared
+ default:
+ self = .cocoa
+ }
+ }
+}
+
#if !(arch(i386) || arch(arm))
// Resiliently write a tagged cocoa string's contents into a buffer
@_effects(releasenone) // @opaque
@@ -185,47 +221,49 @@
@usableFromInline
@_effects(releasenone) // @opaque
internal func _bridgeCocoaString(_ cocoaString: _CocoaString) -> _StringGuts {
- if let abstract = cocoaString as? _AbstractStringStorage {
- return abstract.asString._guts
- }
+ switch _KnownCocoaString(cocoaString) {
+ case .storage:
+ return _unsafeUncheckedDowncast(cocoaString, to: _StringStorage.self).asString._guts
+ case .shared:
+ return _unsafeUncheckedDowncast(cocoaString, to: _SharedStringStorage.self).asString._guts
#if !(arch(i386) || arch(arm))
- if _isObjCTaggedPointer(cocoaString) {
- return _StringGuts(_SmallString(taggedCocoa: cocoaString))
- }
+ case .tagged:
+ return _StringGuts(_SmallString(taggedCocoa: cocoaString))
#endif
-
- // "copy" it into a value to be sure nobody will modify behind
- // our backs. In practice, when value is already immutable, this
- // just does a retain.
- //
- // TODO: Only in certain circumstances should we emit this call:
- // 1) If it's immutable, just retain it.
- // 2) If it's mutable with no associated information, then a copy must
- // happen; might as well eagerly bridge it in.
- // 3) If it's mutable with associated information, must make the call
- //
- let immutableCopy
- = _stdlib_binary_CFStringCreateCopy(cocoaString) as AnyObject
-
-#if !(arch(i386) || arch(arm))
- if _isObjCTaggedPointer(immutableCopy) {
- return _StringGuts(_SmallString(taggedCocoa: immutableCopy))
- }
-#endif
-
- let (fastUTF8, isASCII): (Bool, Bool)
- switch _getCocoaStringPointer(immutableCopy) {
+ case .cocoa:
+ // "copy" it into a value to be sure nobody will modify behind
+ // our backs. In practice, when value is already immutable, this
+ // just does a retain.
+ //
+ // TODO: Only in certain circumstances should we emit this call:
+ // 1) If it's immutable, just retain it.
+ // 2) If it's mutable with no associated information, then a copy must
+ // happen; might as well eagerly bridge it in.
+ // 3) If it's mutable with associated information, must make the call
+ //
+ let immutableCopy
+ = _stdlib_binary_CFStringCreateCopy(cocoaString) as AnyObject
+
+ #if !(arch(i386) || arch(arm))
+ if _isObjCTaggedPointer(immutableCopy) {
+ return _StringGuts(_SmallString(taggedCocoa: immutableCopy))
+ }
+ #endif
+
+ let (fastUTF8, isASCII): (Bool, Bool)
+ switch _getCocoaStringPointer(immutableCopy) {
case .ascii(_): (fastUTF8, isASCII) = (true, true)
case .utf8(_): (fastUTF8, isASCII) = (true, false)
default: (fastUTF8, isASCII) = (false, false)
+ }
+ let length = _stdlib_binary_CFStringGetLength(immutableCopy)
+
+ return _StringGuts(
+ cocoa: immutableCopy,
+ providesFastUTF8: fastUTF8,
+ isASCII: isASCII,
+ length: length)
}
- let length = _stdlib_binary_CFStringGetLength(immutableCopy)
-
- return _StringGuts(
- cocoa: immutableCopy,
- providesFastUTF8: fastUTF8,
- isASCII: isASCII,
- length: length)
}
extension String {
diff --git a/stdlib/public/core/StringStorage.swift b/stdlib/public/core/StringStorage.swift
index 768da04..babb40a 100644
--- a/stdlib/public/core/StringStorage.swift
+++ b/stdlib/public/core/StringStorage.swift
@@ -12,159 +12,160 @@
import SwiftShims
+//Having @objc stuff in an extension creates an ObjC category, which we don't want
#if _runtime(_ObjC)
-internal class _AbstractStringStorage: __SwiftNativeNSString, _NSCopying {
- // Abstract interface
- internal var asGuts: _StringGuts { @_effects(readonly) get { Builtin.unreachable() } }
- final internal var asString: String { @_effects(readonly) get { return String(asGuts) } }
- internal var count: Int { @_effects(readonly) get { Builtin.unreachable() } }
- fileprivate var isASCII: Bool { @_effects(readonly) get { Builtin.unreachable() } }
-
- //Having these in an extension creates an ObjC category, which we don't want
+internal protocol _AbstractStringStorage : _NSCopying {
+ var asString: String { get }
+ var count: Int { get }
+ var isASCII: Bool { get }
+ var start: UnsafePointer<UInt8> { get }
//in UTF16 code units
- @objc(length) internal var length: Int { @_effects(readonly) get { Builtin.unreachable() } }
-
- @objc(copyWithZone:)
- final internal func copy(with zone: _SwiftNSZone?) -> AnyObject {
- // While _StringStorage instances aren't immutable in general,
- // mutations may only occur when instances are uniquely referenced.
- // Therefore, it is safe to return self here; any outstanding Objective-C
- // reference will make the instance non-unique.
- return self
- }
-}
-
-#else
-
-internal class _AbstractStringStorage: __SwiftNativeNSString {
- // Abstract interface
- internal var asGuts: _StringGuts { @_effects(readonly) get { Builtin.unreachable() } }
- final internal var asString: String { @_effects(readonly) get { return String(asGuts) } }
- internal var count: Int { @_effects(readonly) get { Builtin.unreachable() } }
- fileprivate var isASCII: Bool { @_effects(readonly) get { Builtin.unreachable() } }
-}
-
-#endif
-
-// ObjC interfaces
-#if _runtime(_ObjC)
-
-@_effects(releasenone)
-private func _getCharacters<T:_AbstractStringStorage>(_ this:T,
- _ buffer: UnsafeMutablePointer<UInt16>,
- _ aRange: _SwiftNSRange) {
- _precondition(aRange.location >= 0 && aRange.length >= 0,
- "Range out of bounds")
- _precondition(aRange.location + aRange.length <= Int(this.count),
- "Range out of bounds")
-
- let range = Range(
- uncheckedBounds: (aRange.location, aRange.location+aRange.length))
- let str = this.asString
- str._copyUTF16CodeUnits(
- into: UnsafeMutableBufferPointer(start: buffer, count: range.count),
- range: range)
-}
-
-@_effects(releasenone)
-private func _getCString<T:_AbstractStringStorage>(
- _ this: T,
- _ utf8: String.UTF8View,
- _ outputPtr: UnsafeMutablePointer<UInt8>,
- _ maxLength: Int,
- _ encoding: UInt)
- -> Int8 {
- switch (encoding, this.isASCII) {
- case (_cocoaASCIIEncoding, true):
- fallthrough
- case (_cocoaUTF8Encoding, _):
- guard maxLength >= this.count + 1 else { return 0 }
- let buffer = UnsafeMutableBufferPointer(start: outputPtr, count: maxLength)
- let (_, end) = utf8._copyContents(initializing: buffer)
- buffer[end] = 0
- return 1
- default:
- return _cocoaGetCStringTrampoline(this,
- outputPtr,
- maxLength,
- encoding)
- }
-}
-
-@inline(never) //hide the shim call so we can use @_effects
-@_effects(readonly)
-private func _isNSString(_ str:AnyObject) -> UInt8 {
- return _swift_stdlib_isNSString(str)
-}
-
-//This used to be on _AbstractStringStorage, which meant it went through the
-//dynamic version of asString.
-//Making it generic and calling it from the subclasses lets us avoid that.
-@_effects(readonly)
-private func _isEqual<T:_AbstractStringStorage>(_ this:T, _ other:AnyObject?)
- -> Int8 {
- guard let other = other else {
- return 0
- }
-
- if this === other {
- return 1
- }
-
- let ourGuts = this.asGuts
- defer { _fixLifetime(ourGuts) }
-
- //Handle the case where both strings were bridged from Swift.
- //We can't use String.== because it doesn't match NSString semantics.
- if let otherGuts = (other as? _AbstractStringStorage)?.asGuts {
- if otherGuts.count != ourGuts.count {
- return 0
- }
- return ourGuts.withFastUTF8 { ourBytes in
- return otherGuts.withFastUTF8 { otherBytes in
- return (ourBytes.baseAddress == otherBytes.baseAddress ||
- (memcmp(ourBytes.baseAddress!, otherBytes.baseAddress!, ourBytes.count) == 0)) ? 1 : 0
- }
- }
- }
-
- //we're allowed to crash, but for compatibility reasons NSCFString allows non-strings here
- if _isNSString(other) != 1 {
- return 0
- }
-
- //At this point we've proven that it is an NSString of some sort, but not one of ours
- if this.length != _stdlib_binary_CFStringGetLength(other) {
- return 0
- }
-
- defer {
- _fixLifetime(other)
- }
-
- //CFString will only give us ASCII bytes here, but that's fine
- //We already handled non-ASCII UTF8 strings earlier since they're Swift
- if let otherBytes = _cocoaUTF8Pointer(other) {
- return ourGuts.withFastUTF8 { ourBytes in
- return (ourBytes.baseAddress == otherBytes ||
- (memcmp(ourBytes.baseAddress!, otherBytes, ourBytes.count) == 0)) ? 1 : 0
- }
- }
-
- /*
- The abstract implementation of -isEqualToString: falls back to -compare:
- immediately, so when we run out of fast options to try, do the same.
- We can likely be more clever here if need be
- */
- return _cocoaStringCompare(this, other) == 0 ? 1 : 0
+ var length: Int { get }
}
internal let _cocoaASCIIEncoding:UInt = 1 /* NSASCIIStringEncoding */
internal let _cocoaUTF8Encoding:UInt = 4 /* NSUTF8StringEncoding */
+@_effects(readonly)
+private func _isNSString(_ str:AnyObject) -> UInt8 {
+ return _swift_stdlib_isNSString(str)
+}
-#endif // _runtime(_ObjC)
+#else
+
+internal protocol _AbstractStringStorage {
+ var asString: String { get }
+ var count: Int { get }
+ var isASCII: Bool { get }
+ var start: UnsafePointer<UInt8> { get }
+}
+
+#endif
+
+extension _AbstractStringStorage {
+
+ // ObjC interfaces
+ #if _runtime(_ObjC)
+
+ @inline(__always)
+ @_effects(releasenone)
+ internal func _getCharacters(
+ _ buffer: UnsafeMutablePointer<UInt16>,
+ _ aRange: _SwiftNSRange
+ ) {
+ _precondition(aRange.location >= 0 && aRange.length >= 0,
+ "Range out of bounds")
+ _precondition(aRange.location + aRange.length <= Int(count),
+ "Range out of bounds")
+
+ let range = Range(
+ uncheckedBounds: (aRange.location, aRange.location+aRange.length))
+ let str = asString
+ str._copyUTF16CodeUnits(
+ into: UnsafeMutableBufferPointer(start: buffer, count: range.count),
+ range: range)
+ }
+
+ @inline(__always)
+ @_effects(releasenone)
+ internal func _getCString(
+ _ outputPtr: UnsafeMutablePointer<UInt8>,
+ _ maxLength: Int,
+ _ encoding: UInt)
+ -> Int8 {
+ switch (encoding, isASCII) {
+ case (_cocoaASCIIEncoding, true):
+ fallthrough
+ case (_cocoaUTF8Encoding, _):
+ guard maxLength >= count + 1 else { return 0 }
+ let buffer = UnsafeMutableBufferPointer(start: outputPtr, count: maxLength)
+ buffer.initialize(from: UnsafeBufferPointer(start: start, count: count))
+ buffer[count] = 0
+ return 1
+ default:
+ return _cocoaGetCStringTrampoline(self,
+ outputPtr,
+ maxLength,
+ encoding)
+ }
+ }
+
+ @inline(__always)
+ @_effects(readonly)
+ internal func _cString(encoding: UInt) -> UnsafePointer<UInt8>? {
+ switch (encoding, isASCII) {
+ case (_cocoaASCIIEncoding, true):
+ fallthrough
+ case (_cocoaUTF8Encoding, _):
+ return start
+ default:
+ return _cocoaCStringUsingEncodingTrampoline(self, encoding)
+ }
+ }
+
+ @_effects(readonly)
+ internal func _nativeIsEqual<T:_AbstractStringStorage>(_ nativeOther: T) -> Int8 {
+ if count != nativeOther.count {
+ return 0
+ }
+ return (start == nativeOther.start || (memcmp(start, nativeOther.start, count) == 0)) ? 1 : 0
+ }
+
+ @inline(__always)
+ @_effects(readonly)
+ internal func _isEqual(_ other:AnyObject?)
+ -> Int8 {
+ guard let other = other else {
+ return 0
+ }
+
+ if self === other {
+ return 1
+ }
+
+ // Handle the case where both strings were bridged from Swift.
+ // We can't use String.== because it doesn't match NSString semantics.
+ let knownOther = _KnownCocoaString(other)
+ switch knownOther {
+ case .storage:
+ return _nativeIsEqual(_unsafeUncheckedDowncast(other, to: _StringStorage.self))
+ case .shared:
+ return _nativeIsEqual(_unsafeUncheckedDowncast(other, to: _SharedStringStorage.self))
+#if !(arch(i386) || arch(arm))
+ case .tagged:
+ fallthrough
+#endif
+ case .cocoa:
+ //we're allowed to crash, but for compatibility reasons NSCFString allows non-strings here
+ if _isNSString(other) != 1 {
+ return 0
+ }
+
+ //At this point we've proven that it is an NSString of some sort, but not one of ours
+ if length != _stdlib_binary_CFStringGetLength(other) {
+ return 0
+ }
+
+ defer { _fixLifetime(other) }
+
+ //CFString will only give us ASCII bytes here, but that's fine
+ //We already handled non-ASCII UTF8 strings earlier since they're Swift
+ if let otherStart = _cocoaUTF8Pointer(other) {
+ return (start == otherStart || (memcmp(start, otherStart, count) == 0)) ? 1 : 0
+ }
+
+ /*
+ The abstract implementation of -isEqualToString: falls back to -compare:
+ immediately, so when we run out of fast options to try, do the same.
+ We can likely be more clever here if need be
+ */
+ return _cocoaStringCompare(self, other) == 0 ? 1 : 0
+ }
+ }
+
+ #endif //_runtime(_ObjC)
+}
#if arch(i386) || arch(arm)
private typealias Flags = _StringObject.Flags
@@ -177,7 +178,7 @@
// Optional<_StringBreadcrumbs>.
//
-final internal class _StringStorage: _AbstractStringStorage {
+final internal class _StringStorage: __SwiftNativeNSString, _AbstractStringStorage {
#if arch(i386) || arch(arm)
// The total allocated storage capacity. Note that this includes the required
// nul-terminator
@@ -189,7 +190,7 @@
internal var _reserved: UInt16
- override internal var count: Int {
+ internal var count: Int {
@inline(__always) get { return _count }
@inline(__always) set { _count = newValue }
}
@@ -200,7 +201,7 @@
internal var _countAndFlags: _StringObject.CountAndFlags
- override internal var count: Int {
+ internal var count: Int {
@inline(__always) get { return _countAndFlags.count }
@inline(__always) set { _countAndFlags.count = newValue }
}
@@ -215,29 +216,28 @@
}
#endif
- fileprivate final override var isASCII: Bool {
-#if arch(i386) || arch(arm)
- return _flags.isASCII
-#else
- return _countAndFlags.isASCII
-#endif
+ internal final var isASCII: Bool {
+ @inline(__always) get {
+ #if arch(i386) || arch(arm)
+ return _flags.isASCII
+ #else
+ return _countAndFlags.isASCII
+ #endif
+ }
}
- override final internal var asGuts: _StringGuts {
- @inline(__always) @_effects(readonly) get {
- return _StringGuts(self)
+ final internal var asString: String {
+ @_effects(readonly) @inline(__always) get {
+ return String(_StringGuts(self))
}
}
#if _runtime(_ObjC)
@objc(length)
- override final internal var length: Int {
+ final internal var length: Int {
@_effects(readonly) @inline(__always) get {
- if isASCII {
- return count
- }
- return asString.utf16.count
+ return asString.utf16.count //UTF16View special-cases ASCII for us
}
}
@@ -262,7 +262,7 @@
final internal func getCharacters(
_ buffer: UnsafeMutablePointer<UInt16>,
range aRange: _SwiftNSRange) {
- _getCharacters(self, buffer, aRange)
+ _getCharacters(buffer, aRange)
}
@objc(_fastCStringContents:)
@@ -283,15 +283,8 @@
@objc(cStringUsingEncoding:)
@_effects(readonly)
- final internal func _cString(encoding: UInt) -> UnsafePointer<UInt8>? {
- switch (encoding, isASCII) {
- case (_cocoaASCIIEncoding, true):
- fallthrough
- case (_cocoaUTF8Encoding, _):
- return start
- default:
- return _cocoaCStringUsingEncodingTrampoline(self, encoding)
- }
+ final internal func cString(encoding: UInt) -> UnsafePointer<UInt8>? {
+ return _cString(encoding: encoding)
}
@objc(getCString:maxLength:encoding:)
@@ -300,7 +293,7 @@
maxLength: Int,
encoding: UInt)
-> Int8 {
- return _getCString(self, asString.utf8, outputPtr, maxLength, encoding)
+ return _getCString(outputPtr, maxLength, encoding)
}
@objc
@@ -316,7 +309,16 @@
@objc(isEqualToString:)
@_effects(readonly)
final internal func isEqual(to other:AnyObject?) -> Int8 {
- return _isEqual(self, other)
+ return _isEqual(other)
+ }
+
+ @objc(copyWithZone:)
+ final internal func copy(with zone: _SwiftNSZone?) -> AnyObject {
+ // While _StringStorage instances aren't immutable in general,
+ // mutations may only occur when instances are uniquely referenced.
+ // Therefore, it is safe to return self here; any outstanding Objective-C
+ // reference will make the instance non-unique.
+ return self
}
#endif // _runtime(_ObjC)
@@ -666,10 +668,10 @@
}
// For shared storage and bridging literals
-final internal class _SharedStringStorage: _AbstractStringStorage {
+final internal class _SharedStringStorage: __SwiftNativeNSString, _AbstractStringStorage {
internal var _owner: AnyObject?
- internal var _start: UnsafePointer<UInt8>
+ internal var start: UnsafePointer<UInt8>
#if arch(i386) || arch(arm)
internal var _count: Int
@@ -688,14 +690,14 @@
internal var _breadcrumbs: _StringBreadcrumbs? = nil
- internal var start: UnsafePointer<UInt8> { return _start }
-
- override internal var count: Int {
-#if arch(i386) || arch(arm)
- return _count
-#else
- return _countAndFlags.count
-#endif
+ internal var count: Int {
+ @_effects(readonly) @inline(__always) get {
+ #if arch(i386) || arch(arm)
+ return _count
+ #else
+ return _countAndFlags.count
+ #endif
+ }
}
internal init(
@@ -703,7 +705,7 @@
countAndFlags: _StringObject.CountAndFlags
) {
self._owner = nil
- self._start = ptr
+ self.start = ptr
#if arch(i386) || arch(arm)
self._count = countAndFlags.count
self._flags = countAndFlags.flags
@@ -714,29 +716,28 @@
self._invariantCheck()
}
- fileprivate final override var isASCII: Bool {
- #if arch(i386) || arch(arm)
- return _flags.isASCII
- #else
- return _countAndFlags.isASCII
- #endif
+ internal final var isASCII: Bool {
+ @inline(__always) get {
+ #if arch(i386) || arch(arm)
+ return _flags.isASCII
+ #else
+ return _countAndFlags.isASCII
+ #endif
+ }
}
- override final internal var asGuts: _StringGuts {
- @_effects(readonly) get {
- return _StringGuts(self)
+ final internal var asString: String {
+ @_effects(readonly) @inline(__always) get {
+ return String(_StringGuts(self))
}
}
#if _runtime(_ObjC)
@objc(length)
- override final internal var length: Int {
+ final internal var length: Int {
@_effects(readonly) get {
- if isASCII {
- return count
- }
- return asString.utf16.count
+ return asString.utf16.count //UTF16View special-cases ASCII for us
}
}
@@ -761,7 +762,7 @@
final internal func getCharacters(
_ buffer: UnsafeMutablePointer<UInt16>,
range aRange: _SwiftNSRange) {
- _getCharacters(self, buffer, aRange)
+ _getCharacters(buffer, aRange)
}
@objc
@@ -792,15 +793,8 @@
@objc(cStringUsingEncoding:)
@_effects(readonly)
- final internal func _cString(encoding: UInt) -> UnsafePointer<UInt8>? {
- switch (encoding, isASCII) {
- case (_cocoaASCIIEncoding, true):
- fallthrough
- case (_cocoaUTF8Encoding, _):
- return start
- default:
- return _cocoaCStringUsingEncodingTrampoline(self, encoding)
- }
+ final internal func cString(encoding: UInt) -> UnsafePointer<UInt8>? {
+ return _cString(encoding: encoding)
}
@objc(getCString:maxLength:encoding:)
@@ -809,13 +803,22 @@
maxLength: Int,
encoding: UInt)
-> Int8 {
- return _getCString(self, asString.utf8, outputPtr, maxLength, encoding)
+ return _getCString(outputPtr, maxLength, encoding)
}
@objc(isEqualToString:)
@_effects(readonly)
final internal func isEqual(to other:AnyObject?) -> Int8 {
- return _isEqual(self, other)
+ return _isEqual(other)
+ }
+
+ @objc(copyWithZone:)
+ final internal func copy(with zone: _SwiftNSZone?) -> AnyObject {
+ // While _StringStorage instances aren't immutable in general,
+ // mutations may only occur when instances are uniquely referenced.
+ // Therefore, it is safe to return self here; any outstanding Objective-C
+ // reference will make the instance non-unique.
+ return self
}
#endif // _runtime(_ObjC)
diff --git a/stdlib/public/core/StringUTF16View.swift b/stdlib/public/core/StringUTF16View.swift
index 2537294..0cbd168 100644
--- a/stdlib/public/core/StringUTF16View.swift
+++ b/stdlib/public/core/StringUTF16View.swift
@@ -117,7 +117,8 @@
#else
@usableFromInline @inline(never) @_effects(releasenone)
internal func _invariantCheck() {
- // TODO: Ensure start/end are not sub-scalr UTF-8 transcoded indices
+ _internalInvariant(
+ startIndex.transcodedOffset == 0 && endIndex.transcodedOffset == 0)
}
#endif // INTERNAL_CHECKS_ENABLED
}
@@ -143,9 +144,8 @@
@inlinable @inline(__always)
public func index(after i: Index) -> Index {
- // TODO(String performance) known-ASCII fast path
-
if _slowPath(_guts.isForeign) { return _foreignIndex(after: i) }
+ if _guts.isASCII { return i.nextEncoded }
// For a BMP scalar (1-3 UTF-8 code units), advance past it. For a non-BMP
// scalar, use a transcoded offset first.
@@ -159,9 +159,8 @@
@inlinable @inline(__always)
public func index(before i: Index) -> Index {
precondition(!i.isZeroPosition)
- // TODO(String performance) known-ASCII fast path
-
if _slowPath(_guts.isForeign) { return _foreignIndex(before: i) }
+ if _guts.isASCII { return i.priorEncoded }
if i.transcodedOffset != 0 {
_internalInvariant(i.transcodedOffset == 1)
@@ -181,7 +180,6 @@
}
public func index(_ i: Index, offsetBy n: Int) -> Index {
- // TODO(String performance) known-ASCII fast path
if _slowPath(_guts.isForeign) {
return _foreignIndex(i, offsetBy: n)
}
@@ -194,7 +192,6 @@
public func index(
_ i: Index, offsetBy n: Int, limitedBy limit: Index
) -> Index? {
- // TODO(String performance) known-ASCII fast path
if _slowPath(_guts.isForeign) {
return _foreignIndex(i, offsetBy: n, limitedBy: limit)
}
@@ -216,7 +213,6 @@
}
public func distance(from start: Index, to end: Index) -> Int {
- // TODO(String performance) known-ASCII fast path
if _slowPath(_guts.isForeign) {
return _foreignDistance(from: start, to: end)
}
@@ -249,7 +245,6 @@
@inlinable
public subscript(i: Index) -> UTF16.CodeUnit {
@inline(__always) get {
- // TODO(String performance) known-ASCII fast path
String(_guts)._boundsCheck(i)
if _fastPath(_guts.isFastUTF8) {
@@ -319,16 +314,16 @@
extension String.UTF16View: CustomStringConvertible {
- @inlinable
- public var description: String {
- @inline(__always) get { return String(_guts) }
- }
+ @inlinable
+ public var description: String {
+ @inline(__always) get { return String(_guts) }
+ }
}
extension String.UTF16View: CustomDebugStringConvertible {
- public var debugDescription: String {
- return "StringUTF16(\(self.description.debugDescription))"
- }
+ public var debugDescription: String {
+ return "StringUTF16(\(self.description.debugDescription))"
+ }
}
extension String {
@@ -514,10 +509,15 @@
// Trivial and common: start
if idx == startIndex { return 0 }
+ if _guts.isASCII {
+ _internalInvariant(idx.transcodedOffset == 0)
+ return idx.encodedOffset
+ }
+
if idx.encodedOffset < _shortHeuristic || !_guts.hasBreadcrumbs {
return _distance(from: startIndex, to: idx)
}
-
+
// Simple and common: endIndex aka `length`.
let breadcrumbsPtr = _guts.getBreadcrumbsPtr()
if idx == endIndex { return breadcrumbsPtr.pointee.utf16Length }
@@ -534,6 +534,8 @@
// Trivial and common: start
if offset == 0 { return startIndex }
+ if _guts.isASCII { return Index(encodedOffset: offset) }
+
if offset < _shortHeuristic || !_guts.hasBreadcrumbs {
return _index(startIndex, offsetBy: offset)
}
@@ -592,12 +594,26 @@
if _slowPath(range.isEmpty) { return }
+ let isASCII = _guts.isASCII
return _guts.withFastUTF8 { utf8 in
var writeIdx = 0
let writeEnd = buffer.count
var readIdx = range.lowerBound.encodedOffset
let readEnd = range.upperBound.encodedOffset
+ if isASCII {
+ _internalInvariant(range.lowerBound.transcodedOffset == 0)
+ _internalInvariant(range.upperBound.transcodedOffset == 0)
+ while readIdx < readEnd {
+ _internalInvariant(utf8[readIdx] < 0x80)
+ buffer[_unchecked: writeIdx] = UInt16(
+ truncatingIfNeeded: utf8[_unchecked: readIdx])
+ readIdx &+= 1
+ writeIdx &+= 1
+ }
+ return
+ }
+
// Handle mid-transcoded-scalar initial index
if _slowPath(range.lowerBound.transcodedOffset != 0) {
_internalInvariant(range.lowerBound.transcodedOffset == 1)
diff --git a/stdlib/public/stubs/FoundationHelpers.mm b/stdlib/public/stubs/FoundationHelpers.mm
index 32668d7..e2cb9c8 100644
--- a/stdlib/public/stubs/FoundationHelpers.mm
+++ b/stdlib/public/stubs/FoundationHelpers.mm
@@ -175,6 +175,11 @@
}
+__swift_uintptr_t
+swift::_swift_stdlib_unsafeAddressOfClass(id _Nonnull obj) {
+ return (__swift_uintptr_t)object_getClass(obj); //TODO: do direct isa access when in the OS
+}
+
#endif
diff --git a/test/api-digester/Outputs/stability-stdlib-abi.swift.expected b/test/api-digester/Outputs/stability-stdlib-abi.swift.expected
index 9fad767..152c968 100644
--- a/test/api-digester/Outputs/stability-stdlib-abi.swift.expected
+++ b/test/api-digester/Outputs/stability-stdlib-abi.swift.expected
@@ -230,10 +230,8 @@
Var Hasher._Core._buffer is added to a non-resilient type
Var Hasher._Core._state in a non-resilient type changes position from 0 to 1
-Class _AbstractStringStorage has been removed
Constructor _StringGuts.init(_:) has been removed
Constructor _StringObject.init(_:) has been removed
-Constructor _StringStorage.init() has been removed
Var _StringObject.nativeStorage has been removed
Var _StringStorage._countAndFlags has been removed
Var _StringStorage._realCapacityAndFlags has been removed
@@ -520,3 +518,15 @@
Var LazyCollectionProtocol.lazy has declared type change from LazyCollection<τ_0_0.Elements> to LazySequence<τ_0_0.Elements>
Func Sequence.reduce(into:_:) has parameter 0 changing from Default to Owned
+
+Class _AbstractStringStorage has been changed to a Protocol
+Class _AbstractStringStorage has generic signature change from to <τ_0_0 : _NSCopying>
+Class _AbstractStringStorage has removed conformance to _NSCopying
+Class _AbstractStringStorage has removed conformance to _NSStringCore
+Class _AbstractStringStorage has removed conformance to _ShadowProtocol
+Class _AbstractStringStorage is now without @_fixed_layout
+Class _SharedStringStorage has changed its super class from _AbstractStringStorage to __SwiftNativeNSString
+Class _StringStorage has changed its super class from _AbstractStringStorage to __SwiftNativeNSString
+Constructor _AbstractStringStorage.init() has been removed
+Func _AbstractStringStorage.copy(with:) has been removed
+Func _AbstractStringStorage.getOrComputeBreadcrumbs() has been removed