mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
[stdlib]Unify String hashing implementation (#14921)
* Add partial range subscripts to _UnmanagedOpaqueString * Use SipHash13+_NormalizedCodeUnitIterator for String hashes on all platforms * Remove unecessary collation algorithm shims * Pass the buffer to the SipHasher for ASCII * Hash the ascii parts of UTF16 strings the same way we hash pure ascii strings * De-dupe some code that can be shared between _UnmanagedOpaqueString and _UnmanagedString<UInt16> * ASCII strings now hash consistently for in hashASCII() and hashUTF16() * Fix zalgo comparison regression * Use hasher * Fix crash when appending to an empty _FixedArray * Compact ASCII characters into a single UInt64 for hashing * String: Switch to _hash(into:)-based hashing This should speed up String hashing quite a bit, as doing it through hashValue involves two rounds of SipHash nested in each other. * Remove obsolete workaround for ARC traffic * Ditch _FixedArray<UInt8> in favor of _UIntBuffer<UInt64, UInt8> * Bad rebase remnants * Fix failing benchmarks * michael's feedback * clarify the comment about nul-terminated string hashes
This commit is contained in:
@@ -62,22 +62,6 @@ SWIFT_RUNTIME_STDLIB_INTERFACE
|
||||
const __swift_uint16_t *
|
||||
_swift_stdlib_ExtendedGraphemeClusterNoBoundaryRulesMatrix;
|
||||
|
||||
SWIFT_RUNTIME_STDLIB_INTERFACE
|
||||
void *_swift_stdlib_unicodeCollationIterator_create(
|
||||
const __swift_uint16_t *Str,
|
||||
__swift_uint32_t Length);
|
||||
|
||||
SWIFT_RUNTIME_STDLIB_INTERFACE
|
||||
__swift_int32_t _swift_stdlib_unicodeCollationIterator_next(
|
||||
void *CollationIterator, __swift_bool *HitEnd);
|
||||
|
||||
SWIFT_RUNTIME_STDLIB_INTERFACE
|
||||
void _swift_stdlib_unicodeCollationIterator_delete(
|
||||
void *CollationIterator);
|
||||
|
||||
SWIFT_RUNTIME_STDLIB_INTERFACE
|
||||
const __swift_int32_t *_swift_stdlib_unicode_getASCIICollationTable();
|
||||
|
||||
SWIFT_RUNTIME_STDLIB_INTERFACE
|
||||
__swift_int32_t _swift_stdlib_unicode_strToUpper(
|
||||
__swift_uint16_t *Destination, __swift_int32_t DestinationCapacity,
|
||||
|
||||
@@ -127,8 +127,8 @@ extension _FixedArray${N} {
|
||||
@_versioned
|
||||
internal mutating func append(_ newElement: T) {
|
||||
_sanityCheck(count < capacity)
|
||||
self[count] = newElement
|
||||
_count += 1
|
||||
self[count-1] = newElement
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,12 +23,11 @@ struct _NormalizedCodeUnitIterator: IteratorProtocol {
|
||||
|
||||
typealias CodeUnit = UInt16
|
||||
|
||||
init(_ opaqueString: _UnmanagedOpaqueString, startIndex: Int = 0) {
|
||||
source = _UnmanagedOpaqueStringSource(opaqueString, start: startIndex)
|
||||
}
|
||||
|
||||
init(_ unmanagedString: _UnmanagedString<UInt16>, startIndex: Int = 0) {
|
||||
source = _UnmanagedStringSource(unmanagedString, start: startIndex)
|
||||
init<Source: BidirectionalCollection>
|
||||
(_ collection: Source)
|
||||
where Source.Element == UInt16, Source.SubSequence == Source
|
||||
{
|
||||
source = _CollectionSource(collection)
|
||||
}
|
||||
|
||||
init(_ guts: _StringGuts, _ range: Range<Int>, startIndex: Int = 0) {
|
||||
@@ -60,23 +59,27 @@ struct _NormalizedCodeUnitIterator: IteratorProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
struct _UnmanagedOpaqueStringSource: _SegmentSource {
|
||||
struct _CollectionSource<Source: BidirectionalCollection>: _SegmentSource
|
||||
where Source.Element == UInt16, Source.SubSequence == Source
|
||||
{
|
||||
var remaining: Int {
|
||||
return opaqueString.count - index
|
||||
return collection.distance(from: index, to: collection.endIndex)
|
||||
}
|
||||
var opaqueString: _UnmanagedOpaqueString
|
||||
var index: Int
|
||||
var collection: Source
|
||||
var index: Source.Index
|
||||
|
||||
init(_ opaqueString: _UnmanagedOpaqueString, start: Int = 0) {
|
||||
self.opaqueString = opaqueString
|
||||
index = start
|
||||
init(_ collection: Source) {
|
||||
self.collection = collection
|
||||
index = collection.startIndex
|
||||
}
|
||||
|
||||
@_specialize(where Source == _UnmanagedString<UInt16>)
|
||||
@_specialize(where Source == _UnmanagedOpaqueString)
|
||||
mutating func tryFill(buffer: UnsafeMutableBufferPointer<UInt16>) -> Int? {
|
||||
var bufferIndex = 0
|
||||
let originalIndex = index
|
||||
repeat {
|
||||
guard index < opaqueString.count else {
|
||||
guard index != collection.endIndex else {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -86,49 +89,11 @@ struct _NormalizedCodeUnitIterator: IteratorProtocol {
|
||||
return nil
|
||||
}
|
||||
|
||||
let cu = opaqueString[index]
|
||||
let cu = collection[index]
|
||||
buffer[bufferIndex] = cu
|
||||
index += 1
|
||||
index = collection.index(after: index)
|
||||
bufferIndex += 1
|
||||
} while !opaqueString.hasNormalizationBoundary(after: index - 1)
|
||||
|
||||
return bufferIndex
|
||||
}
|
||||
}
|
||||
|
||||
struct _UnmanagedStringSource: _SegmentSource {
|
||||
var remaining: Int {
|
||||
return unmanagedString.count - index
|
||||
}
|
||||
|
||||
var unmanagedString: _UnmanagedString<UInt16>
|
||||
var index: Int
|
||||
|
||||
init(_ unmanagedString: _UnmanagedString<UInt16>, start: Int = 0) {
|
||||
self.unmanagedString = unmanagedString
|
||||
index = start
|
||||
}
|
||||
|
||||
mutating func tryFill(buffer: UnsafeMutableBufferPointer<UInt16>) -> Int? {
|
||||
var bufferIndex = 0
|
||||
let originalIndex = index
|
||||
repeat {
|
||||
guard index < unmanagedString.count else {
|
||||
break
|
||||
}
|
||||
|
||||
guard bufferIndex < buffer.count else {
|
||||
//The buffer isn't big enough for the current segment
|
||||
index = originalIndex
|
||||
return nil
|
||||
}
|
||||
|
||||
let cu = unmanagedString[index]
|
||||
buffer[bufferIndex] = cu
|
||||
index += 1
|
||||
bufferIndex += 1
|
||||
} while unmanagedString.hasNormalizationBoundary(
|
||||
after: index - 1) == false
|
||||
} while !collection.hasNormalizationBoundary(after: collection.index(before: index))
|
||||
|
||||
return bufferIndex
|
||||
}
|
||||
|
||||
@@ -641,7 +641,7 @@ extension _UnmanagedOpaqueString {
|
||||
) -> _Ordering {
|
||||
// Compare by pulling in a segment at a time, normalizing then comparing
|
||||
// individual code units
|
||||
var selfIterator = _NormalizedCodeUnitIterator(self, startIndex: startingFrom)
|
||||
var selfIterator = _NormalizedCodeUnitIterator(self[startingFrom...])
|
||||
return selfIterator.compare(with:
|
||||
_NormalizedCodeUnitIterator(other, otherRange, startIndex: startingFrom)
|
||||
)
|
||||
@@ -788,8 +788,8 @@ extension _UnmanagedString where CodeUnit == UInt8 {
|
||||
let otherCU = other[idx]
|
||||
|
||||
//
|
||||
// Fast path: if other is super-ASCII post-normalization, we must be less. If
|
||||
// other is ASCII and a single-scalar segment, we have our answer.
|
||||
// Fast path: if other is super-ASCII post-normalization, we must be less.
|
||||
// If other is ASCII and a single-scalar segment, we have our answer.
|
||||
//
|
||||
if otherCU > 0x7F {
|
||||
if _fastPath(
|
||||
@@ -819,7 +819,7 @@ extension _UnmanagedString where CodeUnit == UInt8 {
|
||||
}
|
||||
|
||||
extension _StringGuts {
|
||||
func hasNormalizationBoundary(after index: Int) -> Bool {
|
||||
internal func hasNormalizationBoundary(after index: Int) -> Bool {
|
||||
let nextIndex = index + 1
|
||||
if nextIndex >= self.count {
|
||||
return true
|
||||
@@ -831,7 +831,7 @@ extension _StringGuts {
|
||||
}
|
||||
|
||||
extension _UnmanagedOpaqueString {
|
||||
func hasNormalizationBoundary(after index: Int) -> Bool {
|
||||
internal func hasNormalizationBoundary(after index: Int) -> Bool {
|
||||
let nextIndex = index + 1
|
||||
if nextIndex >= self.count {
|
||||
return true
|
||||
@@ -843,7 +843,7 @@ extension _UnmanagedOpaqueString {
|
||||
}
|
||||
|
||||
extension _UnmanagedString where CodeUnit == UInt16 {
|
||||
func hasNormalizationBoundary(after index: Int) -> Bool {
|
||||
internal func hasNormalizationBoundary(after index: Int) -> Bool {
|
||||
let nextIndex = index + 1
|
||||
if nextIndex >= self.count {
|
||||
return true
|
||||
@@ -854,6 +854,18 @@ extension _UnmanagedString where CodeUnit == UInt16 {
|
||||
}
|
||||
}
|
||||
|
||||
extension BidirectionalCollection where Element == UInt16, SubSequence == Self {
|
||||
internal func hasNormalizationBoundary(after index: Index) -> Bool {
|
||||
let nextIndex = self.index(after: index)
|
||||
if nextIndex == self.endIndex {
|
||||
return true
|
||||
}
|
||||
|
||||
let nextCU = self[nextIndex]
|
||||
return _hasNormalizationBoundary(before: nextCU)
|
||||
}
|
||||
}
|
||||
|
||||
private func _compareStringsPostSuffix(
|
||||
selfASCIIChar: UInt16,
|
||||
otherUTF16: _UnmanagedString<UInt16>
|
||||
|
||||
@@ -12,181 +12,146 @@
|
||||
|
||||
import SwiftShims
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
@_inlineable // FIXME(sil-serialize-all)
|
||||
@_versioned // FIXME(sil-serialize-all)
|
||||
@_silgen_name("swift_stdlib_NSStringHashValue")
|
||||
internal func _stdlib_NSStringHashValue(
|
||||
_ str: AnyObject, isASCII: Bool) -> Int
|
||||
|
||||
@_inlineable // FIXME(sil-serialize-all)
|
||||
@_versioned // FIXME(sil-serialize-all)
|
||||
@_silgen_name("swift_stdlib_NSStringHashValuePointer")
|
||||
internal func _stdlib_NSStringHashValuePointer(
|
||||
_ str: OpaquePointer, isASCII: Bool) -> Int
|
||||
|
||||
@_inlineable // FIXME(sil-serialize-all)
|
||||
@_versioned // FIXME(sil-serialize-all)
|
||||
@_silgen_name("swift_stdlib_CFStringHashCString")
|
||||
internal func _stdlib_CFStringHashCString(
|
||||
_ str: OpaquePointer, _ len: Int) -> Int
|
||||
#endif
|
||||
|
||||
extension Unicode {
|
||||
// FIXME: cannot be marked @_versioned. See <rdar://problem/34438258>
|
||||
// @_inlineable // FIXME(sil-serialize-all)
|
||||
// @_versioned // FIXME(sil-serialize-all)
|
||||
internal static func hashASCII(
|
||||
_ string: UnsafeBufferPointer<UInt8>,
|
||||
into hasher: inout _Hasher
|
||||
) {
|
||||
let collationTable = _swift_stdlib_unicode_getASCIICollationTable()
|
||||
for c in string {
|
||||
_precondition(c <= 127)
|
||||
let element = collationTable[Int(c)]
|
||||
// Ignore zero valued collation elements. They don't participate in the
|
||||
// ordering relation.
|
||||
if element != 0 {
|
||||
hasher.append(Int(truncatingIfNeeded: element))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: cannot be marked @_versioned. See <rdar://problem/34438258>
|
||||
// @_inlineable // FIXME(sil-serialize-all)
|
||||
// @_versioned // FIXME(sil-serialize-all)
|
||||
internal static func hashUTF16(
|
||||
_ string: UnsafeBufferPointer<UInt16>,
|
||||
into hasher: inout _Hasher
|
||||
) {
|
||||
let collationIterator = _swift_stdlib_unicodeCollationIterator_create(
|
||||
string.baseAddress!,
|
||||
UInt32(string.count))
|
||||
defer { _swift_stdlib_unicodeCollationIterator_delete(collationIterator) }
|
||||
|
||||
while true {
|
||||
var hitEnd = false
|
||||
let element =
|
||||
_swift_stdlib_unicodeCollationIterator_next(collationIterator, &hitEnd)
|
||||
if hitEnd {
|
||||
break
|
||||
}
|
||||
// Ignore zero valued collation elements. They don't participate in the
|
||||
// ordering relation.
|
||||
if element != 0 {
|
||||
hasher.append(Int(truncatingIfNeeded: element))
|
||||
}
|
||||
}
|
||||
}
|
||||
func _emptyASCIIHashBuffer() -> _UIntBuffer<UInt64, UInt8> {
|
||||
var buffer = _UIntBuffer<UInt64, UInt8>()
|
||||
// we need to take into account the nul suffix for nul-terminated strings.
|
||||
// A partially filled ascii buffer should have 1s in the leftover space
|
||||
buffer._storage = UInt64.max
|
||||
return buffer
|
||||
}
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
#if arch(i386) || arch(arm)
|
||||
private let stringHashOffset = Int(bitPattern: 0x88dd_cc21)
|
||||
#else
|
||||
private let stringHashOffset = Int(bitPattern: 0x429b_1266_88dd_cc21)
|
||||
#endif // arch(i386) || arch(arm)
|
||||
#endif // _runtime(_ObjC)
|
||||
internal struct ASCIIHasher {
|
||||
private var buffer = _emptyASCIIHashBuffer()
|
||||
|
||||
internal mutating func consume() -> UInt64? {
|
||||
if !buffer.isEmpty {
|
||||
defer { resetBuffer() }
|
||||
return buffer._storage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private mutating func resetBuffer() {
|
||||
buffer = _emptyASCIIHashBuffer()
|
||||
}
|
||||
|
||||
internal mutating func append(_ c: UInt8) -> UInt64? {
|
||||
if buffer.count < buffer.capacity {
|
||||
buffer.append(c)
|
||||
}
|
||||
|
||||
if buffer.count == buffer.capacity {
|
||||
defer { resetBuffer() }
|
||||
return buffer._storage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
extension _UnmanagedString where CodeUnit == UInt8 {
|
||||
@_versioned
|
||||
@inline(never) // Hide the CF dependency
|
||||
internal func computeHashValue() -> Int {
|
||||
#if _runtime(_ObjC)
|
||||
let hash = _stdlib_CFStringHashCString(OpaquePointer(start), count)
|
||||
// Mix random bits into NSString's hash so that clients don't rely on
|
||||
// Swift.String.hashValue and NSString.hash being the same.
|
||||
return stringHashOffset ^ hash
|
||||
#else
|
||||
var hasher = _Hasher()
|
||||
Unicode.hashASCII(self.buffer, into: &hasher)
|
||||
return Int(truncatingIfNeeded: hasher.finalize())
|
||||
#endif // _runtime(_ObjC)
|
||||
// NOT @_versioned
|
||||
@effects(releasenone)
|
||||
internal func hashASCII(into hasher: inout _Hasher) {
|
||||
var asciiHasher = ASCIIHasher()
|
||||
for c in self {
|
||||
if let combined = asciiHasher.append(UInt8(truncatingIfNeeded: c)) {
|
||||
hasher.append(combined)
|
||||
}
|
||||
}
|
||||
|
||||
if let combined = asciiHasher.consume() {
|
||||
hasher.append(combined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension _UnmanagedString where CodeUnit == UTF16.CodeUnit {
|
||||
@_versioned
|
||||
@inline(never) // Hide the CF dependency
|
||||
internal func computeHashValue() -> Int {
|
||||
#if _runtime(_ObjC)
|
||||
let temp = _NSContiguousString(_StringGuts(_large: self))
|
||||
let hash = temp._unsafeWithNotEscapedSelfPointer {
|
||||
return _stdlib_NSStringHashValuePointer($0, isASCII: false)
|
||||
extension BidirectionalCollection where Element == UInt16, SubSequence == Self {
|
||||
// NOT @_versioned
|
||||
internal func hashUTF16(into hasher: inout _Hasher) {
|
||||
var asciiHasher = ASCIIHasher()
|
||||
|
||||
for i in self.indices {
|
||||
let cu = self[i]
|
||||
let cuIsASCII = cu <= 0x7F
|
||||
let isSingleSegmentScalar = self.hasNormalizationBoundary(after: i)
|
||||
|
||||
guard cuIsASCII && isSingleSegmentScalar else {
|
||||
if let combined = asciiHasher.consume() {
|
||||
hasher.append(combined)
|
||||
}
|
||||
// Mix random bits into NSString's hash so that clients don't rely on
|
||||
// Swift.String.hashValue and NSString.hash being the same.
|
||||
return stringHashOffset ^ hash
|
||||
#else
|
||||
var hasher = _Hasher()
|
||||
Unicode.hashUTF16(self.buffer, into: &hasher)
|
||||
return Int(truncatingIfNeeded: hasher.finalize())
|
||||
#endif // _runtime(_ObjC)
|
||||
|
||||
let codeUnitSequence = IteratorSequence(
|
||||
_NormalizedCodeUnitIterator(self[i..<endIndex])
|
||||
)
|
||||
for element in codeUnitSequence {
|
||||
hasher.append(UInt(element))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if let combined = asciiHasher.append(UInt8(truncatingIfNeeded: cu)) {
|
||||
hasher.append(combined)
|
||||
}
|
||||
}
|
||||
|
||||
if let combined = asciiHasher.consume() {
|
||||
hasher.append(combined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension _UnmanagedString where CodeUnit == UInt8 {
|
||||
@effects(releasenone)
|
||||
@_versioned
|
||||
internal func computeHashValue(into hasher: inout _Hasher) {
|
||||
self.hashASCII(into: &hasher)
|
||||
}
|
||||
}
|
||||
|
||||
extension _UnmanagedString where CodeUnit == UInt16 {
|
||||
@effects(releasenone)
|
||||
@_versioned
|
||||
internal func computeHashValue(into hasher: inout _Hasher) {
|
||||
self.hashUTF16(into: &hasher)
|
||||
}
|
||||
}
|
||||
|
||||
extension _UnmanagedOpaqueString {
|
||||
@_versioned
|
||||
@inline(never) // Hide the CF dependency
|
||||
internal func computeHashValue() -> Int {
|
||||
#if _runtime(_ObjC)
|
||||
// TODO: ranged hash?
|
||||
let hash = _stdlib_NSStringHashValue(cocoaSlice(), isASCII: false)
|
||||
// Mix random bits into NSString's hash so that clients don't rely on
|
||||
// Swift.String.hashValue and NSString.hash being the same.
|
||||
return stringHashOffset ^ hash
|
||||
#else
|
||||
// FIXME: Streaming hash
|
||||
let p = UnsafeMutablePointer<UTF16.CodeUnit>.allocate(capacity: count)
|
||||
defer { p.deallocate(capacity: count) }
|
||||
let buffer = UnsafeMutableBufferPointer(start: p, count: count)
|
||||
_copy(into: buffer)
|
||||
var hasher = _Hasher()
|
||||
Unicode.hashUTF16(
|
||||
UnsafeBufferPointer(start: p, count: count),
|
||||
into: &hasher)
|
||||
return Int(truncatingIfNeeded: hasher.finalize())
|
||||
#endif
|
||||
internal func computeHashValue(into hasher: inout _Hasher) {
|
||||
self.hashUTF16(into: &hasher)
|
||||
}
|
||||
}
|
||||
|
||||
extension _StringGuts {
|
||||
//
|
||||
// FIXME(TODO: JIRA): HACK HACK HACK: Work around for ARC :-(
|
||||
//
|
||||
@_versioned
|
||||
@effects(readonly)
|
||||
@inline(never) // Hide the CF dependency
|
||||
internal static func _computeHashValue(
|
||||
_unsafeBitPattern: _RawBitPattern
|
||||
) -> Int {
|
||||
return _StringGuts(rawBits: _unsafeBitPattern)._computeHashValue()
|
||||
@effects(releasenone) // FIXME: Is this guaranteed in the opaque case?
|
||||
internal func _hash(into hasher: inout _Hasher) {
|
||||
defer { _fixLifetime(self) }
|
||||
if _slowPath(_isOpaque) {
|
||||
_asOpaque().computeHashValue(into: &hasher)
|
||||
return
|
||||
}
|
||||
if isASCII {
|
||||
_unmanagedASCIIView.computeHashValue(into: &hasher)
|
||||
return
|
||||
}
|
||||
_unmanagedUTF16View.computeHashValue(into: &hasher)
|
||||
}
|
||||
|
||||
@_versioned
|
||||
// TODO: After removing above hack: @inline(never) // Hide the CF dependency
|
||||
internal func _computeHashValue() -> Int {
|
||||
@effects(releasenone) // FIXME: Is this guaranteed in the opaque case?
|
||||
internal func _hash(_ range: Range<Int>, into hasher: inout _Hasher) {
|
||||
defer { _fixLifetime(self) }
|
||||
if _slowPath(_isOpaque) {
|
||||
return _asOpaque().computeHashValue()
|
||||
_asOpaque()[range].computeHashValue(into: &hasher)
|
||||
return
|
||||
}
|
||||
if isASCII {
|
||||
return _unmanagedASCIIView.computeHashValue()
|
||||
_unmanagedASCIIView[range].computeHashValue(into: &hasher)
|
||||
return
|
||||
}
|
||||
return _unmanagedUTF16View.computeHashValue()
|
||||
}
|
||||
|
||||
@_versioned
|
||||
// TODO: After removing above hack: @inline(never) // Hide the CF dependency
|
||||
internal func _computeHashValue(_ range: Range<Int>) -> Int {
|
||||
defer { _fixLifetime(self) }
|
||||
if _slowPath(_isOpaque) {
|
||||
return _asOpaque()[range].computeHashValue()
|
||||
}
|
||||
if isASCII {
|
||||
return _unmanagedASCIIView[range].computeHashValue()
|
||||
}
|
||||
return _unmanagedUTF16View[range].computeHashValue()
|
||||
_unmanagedUTF16View[range].computeHashValue(into: &hasher)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,27 +160,25 @@ extension String : Hashable {
|
||||
///
|
||||
/// Hash values are not guaranteed to be equal across different executions of
|
||||
/// your program. Do not save hash values to use during a future execution.
|
||||
@_inlineable // FIXME(sil-serialize-all)
|
||||
@_inlineable
|
||||
public var hashValue: Int {
|
||||
defer { _fixLifetime(self) }
|
||||
let gutsBits = _guts.rawBits
|
||||
return _StringGuts._computeHashValue(_unsafeBitPattern: gutsBits)
|
||||
return _hashValue(for: self)
|
||||
}
|
||||
|
||||
@_inlineable
|
||||
public func _hash(into hasher: inout _Hasher) {
|
||||
hasher.append(self.hashValue)
|
||||
_guts._hash(into: &hasher)
|
||||
}
|
||||
}
|
||||
|
||||
extension StringProtocol {
|
||||
@_inlineable // FIXME(sil-serialize-all)
|
||||
@_inlineable
|
||||
public var hashValue : Int {
|
||||
return _wholeString._guts._computeHashValue(_encodedOffsetRange)
|
||||
return _hashValue(for: self)
|
||||
}
|
||||
|
||||
@_inlineable
|
||||
public func _hash(into hasher: inout _Hasher) {
|
||||
hasher.append(self.hashValue)
|
||||
_wholeString._guts._hash(_encodedOffsetRange, into: &hasher)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,6 +192,7 @@ extension _UIntBuffer : RangeReplaceableCollection {
|
||||
@inline(__always)
|
||||
public mutating func append(_ newElement: Element) {
|
||||
_debugPrecondition(count + 1 <= capacity)
|
||||
_storage &= ~(Storage(Element.max) &<< _bitCount)
|
||||
_storage |= Storage(newElement) &<< _bitCount
|
||||
_bitCount = _bitCount &+ _elementWidth
|
||||
}
|
||||
|
||||
@@ -302,6 +302,39 @@ extension _UnmanagedOpaqueString : _StringVariant {
|
||||
return _UnmanagedOpaqueString(object, range: b, isSlice: newSlice)
|
||||
}
|
||||
|
||||
@_inlineable // FIXME(sil-serialize-all)
|
||||
@_versioned // FIXME(sil-serialize-all)
|
||||
internal subscript(offsetRange: PartialRangeUpTo<Int>) -> SubSequence {
|
||||
_sanityCheck(offsetRange.upperBound <= range.count)
|
||||
let b: Range<Int> =
|
||||
range.lowerBound ..<
|
||||
range.lowerBound + offsetRange.upperBound
|
||||
let newSlice = self.isSlice || b.count != range.count
|
||||
return _UnmanagedOpaqueString(object, range: b, isSlice: newSlice)
|
||||
}
|
||||
|
||||
@_inlineable // FIXME(sil-serialize-all)
|
||||
@_versioned // FIXME(sil-serialize-all)
|
||||
internal subscript(offsetRange: PartialRangeThrough<Int>) -> SubSequence {
|
||||
_sanityCheck(offsetRange.upperBound <= range.count)
|
||||
let b: Range<Int> =
|
||||
range.lowerBound ..<
|
||||
range.lowerBound + offsetRange.upperBound + 1
|
||||
let newSlice = self.isSlice || b.count != range.count
|
||||
return _UnmanagedOpaqueString(object, range: b, isSlice: newSlice)
|
||||
}
|
||||
|
||||
@_inlineable // FIXME(sil-serialize-all)
|
||||
@_versioned // FIXME(sil-serialize-all)
|
||||
internal subscript(offsetRange: PartialRangeFrom<Int>) -> SubSequence {
|
||||
_sanityCheck(offsetRange.lowerBound < range.count)
|
||||
let b: Range<Int> =
|
||||
range.lowerBound + offsetRange.lowerBound ..<
|
||||
range.upperBound
|
||||
let newSlice = self.isSlice || b.count != range.count
|
||||
return _UnmanagedOpaqueString(object, range: b, isSlice: newSlice)
|
||||
}
|
||||
|
||||
@_inlineable // FIXME(sil-serialize-all)
|
||||
@_versioned // FIXME(sil-serialize-all)
|
||||
internal func _copy(
|
||||
|
||||
@@ -103,93 +103,6 @@ static const UCollator *GetRootCollator() {
|
||||
return SWIFT_LAZY_CONSTANT(MakeRootCollator());
|
||||
}
|
||||
|
||||
/// This class caches the collation element results for the ASCII subset of
|
||||
/// unicode.
|
||||
class ASCIICollation {
|
||||
public:
|
||||
friend class swift::Lazy<ASCIICollation>;
|
||||
|
||||
static swift::Lazy<ASCIICollation> theTable;
|
||||
static const ASCIICollation *getTable() {
|
||||
return &theTable.get();
|
||||
}
|
||||
|
||||
int32_t CollationTable[128];
|
||||
|
||||
/// Maps an ASCII character to a collation element priority as would be
|
||||
/// returned by a call to ucol_next().
|
||||
int32_t map(unsigned char c) const {
|
||||
return CollationTable[c];
|
||||
}
|
||||
|
||||
private:
|
||||
/// Construct the ASCII collation table.
|
||||
ASCIICollation() {
|
||||
const UCollator *Collator = GetRootCollator();
|
||||
for (unsigned char c = 0; c < 128; ++c) {
|
||||
UErrorCode ErrorCode = U_ZERO_ERROR;
|
||||
intptr_t NumCollationElts = 0;
|
||||
UChar Buffer[1];
|
||||
Buffer[0] = c;
|
||||
|
||||
UCollationElements *CollationIterator =
|
||||
ucol_openElements(Collator, Buffer, 1, &ErrorCode);
|
||||
|
||||
while (U_SUCCESS(ErrorCode)) {
|
||||
intptr_t Elem = ucol_next(CollationIterator, &ErrorCode);
|
||||
if (Elem != UCOL_NULLORDER) {
|
||||
CollationTable[c] = Elem;
|
||||
++NumCollationElts;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ucol_closeElements(CollationIterator);
|
||||
if (U_FAILURE(ErrorCode) || NumCollationElts != 1) {
|
||||
swift::crash("Error setting up the ASCII collation table");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ASCIICollation &operator=(const ASCIICollation &) = delete;
|
||||
ASCIICollation(const ASCIICollation &) = delete;
|
||||
};
|
||||
|
||||
void *swift::_swift_stdlib_unicodeCollationIterator_create(
|
||||
const __swift_uint16_t *Str, __swift_uint32_t Length) {
|
||||
UErrorCode ErrorCode = U_ZERO_ERROR;
|
||||
UCollationElements *CollationIterator =
|
||||
ucol_openElements(GetRootCollator(), reinterpret_cast<const UChar *>(Str),
|
||||
Length, &ErrorCode);
|
||||
if (U_FAILURE(ErrorCode)) {
|
||||
swift::crash("_swift_stdlib_unicodeCollationIterator_create: ucol_openElements() failed.");
|
||||
}
|
||||
return CollationIterator;
|
||||
}
|
||||
|
||||
__swift_int32_t swift::_swift_stdlib_unicodeCollationIterator_next(
|
||||
void *CollationIterator, bool *HitEnd) {
|
||||
UErrorCode ErrorCode = U_ZERO_ERROR;
|
||||
auto Result = ucol_next(
|
||||
static_cast<UCollationElements *>(CollationIterator), &ErrorCode);
|
||||
if (U_FAILURE(ErrorCode)) {
|
||||
swift::crash(
|
||||
"_swift_stdlib_unicodeCollationIterator_next: ucol_next() failed.");
|
||||
}
|
||||
*HitEnd = (Result == UCOL_NULLORDER);
|
||||
return Result;
|
||||
}
|
||||
|
||||
void swift::_swift_stdlib_unicodeCollationIterator_delete(
|
||||
void *CollationIterator) {
|
||||
ucol_closeElements(static_cast<UCollationElements *>(CollationIterator));
|
||||
}
|
||||
|
||||
const __swift_int32_t *swift::_swift_stdlib_unicode_getASCIICollationTable() {
|
||||
return ASCIICollation::getTable()->CollationTable;
|
||||
}
|
||||
|
||||
/// Convert the unicode string to uppercase. This function will return the
|
||||
/// required buffer length as a result. If this length does not match the
|
||||
/// 'DestinationCapacity' this function must be called again with a buffer of
|
||||
@@ -231,8 +144,6 @@ swift::_swift_stdlib_unicode_strToLower(uint16_t *Destination,
|
||||
}
|
||||
return OutputLength;
|
||||
}
|
||||
|
||||
swift::Lazy<ASCIICollation> ASCIICollation::theTable;
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -470,48 +470,6 @@ var nsStringCanaryCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
RuntimeFoundationWrappers.test("_stdlib_NSStringHashValue/NoLeak") {
|
||||
nsStringCanaryCount = 0
|
||||
autoreleasepool {
|
||||
let a = NSStringCanary()
|
||||
expectEqual(1, nsStringCanaryCount)
|
||||
_stdlib_NSStringHashValue(a, isASCII: true)
|
||||
}
|
||||
expectEqual(0, nsStringCanaryCount)
|
||||
}
|
||||
|
||||
RuntimeFoundationWrappers.test("_stdlib_NSStringHashValueNonASCII/NoLeak") {
|
||||
nsStringCanaryCount = 0
|
||||
autoreleasepool {
|
||||
let a = NSStringCanary()
|
||||
expectEqual(1, nsStringCanaryCount)
|
||||
_stdlib_NSStringHashValue(a, isASCII: false)
|
||||
}
|
||||
expectEqual(0, nsStringCanaryCount)
|
||||
}
|
||||
|
||||
RuntimeFoundationWrappers.test("_stdlib_NSStringHashValuePointer/NoLeak") {
|
||||
nsStringCanaryCount = 0
|
||||
autoreleasepool {
|
||||
let a = NSStringCanary()
|
||||
expectEqual(1, nsStringCanaryCount)
|
||||
let ptrA = unsafeBitCast(a, to: OpaquePointer.self)
|
||||
_stdlib_NSStringHashValuePointer(ptrA, isASCII: true)
|
||||
}
|
||||
expectEqual(0, nsStringCanaryCount)
|
||||
}
|
||||
|
||||
RuntimeFoundationWrappers.test("_stdlib_NSStringHashValuePointerNonASCII/NoLeak") {
|
||||
nsStringCanaryCount = 0
|
||||
autoreleasepool {
|
||||
let a = NSStringCanary()
|
||||
expectEqual(1, nsStringCanaryCount)
|
||||
let ptrA = unsafeBitCast(a, to: OpaquePointer.self)
|
||||
_stdlib_NSStringHashValuePointer(ptrA, isASCII: false)
|
||||
}
|
||||
expectEqual(0, nsStringCanaryCount)
|
||||
}
|
||||
|
||||
RuntimeFoundationWrappers.test("_stdlib_NSStringLowercaseString/NoLeak") {
|
||||
nsStringCanaryCount = 0
|
||||
autoreleasepool {
|
||||
|
||||
Reference in New Issue
Block a user