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.
208 lines
5.3 KiB
Swift
208 lines
5.3 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import SwiftShims
|
|
|
|
func _emptyASCIIHashBuffer() -> _UIntBuffer<UInt64, UInt8> {
|
|
var buffer = _UIntBuffer<UInt64, UInt8>()
|
|
// We don't want the unused bits of a partially filled buffer to collide
|
|
// with trailing nuls when hashing
|
|
buffer._storage = UInt64.max
|
|
return buffer
|
|
}
|
|
|
|
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 {
|
|
// 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 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)
|
|
}
|
|
|
|
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
|
|
internal func computeHashValue(into hasher: inout _Hasher) {
|
|
self.hashUTF16(into: &hasher)
|
|
}
|
|
}
|
|
|
|
extension _SmallUTF8String {
|
|
@_versioned
|
|
@_inlineable
|
|
internal func computeHashValue(into hasher: inout _Hasher) {
|
|
#if arch(i386) || arch(arm)
|
|
unsupportedOn32bit()
|
|
#else
|
|
if isASCII {
|
|
return self.withUnmanagedASCII { $0.computeHashValue(into: &hasher) }
|
|
}
|
|
return self.withUnmanagedUTF16 { $0.computeHashValue(into: &hasher) }
|
|
#endif // 64-bit
|
|
}
|
|
}
|
|
|
|
extension _StringGuts {
|
|
@_versioned
|
|
@effects(releasenone) // FIXME: Is this guaranteed in the opaque case?
|
|
internal func _hash(into hasher: inout _Hasher) {
|
|
if _isSmall {
|
|
return _smallUTF8String.computeHashValue(into: &hasher)
|
|
}
|
|
|
|
defer { _fixLifetime(self) }
|
|
if _slowPath(_isOpaque) {
|
|
_asOpaque().computeHashValue(into: &hasher)
|
|
return
|
|
}
|
|
if isASCII {
|
|
_unmanagedASCIIView.computeHashValue(into: &hasher)
|
|
return
|
|
}
|
|
_unmanagedUTF16View.computeHashValue(into: &hasher)
|
|
}
|
|
|
|
@_versioned
|
|
@effects(releasenone) // FIXME: Is this guaranteed in the opaque case?
|
|
internal func _hash(_ range: Range<Int>, into hasher: inout _Hasher) {
|
|
if _isSmall {
|
|
return _smallUTF8String[range].computeHashValue(into: &hasher)
|
|
}
|
|
|
|
defer { _fixLifetime(self) }
|
|
if _slowPath(_isOpaque) {
|
|
_asOpaque()[range].computeHashValue(into: &hasher)
|
|
return
|
|
}
|
|
if isASCII {
|
|
_unmanagedASCIIView[range].computeHashValue(into: &hasher)
|
|
return
|
|
}
|
|
_unmanagedUTF16View[range].computeHashValue(into: &hasher)
|
|
}
|
|
}
|
|
|
|
extension String : Hashable {
|
|
/// The string's hash value.
|
|
///
|
|
/// 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
|
|
public var hashValue: Int {
|
|
return _hashValue(for: self)
|
|
}
|
|
|
|
@_inlineable
|
|
public func _hash(into hasher: inout _Hasher) {
|
|
_guts._hash(into: &hasher)
|
|
}
|
|
}
|
|
|
|
extension StringProtocol {
|
|
@_inlineable
|
|
public var hashValue : Int {
|
|
return _hashValue(for: self)
|
|
}
|
|
|
|
@_inlineable
|
|
public func _hash(into hasher: inout _Hasher) {
|
|
_wholeString._guts._hash(_encodedOffsetRange, into: &hasher)
|
|
}
|
|
}
|