blob: babb40a5f6aa9d256d6976b2756605a0f4c212a9 [file] [log] [blame]
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import SwiftShims
//Having @objc stuff in an extension creates an ObjC category, which we don't want
#if _runtime(_ObjC)
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
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)
}
#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
#endif
private typealias CountAndFlags = _StringObject.CountAndFlags
//
// TODO(String docs): Documentation about the runtime layout of these instances,
// which is a little complex. The second trailing allocation holds an
// Optional<_StringBreadcrumbs>.
//
final internal class _StringStorage: __SwiftNativeNSString, _AbstractStringStorage {
#if arch(i386) || arch(arm)
// The total allocated storage capacity. Note that this includes the required
// nul-terminator
internal var _realCapacity: Int
internal var _count: Int
internal var _flags: _StringObject.Flags
internal var _reserved: UInt16
internal var count: Int {
@inline(__always) get { return _count }
@inline(__always) set { _count = newValue }
}
#else
// The capacity of our allocation. Note that this includes the nul-terminator,
// which is not available for overridding.
internal var _realCapacityAndFlags: UInt64
internal var _countAndFlags: _StringObject.CountAndFlags
internal var count: Int {
@inline(__always) get { return _countAndFlags.count }
@inline(__always) set { _countAndFlags.count = newValue }
}
// The total allocated storage capacity. Note that this includes the required
// nul-terminator
internal var _realCapacity: Int {
@inline(__always) get {
return Int(truncatingIfNeeded:
_realCapacityAndFlags & _StringObject.Nibbles.largeAddressMask)
}
}
#endif
internal final var isASCII: Bool {
@inline(__always) get {
#if arch(i386) || arch(arm)
return _flags.isASCII
#else
return _countAndFlags.isASCII
#endif
}
}
final internal var asString: String {
@_effects(readonly) @inline(__always) get {
return String(_StringGuts(self))
}
}
#if _runtime(_ObjC)
@objc(length)
final internal var length: Int {
@_effects(readonly) @inline(__always) get {
return asString.utf16.count //UTF16View special-cases ASCII for us
}
}
@objc final internal var hash: UInt {
@_effects(readonly) get {
if isASCII {
return _cocoaHashASCIIBytes(start, length: count)
}
return _cocoaHashString(self)
}
}
@objc(characterAtIndex:)
@_effects(readonly)
final internal func character(at offset: Int) -> UInt16 {
let str = asString
return str.utf16[str._toUTF16Index(offset)]
}
@objc(getCharacters:range:)
@_effects(releasenone)
final internal func getCharacters(
_ buffer: UnsafeMutablePointer<UInt16>,
range aRange: _SwiftNSRange) {
_getCharacters(buffer, aRange)
}
@objc(_fastCStringContents:)
@_effects(readonly)
final internal func _fastCStringContents(_ requiresNulTermination:Int8) -> UnsafePointer<CChar>? {
if isASCII {
return start._asCChar
}
return nil
}
@objc(UTF8String)
@_effects(readonly)
final internal func _utf8String() -> UnsafePointer<UInt8>? {
return start
}
@objc(cStringUsingEncoding:)
@_effects(readonly)
final internal func cString(encoding: UInt) -> UnsafePointer<UInt8>? {
return _cString(encoding: encoding)
}
@objc(getCString:maxLength:encoding:)
@_effects(releasenone)
final internal func getCString(_ outputPtr: UnsafeMutablePointer<UInt8>,
maxLength: Int,
encoding: UInt)
-> Int8 {
return _getCString(outputPtr, maxLength, encoding)
}
@objc
final internal var fastestEncoding: UInt {
@_effects(readonly) get {
if isASCII {
return _cocoaASCIIEncoding
}
return _cocoaUTF8Encoding
}
}
@objc(isEqualToString:)
@_effects(readonly)
final internal func isEqual(to other:AnyObject?) -> Int8 {
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)
private init(_doNotCallMe: ()) {
_internalInvariantFailure("Use the create method")
}
deinit {
_breadcrumbsAddress.deinitialize(count: 1)
}
}
// Determine the actual number of code unit capacity to request from malloc. We
// round up the nearest multiple of 8 that isn't a mulitple of 16, to fully
// utilize malloc's small buckets while accounting for the trailing
// _StringBreadCrumbs.
//
// NOTE: We may still under-utilize the spare bytes from the actual allocation
// for Strings ~1KB or larger, though at this point we're well into our growth
// curve.
private func determineCodeUnitCapacity(_ desiredCapacity: Int) -> Int {
#if arch(i386) || arch(arm)
// FIXME: Adapt to actual 32-bit allocator. For now, let's arrange things so
// that the instance size will be a multiple of 4.
let bias = Int(bitPattern: _StringObject.nativeBias)
let minimum = bias + desiredCapacity + 1
let size = (minimum + 3) & ~3
_internalInvariant(size % 4 == 0)
let capacity = size - bias
_internalInvariant(capacity > desiredCapacity)
return capacity
#else
// Bigger than _SmallString, and we need 1 extra for nul-terminator
let minCap = 1 + Swift.max(desiredCapacity, _SmallString.capacity)
_internalInvariant(minCap < 0x1_0000_0000_0000, "max 48-bit length")
// Round up to the nearest multiple of 8 that isn't also a multiple of 16
let capacity = ((minCap + 7) & -16) + 8
_internalInvariant(
capacity > desiredCapacity && capacity % 8 == 0 && capacity % 16 != 0)
return capacity
#endif
}
// Creation
extension _StringStorage {
@_effects(releasenone)
private static func create(
realCodeUnitCapacity: Int, countAndFlags: CountAndFlags
) -> _StringStorage {
let storage = Builtin.allocWithTailElems_2(
_StringStorage.self,
realCodeUnitCapacity._builtinWordValue, UInt8.self,
1._builtinWordValue, Optional<_StringBreadcrumbs>.self)
#if arch(i386) || arch(arm)
storage._realCapacity = realCodeUnitCapacity
storage._count = countAndFlags.count
storage._flags = countAndFlags.flags
#else
storage._realCapacityAndFlags =
UInt64(truncatingIfNeeded: realCodeUnitCapacity)
storage._countAndFlags = countAndFlags
#endif
storage._breadcrumbsAddress.initialize(to: nil)
storage.terminator.pointee = 0 // nul-terminated
// NOTE: We can't _invariantCheck() now, because code units have not been
// initialized. But, _StringGuts's initializer will.
return storage
}
@_effects(releasenone)
private static func create(
capacity: Int, countAndFlags: CountAndFlags
) -> _StringStorage {
_internalInvariant(capacity >= countAndFlags.count)
let realCapacity = determineCodeUnitCapacity(capacity)
_internalInvariant(realCapacity > capacity)
return _StringStorage.create(
realCodeUnitCapacity: realCapacity, countAndFlags: countAndFlags)
}
@_effects(releasenone)
internal static func create(
initializingFrom bufPtr: UnsafeBufferPointer<UInt8>,
capacity: Int,
isASCII: Bool
) -> _StringStorage {
#if arch(i386) || arch(arm)
let flags = Flags(isASCII: isASCII)
let countAndFlags = CountAndFlags(count: bufPtr.count, flags: flags)
#else
let countAndFlags = CountAndFlags(count: bufPtr.count, isASCII: isASCII)
#endif
_internalInvariant(capacity >= bufPtr.count)
let storage = _StringStorage.create(
capacity: capacity, countAndFlags: countAndFlags)
let addr = bufPtr.baseAddress._unsafelyUnwrappedUnchecked
storage.mutableStart.initialize(from: addr, count: bufPtr.count)
storage._invariantCheck()
return storage
}
@_effects(releasenone)
internal static func create(
initializingFrom bufPtr: UnsafeBufferPointer<UInt8>, isASCII: Bool
) -> _StringStorage {
return _StringStorage.create(
initializingFrom: bufPtr, capacity: bufPtr.count, isASCII: isASCII)
}
}
// Usage
extension _StringStorage {
@inlinable
internal var mutableStart: UnsafeMutablePointer<UInt8> {
@inline(__always) get {
return UnsafeMutablePointer(Builtin.projectTailElems(self, UInt8.self))
}
}
private var mutableEnd: UnsafeMutablePointer<UInt8> {
@inline(__always) get { return mutableStart + count }
}
@inlinable
internal var start: UnsafePointer<UInt8> {
@inline(__always) get { return UnsafePointer(mutableStart) }
}
private final var end: UnsafePointer<UInt8> {
@inline(__always) get { return UnsafePointer(mutableEnd) }
}
// Point to the nul-terminator
private final var terminator: UnsafeMutablePointer<UInt8> {
@inline(__always) get { return mutableEnd }
}
private var codeUnits: UnsafeBufferPointer<UInt8> {
@inline(__always) get {
return UnsafeBufferPointer(start: start, count: count)
}
}
// @opaque
internal var _breadcrumbsAddress: UnsafeMutablePointer<_StringBreadcrumbs?> {
let raw = Builtin.getTailAddr_Word(
start._rawValue,
_realCapacity._builtinWordValue,
UInt8.self,
Optional<_StringBreadcrumbs>.self)
return UnsafeMutablePointer(raw)
}
// The total capacity available for code units. Note that this excludes the
// required nul-terminator
internal var capacity: Int {
return _realCapacity &- 1
}
// The unused capacity available for appending. Note that this excludes the
// required nul-terminator.
//
// NOTE: Callers who wish to mutate this storage should enfore nul-termination
private var unusedStorage: UnsafeMutableBufferPointer<UInt8> {
@inline(__always) get {
return UnsafeMutableBufferPointer(
start: mutableEnd, count: unusedCapacity)
}
}
// The capacity available for appending. Note that this excludes the required
// nul-terminator
internal var unusedCapacity: Int {
get { return _realCapacity &- count &- 1 }
}
#if !INTERNAL_CHECKS_ENABLED
@inline(__always) internal func _invariantCheck() {}
#else
internal func _invariantCheck() {
let rawSelf = UnsafeRawPointer(Builtin.bridgeToRawPointer(self))
let rawStart = UnsafeRawPointer(start)
_internalInvariant(unusedCapacity >= 0)
_internalInvariant(count <= capacity)
_internalInvariant(rawSelf + Int(_StringObject.nativeBias) == rawStart)
_internalInvariant(self._realCapacity > self.count, "no room for nul-terminator")
_internalInvariant(self.terminator.pointee == 0, "not nul terminated")
#if arch(i386) || arch(arm)
_flags._invariantCheck()
#else
_countAndFlags._invariantCheck()
#endif
if isASCII {
_internalInvariant(_allASCII(self.codeUnits))
}
if let crumbs = _breadcrumbsAddress.pointee {
crumbs._invariantCheck(for: self.asString)
}
}
#endif // INTERNAL_CHECKS_ENABLED
}
// Appending
extension _StringStorage {
// Perform common post-RRC adjustments and invariant enforcement
@_effects(releasenone)
private func _postRRCAdjust(newCount: Int, newIsASCII: Bool) {
#if arch(i386) || arch(arm)
self._count = newCount
self._flags = Flags(isASCII: newIsASCII)
#else
self._countAndFlags = CountAndFlags(
count: newCount, isASCII: newIsASCII)
#endif
self.terminator.pointee = 0
// TODO(String performance): Consider updating breadcrumbs when feasible
self._breadcrumbsAddress.pointee = nil
_invariantCheck()
}
// Perform common post-append adjustments and invariant enforcement
@_effects(releasenone)
private func _postAppendAdjust(
appendedCount: Int, appendedIsASCII isASCII: Bool
) {
let oldTerminator = self.terminator
_postRRCAdjust(
newCount: self.count + appendedCount, newIsASCII: self.isASCII && isASCII)
_internalInvariant(oldTerminator + appendedCount == self.terminator)
}
@_effects(releasenone)
internal func appendInPlace(
_ other: UnsafeBufferPointer<UInt8>, isASCII: Bool
) {
_internalInvariant(self.capacity >= other.count)
let srcAddr = other.baseAddress._unsafelyUnwrappedUnchecked
let srcCount = other.count
self.mutableEnd.initialize(from: srcAddr, count: srcCount)
_postAppendAdjust(appendedCount: srcCount, appendedIsASCII: isASCII)
}
@_effects(releasenone)
internal func appendInPlace<Iter: IteratorProtocol>(
_ other: inout Iter, isASCII: Bool
) where Iter.Element == UInt8 {
var srcCount = 0
while let cu = other.next() {
_internalInvariant(self.unusedCapacity >= 1)
unusedStorage[srcCount] = cu
srcCount += 1
}
_postAppendAdjust(appendedCount: srcCount, appendedIsASCII: isASCII)
}
internal func clear() {
_postRRCAdjust(newCount: 0, newIsASCII: true)
}
}
// Removing
extension _StringStorage {
@_effects(releasenone)
internal func remove(from lower: Int, to upper: Int) {
_internalInvariant(lower <= upper)
let lowerPtr = mutableStart + lower
let upperPtr = mutableStart + upper
let tailCount = mutableEnd - upperPtr
lowerPtr.moveInitialize(from: upperPtr, count: tailCount)
_postRRCAdjust(
newCount: self.count &- (upper &- lower), newIsASCII: self.isASCII)
}
// Reposition a tail of this storage from src to dst. Returns the length of
// the tail.
@_effects(releasenone)
internal func _slideTail(
src: UnsafeMutablePointer<UInt8>,
dst: UnsafeMutablePointer<UInt8>
) -> Int {
_internalInvariant(dst >= mutableStart && src <= mutableEnd)
let tailCount = mutableEnd - src
dst.moveInitialize(from: src, count: tailCount)
return tailCount
}
@_effects(releasenone)
internal func replace(
from lower: Int, to upper: Int, with replacement: UnsafeBufferPointer<UInt8>
) {
_internalInvariant(lower <= upper)
let replCount = replacement.count
_internalInvariant(replCount - (upper - lower) <= unusedCapacity)
// Position the tail
let lowerPtr = mutableStart + lower
let tailCount = _slideTail(
src: mutableStart + upper, dst: lowerPtr + replCount)
// Copy in the contents
lowerPtr.moveInitialize(
from: UnsafeMutablePointer(
mutating: replacement.baseAddress._unsafelyUnwrappedUnchecked),
count: replCount)
let isASCII = self.isASCII && _allASCII(replacement)
_postRRCAdjust(newCount: lower + replCount + tailCount, newIsASCII: isASCII)
}
@_effects(releasenone)
internal func replace<C: Collection>(
from lower: Int,
to upper: Int,
with replacement: C,
replacementCount replCount: Int
) where C.Element == UInt8 {
_internalInvariant(lower <= upper)
_internalInvariant(replCount - (upper - lower) <= unusedCapacity)
// Position the tail
let lowerPtr = mutableStart + lower
let tailCount = _slideTail(
src: mutableStart + upper, dst: lowerPtr + replCount)
// Copy in the contents
var isASCII = self.isASCII
var srcCount = 0
for cu in replacement {
if cu >= 0x80 { isASCII = false }
lowerPtr[srcCount] = cu
srcCount += 1
}
_internalInvariant(srcCount == replCount)
_postRRCAdjust(
newCount: lower + replCount + tailCount, newIsASCII: isASCII)
}
}
// For shared storage and bridging literals
final internal class _SharedStringStorage: __SwiftNativeNSString, _AbstractStringStorage {
internal var _owner: AnyObject?
internal var start: UnsafePointer<UInt8>
#if arch(i386) || arch(arm)
internal var _count: Int
internal var _flags: _StringObject.Flags
@inlinable
internal var _countAndFlags: _StringObject.CountAndFlags {
@inline(__always) get {
return CountAndFlags(count: _count, flags: _flags)
}
}
#else
internal var _countAndFlags: _StringObject.CountAndFlags
#endif
internal var _breadcrumbs: _StringBreadcrumbs? = nil
internal var count: Int {
@_effects(readonly) @inline(__always) get {
#if arch(i386) || arch(arm)
return _count
#else
return _countAndFlags.count
#endif
}
}
internal init(
immortal ptr: UnsafePointer<UInt8>,
countAndFlags: _StringObject.CountAndFlags
) {
self._owner = nil
self.start = ptr
#if arch(i386) || arch(arm)
self._count = countAndFlags.count
self._flags = countAndFlags.flags
#else
self._countAndFlags = countAndFlags
#endif
super.init()
self._invariantCheck()
}
internal final var isASCII: Bool {
@inline(__always) get {
#if arch(i386) || arch(arm)
return _flags.isASCII
#else
return _countAndFlags.isASCII
#endif
}
}
final internal var asString: String {
@_effects(readonly) @inline(__always) get {
return String(_StringGuts(self))
}
}
#if _runtime(_ObjC)
@objc(length)
final internal var length: Int {
@_effects(readonly) get {
return asString.utf16.count //UTF16View special-cases ASCII for us
}
}
@objc final internal var hash: UInt {
@_effects(readonly) get {
if isASCII {
return _cocoaHashASCIIBytes(start, length: count)
}
return _cocoaHashString(self)
}
}
@objc(characterAtIndex:)
@_effects(readonly)
final internal func character(at offset: Int) -> UInt16 {
let str = asString
return str.utf16[str._toUTF16Index(offset)]
}
@objc(getCharacters:range:)
@_effects(releasenone)
final internal func getCharacters(
_ buffer: UnsafeMutablePointer<UInt16>,
range aRange: _SwiftNSRange) {
_getCharacters(buffer, aRange)
}
@objc
final internal var fastestEncoding: UInt {
@_effects(readonly) get {
if isASCII {
return _cocoaASCIIEncoding
}
return _cocoaUTF8Encoding
}
}
@objc(_fastCStringContents:)
@_effects(readonly)
final internal func _fastCStringContents(_ requiresNulTermination:Int8) -> UnsafePointer<CChar>? {
if isASCII {
return start._asCChar
}
return nil
}
@objc(UTF8String)
@_effects(readonly)
final internal func _utf8String() -> UnsafePointer<UInt8>? {
return start
}
@objc(cStringUsingEncoding:)
@_effects(readonly)
final internal func cString(encoding: UInt) -> UnsafePointer<UInt8>? {
return _cString(encoding: encoding)
}
@objc(getCString:maxLength:encoding:)
@_effects(releasenone)
final internal func getCString(_ outputPtr: UnsafeMutablePointer<UInt8>,
maxLength: Int,
encoding: UInt)
-> Int8 {
return _getCString(outputPtr, maxLength, encoding)
}
@objc(isEqualToString:)
@_effects(readonly)
final internal func isEqual(to other:AnyObject?) -> Int8 {
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)
}
extension _SharedStringStorage {
#if !INTERNAL_CHECKS_ENABLED
@inline(__always) internal func _invariantCheck() {}
#else
internal func _invariantCheck() {
if let crumbs = _breadcrumbs {
crumbs._invariantCheck(for: self.asString)
}
_countAndFlags._invariantCheck()
}
#endif // INTERNAL_CHECKS_ENABLED
}