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_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
__attribute__((ns_returns_retained))
_swift_shims_CFStringRef _Nonnull _swift_stdlib_CFStringCreateWithBytes(
@@ -110,11 +72,6 @@ SWIFT_RUNTIME_STDLIB_API
_swift_shims_CFStringRef _Nonnull _swift_stdlib_objcDebugDescription(
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_uint8_t _swift_stdlib_isNSString(id _Nonnull obj);
@@ -138,10 +95,6 @@ _swift_stdlib_NSStringGetCStringTrampoline(id _Nonnull obj,
_swift_shims_CFIndex maxLength,
unsigned long encoding);
SWIFT_RUNTIME_STDLIB_API
__swift_uintptr_t
_swift_stdlib_unsafeAddressOfClass(id _Nonnull obj);
#endif // __OBJC2__
#ifdef __cplusplus

View File

@@ -17,17 +17,80 @@ import SwiftShims
internal typealias _CocoaString = AnyObject
#if _runtime(_ObjC)
// Swift's String bridges NSString via this protocol and these
// variables, allowing the core stdlib to remain decoupled from
// 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
@_effects(releasenone)
internal func _stdlib_binary_CFStringCreateCopy(
_ source: _CocoaString
) -> _CocoaString {
let result = _swift_stdlib_CFStringCreateCopy(nil, source) as AnyObject
return result
return _copyNSString(_objc(source))
}
@_effects(readonly)
private func _NSStringLen(_ str: _StringSelectorHolder) -> Int {
return str.length
}
@usableFromInline // @testable
@@ -35,7 +98,12 @@ internal func _stdlib_binary_CFStringCreateCopy(
internal func _stdlib_binary_CFStringGetLength(
_ source: _CocoaString
) -> 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
@@ -43,8 +111,19 @@ internal func _stdlib_binary_CFStringGetLength(
internal func _stdlib_binary_CFStringGetCharactersPtr(
_ source: _CocoaString
) -> UnsafeMutablePointer<UTF16.CodeUnit>? {
return UnsafeMutablePointer(
mutating: _swift_stdlib_CFStringGetCharactersPtr(source))
return _NSStringCharactersPtr(_objc(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
@@ -55,18 +134,45 @@ internal func _cocoaStringCopyCharacters(
range: Range<Int>,
into destination: UnsafeMutablePointer<UTF16.CodeUnit>
) {
_swift_stdlib_CFStringGetCharacters(
source,
_swift_shims_CFRange(location: range.lowerBound, length: range.count),
destination)
_NSStringGetCharacters(from: _objc(source), range: range, into: destination)
}
@_effects(readonly)
private func _NSStringGetCharacter(
_ target: _StringSelectorHolder, _ position: Int
) -> UTF16.CodeUnit {
return target.character(at: position)
}
@_effects(readonly)
internal func _cocoaStringSubscript(
_ target: _CocoaString, _ position: Int
) -> UTF16.CodeUnit {
let cfSelf: _swift_shims_CFStringRef = target
return _swift_stdlib_CFStringGetCharacterAtIndex(cfSelf, position)
return _NSStringGetCharacter(_objc(target), 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)
@@ -74,20 +180,29 @@ internal func _cocoaStringCopyUTF8(
_ target: _CocoaString,
into bufPtr: UnsafeMutableBufferPointer<UInt8>
) -> Int? {
let ptr = bufPtr.baseAddress._unsafelyUnwrappedUnchecked
let len = _stdlib_binary_CFStringGetLength(target)
var count = 0
let converted = _swift_stdlib_CFStringGetBytes(
target,
_swift_shims_CFRange(location: 0, length: len),
kCFStringEncodingUTF8,
0,
0,
ptr,
bufPtr.count,
&count
return _NSStringCopyUTF8(_objc(target), into: bufPtr)
}
@_effects(readonly)
private func _NSStringUTF8Count(
_ o: _StringSelectorHolder,
range: Range<Int>
) -> Int? {
var remainingRange = _SwiftNSRange(location: 0, length: 0)
var usedLen = 0
let success = 0 != o.getBytes(
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)
@@ -95,28 +210,23 @@ internal func _cocoaStringUTF8Count(
_ target: _CocoaString,
range: Range<Int>
) -> Int? {
var count = 0
let len = _stdlib_binary_CFStringGetLength(target)
let converted = _swift_stdlib_CFStringGetBytes(
target,
_swift_shims_CFRange(location: range.startIndex, length: range.count),
kCFStringEncodingUTF8,
0,
0,
UnsafeMutablePointer<UInt8>(Builtin.inttoptr_Word(0._builtinWordValue)),
0,
&count
)
return converted == len ? count : nil
return _NSStringUTF8Count(_objc(target), range: range)
}
@_effects(readonly)
private func _NSStringCompare(
_ o: _StringSelectorHolder, _ other: _CocoaString
) -> Int {
let range = _SwiftNSRange(location: 0, length: o.length)
let options = UInt(2) /* NSLiteralSearch*/
return o.compare(other, options: options, range: range, locale: nil)
}
@_effects(readonly)
internal func _cocoaStringCompare(
_ string: _CocoaString, _ other: _CocoaString
) -> Int {
let cfSelf: _swift_shims_CFStringRef = string
let cfOther: _swift_shims_CFStringRef = other
return _swift_stdlib_CFStringCompare(cfSelf, cfOther)
return _NSStringCompare(_objc(string), other)
}
@_effects(readonly)
@@ -196,33 +306,30 @@ internal enum _KnownCocoaString {
}
#if !(arch(i386) || arch(arm))
// 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
internal func _bridgeTagged(
_ cocoa: _CocoaString,
intoUTF8 bufPtr: UnsafeMutableBufferPointer<UInt8>
) -> Int? {
_internalInvariant(_isObjCTaggedPointer(cocoa))
let ptr = bufPtr.baseAddress._unsafelyUnwrappedUnchecked
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
return _cocoaStringCopyUTF8(cocoa, into: bufPtr)
}
#endif
@_effects(releasenone) // @opaque
internal func _cocoaUTF8Pointer(_ str: _CocoaString) -> UnsafePointer<UInt8>? {
// TODO(String bridging): Is there a better interface here? This requires nul
// termination and may assume ASCII.
guard let ptr = _swift_stdlib_CFStringGetCStringPtr(
str, kCFStringEncodingUTF8
) else { return nil }
@_effects(readonly)
private func _NSStringASCIIPointer(_ str: _StringSelectorHolder) -> UnsafePointer<UInt8>? {
// TODO(String bridging): Is there a better interface here? Ideally we'd be
// able to ask for UTF8 rather than just ASCII
return str._fastCStringContents(0)?._asUInt8
}
return ptr._asUInt8
@_effects(readonly) // @opaque
internal func _cocoaASCIIPointer(_ str: _CocoaString) -> UnsafePointer<UInt8>? {
return _NSStringASCIIPointer(_objc(str))
}
private enum CocoaStringPointer {
@@ -236,11 +343,11 @@ private enum CocoaStringPointer {
private func _getCocoaStringPointer(
_ cfImmutableValue: _CocoaString
) -> CocoaStringPointer {
if let utf8Ptr = _cocoaUTF8Pointer(cfImmutableValue) {
if let asciiPtr = _cocoaASCIIPointer(cfImmutableValue) {
// 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 .none

View File

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

View File

@@ -146,7 +146,7 @@ extension _AbstractStringStorage {
// 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) {
if let otherStart = _cocoaASCIIPointer(other) {
//We know that otherUTF16Length is also its byte count at this point
if count != otherUTF16Length {
return 0

View File

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

View File

@@ -44,59 +44,6 @@ static typename DestType<FromTy>::type cast(FromTy 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::_swift_stdlib_isNSString(id obj) {
//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;
}
_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::_swift_stdlib_CFStringCreateWithBytes(
const void *unused, const uint8_t *bytes,
@@ -128,12 +62,6 @@ swift::_swift_stdlib_CFStringCreateWithBytes(
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::_swift_stdlib_objcDebugDescription(id _Nonnull nsObject) {
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