| //===----------------------------------------------------------------------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2018 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 |
| |
| @inlinable @inline(__always) // top-level fastest-paths |
| @_effects(readonly) |
| internal func _stringCompare( |
| _ lhs: _StringGuts, _ rhs: _StringGuts, expecting: _StringComparisonResult |
| ) -> Bool { |
| if lhs.rawBits == rhs.rawBits { return expecting == .equal } |
| return _stringCompareWithSmolCheck(lhs, rhs, expecting: expecting) |
| } |
| |
| @usableFromInline |
| @_effects(readonly) |
| internal func _stringCompareWithSmolCheck( |
| _ lhs: _StringGuts, _ rhs: _StringGuts, expecting: _StringComparisonResult |
| ) -> Bool { |
| // ASCII small-string fast-path: |
| if lhs.isSmallASCII && rhs.isSmallASCII { |
| let lhsRaw = lhs.asSmall._storage |
| let rhsRaw = rhs.asSmall._storage |
| |
| if lhsRaw.0 != rhsRaw.0 { |
| return _lexicographicalCompare( |
| lhsRaw.0.byteSwapped, rhsRaw.0.byteSwapped, expecting: expecting) |
| } |
| return _lexicographicalCompare( |
| lhsRaw.1.byteSwapped, rhsRaw.1.byteSwapped, expecting: expecting) |
| } |
| |
| return _stringCompareInternal(lhs, rhs, expecting: expecting) |
| } |
| |
| @inline(never) // Keep `_stringCompareWithSmolCheck` fast-path fast |
| @usableFromInline |
| @_effects(readonly) |
| internal func _stringCompareInternal( |
| _ lhs: _StringGuts, _ rhs: _StringGuts, expecting: _StringComparisonResult |
| ) -> Bool { |
| guard _fastPath(lhs.isFastUTF8 && rhs.isFastUTF8) else { |
| return _stringCompareSlow(lhs, rhs, expecting: expecting) |
| } |
| |
| let isNFC = lhs.isNFC && rhs.isNFC |
| return lhs.withFastUTF8 { lhsUTF8 in |
| return rhs.withFastUTF8 { rhsUTF8 in |
| return _stringCompareFastUTF8( |
| lhsUTF8, rhsUTF8, expecting: expecting, bothNFC: isNFC) |
| } |
| } |
| } |
| |
| @inlinable @inline(__always) // top-level fastest-paths |
| @_effects(readonly) |
| internal func _stringCompare( |
| _ lhs: _StringGuts, _ lhsRange: Range<Int>, |
| _ rhs: _StringGuts, _ rhsRange: Range<Int>, |
| expecting: _StringComparisonResult |
| ) -> Bool { |
| if lhs.rawBits == rhs.rawBits && lhsRange == rhsRange { |
| return expecting == .equal |
| } |
| return _stringCompareInternal( |
| lhs, lhsRange, rhs, rhsRange, expecting: expecting) |
| } |
| |
| @usableFromInline |
| @_effects(readonly) |
| internal func _stringCompareInternal( |
| _ lhs: _StringGuts, _ lhsRange: Range<Int>, |
| _ rhs: _StringGuts, _ rhsRange: Range<Int>, |
| expecting: _StringComparisonResult |
| ) -> Bool { |
| guard _fastPath(lhs.isFastUTF8 && rhs.isFastUTF8) else { |
| return _stringCompareSlow( |
| lhs, lhsRange, rhs, rhsRange, expecting: expecting) |
| } |
| |
| let isNFC = lhs.isNFC && rhs.isNFC |
| return lhs.withFastUTF8(range: lhsRange) { lhsUTF8 in |
| return rhs.withFastUTF8(range: rhsRange) { rhsUTF8 in |
| return _stringCompareFastUTF8( |
| lhsUTF8, rhsUTF8, expecting: expecting, bothNFC: isNFC) |
| } |
| } |
| } |
| |
| @_effects(readonly) |
| private func _stringCompareFastUTF8( |
| _ utf8Left: UnsafeBufferPointer<UInt8>, |
| _ utf8Right: UnsafeBufferPointer<UInt8>, |
| expecting: _StringComparisonResult, |
| bothNFC: Bool |
| ) -> Bool { |
| if _fastPath(bothNFC) { |
| let cmp = _binaryCompare(utf8Left, utf8Right) |
| return _lexicographicalCompare(cmp, 0, expecting: expecting) |
| } |
| |
| return _stringCompareFastUTF8Abnormal( |
| utf8Left, utf8Right, expecting: expecting) |
| } |
| |
| @_effects(readonly) |
| private func _stringCompareFastUTF8Abnormal( |
| _ utf8Left: UnsafeBufferPointer<UInt8>, |
| _ utf8Right: UnsafeBufferPointer<UInt8>, |
| expecting: _StringComparisonResult |
| ) -> Bool { |
| // Do a binary-equality prefix scan, to skip over long common prefixes. |
| guard let diffIdx = _findDiffIdx(utf8Left, utf8Right) else { |
| // We finished one of our inputs. |
| // |
| // TODO: This gives us a consistent and good ordering, but technically it |
| // could differ from our stated ordering if combination with a prior scalar |
| // did not produce a greater-value scalar. Consider checking normality. |
| return _lexicographicalCompare( |
| utf8Left.count, utf8Right.count, expecting: expecting) |
| } |
| |
| let scalarDiffIdx = _scalarAlign(utf8Left, diffIdx) |
| _internalInvariant(scalarDiffIdx == _scalarAlign(utf8Right, diffIdx)) |
| |
| let (leftScalar, leftLen) = _decodeScalar(utf8Left, startingAt: scalarDiffIdx) |
| let (rightScalar, rightLen) = _decodeScalar( |
| utf8Right, startingAt: scalarDiffIdx) |
| |
| // Very frequent fast-path: point of binary divergence is a NFC single-scalar |
| // segment. Check that we diverged at the start of a segment, and the next |
| // scalar is both NFC and its own segment. |
| if _fastPath( |
| leftScalar._isNFCStarter && rightScalar._isNFCStarter && |
| utf8Left.hasNormalizationBoundary(before: scalarDiffIdx &+ leftLen) && |
| utf8Right.hasNormalizationBoundary(before: scalarDiffIdx &+ rightLen) |
| ) { |
| guard expecting == .less else { |
| // We diverged |
| _internalInvariant(expecting == .equal) |
| return false |
| } |
| return _lexicographicalCompare( |
| leftScalar.value, rightScalar.value, expecting: .less) |
| } |
| |
| // Back up to the nearest normalization boundary before doing a slow |
| // normalizing compare. |
| let boundaryIdx = Swift.min( |
| _findBoundary(utf8Left, before: diffIdx), |
| _findBoundary(utf8Right, before: diffIdx)) |
| _internalInvariant(boundaryIdx <= diffIdx) |
| |
| return _stringCompareSlow( |
| UnsafeBufferPointer(rebasing: utf8Left[boundaryIdx...]), |
| UnsafeBufferPointer(rebasing: utf8Right[boundaryIdx...]), |
| expecting: expecting) |
| } |
| |
| @_effects(readonly) |
| private func _stringCompareSlow( |
| _ lhs: _StringGuts, _ rhs: _StringGuts, expecting: _StringComparisonResult |
| ) -> Bool { |
| return _stringCompareSlow( |
| lhs, 0..<lhs.count, rhs, 0..<rhs.count, expecting: expecting) |
| } |
| |
| @_effects(readonly) |
| private func _stringCompareSlow( |
| _ lhs: _StringGuts, _ lhsRange: Range<Int>, |
| _ rhs: _StringGuts, _ rhsRange: Range<Int>, |
| expecting: _StringComparisonResult |
| ) -> Bool { |
| // TODO: Just call the normalizer directly with range |
| |
| return _StringGutsSlice(lhs, lhsRange).compare( |
| with: _StringGutsSlice(rhs, rhsRange), |
| expecting: expecting) |
| } |
| |
| @_effects(readonly) |
| private func _stringCompareSlow( |
| _ leftUTF8: UnsafeBufferPointer<UInt8>, |
| _ rightUTF8: UnsafeBufferPointer<UInt8>, |
| expecting: _StringComparisonResult |
| ) -> Bool { |
| // TODO: Just call the normalizer directly |
| |
| let left = _StringGutsSlice(_StringGuts(leftUTF8, isASCII: false)) |
| let right = _StringGutsSlice(_StringGuts(rightUTF8, isASCII: false)) |
| return left.compare(with: right, expecting: expecting) |
| } |
| |
| // Return the point of binary divergence. If they have no binary difference |
| // (even if one is longer), returns nil. |
| @_effects(readonly) |
| private func _findDiffIdx( |
| _ left: UnsafeBufferPointer<UInt8>, _ right: UnsafeBufferPointer<UInt8> |
| ) -> Int? { |
| let count = Swift.min(left.count, right.count) |
| var idx = 0 |
| while idx < count { |
| guard left[_unchecked: idx] == right[_unchecked: idx] else { |
| return idx |
| } |
| idx &+= 1 |
| } |
| return nil |
| } |
| |
| @_effects(readonly) |
| @inline(__always) |
| private func _lexicographicalCompare<I: FixedWidthInteger>( |
| _ lhs: I, _ rhs: I, expecting: _StringComparisonResult |
| ) -> Bool { |
| return expecting == .equal ? lhs == rhs : lhs < rhs |
| } |
| |
| @_effects(readonly) |
| private func _findBoundary( |
| _ utf8: UnsafeBufferPointer<UInt8>, before: Int |
| ) -> Int { |
| var idx = before |
| _internalInvariant(idx >= 0) |
| |
| // End of string is a normalization boundary |
| guard idx < utf8.count else { |
| _internalInvariant(before == utf8.count) |
| return utf8.count |
| } |
| |
| // Back up to scalar boundary |
| while _isContinuation(utf8[_unchecked: idx]) { |
| idx &-= 1 |
| } |
| |
| while true { |
| if idx == 0 { return 0 } |
| |
| let scalar = _decodeScalar(utf8, startingAt: idx).0 |
| if scalar._hasNormalizationBoundaryBefore { return idx } |
| |
| idx &-= _utf8ScalarLength(utf8, endingAt: idx) |
| } |
| } |
| |
| @_frozen |
| @usableFromInline |
| internal enum _StringComparisonResult { |
| case equal |
| case less |
| |
| @inlinable @inline(__always) |
| internal init(signedNotation int: Int) { |
| _internalInvariant(int <= 0) |
| self = int == 0 ? .equal : .less |
| } |
| |
| @inlinable @inline(__always) |
| static func ==( |
| _ lhs: _StringComparisonResult, _ rhs: _StringComparisonResult |
| ) -> Bool { |
| switch (lhs, rhs) { |
| case (.equal, .equal): return true |
| case (.less, .less): return true |
| default: return false |
| } |
| } |
| } |
| |
| extension _StringGutsSlice { |
| @inline(__always) |
| @_effects(readonly) |
| internal func withNFCCodeUnitsIterator<R>( |
| _ f: (_NormalizedUTF8CodeUnitIterator) throws -> R |
| ) rethrows -> R { |
| defer { _fixLifetime(self) } |
| if self.isNFCFastUTF8 { |
| return try self.withFastUTF8 { |
| return try f(_NormalizedUTF8CodeUnitIterator($0, range: 0..<$0.count)) |
| } |
| } |
| if self.isFastUTF8 { |
| return try self.withFastUTF8 { |
| return try f(_NormalizedUTF8CodeUnitIterator($0, range: 0..<$0.count)) |
| } |
| } |
| return try f(_NormalizedUTF8CodeUnitIterator( |
| foreign: self._guts, range: self.range)) |
| } |
| @inline(__always) |
| @_effects(readonly) |
| internal func withNFCCodeUnitsIterator_2<R>( |
| _ f: (_NormalizedUTF8CodeUnitIterator_2) throws -> R |
| ) rethrows -> R { |
| defer { _fixLifetime(self) } |
| return try f(_NormalizedUTF8CodeUnitIterator_2(self)) |
| } |
| } |
| |
| // Perform a binary comparison of bytes in memory. Return value is negative if |
| // less, 0 if equal, positive if greater. |
| @_effects(readonly) |
| internal func _binaryCompare<UInt8>( |
| _ lhs: UnsafeBufferPointer<UInt8>, _ rhs: UnsafeBufferPointer<UInt8> |
| ) -> Int { |
| var cmp = Int(truncatingIfNeeded: |
| _swift_stdlib_memcmp( |
| lhs.baseAddress._unsafelyUnwrappedUnchecked, |
| rhs.baseAddress._unsafelyUnwrappedUnchecked, |
| Swift.min(lhs.count, rhs.count))) |
| if cmp == 0 { |
| cmp = lhs.count &- rhs.count |
| } |
| return cmp |
| } |
| |
| // Double dispatch functions |
| extension _StringGutsSlice { |
| @_effects(readonly) |
| internal func compare( |
| with other: _StringGutsSlice, expecting: _StringComparisonResult |
| ) -> Bool { |
| if _fastPath(self.isFastUTF8 && other.isFastUTF8) { |
| Builtin.onFastPath() // aggressively inline / optimize |
| let isEqual = self.withFastUTF8 { utf8Self in |
| return other.withFastUTF8 { utf8Other in |
| return 0 == _binaryCompare(utf8Self, utf8Other) |
| } |
| } |
| if isEqual { return expecting == .equal } |
| } |
| |
| return _slowCompare(with: other, expecting: expecting) |
| } |
| |
| @inline(never) // opaque slow-path |
| @_effects(readonly) |
| internal func _slowCompare( |
| with other: _StringGutsSlice, |
| expecting: _StringComparisonResult |
| ) -> Bool { |
| return self.withNFCCodeUnitsIterator_2 { |
| var selfIter = $0 |
| return other.withNFCCodeUnitsIterator_2 { |
| let otherIter = $0 |
| return selfIter.compare(with: otherIter, expecting: expecting) |
| } |
| } |
| } |
| } |