mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Switch StringObject and StringGuts from opaquely storing tagged cocoa strings into storing small strings. Plumb small string support throughout the standard library's routines.
1029 lines
24 KiB
Swift
1029 lines
24 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// TODO: Comments. Supposed to abstract bit-twiddling operations. Meant to be a
|
|
// completely transparent struct. That is, it's just a trivial encapsulation to
|
|
// host many operations that would otherwise be scattered throughout StringGuts
|
|
// implementation.
|
|
//
|
|
@_fixed_layout
|
|
public // @testable
|
|
struct _StringObject {
|
|
// TODO: Proper built-in string object support.
|
|
#if arch(i386) || arch(arm)
|
|
// BridgeObject lacks support for tagged pointers on 32-bit platforms, and
|
|
// there are no free bits available to implement it. We use a single-word
|
|
// enum instead, with an additional word for holding tagged values and (in the
|
|
// non-tagged case) spilled flags.
|
|
@_frozen
|
|
@_versioned
|
|
internal enum _Variant {
|
|
case strong(AnyObject) // _bits stores flags
|
|
case unmanagedSingleByte // _bits is the start address
|
|
case unmanagedDoubleByte // _bits is the start address
|
|
case smallSingleByte // _bits is the payload
|
|
case smallDoubleByte // _bits is the payload
|
|
// TODO small strings
|
|
}
|
|
|
|
@_versioned
|
|
internal
|
|
var _variant: _Variant
|
|
|
|
@_versioned
|
|
internal
|
|
var _bits: UInt
|
|
#else
|
|
// On 64-bit platforms, we use BridgeObject for now. This might be very
|
|
// slightly suboptimal and different than hand-optimized bit patterns, but
|
|
// provides us the runtime functionality we want.
|
|
@_versioned
|
|
internal
|
|
var _object: Builtin.BridgeObject
|
|
#endif
|
|
|
|
#if arch(i386) || arch(arm)
|
|
@_versioned
|
|
@_inlineable
|
|
@inline(__always)
|
|
internal
|
|
init(_ variant: _Variant, _ bits: UInt) {
|
|
self._variant = variant
|
|
self._bits = bits
|
|
_invariantCheck()
|
|
}
|
|
#else
|
|
@_versioned
|
|
@_inlineable
|
|
@inline(__always)
|
|
internal
|
|
init(_ object: Builtin.BridgeObject) {
|
|
self._object = object
|
|
_invariantCheck()
|
|
}
|
|
#endif
|
|
}
|
|
|
|
extension _StringObject {
|
|
#if arch(i386) || arch(arm)
|
|
public typealias _RawBitPattern = UInt64
|
|
#else
|
|
public typealias _RawBitPattern = UInt
|
|
#endif
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var rawBits: _RawBitPattern {
|
|
@inline(__always)
|
|
get {
|
|
#if arch(i386) || arch(arm)
|
|
let variantBits: UInt = Builtin.reinterpretCast(_variant)
|
|
return _RawBitPattern(_bits) &<< 32 | _RawBitPattern(variantBits)
|
|
#else
|
|
return Builtin.reinterpretCast(_object)
|
|
#endif
|
|
}
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
@inline(__always)
|
|
// TODO: private
|
|
internal
|
|
init(taggedRawBits: _RawBitPattern) {
|
|
#if arch(i386) || arch(arm)
|
|
self.init(
|
|
Builtin.reinterpretCast(UInt(truncatingIfNeeded: taggedRawBits)),
|
|
UInt(truncatingIfNeeded: taggedRawBits &>> 32))
|
|
#else
|
|
self.init(_bridgeObject(fromTagged: taggedRawBits))
|
|
_sanityCheck(self.isValue)
|
|
#endif
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
@inline(__always)
|
|
// TODO: private
|
|
internal
|
|
init(nonTaggedRawBits: _RawBitPattern) {
|
|
#if arch(i386) || arch(arm)
|
|
self.init(
|
|
Builtin.reinterpretCast(UInt(truncatingIfNeeded: nonTaggedRawBits)),
|
|
UInt(truncatingIfNeeded: nonTaggedRawBits &>> 32))
|
|
#else
|
|
self.init(Builtin.reinterpretCast(nonTaggedRawBits))
|
|
_sanityCheck(!self.isValue)
|
|
#endif
|
|
}
|
|
|
|
// For when you need to hack around ARC. Note that by using this initializer,
|
|
// we are giving up on compile-time constant folding of ARC of values. Thus,
|
|
// this should only be called from the callee of a non-inlineable function
|
|
// that has no knowledge of the value-ness of the object.
|
|
@_versioned
|
|
@_inlineable
|
|
@inline(__always)
|
|
// TODO: private
|
|
internal
|
|
init(noReallyHereAreTheRawBits bits: _RawBitPattern) {
|
|
#if arch(i386) || arch(arm)
|
|
self.init(
|
|
Builtin.reinterpretCast(UInt(truncatingIfNeeded: bits)),
|
|
UInt(truncatingIfNeeded: bits &>> 32))
|
|
#else
|
|
self.init(Builtin.reinterpretCast(bits))
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// ## _StringObject bit layout
|
|
//
|
|
// x86-64 and arm64: (one 64-bit word)
|
|
// +---+---+---|---+------+----------------------------------------------------+
|
|
// + t | v | o | w | uuuu | payload (56 bits) |
|
|
// +---+---+---|---+------+----------------------------------------------------+
|
|
// msb lsb
|
|
//
|
|
// i386 and arm: (two 32-bit words)
|
|
// _variant _bits
|
|
// +------------------------------------+ +------------------------------------+
|
|
// + .strong(AnyObject) | | v | o | w | unused (29 bits) |
|
|
// +------------------------------------+ +------------------------------------+
|
|
// + .unmanaged{Single,Double}Byte | | start address (32 bits) |
|
|
// +------------------------------------+ +------------------------------------+
|
|
// + .small{Single,Double}Byte | | payload (32 bits) |
|
|
// +------------------------------------+ +------------------------------------+
|
|
// msb lsb msb lsb
|
|
//
|
|
// where t: is-a-value, i.e. a tag bit that says not to perform ARC
|
|
// v: sub-variant bit, i.e. set for isCocoa or isSmall
|
|
// o: is-opaque, i.e. opaque vs contiguously stored strings
|
|
// w: width indicator bit (0: ASCII, 1: UTF-16)
|
|
// u: unused bits
|
|
//
|
|
// payload is:
|
|
// isNative: the native StringStorage object
|
|
// isCocoa: the Cocoa object
|
|
// isOpaque & !isCocoa: the _OpaqueString object
|
|
// isUnmanaged: the pointer to code units
|
|
// isSmall: opaque bits used for inline storage // TODO: use them!
|
|
//
|
|
extension _StringObject {
|
|
#if arch(i386) || arch(arm)
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
static var _isCocoaBit: UInt {
|
|
@inline(__always)
|
|
get {
|
|
return 0x8000_0000
|
|
}
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
static var _isOpaqueBit: UInt {
|
|
@inline(__always)
|
|
get {
|
|
return 0x4000_0000
|
|
}
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
static var _twoByteBit: UInt {
|
|
@inline(__always)
|
|
get {
|
|
return 0x2000_0000
|
|
}
|
|
}
|
|
#else // !(arch(i386) || arch(arm))
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
static var _isValueBit: UInt {
|
|
@inline(__always)
|
|
get {
|
|
// NOTE: deviating from ObjC tagged pointer bits, as we just want to avoid
|
|
// swift runtime management, and top bit suffices for that.
|
|
return 0x80_00_0000_0000_0000
|
|
}
|
|
}
|
|
|
|
// After deciding isValue, which of the two variants (on both sides) are we.
|
|
// That is, native vs objc or unsafe vs small.
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
static var _subVariantBit: UInt {
|
|
@inline(__always)
|
|
get {
|
|
return 0x40_00_0000_0000_0000
|
|
}
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
static var _isOpaqueBit: UInt {
|
|
@inline(__always)
|
|
get {
|
|
return 0x20_00_0000_0000_0000
|
|
}
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
static var _twoByteBit: UInt {
|
|
@inline(__always)
|
|
get {
|
|
return 0x10_00_0000_0000_0000
|
|
}
|
|
}
|
|
|
|
// There are 4 sub-variants depending on the isValue and subVariant bits
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
static var _variantMask: UInt {
|
|
@inline(__always)
|
|
get { return UInt(Builtin.stringObjectOr_Int64(
|
|
_isValueBit._value, _subVariantBit._value)) }
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
static var _payloadMask: UInt {
|
|
@inline(__always)
|
|
get {
|
|
return 0x00FF_FFFF_FFFF_FFFF
|
|
}
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var _variantBits: UInt {
|
|
@inline(__always)
|
|
get {
|
|
return rawBits & _StringObject._variantMask
|
|
}
|
|
}
|
|
#endif // arch(i386) || arch(arm)
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var referenceBits: UInt {
|
|
@inline(__always)
|
|
get {
|
|
#if arch(i386) || arch(arm)
|
|
guard case let .strong(object) = _variant else {
|
|
_sanityCheckFailure("internal error: expected a non-tagged String")
|
|
}
|
|
return Builtin.reinterpretCast(object)
|
|
#else
|
|
_sanityCheck(isNative || isCocoa)
|
|
return rawBits & _StringObject._payloadMask
|
|
#endif
|
|
}
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var payloadBits: UInt {
|
|
@inline(__always)
|
|
get {
|
|
#if arch(i386) || arch(arm)
|
|
if case .strong(_) = _variant {
|
|
_sanityCheckFailure("internal error: expected a tagged String")
|
|
}
|
|
return _bits
|
|
#else
|
|
_sanityCheck(!isNative && !isCocoa)
|
|
return rawBits & _StringObject._payloadMask
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Empty strings
|
|
//
|
|
|
|
#if arch(i386) || arch(arm)
|
|
@_versioned // FIXME(sil-serialize-all)
|
|
internal var _emptyStringStorage: UInt32 = 0
|
|
|
|
@_inlineable // FIXME(sil-serialize-all)
|
|
@_versioned // FIXME(sil-serialize-all)
|
|
internal var _emptyStringAddressBits: UInt {
|
|
let p = UnsafeRawPointer(Builtin.addressof(&_emptyStringStorage))
|
|
return UInt(bitPattern: p)
|
|
}
|
|
#endif // arch(i386) || arch(arm)
|
|
|
|
extension _StringObject {
|
|
#if arch(i386) || arch(arm)
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var isEmptySingleton: Bool {
|
|
guard _bits == _emptyStringAddressBits else { return false }
|
|
switch _variant {
|
|
case .unmanagedSingleByte, .unmanagedDoubleByte:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
@inline(__always)
|
|
internal
|
|
init() {
|
|
self.init(.unmanagedSingleByte, _emptyStringAddressBits)
|
|
}
|
|
#else
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
static var _emptyStringBitPattern: UInt {
|
|
@inline(__always)
|
|
get { return _smallUTF8TopNibble }
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var isEmptySingleton: Bool {
|
|
@inline(__always)
|
|
get { return rawBits == _StringObject._emptyStringBitPattern }
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
@inline(__always)
|
|
internal
|
|
init() {
|
|
self.init(taggedRawBits: _StringObject._emptyStringBitPattern)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// Small strings
|
|
//
|
|
extension _StringObject {
|
|
// TODO: Decide what to do for the last bit in the top nibble, when we scrap
|
|
// the opaque bit (which should really be the isSmallUTF8String bit)
|
|
//
|
|
// TODO: Pretty ASCII art, better description
|
|
//
|
|
// An encoded small UTF-8 string's first byte has a leading nibble of 1110
|
|
// and a trailing nibble containing the count.
|
|
|
|
#if arch(i386) || arch(arm)
|
|
#else
|
|
@_versioned @_inlineable internal
|
|
static var _topNibbleMask: UInt {
|
|
@inline(__always)
|
|
get { return 0xF000_0000_0000_0000 }
|
|
}
|
|
@_versioned @_inlineable internal
|
|
static var _smallUTF8TopNibble: UInt {
|
|
@inline(__always)
|
|
get { return 0xE000_0000_0000_0000 }
|
|
}
|
|
@_versioned @_inlineable internal
|
|
static var _smallUTF8CountMask: UInt {
|
|
@inline(__always)
|
|
get { return 0x0F00_0000_0000_0000 }
|
|
}
|
|
#endif
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var _isSmallUTF8: Bool {
|
|
@inline(__always)
|
|
get {
|
|
#if arch(i386) || arch(arm)
|
|
return false
|
|
#else
|
|
return rawBits & _StringObject._topNibbleMask
|
|
== _StringObject._smallUTF8TopNibble
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// TODO: describe better
|
|
//
|
|
// The top nibble is the mask, second nibble the count. Turn off the mask and
|
|
// keep the count. The StringObject represents the second word of a
|
|
// SmallUTF8String.
|
|
//
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var asSmallUTF8SecondWord: UInt {
|
|
@inline(__always)
|
|
get {
|
|
#if arch(i386) || arch(arm)
|
|
unsupportedOn32bit()
|
|
#else
|
|
_sanityCheck(_isSmallUTF8)
|
|
return rawBits & ~_StringObject._topNibbleMask
|
|
#endif
|
|
}
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var smallUTF8Count: Int {
|
|
@inline(__always)
|
|
get {
|
|
#if arch(i386) || arch(arm)
|
|
unsupportedOn32bit()
|
|
#else
|
|
_sanityCheck(_isSmallUTF8)
|
|
let count = (rawBits & _StringObject._smallUTF8CountMask) &>> 56
|
|
_sanityCheck(count <= _SmallUTF8String.capacity)
|
|
return Int(bitPattern: count)
|
|
#endif
|
|
}
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
init(_smallUTF8SecondWord bits: UInt) {
|
|
#if arch(i386) || arch(arm)
|
|
unsupportedOn32bit()
|
|
#else
|
|
_sanityCheck(bits & _StringObject._topNibbleMask == 0)
|
|
self.init(taggedRawBits: bits | _StringObject._smallUTF8TopNibble)
|
|
#endif
|
|
}
|
|
}
|
|
|
|
//
|
|
// Private convenience helpers to layer on top of BridgeObject
|
|
//
|
|
// TODO: private!
|
|
//
|
|
extension _StringObject {
|
|
@_versioned
|
|
@_inlineable
|
|
internal // TODO: private!
|
|
var asNativeObject: AnyObject {
|
|
@inline(__always)
|
|
get {
|
|
#if arch(i386) || arch(arm)
|
|
switch _variant {
|
|
case .strong(let object):
|
|
_sanityCheck(_bits & _StringObject._isCocoaBit == 0)
|
|
_sanityCheck(_usesNativeSwiftReferenceCounting(type(of: object)))
|
|
return object
|
|
default:
|
|
_sanityCheckFailure("asNativeObject on unmanaged _StringObject")
|
|
}
|
|
#else
|
|
_sanityCheck(isNative)
|
|
_sanityCheck(
|
|
_usesNativeSwiftReferenceCounting(
|
|
type(of: Builtin.reinterpretCast(referenceBits) as AnyObject)))
|
|
return Builtin.reinterpretCast(referenceBits)
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if _runtime(_ObjC)
|
|
@_versioned
|
|
@_inlineable
|
|
internal // TODO: private!
|
|
var asCocoaObject: _CocoaString {
|
|
@inline(__always)
|
|
get {
|
|
#if arch(i386) || arch(arm)
|
|
switch _variant {
|
|
case .strong(let object):
|
|
_sanityCheck(_bits & _StringObject._isCocoaBit != 0)
|
|
_sanityCheck(!_usesNativeSwiftReferenceCounting(type(of: object)))
|
|
return object
|
|
default:
|
|
_sanityCheckFailure("asCocoaObject on unmanaged _StringObject")
|
|
}
|
|
#else
|
|
_sanityCheck(isCocoa)
|
|
_sanityCheck(
|
|
!_usesNativeSwiftReferenceCounting(
|
|
type(of: Builtin.reinterpretCast(referenceBits) as AnyObject)))
|
|
return Builtin.reinterpretCast(referenceBits)
|
|
#endif
|
|
}
|
|
}
|
|
#endif
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal // TODO: private!
|
|
var asOpaqueObject: _OpaqueString {
|
|
@inline(__always)
|
|
get {
|
|
_sanityCheck(isOpaque)
|
|
let object = Builtin.reinterpretCast(referenceBits) as AnyObject
|
|
return object as! _OpaqueString
|
|
}
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var asUnmanagedRawStart: UnsafeRawPointer {
|
|
@inline(__always)
|
|
get {
|
|
_sanityCheck(isUnmanaged)
|
|
#if arch(i386) || arch(arm)
|
|
return UnsafeRawPointer(bitPattern: _bits)._unsafelyUnwrappedUnchecked
|
|
#else
|
|
return UnsafeRawPointer(
|
|
bitPattern: payloadBits
|
|
)._unsafelyUnwrappedUnchecked
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Queries on a StringObject
|
|
//
|
|
extension _StringObject {
|
|
//
|
|
// Determine which of the 4 major variants we are
|
|
//
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var isNative: Bool {
|
|
@inline(__always)
|
|
get {
|
|
#if arch(i386) || arch(arm)
|
|
guard case .strong(_) = _variant else { return false }
|
|
return _bits & _StringObject._isCocoaBit == 0
|
|
#else
|
|
return _variantBits == 0
|
|
#endif
|
|
}
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var isCocoa: Bool {
|
|
@inline(__always)
|
|
get {
|
|
#if arch(i386) || arch(arm)
|
|
guard case .strong(_) = _variant else { return false }
|
|
return _bits & _StringObject._isCocoaBit != 0
|
|
#else
|
|
return _variantBits == _StringObject._subVariantBit
|
|
#endif
|
|
}
|
|
}
|
|
|
|
public // @testable
|
|
var owner: AnyObject? { // For testing only
|
|
#if arch(i386) || arch(arm)
|
|
guard case .strong(let object) = _variant else { return nil }
|
|
return object
|
|
#else
|
|
if _fastPath(isNative || isCocoa) {
|
|
return Builtin.reinterpretCast(referenceBits)
|
|
}
|
|
return nil
|
|
#endif
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var isValue: Bool {
|
|
@inline(__always)
|
|
get {
|
|
#if arch(i386) || arch(arm)
|
|
switch _variant {
|
|
case .strong(_): return false
|
|
default:
|
|
return true
|
|
}
|
|
#else
|
|
return rawBits & _StringObject._isValueBit != 0
|
|
#endif
|
|
}
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var isUnmanaged: Bool {
|
|
@inline(__always)
|
|
get {
|
|
#if arch(i386) || arch(arm)
|
|
switch _variant {
|
|
case .unmanagedSingleByte, .unmanagedDoubleByte:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
#else
|
|
return _variantBits == _StringObject._isValueBit
|
|
#endif
|
|
}
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var isSmall: Bool {
|
|
@inline(__always)
|
|
get {
|
|
#if arch(i386) || arch(arm)
|
|
switch _variant {
|
|
case .smallSingleByte, .smallDoubleByte:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
#else
|
|
return _variantBits == _StringObject._variantMask
|
|
#endif
|
|
}
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var isSmallOrCocoa: Bool {
|
|
@inline(__always)
|
|
get {
|
|
#if arch(i386) || arch(arm)
|
|
switch _variant {
|
|
case .smallSingleByte, .smallDoubleByte:
|
|
return true
|
|
default:
|
|
return isCocoa
|
|
}
|
|
#else
|
|
return rawBits & _StringObject._subVariantBit != 0
|
|
#endif
|
|
}
|
|
}
|
|
|
|
//
|
|
// Frequently queried properties
|
|
//
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var isContiguous: Bool {
|
|
@inline(__always)
|
|
get {
|
|
#if arch(i386) || arch(arm)
|
|
switch _variant {
|
|
case .strong(_):
|
|
return _bits & _StringObject._isOpaqueBit == 0
|
|
case .unmanagedSingleByte, .unmanagedDoubleByte:
|
|
return true
|
|
case .smallSingleByte, .smallDoubleByte:
|
|
return false
|
|
}
|
|
#else
|
|
return rawBits & _StringObject._isOpaqueBit == 0
|
|
#endif
|
|
}
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var isOpaque: Bool {
|
|
@inline(__always)
|
|
get { return !isContiguous }
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var isContiguousCocoa: Bool {
|
|
@inline(__always)
|
|
get { return isContiguous && isCocoa }
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
internal
|
|
var isNoncontiguousCocoa: Bool {
|
|
@inline(__always)
|
|
get { return isCocoa && isOpaque }
|
|
}
|
|
|
|
@_inlineable
|
|
public // @testable
|
|
var isSingleByte: Bool {
|
|
@inline(__always)
|
|
get {
|
|
#if arch(i386) || arch(arm)
|
|
switch _variant {
|
|
case .strong(_):
|
|
return _bits & _StringObject._twoByteBit == 0
|
|
case .unmanagedSingleByte, .smallSingleByte:
|
|
return true
|
|
case .unmanagedDoubleByte, .smallDoubleByte:
|
|
return false
|
|
}
|
|
#else
|
|
return rawBits & _StringObject._twoByteBit == 0
|
|
#endif
|
|
}
|
|
}
|
|
|
|
@_inlineable
|
|
public // @testable
|
|
var byteWidth: Int {
|
|
@inline(__always)
|
|
get { return isSingleByte ? 1 : 2 }
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
var bitWidth: Int {
|
|
@inline(__always)
|
|
get { return byteWidth &<< 3 }
|
|
}
|
|
|
|
@_inlineable
|
|
public // @testable
|
|
var isContiguousASCII: Bool {
|
|
@inline(__always)
|
|
get { return isContiguous && isSingleByte }
|
|
}
|
|
|
|
@_inlineable
|
|
public // @testable
|
|
var isContiguousUTF16: Bool {
|
|
@inline(__always)
|
|
get { return isContiguous && !isSingleByte }
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
@inline(__always)
|
|
internal
|
|
func nativeStorage<CodeUnit>(
|
|
of codeUnit: CodeUnit.Type = CodeUnit.self
|
|
) -> _SwiftStringStorage<CodeUnit>
|
|
where CodeUnit : FixedWidthInteger & UnsignedInteger {
|
|
_sanityCheck(isNative)
|
|
_sanityCheck(CodeUnit.bitWidth == self.bitWidth)
|
|
// TODO: Is this the way to do it?
|
|
return _unsafeUncheckedDowncast(
|
|
asNativeObject, to: _SwiftStringStorage<CodeUnit>.self)
|
|
}
|
|
|
|
@_inlineable
|
|
public // @testable
|
|
var nativeRawStorage: _SwiftRawStringStorage {
|
|
@inline(__always) get {
|
|
_sanityCheck(isNative)
|
|
return _unsafeUncheckedDowncast(
|
|
asNativeObject, to: _SwiftRawStringStorage.self)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension _StringObject {
|
|
@_inlineable // FIXME(sil-serialize-all)
|
|
@_versioned // FIXME(sil-serialize-all)
|
|
internal func _invariantCheck() {
|
|
#if INTERNAL_CHECKS_ENABLED
|
|
_sanityCheck(MemoryLayout<_StringObject>.size == 8)
|
|
_sanityCheck(isContiguous || isOpaque)
|
|
_sanityCheck(isOpaque || isContiguousASCII || isContiguousUTF16)
|
|
if isNative {
|
|
_sanityCheck(isContiguous)
|
|
if isSingleByte {
|
|
_sanityCheck(isContiguousASCII)
|
|
_sanityCheck(asNativeObject is _SwiftStringStorage<UInt8>)
|
|
} else {
|
|
_sanityCheck(asNativeObject is _SwiftStringStorage<UInt16>)
|
|
}
|
|
} else if isUnmanaged {
|
|
_sanityCheck(isContiguous)
|
|
_sanityCheck(payloadBits > 0) // TODO: inside address space
|
|
} else if isCocoa {
|
|
#if _runtime(_ObjC)
|
|
let object = asCocoaObject
|
|
_sanityCheck(
|
|
!_usesNativeSwiftReferenceCounting(type(of: object as AnyObject)))
|
|
#else
|
|
_sanityCheckFailure("Cocoa objects aren't supported on this platform")
|
|
#endif
|
|
} else if isSmall {
|
|
// TODO: Drop the whole opaque bit thing...
|
|
_sanityCheck(isOpaque)
|
|
|
|
} else {
|
|
fatalError("Unimplemented string form")
|
|
}
|
|
#endif // INTERNAL_CHECKS_ENABLED
|
|
}
|
|
}
|
|
|
|
//
|
|
// Conveniently construct, tag, flag, etc. StringObjects
|
|
//
|
|
extension _StringObject {
|
|
@_versioned
|
|
@_inlineable
|
|
@inline(__always)
|
|
internal
|
|
init(
|
|
_payloadBits: UInt,
|
|
isValue: Bool,
|
|
isSmallOrObjC: Bool,
|
|
isOpaque: Bool,
|
|
isTwoByte: Bool
|
|
) {
|
|
#if INTERNAL_CHECKS_ENABLED
|
|
defer {
|
|
_sanityCheck(isSmall == (isValue && isSmallOrObjC))
|
|
_sanityCheck(isUnmanaged == (isValue && !isSmallOrObjC))
|
|
_sanityCheck(isCocoa == (!isValue && isSmallOrObjC))
|
|
_sanityCheck(isNative == (!isValue && !isSmallOrObjC))
|
|
}
|
|
#endif
|
|
|
|
#if arch(i386) || arch(arm)
|
|
if isValue {
|
|
if isSmallOrObjC {
|
|
_sanityCheck(isOpaque)
|
|
self.init(isTwoByte ? .smallDoubleByte : .smallSingleByte, _payloadBits)
|
|
} else {
|
|
_sanityCheck(!isOpaque)
|
|
self.init(
|
|
isTwoByte ? .unmanagedDoubleByte : .unmanagedSingleByte, _payloadBits)
|
|
}
|
|
return
|
|
}
|
|
|
|
var bits: UInt = 0
|
|
if isSmallOrObjC {
|
|
bits |= _StringObject._isCocoaBit
|
|
}
|
|
if isOpaque {
|
|
bits |= _StringObject._isOpaqueBit
|
|
}
|
|
if isTwoByte {
|
|
bits |= _StringObject._twoByteBit
|
|
}
|
|
self.init(.strong(Builtin.reinterpretCast(_payloadBits)), bits)
|
|
#else
|
|
_sanityCheck(_payloadBits & ~_StringObject._payloadMask == 0)
|
|
var rawBits = _payloadBits & _StringObject._payloadMask
|
|
if isValue {
|
|
var rawBitsBuiltin = Builtin.stringObjectOr_Int64(
|
|
rawBits._value, _StringObject._isValueBit._value)
|
|
if isSmallOrObjC {
|
|
rawBitsBuiltin = Builtin.stringObjectOr_Int64(
|
|
rawBitsBuiltin, _StringObject._subVariantBit._value)
|
|
}
|
|
if isOpaque {
|
|
rawBitsBuiltin = Builtin.stringObjectOr_Int64(
|
|
rawBitsBuiltin, _StringObject._isOpaqueBit._value)
|
|
}
|
|
if isTwoByte {
|
|
rawBitsBuiltin = Builtin.stringObjectOr_Int64(
|
|
rawBitsBuiltin, _StringObject._twoByteBit._value)
|
|
}
|
|
rawBits = UInt(rawBitsBuiltin)
|
|
self.init(taggedRawBits: rawBits)
|
|
} else {
|
|
if isSmallOrObjC {
|
|
rawBits |= _StringObject._subVariantBit
|
|
}
|
|
if isOpaque {
|
|
rawBits |= _StringObject._isOpaqueBit
|
|
}
|
|
if isTwoByte {
|
|
rawBits |= _StringObject._twoByteBit
|
|
}
|
|
self.init(nonTaggedRawBits: rawBits)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
@inline(__always)
|
|
internal
|
|
init(
|
|
_someObject: AnyObject,
|
|
isCocoa: Bool,
|
|
isContiguous: Bool,
|
|
isSingleByte: Bool
|
|
) {
|
|
defer { _fixLifetime(_someObject) }
|
|
self.init(
|
|
_payloadBits: Builtin.reinterpretCast(_someObject),
|
|
isValue: false,
|
|
isSmallOrObjC: isCocoa,
|
|
isOpaque: !isContiguous,
|
|
isTwoByte: !isSingleByte)
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
@inline(__always)
|
|
internal
|
|
init(nativeObject: AnyObject, isSingleByte: Bool) {
|
|
self.init(
|
|
_someObject: nativeObject,
|
|
isCocoa: false,
|
|
isContiguous: true,
|
|
isSingleByte: isSingleByte)
|
|
}
|
|
|
|
#if _runtime(_ObjC)
|
|
@_versioned
|
|
@_inlineable
|
|
@inline(__always)
|
|
internal
|
|
init(cocoaObject: AnyObject, isSingleByte: Bool, isContiguous: Bool) {
|
|
// TODO: is it possible to sanity check? maybe `is NSObject`?
|
|
self.init(
|
|
_someObject: cocoaObject,
|
|
isCocoa: true,
|
|
isContiguous: isContiguous,
|
|
isSingleByte: isSingleByte)
|
|
}
|
|
#else
|
|
@_versioned
|
|
@_inlineable
|
|
@inline(__always)
|
|
internal
|
|
init<S: _OpaqueString>(opaqueString: S) {
|
|
self.init(
|
|
_someObject: opaqueString,
|
|
isCocoa: false,
|
|
isContiguous: false,
|
|
isSingleByte: false)
|
|
}
|
|
#endif
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
@inline(__always)
|
|
internal
|
|
init<CodeUnit>(
|
|
unmanaged: UnsafePointer<CodeUnit>
|
|
) where CodeUnit : FixedWidthInteger & UnsignedInteger {
|
|
self.init(
|
|
_payloadBits: UInt(bitPattern: unmanaged),
|
|
isValue: true,
|
|
isSmallOrObjC: false,
|
|
isOpaque: false,
|
|
isTwoByte: CodeUnit.bitWidth == 16)
|
|
_sanityCheck(isSingleByte == (CodeUnit.bitWidth == 8))
|
|
}
|
|
|
|
@_versioned
|
|
@_inlineable
|
|
@inline(__always)
|
|
internal
|
|
init<CodeUnit>(
|
|
_ storage: _SwiftStringStorage<CodeUnit>
|
|
) where CodeUnit : FixedWidthInteger & UnsignedInteger {
|
|
self.init(nativeObject: storage, isSingleByte: CodeUnit.bitWidth == 8)
|
|
_sanityCheck(isSingleByte == (CodeUnit.bitWidth == 8))
|
|
}
|
|
}
|