blob: 65718d15bf0d2eb6e495c50e1b53548a5c84e2fa [file] [log] [blame]
//===----------------------------------------------------------------------===//
//
// 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
@usableFromInline
@_effects(readonly)
internal func _stringCompare(
_ lhs: _StringGuts, _ rhs: _StringGuts,
expecting: _StringComparisonResult
) -> Bool {
_internalInvariant(expecting == .equal || expecting == .less)
if lhs.rawBits == rhs.rawBits {
return expecting == .equal
}
if _fastPath(lhs.isFastUTF8 && rhs.isFastUTF8) {
let isNFC = lhs.isNFC && rhs.isNFC
return lhs.withFastUTF8 { utf8Left in
return rhs.withFastUTF8 { utf8Right in
if isNFC {
let cmp = _binaryCompare(utf8Left, utf8Right)
if expecting == .equal {
return cmp == 0
}
_internalInvariant(expecting == .less)
return cmp < 0
}
return _stringCompare(utf8Left, utf8Right, expecting: expecting)
}
}
}
return _stringCompareSlow(
lhs, 0..<lhs.count, rhs, 0..<rhs.count, expecting: expecting)
}
@usableFromInline
@_effects(readonly)
internal func _stringCompare(
_ lhs: _StringGuts, _ lhsRange: Range<Int>,
_ rhs: _StringGuts, _ rhsRange: Range<Int>,
expecting: _StringComparisonResult
) -> Bool {
_internalInvariant(expecting == .equal || expecting == .less)
if lhs.rawBits == rhs.rawBits && lhsRange == rhsRange {
return expecting == .equal
}
if _fastPath(lhs.isFastUTF8 && rhs.isFastUTF8) {
let isNFC = lhs.isNFC && rhs.isNFC
return lhs.withFastUTF8(range: lhsRange) { utf8Left in
return rhs.withFastUTF8(range: rhsRange) { utf8Right in
if isNFC {
let cmp = _binaryCompare(utf8Left, utf8Right)
if expecting == .equal {
return cmp == 0
}
_internalInvariant(expecting == .less)
return cmp < 0
}
return _stringCompare(utf8Left, utf8Right, expecting: expecting)
}
}
}
return _stringCompareSlow(lhs, lhsRange, rhs, rhsRange, expecting: expecting)
}
@usableFromInline
@_effects(readonly)
internal func _stringCompareSlow(
_ lhs: _StringGuts, _ lhsRange: Range<Int>?,
_ rhs: _StringGuts, _ rhsRange: Range<Int>?,
expecting: _StringComparisonResult
) -> Bool {
return _StringGutsSlice(lhs, lhsRange ?? 0..<lhs.count).compare(
with: _StringGutsSlice(rhs, rhsRange ?? 0..<rhs.count),
expecting: expecting)
}
@usableFromInline
@_effects(readonly)
internal func _stringCompare(
_ left: UnsafeBufferPointer<UInt8>,
_ right: UnsafeBufferPointer<UInt8>,
expecting: _StringComparisonResult
) -> Bool {
_internalInvariant(expecting == .equal || expecting == .less)
if _binaryCompare(left, right) == 0 {
return expecting == .equal
}
// Do a binary scan finding the point of divergence. From there, see if we can
// determine our answer right away, otherwise back up to the beginning of the
// current normalization segment and fall back to the slow path.
var idx = 0
let end = Swift.min(left.count, right.count)
let leftPtr = left.baseAddress._unsafelyUnwrappedUnchecked
let rightPtr = right.baseAddress._unsafelyUnwrappedUnchecked
while idx < end {
guard leftPtr[idx] == rightPtr[idx] else { break }
idx &+= 1
}
_internalInvariant(idx != left.count || idx != right.count,
"should of been cought by prior binary compare")
if idx == end {
// 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 expecting == _lexicographicalCompare(left.count, right.count)
}
// Back up to nearest scalar boundary and check if normal and end of segment.
// If so, we have our answer.
while _isContinuation(left[_unchecked: idx]) && idx > 0 { idx &-= 1 }
// TODO: Refactor this into a function, handle these boundary condition as
// early returns...
if !_isContinuation(right[_unchecked: idx]) {
let (leftScalar, leftLen) = _decodeScalar(left, startingAt: idx)
let (rightScalar, rightLen) = _decodeScalar(right, startingAt: idx)
_internalInvariant(leftScalar != rightScalar)
let nfcQC = leftScalar._isNFCQCYes && rightScalar._isNFCQCYes
let isSegmentEnd = left.hasNormalizationBoundary(before: idx + leftLen)
&& right.hasNormalizationBoundary(before: idx + rightLen)
if _fastPath(nfcQC && isSegmentEnd) {
return expecting == _lexicographicalCompare(leftScalar, rightScalar)
}
}
// TODO: Back up to start of segment, and slow-path resume from there
return _StringGutsSlice(_StringGuts(left, isASCII: false)).compare(with:
_StringGutsSlice(_StringGuts(right, isASCII: false)), expecting: expecting)
}
@_frozen
@usableFromInline
internal enum _StringComparisonResult {
case less
case equal
case greater
@inlinable @inline(__always)
internal init(signedNotation int: Int) {
self = int < 0 ? .less : int == 0 ? .equal : .greater
}
}
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.
@inlinable @inline(__always) // Memcmp wrapper
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 {
@usableFromInline
@_effects(readonly)
internal func compare(
with other: _StringGutsSlice, expecting: _StringComparisonResult
) -> Bool {
if self._guts.rawBits == other._guts.rawBits
&& self._offsetRange == other._offsetRange {
return expecting == .equal
}
if _fastPath(self.isNFCFastUTF8 && other.isNFCFastUTF8) {
Builtin.onFastPath() // aggressively inline / optimize
return self.withFastUTF8 { nfcSelf in
return other.withFastUTF8 { nfcOther in
return expecting == _StringComparisonResult(
signedNotation: _binaryCompare(nfcSelf, nfcOther))
}
}
}
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 expecting == _slowCompare(with: other)
}
@inline(never) // opaque slow-path
@_effects(readonly)
internal func _slowCompare(
with other: _StringGutsSlice
) -> _StringComparisonResult {
return self.withNFCCodeUnitsIterator_2 {
var selfIter = $0
return other.withNFCCodeUnitsIterator_2 {
let otherIter = $0
return selfIter.compare(with: otherIter)
}
}
}
}
@inline(__always)
internal func _lexicographicalCompare<I: FixedWidthInteger>(
_ lhs: I, _ rhs: I
) -> _StringComparisonResult {
return lhs < rhs ? .less : (lhs > rhs ? .greater : .equal)
}
@inline(__always)
internal func _lexicographicalCompare(
_ lhs: Unicode.Scalar, _ rhs: Unicode.Scalar
) -> _StringComparisonResult {
return _lexicographicalCompare(lhs.value, rhs.value)
}