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.
This commit is contained in:
David Smith
2019-07-30 16:21:23 -07:00
parent d9fd3d3ec2
commit c5fc715746
6 changed files with 172 additions and 189 deletions

View File

@@ -55,44 +55,6 @@ typedef unsigned char _swift_shims_Boolean;
typedef __swift_uint16_t _swift_shims_UniChar; typedef __swift_uint16_t _swift_shims_UniChar;
typedef __swift_uint8_t _swift_shims_UInt8; typedef __swift_uint8_t _swift_shims_UInt8;
// Buffer is nullable in case the string is zero-length.
SWIFT_RUNTIME_STDLIB_API
void _swift_stdlib_CFStringGetCharacters(
_swift_shims_CFStringRef _Nonnull theString, _swift_shims_CFRange range,
_swift_shims_UniChar *_Nullable buffer);
SWIFT_RUNTIME_STDLIB_API
_swift_shims_CFIndex _swift_stdlib_CFStringGetBytes(
_swift_shims_CFStringRef _Nonnull theString, _swift_shims_CFRange range,
_swift_shims_CFStringEncoding encoding, _swift_shims_UInt8 lossByte,
_swift_shims_Boolean isExternalRepresentation,
_swift_shims_UInt8 *_Nonnull buffer, _swift_shims_CFIndex maxBufLen,
_swift_shims_CFIndex *_Nullable usedBufLen);
SWIFT_RUNTIME_STDLIB_API
const _swift_shims_UniChar *_Nullable _swift_stdlib_CFStringGetCharactersPtr(
_swift_shims_CFStringRef _Nonnull theString);
SWIFT_RUNTIME_STDLIB_API
_swift_shims_CFIndex _swift_stdlib_CFStringGetLength(
_swift_shims_CFStringRef _Nonnull theString);
SWIFT_RUNTIME_STDLIB_API
__attribute__((ns_returns_retained))
_swift_shims_CFStringRef _Nonnull _swift_stdlib_CFStringCreateWithSubstring(
const void * _Nullable unused,
_swift_shims_CFStringRef _Nonnull str, _swift_shims_CFRange range);
SWIFT_RUNTIME_STDLIB_API
_swift_shims_UniChar _swift_stdlib_CFStringGetCharacterAtIndex(
_swift_shims_CFStringRef _Nonnull theString, _swift_shims_CFIndex idx);
SWIFT_RUNTIME_STDLIB_API
__attribute__((ns_returns_retained))
_swift_shims_CFStringRef _Nonnull _swift_stdlib_CFStringCreateCopy(
const void * _Nullable unused,
_swift_shims_CFStringRef _Nonnull theString);
SWIFT_RUNTIME_STDLIB_API SWIFT_RUNTIME_STDLIB_API
__attribute__((ns_returns_retained)) __attribute__((ns_returns_retained))
_swift_shims_CFStringRef _Nonnull _swift_stdlib_CFStringCreateWithBytes( _swift_shims_CFStringRef _Nonnull _swift_stdlib_CFStringCreateWithBytes(
@@ -109,12 +71,7 @@ const char *_Nullable _swift_stdlib_CFStringGetCStringPtr(
SWIFT_RUNTIME_STDLIB_API SWIFT_RUNTIME_STDLIB_API
_swift_shims_CFStringRef _Nonnull _swift_stdlib_objcDebugDescription( _swift_shims_CFStringRef _Nonnull _swift_stdlib_objcDebugDescription(
id _Nonnull nsObject); id _Nonnull nsObject);
SWIFT_RUNTIME_STDLIB_API
_swift_shims_CFComparisonResult _swift_stdlib_CFStringCompare(
_swift_shims_CFStringRef _Nonnull string,
_swift_shims_CFStringRef _Nonnull string2);
SWIFT_RUNTIME_STDLIB_API SWIFT_RUNTIME_STDLIB_API
__swift_uint8_t _swift_stdlib_isNSString(id _Nonnull obj); __swift_uint8_t _swift_stdlib_isNSString(id _Nonnull obj);
@@ -137,10 +94,6 @@ _swift_stdlib_NSStringGetCStringTrampoline(id _Nonnull obj,
_swift_shims_UInt8 *_Nonnull buffer, _swift_shims_UInt8 *_Nonnull buffer,
_swift_shims_CFIndex maxLength, _swift_shims_CFIndex maxLength,
unsigned long encoding); unsigned long encoding);
SWIFT_RUNTIME_STDLIB_API
__swift_uintptr_t
_swift_stdlib_unsafeAddressOfClass(id _Nonnull obj);
#endif // __OBJC2__ #endif // __OBJC2__

View File

@@ -17,17 +17,80 @@ import SwiftShims
internal typealias _CocoaString = AnyObject internal typealias _CocoaString = AnyObject
#if _runtime(_ObjC) #if _runtime(_ObjC)
// Swift's String bridges NSString via this protocol and these // Swift's String bridges NSString via this protocol and these
// variables, allowing the core stdlib to remain decoupled from // variables, allowing the core stdlib to remain decoupled from
// Foundation. // Foundation.
@objc internal protocol _StringSelectorHolder : _NSCopying {
@objc var length: Int { get }
@objc var hash: UInt { get }
@objc(characterAtIndex:)
func character(at offset: Int) -> UInt16
@objc(getCharacters:range:)
func getCharacters(
_ buffer: UnsafeMutablePointer<UInt16>, range aRange: _SwiftNSRange
)
@objc(_fastCStringContents:)
func _fastCStringContents(
_ requiresNulTermination: Int8
) -> UnsafePointer<CChar>?
@objc(_fastCharacterContents)
func _fastCharacterContents() -> UnsafePointer<UInt16>?
@objc(getBytes:maxLength:usedLength:encoding:options:range:remainingRange:)
func getBytes(_ buffer: UnsafeMutableRawPointer?,
maxLength maxBufferCount: Int,
usedLength usedBufferCount: UnsafeMutablePointer<Int>?,
encoding: UInt,
options: UInt,
range: _SwiftNSRange,
remaining leftover: UnsafeMutablePointer<_SwiftNSRange>?) -> Int8
@objc(compare:options:range:locale:)
func compare(_ string: _CocoaString,
options: UInt,
range: _SwiftNSRange,
locale: AnyObject?) -> Int
}
/*
Passing a _CocoaString through _objc() lets you call ObjC methods that the
compiler doesn't know about, via the protocol above. In order to get good
performance, you need a double indirection like this:
func a -> _objc -> func a'
because any refcounting @_effects on 'a' will be lost when _objc breaks ARC's
knowledge that the _CocoaString and _StringSelectorHolder are the same object.
*/
@inline(__always)
internal func _objc(_ str: _CocoaString) -> _StringSelectorHolder {
return unsafeBitCast(str, to: _StringSelectorHolder.self)
}
@_effects(releasenone)
private func _copyNSString(_ str: _StringSelectorHolder) -> _CocoaString {
return str.copy(with: nil)
}
@usableFromInline // @testable @usableFromInline // @testable
@_effects(releasenone) @_effects(releasenone)
internal func _stdlib_binary_CFStringCreateCopy( internal func _stdlib_binary_CFStringCreateCopy(
_ source: _CocoaString _ source: _CocoaString
) -> _CocoaString { ) -> _CocoaString {
let result = _swift_stdlib_CFStringCreateCopy(nil, source) as AnyObject return _copyNSString(_objc(source))
return result }
@_effects(readonly)
private func _NSStringLen(_ str: _StringSelectorHolder) -> Int {
return str.length
} }
@usableFromInline // @testable @usableFromInline // @testable
@@ -35,7 +98,12 @@ internal func _stdlib_binary_CFStringCreateCopy(
internal func _stdlib_binary_CFStringGetLength( internal func _stdlib_binary_CFStringGetLength(
_ source: _CocoaString _ source: _CocoaString
) -> Int { ) -> Int {
return _swift_stdlib_CFStringGetLength(source) return _NSStringLen(_objc(source))
}
@_effects(readonly)
private func _NSStringCharactersPtr(_ str: _StringSelectorHolder) -> UnsafeMutablePointer<UTF16.CodeUnit>? {
return UnsafeMutablePointer(mutating: str._fastCharacterContents())
} }
@usableFromInline // @testable @usableFromInline // @testable
@@ -43,8 +111,19 @@ internal func _stdlib_binary_CFStringGetLength(
internal func _stdlib_binary_CFStringGetCharactersPtr( internal func _stdlib_binary_CFStringGetCharactersPtr(
_ source: _CocoaString _ source: _CocoaString
) -> UnsafeMutablePointer<UTF16.CodeUnit>? { ) -> UnsafeMutablePointer<UTF16.CodeUnit>? {
return UnsafeMutablePointer( return _NSStringCharactersPtr(_objc(source))
mutating: _swift_stdlib_CFStringGetCharactersPtr(source)) }
@_effects(releasenone)
private func _NSStringGetCharacters(
from source: _StringSelectorHolder,
range: Range<Int>,
into destination: UnsafeMutablePointer<UTF16.CodeUnit>
) {
source.getCharacters(destination, range: _SwiftNSRange(
location: range.startIndex,
length: range.count)
)
} }
/// Copies a slice of a _CocoaString into contiguous storage of sufficient /// Copies a slice of a _CocoaString into contiguous storage of sufficient
@@ -55,18 +134,45 @@ internal func _cocoaStringCopyCharacters(
range: Range<Int>, range: Range<Int>,
into destination: UnsafeMutablePointer<UTF16.CodeUnit> into destination: UnsafeMutablePointer<UTF16.CodeUnit>
) { ) {
_swift_stdlib_CFStringGetCharacters( _NSStringGetCharacters(from: _objc(source), range: range, into: destination)
source, }
_swift_shims_CFRange(location: range.lowerBound, length: range.count),
destination) @_effects(readonly)
private func _NSStringGetCharacter(
_ target: _StringSelectorHolder, _ position: Int
) -> UTF16.CodeUnit {
return target.character(at: position)
} }
@_effects(readonly) @_effects(readonly)
internal func _cocoaStringSubscript( internal func _cocoaStringSubscript(
_ target: _CocoaString, _ position: Int _ target: _CocoaString, _ position: Int
) -> UTF16.CodeUnit { ) -> UTF16.CodeUnit {
let cfSelf: _swift_shims_CFStringRef = target return _NSStringGetCharacter(_objc(target), position)
return _swift_stdlib_CFStringGetCharacterAtIndex(cfSelf, position) }
@_effects(releasenone)
private func _NSStringCopyUTF8(
_ o: _StringSelectorHolder,
into bufPtr: UnsafeMutableBufferPointer<UInt8>
) -> Int? {
let ptr = bufPtr.baseAddress._unsafelyUnwrappedUnchecked
let len = o.length
var remainingRange = _SwiftNSRange(location: 0, length: 0)
var usedLen = 0
let success = 0 != o.getBytes(
ptr,
maxLength: bufPtr.count,
usedLength: &usedLen,
encoding: _cocoaUTF8Encoding,
options: 0,
range: _SwiftNSRange(location: 0, length: len),
remaining: &remainingRange
)
if success && remainingRange.length == 0 {
return usedLen
}
return nil
} }
@_effects(releasenone) @_effects(releasenone)
@@ -74,20 +180,29 @@ internal func _cocoaStringCopyUTF8(
_ target: _CocoaString, _ target: _CocoaString,
into bufPtr: UnsafeMutableBufferPointer<UInt8> into bufPtr: UnsafeMutableBufferPointer<UInt8>
) -> Int? { ) -> Int? {
let ptr = bufPtr.baseAddress._unsafelyUnwrappedUnchecked return _NSStringCopyUTF8(_objc(target), into: bufPtr)
let len = _stdlib_binary_CFStringGetLength(target) }
var count = 0
let converted = _swift_stdlib_CFStringGetBytes( @_effects(readonly)
target, private func _NSStringUTF8Count(
_swift_shims_CFRange(location: 0, length: len), _ o: _StringSelectorHolder,
kCFStringEncodingUTF8, range: Range<Int>
0, ) -> Int? {
0, var remainingRange = _SwiftNSRange(location: 0, length: 0)
ptr, var usedLen = 0
bufPtr.count, let success = 0 != o.getBytes(
&count UnsafeMutablePointer<UInt8>(Builtin.inttoptr_Word(0._builtinWordValue)),
maxLength: 0,
usedLength: &usedLen,
encoding: _cocoaUTF8Encoding,
options: 0,
range: _SwiftNSRange(location: range.startIndex, length: range.count),
remaining: &remainingRange
) )
return len == converted ? count : nil if success && remainingRange.length == 0 {
return usedLen
}
return nil
} }
@_effects(readonly) @_effects(readonly)
@@ -95,28 +210,23 @@ internal func _cocoaStringUTF8Count(
_ target: _CocoaString, _ target: _CocoaString,
range: Range<Int> range: Range<Int>
) -> Int? { ) -> Int? {
var count = 0 return _NSStringUTF8Count(_objc(target), range: range)
let len = _stdlib_binary_CFStringGetLength(target) }
let converted = _swift_stdlib_CFStringGetBytes(
target, @_effects(readonly)
_swift_shims_CFRange(location: range.startIndex, length: range.count), private func _NSStringCompare(
kCFStringEncodingUTF8, _ o: _StringSelectorHolder, _ other: _CocoaString
0, ) -> Int {
0, let range = _SwiftNSRange(location: 0, length: o.length)
UnsafeMutablePointer<UInt8>(Builtin.inttoptr_Word(0._builtinWordValue)), let options = UInt(2) /* NSLiteralSearch*/
0, return o.compare(other, options: options, range: range, locale: nil)
&count
)
return converted == len ? count : nil
} }
@_effects(readonly) @_effects(readonly)
internal func _cocoaStringCompare( internal func _cocoaStringCompare(
_ string: _CocoaString, _ other: _CocoaString _ string: _CocoaString, _ other: _CocoaString
) -> Int { ) -> Int {
let cfSelf: _swift_shims_CFStringRef = string return _NSStringCompare(_objc(string), other)
let cfOther: _swift_shims_CFStringRef = other
return _swift_stdlib_CFStringCompare(cfSelf, cfOther)
} }
@_effects(readonly) @_effects(readonly)
@@ -196,33 +306,30 @@ internal enum _KnownCocoaString {
} }
#if !(arch(i386) || arch(arm)) #if !(arch(i386) || arch(arm))
// Resiliently write a tagged _CocoaString's contents into a buffer. // Resiliently write a tagged _CocoaString's contents into a buffer.
// TODO: move this to the Foundation overlay and reimplement it with
// _NSTaggedPointerStringGetBytes
@_effects(releasenone) // @opaque @_effects(releasenone) // @opaque
internal func _bridgeTagged( internal func _bridgeTagged(
_ cocoa: _CocoaString, _ cocoa: _CocoaString,
intoUTF8 bufPtr: UnsafeMutableBufferPointer<UInt8> intoUTF8 bufPtr: UnsafeMutableBufferPointer<UInt8>
) -> Int? { ) -> Int? {
_internalInvariant(_isObjCTaggedPointer(cocoa)) _internalInvariant(_isObjCTaggedPointer(cocoa))
let ptr = bufPtr.baseAddress._unsafelyUnwrappedUnchecked return _cocoaStringCopyUTF8(cocoa, into: bufPtr)
let length = _stdlib_binary_CFStringGetLength(cocoa)
_internalInvariant(length <= _SmallString.capacity)
var count = 0
let numCharWritten = _swift_stdlib_CFStringGetBytes(
cocoa, _swift_shims_CFRange(location: 0, length: length),
kCFStringEncodingUTF8, 0, 0, ptr, bufPtr.count, &count)
return length == numCharWritten ? count : nil
} }
#endif #endif
@_effects(releasenone) // @opaque @_effects(readonly)
internal func _cocoaUTF8Pointer(_ str: _CocoaString) -> UnsafePointer<UInt8>? { private func _NSStringASCIIPointer(_ str: _StringSelectorHolder) -> UnsafePointer<UInt8>? {
// TODO(String bridging): Is there a better interface here? This requires nul // TODO(String bridging): Is there a better interface here? Ideally we'd be
// termination and may assume ASCII. // able to ask for UTF8 rather than just ASCII
guard let ptr = _swift_stdlib_CFStringGetCStringPtr( return str._fastCStringContents(0)?._asUInt8
str, kCFStringEncodingUTF8 }
) else { return nil }
return ptr._asUInt8 @_effects(readonly) // @opaque
internal func _cocoaASCIIPointer(_ str: _CocoaString) -> UnsafePointer<UInt8>? {
return _NSStringASCIIPointer(_objc(str))
} }
private enum CocoaStringPointer { private enum CocoaStringPointer {
@@ -236,11 +343,11 @@ private enum CocoaStringPointer {
private func _getCocoaStringPointer( private func _getCocoaStringPointer(
_ cfImmutableValue: _CocoaString _ cfImmutableValue: _CocoaString
) -> CocoaStringPointer { ) -> CocoaStringPointer {
if let utf8Ptr = _cocoaUTF8Pointer(cfImmutableValue) { if let asciiPtr = _cocoaASCIIPointer(cfImmutableValue) {
// NOTE: CFStringGetCStringPointer means ASCII // NOTE: CFStringGetCStringPointer means ASCII
return .ascii(utf8Ptr) return .ascii(asciiPtr)
} }
if let utf16Ptr = _swift_stdlib_CFStringGetCharactersPtr(cfImmutableValue) { if let utf16Ptr = _stdlib_binary_CFStringGetCharactersPtr(cfImmutableValue) {
return .utf16(utf16Ptr) return .utf16(utf16Ptr)
} }
return .none return .none

View File

@@ -801,7 +801,7 @@ extension _StringObject {
_internalInvariant(largeFastIsShared) _internalInvariant(largeFastIsShared)
#if _runtime(_ObjC) #if _runtime(_ObjC)
if largeIsCocoa { if largeIsCocoa {
return _cocoaUTF8Pointer(cocoaObject)._unsafelyUnwrappedUnchecked return _cocoaASCIIPointer(cocoaObject)._unsafelyUnwrappedUnchecked
} }
#endif #endif

View File

@@ -146,7 +146,7 @@ extension _AbstractStringStorage {
// CFString will only give us ASCII bytes here, but that's fine. // CFString will only give us ASCII bytes here, but that's fine.
// We already handled non-ASCII UTF8 strings earlier since they're Swift. // We already handled non-ASCII UTF8 strings earlier since they're Swift.
if let otherStart = _cocoaUTF8Pointer(other) { if let otherStart = _cocoaASCIIPointer(other) {
//We know that otherUTF16Length is also its byte count at this point //We know that otherUTF16Length is also its byte count at this point
if count != otherUTF16Length { if count != otherUTF16Length {
return 0 return 0

View File

@@ -423,7 +423,7 @@ extension String.UTF8View {
if utf8Len == 1 { if utf8Len == 1 {
_internalInvariant(idx.transcodedOffset == 0) _internalInvariant(idx.transcodedOffset == 0)
return idx.nextEncoded return idx.nextEncoded._scalarAligned
} }
// Check if we're still transcoding sub-scalar // Check if we're still transcoding sub-scalar
@@ -432,7 +432,8 @@ extension String.UTF8View {
} }
// Skip to the next scalar // Skip to the next scalar
return idx.encoded(offsetBy: scalarLen) _internalInvariant(idx.transcodedOffset == utf8Len - 1)
return idx.encoded(offsetBy: scalarLen)._scalarAligned
} }
@usableFromInline @inline(never) @usableFromInline @inline(never)

View File

@@ -44,59 +44,6 @@ static typename DestType<FromTy>::type cast(FromTy value) {
return (typename DestType<FromTy>::type) value; return (typename DestType<FromTy>::type) value;
} }
static CFRange cast(_swift_shims_CFRange value) {
return { value.location, value.length };
}
void swift::_swift_stdlib_CFStringGetCharacters(
_swift_shims_CFStringRef theString,
_swift_shims_CFRange range,
_swift_shims_UniChar *buffer) {
return CFStringGetCharacters(cast(theString), cast(range), cast(buffer));
}
const _swift_shims_UniChar *
swift::_swift_stdlib_CFStringGetCharactersPtr(
_swift_shims_CFStringRef theString) {
return CFStringGetCharactersPtr(cast(theString));
}
_swift_shims_CFIndex swift::_swift_stdlib_CFStringGetBytes(
_swift_shims_CFStringRef theString, _swift_shims_CFRange range,
_swift_shims_CFStringEncoding encoding, _swift_shims_UInt8 lossByte,
_swift_shims_Boolean isExternalRepresentation, _swift_shims_UInt8 *buffer,
_swift_shims_CFIndex maxBufLen, _swift_shims_CFIndex *usedBufLen) {
return CFStringGetBytes(cast(theString), cast(range), encoding, lossByte,
isExternalRepresentation, buffer, maxBufLen, usedBufLen);
}
_swift_shims_CFIndex
swift::_swift_stdlib_CFStringGetLength(_swift_shims_CFStringRef theString) {
return CFStringGetLength(cast(theString));
}
_swift_shims_CFStringRef
swift::_swift_stdlib_CFStringCreateWithSubstring(
const void *unused,
_swift_shims_CFStringRef str,
_swift_shims_CFRange range) {
assert(unused == NULL);
return cast(CFStringCreateWithSubstring(kCFAllocatorSystemDefault,
cast(str),
cast(range)));
}
_swift_shims_CFComparisonResult
swift::_swift_stdlib_CFStringCompare(
_swift_shims_CFStringRef string,
_swift_shims_CFStringRef string2) {
return cast(CFStringCompareWithOptionsAndLocale(cast(string),
cast(string2),
{ 0, CFStringGetLength(cast(string)) },
0,
NULL));
}
__swift_uint8_t __swift_uint8_t
swift::_swift_stdlib_isNSString(id obj) { swift::_swift_stdlib_isNSString(id obj) {
//TODO: we can likely get a small perf win by using _NSIsNSString on //TODO: we can likely get a small perf win by using _NSIsNSString on
@@ -104,19 +51,6 @@ swift::_swift_stdlib_isNSString(id obj) {
return CFGetTypeID((CFTypeRef)obj) == CFStringGetTypeID() ? 1 : 0; return CFGetTypeID((CFTypeRef)obj) == CFStringGetTypeID() ? 1 : 0;
} }
_swift_shims_UniChar
swift::_swift_stdlib_CFStringGetCharacterAtIndex(_swift_shims_CFStringRef theString,
_swift_shims_CFIndex idx) {
return CFStringGetCharacterAtIndex(cast(theString), idx);
}
_swift_shims_CFStringRef
swift::_swift_stdlib_CFStringCreateCopy(const void *unused,
_swift_shims_CFStringRef theString) {
assert(unused == NULL);
return cast(CFStringCreateCopy(kCFAllocatorSystemDefault, cast(theString)));
}
_swift_shims_CFStringRef _swift_shims_CFStringRef
swift::_swift_stdlib_CFStringCreateWithBytes( swift::_swift_stdlib_CFStringCreateWithBytes(
const void *unused, const uint8_t *bytes, const void *unused, const uint8_t *bytes,
@@ -128,12 +62,6 @@ swift::_swift_stdlib_CFStringCreateWithBytes(
isExternalRepresentation)); isExternalRepresentation));
} }
const char *
swift::_swift_stdlib_CFStringGetCStringPtr(_swift_shims_CFStringRef theString,
_swift_shims_CFStringEncoding encoding) {
return CFStringGetCStringPtr(cast(theString), cast(encoding));
}
_swift_shims_CFStringRef _swift_shims_CFStringRef
swift::_swift_stdlib_objcDebugDescription(id _Nonnull nsObject) { swift::_swift_stdlib_objcDebugDescription(id _Nonnull nsObject) {
return [nsObject debugDescription]; return [nsObject debugDescription];
@@ -180,11 +108,5 @@ swift::_swift_stdlib_NSStringGetCStringTrampoline(id _Nonnull obj,
} }
__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 #endif