From cb4dd6072c331d2e6a3d75d10321138020666cc4 Mon Sep 17 00:00:00 2001 From: Ahson Khan Date: Thu, 22 Feb 2018 13:01:35 -0800 Subject: [PATCH] Add ReadOnlySpan string-like Equals/CompareTo/IndexOf/Contains API with globalization support (#16467) * Add ReadOnlySpan string-like Equals/CompareTo/IndexOf/Contains API with globalization support * Address PR feedback. * Fix unix implementation --- .../Interop.Collation.cs | 4 + .../System/Globalization/CompareInfo.Invariant.cs | 13 ++ .../shared/System/Globalization/CompareInfo.cs | 22 ++- src/mscorlib/shared/System/Span.NonGeneric.cs | 195 ++++++++++++++++++--- .../src/System/Globalization/CompareInfo.Unix.cs | 194 ++++++++++++++++++++ .../System/Globalization/CompareInfo.Windows.cs | 44 +++++ src/mscorlib/src/System/String.Comparison.cs | 39 ++++- 7 files changed, 473 insertions(+), 38 deletions(-) diff --git a/src/mscorlib/shared/Interop/Unix/System.Globalization.Native/Interop.Collation.cs b/src/mscorlib/shared/Interop/Unix/System.Globalization.Native/Interop.Collation.cs index 021f774..08aa611 100644 --- a/src/mscorlib/shared/Interop/Unix/System.Globalization.Native/Interop.Collation.cs +++ b/src/mscorlib/shared/Interop/Unix/System.Globalization.Native/Interop.Collation.cs @@ -22,12 +22,16 @@ internal static partial class Interop [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOf")] internal unsafe static extern int IndexOf(SafeSortHandle sortHandle, string target, int cwTargetLength, char* pSource, int cwSourceLength, CompareOptions options, int* matchLengthPtr); + [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOf")] + internal unsafe static extern int IndexOf(SafeSortHandle sortHandle, char* target, int cwTargetLength, char* pSource, int cwSourceLength, CompareOptions options, int* matchLengthPtr); [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_LastIndexOf")] internal unsafe static extern int LastIndexOf(SafeSortHandle sortHandle, string target, int cwTargetLength, char* pSource, int cwSourceLength, CompareOptions options); [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOfOrdinalIgnoreCase")] internal unsafe static extern int IndexOfOrdinalIgnoreCase(string target, int cwTargetLength, char* pSource, int cwSourceLength, bool findLast); + [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOfOrdinalIgnoreCase")] + internal unsafe static extern int IndexOfOrdinalIgnoreCase(char* target, int cwTargetLength, char* pSource, int cwSourceLength, bool findLast); [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_StartsWith")] [return: MarshalAs(UnmanagedType.Bool)] diff --git a/src/mscorlib/shared/System/Globalization/CompareInfo.Invariant.cs b/src/mscorlib/shared/System/Globalization/CompareInfo.Invariant.cs index 13725bc..29e4f53 100644 --- a/src/mscorlib/shared/System/Globalization/CompareInfo.Invariant.cs +++ b/src/mscorlib/shared/System/Globalization/CompareInfo.Invariant.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics; +using System.Runtime.InteropServices; namespace System.Globalization { @@ -26,6 +27,18 @@ namespace System.Globalization } } + internal static unsafe int InvariantIndexOf(ReadOnlySpan source, ReadOnlySpan value, bool ignoreCase) + { + Debug.Assert(source.Length != 0); + Debug.Assert(value.Length != 0); + + fixed (char* pSource = &MemoryMarshal.GetReference(source)) + fixed (char* pValue = &MemoryMarshal.GetReference(value)) + { + return InvariantFindString(pSource, source.Length, pValue, value.Length, ignoreCase, start: true); + } + } + internal static unsafe int InvariantLastIndexOf(string source, string value, int startIndex, int count, bool ignoreCase) { Debug.Assert(source != null); diff --git a/src/mscorlib/shared/System/Globalization/CompareInfo.cs b/src/mscorlib/shared/System/Globalization/CompareInfo.cs index 88af26d..8020fad 100644 --- a/src/mscorlib/shared/System/Globalization/CompareInfo.cs +++ b/src/mscorlib/shared/System/Globalization/CompareInfo.cs @@ -298,7 +298,7 @@ namespace System.Globalization return (Compare(string1, string2, CompareOptions.None)); } - public unsafe virtual int Compare(string string1, string string2, CompareOptions options) + public virtual int Compare(string string1, string string2, CompareOptions options) { if (options == CompareOptions.OrdinalIgnoreCase) { @@ -350,7 +350,7 @@ namespace System.Globalization // TODO https://github.com/dotnet/coreclr/issues/13827: // This method shouldn't be necessary, as we should be able to just use the overload // that takes two spans. But due to this issue, that's adding significant overhead. - internal unsafe int Compare(ReadOnlySpan string1, string string2, CompareOptions options) + internal int Compare(ReadOnlySpan string1, string string2, CompareOptions options) { if (options == CompareOptions.OrdinalIgnoreCase) { @@ -390,7 +390,7 @@ namespace System.Globalization } // TODO https://github.com/dotnet/corefx/issues/21395: Expose this publicly? - internal unsafe virtual int Compare(ReadOnlySpan string1, ReadOnlySpan string2, CompareOptions options) + internal virtual int Compare(ReadOnlySpan string1, ReadOnlySpan string2, CompareOptions options) { if (options == CompareOptions.OrdinalIgnoreCase) { @@ -436,7 +436,7 @@ namespace System.Globalization //////////////////////////////////////////////////////////////////////// - public unsafe virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2) + public virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2) { return Compare(string1, offset1, length1, string2, offset2, length2, 0); } @@ -547,7 +547,7 @@ namespace System.Globalization // it assumes the strings are Ascii string till we hit non Ascii character in strA or strB and then we continue the comparison by // calling the OS. // - internal static unsafe int CompareOrdinalIgnoreCase(string strA, int indexA, int lengthA, string strB, int indexB, int lengthB) + internal static int CompareOrdinalIgnoreCase(string strA, int indexA, int lengthA, string strB, int indexB, int lengthB) { Debug.Assert(indexA + lengthA <= strA.Length); Debug.Assert(indexB + lengthB <= strB.Length); @@ -910,6 +910,18 @@ namespace System.Globalization return IndexOfCore(source, value, startIndex, count, options, null); } + internal virtual int IndexOfOrdinal(ReadOnlySpan source, ReadOnlySpan value, bool ignoreCase) + { + Debug.Assert(!_invariantMode); + return IndexOfOrdinalCore(source, value, ignoreCase); + } + + internal unsafe virtual int IndexOf(ReadOnlySpan source, ReadOnlySpan value, CompareOptions options) + { + Debug.Assert(!_invariantMode); + return IndexOfCore(source, value, options, null); + } + // The following IndexOf overload is mainly used by String.Replace. This overload assumes the parameters are already validated // and the caller is passing a valid matchLengthPtr pointer. internal unsafe int IndexOf(string source, string value, int startIndex, int count, CompareOptions options, int* matchLengthPtr) diff --git a/src/mscorlib/shared/System/Span.NonGeneric.cs b/src/mscorlib/shared/System/Span.NonGeneric.cs index 6cc801c..2331664 100644 --- a/src/mscorlib/shared/System/Span.NonGeneric.cs +++ b/src/mscorlib/shared/System/Span.NonGeneric.cs @@ -23,6 +23,157 @@ namespace System /// public static class Span { + public static bool Contains(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) + { + return (IndexOf(span, value, comparisonType) >= 0); + } + + public static bool Equals(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) + { + StringSpanHelpers.CheckStringComparison(comparisonType); + + switch (comparisonType) + { + case StringComparison.CurrentCulture: + return (CultureInfo.CurrentCulture.CompareInfo.Compare(span, value, CompareOptions.None) == 0); + + case StringComparison.CurrentCultureIgnoreCase: + return (CultureInfo.CurrentCulture.CompareInfo.Compare(span, value, CompareOptions.IgnoreCase) == 0); + + case StringComparison.InvariantCulture: + return (CompareInfo.Invariant.Compare(span, value, CompareOptions.None) == 0); + + case StringComparison.InvariantCultureIgnoreCase: + return (CompareInfo.Invariant.Compare(span, value, CompareOptions.IgnoreCase) == 0); + + case StringComparison.Ordinal: + if (span.Length != value.Length) + return false; + if (value.Length == 0) // span.Length == value.Length == 0 + return true; + return OrdinalHelper(span, value, value.Length); + + case StringComparison.OrdinalIgnoreCase: + if (span.Length != value.Length) + return false; + if (value.Length == 0) // span.Length == value.Length == 0 + return true; + return (CompareInfo.CompareOrdinalIgnoreCase(span, value) == 0); + } + + Debug.Fail("StringComparison outside range"); + return false; + } + + public static int CompareTo(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) + { + StringSpanHelpers.CheckStringComparison(comparisonType); + + switch (comparisonType) + { + case StringComparison.CurrentCulture: + return CultureInfo.CurrentCulture.CompareInfo.Compare(span, value, CompareOptions.None); + + case StringComparison.CurrentCultureIgnoreCase: + return CultureInfo.CurrentCulture.CompareInfo.Compare(span, value, CompareOptions.IgnoreCase); + + case StringComparison.InvariantCulture: + return CompareInfo.Invariant.Compare(span, value, CompareOptions.None); + + case StringComparison.InvariantCultureIgnoreCase: + return CompareInfo.Invariant.Compare(span, value, CompareOptions.IgnoreCase); + + case StringComparison.Ordinal: + if (span.Length == 0 || value.Length == 0) + return span.Length - value.Length; + return string.CompareOrdinal(span, value); + + case StringComparison.OrdinalIgnoreCase: + return CompareInfo.CompareOrdinalIgnoreCase(span, value); + } + + Debug.Fail("StringComparison outside range"); + return 0; + } + + public static int IndexOf(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) + { + StringSpanHelpers.CheckStringComparison(comparisonType); + + if (value.Length == 0) + { + return 0; + } + + if (span.Length == 0) + { + return -1; + } + + switch (comparisonType) + { + case StringComparison.CurrentCulture: + return IndexOfCultureHelper(span, value, CultureInfo.CurrentCulture.CompareInfo); + + case StringComparison.CurrentCultureIgnoreCase: + return IndexOfCultureIgnoreCaseHelper(span, value, CultureInfo.CurrentCulture.CompareInfo); + + case StringComparison.InvariantCulture: + return IndexOfCultureHelper(span, value, CompareInfo.Invariant); + + case StringComparison.InvariantCultureIgnoreCase: + return IndexOfCultureIgnoreCaseHelper(span, value, CompareInfo.Invariant); + + case StringComparison.Ordinal: + return IndexOfOrdinalHelper(span, value, ignoreCase: false); + + case StringComparison.OrdinalIgnoreCase: + return IndexOfOrdinalHelper(span, value, ignoreCase: true); + } + + Debug.Fail("StringComparison outside range"); + return -1; + } + + internal static int IndexOfCultureHelper(ReadOnlySpan span, ReadOnlySpan value, CompareInfo compareInfo) + { + Debug.Assert(span.Length != 0); + Debug.Assert(value.Length != 0); + + if (GlobalizationMode.Invariant) + { + return CompareInfo.InvariantIndexOf(span, value, ignoreCase: false); + } + + return compareInfo.IndexOf(span, value, CompareOptions.None); + } + + internal static int IndexOfCultureIgnoreCaseHelper(ReadOnlySpan span, ReadOnlySpan value, CompareInfo compareInfo) + { + Debug.Assert(span.Length != 0); + Debug.Assert(value.Length != 0); + + if (GlobalizationMode.Invariant) + { + return CompareInfo.InvariantIndexOf(span, value, ignoreCase: true); + } + + return compareInfo.IndexOf(span, value, CompareOptions.IgnoreCase); + } + + internal static int IndexOfOrdinalHelper(ReadOnlySpan span, ReadOnlySpan value, bool ignoreCase) + { + Debug.Assert(span.Length != 0); + Debug.Assert(value.Length != 0); + + if (GlobalizationMode.Invariant) + { + return CompareInfo.InvariantIndexOf(span, value, ignoreCase); + } + + return CompareInfo.Invariant.IndexOfOrdinal(span, value, ignoreCase); + } + /// /// Copies the characters from the source span into the destination, converting each character to lowercase. /// @@ -109,16 +260,16 @@ namespace System switch (comparisonType) { case StringComparison.CurrentCulture: - return StartsWithCultureHelper(span, value); + return StartsWithCultureHelper(span, value, CultureInfo.CurrentCulture.CompareInfo); case StringComparison.CurrentCultureIgnoreCase: - return StartsWithCultureIgnoreCaseHelper(span, value); + return StartsWithCultureIgnoreCaseHelper(span, value, CultureInfo.CurrentCulture.CompareInfo); case StringComparison.InvariantCulture: - return StartsWithCultureHelper(span, value, invariantCulture: true); + return StartsWithCultureHelper(span, value, CompareInfo.Invariant); case StringComparison.InvariantCultureIgnoreCase: - return StartsWithCultureIgnoreCaseHelper(span, value, invariantCulture: true); + return StartsWithCultureIgnoreCaseHelper(span, value, CompareInfo.Invariant); case StringComparison.Ordinal: return StartsWithOrdinalHelper(span, value); @@ -131,7 +282,7 @@ namespace System } } - internal static bool StartsWithCultureHelper(ReadOnlySpan span, ReadOnlySpan value, bool invariantCulture = false) + internal static bool StartsWithCultureHelper(ReadOnlySpan span, ReadOnlySpan value, CompareInfo compareInfo) { Debug.Assert(value.Length != 0); @@ -143,12 +294,10 @@ namespace System { return false; } - return invariantCulture ? - CompareInfo.Invariant.IsPrefix(span, value, CompareOptions.None) : - CultureInfo.CurrentCulture.CompareInfo.IsPrefix(span, value, CompareOptions.None); + return compareInfo.IsPrefix(span, value, CompareOptions.None); } - internal static bool StartsWithCultureIgnoreCaseHelper(ReadOnlySpan span, ReadOnlySpan value, bool invariantCulture = false) + internal static bool StartsWithCultureIgnoreCaseHelper(ReadOnlySpan span, ReadOnlySpan value, CompareInfo compareInfo) { Debug.Assert(value.Length != 0); @@ -160,9 +309,7 @@ namespace System { return false; } - return invariantCulture ? - CompareInfo.Invariant.IsPrefix(span, value, CompareOptions.IgnoreCase) : - CultureInfo.CurrentCulture.CompareInfo.IsPrefix(span, value, CompareOptions.IgnoreCase); + return compareInfo.IsPrefix(span, value, CompareOptions.IgnoreCase); } internal static bool StartsWithOrdinalIgnoreCaseHelper(ReadOnlySpan span, ReadOnlySpan value) @@ -176,7 +323,7 @@ namespace System return CompareInfo.CompareOrdinalIgnoreCase(span.Slice(0, value.Length), value) == 0; } - internal static unsafe bool StartsWithOrdinalHelper(ReadOnlySpan span, ReadOnlySpan value) + internal static bool StartsWithOrdinalHelper(ReadOnlySpan span, ReadOnlySpan value) { Debug.Assert(value.Length != 0); @@ -263,16 +410,16 @@ namespace System switch (comparisonType) { case StringComparison.CurrentCulture: - return EndsWithCultureHelper(span, value); + return EndsWithCultureHelper(span, value, CultureInfo.CurrentCulture.CompareInfo); case StringComparison.CurrentCultureIgnoreCase: - return EndsWithCultureIgnoreCaseHelper(span, value); + return EndsWithCultureIgnoreCaseHelper(span, value, CultureInfo.CurrentCulture.CompareInfo); case StringComparison.InvariantCulture: - return EndsWithCultureHelper(span, value, invariantCulture: true); + return EndsWithCultureHelper(span, value, CompareInfo.Invariant); case StringComparison.InvariantCultureIgnoreCase: - return EndsWithCultureIgnoreCaseHelper(span, value, invariantCulture: true); + return EndsWithCultureIgnoreCaseHelper(span, value, CompareInfo.Invariant); case StringComparison.Ordinal: return EndsWithOrdinalHelper(span, value); @@ -285,7 +432,7 @@ namespace System } } - internal static bool EndsWithCultureHelper(ReadOnlySpan span, ReadOnlySpan value, bool invariantCulture = false) + internal static bool EndsWithCultureHelper(ReadOnlySpan span, ReadOnlySpan value, CompareInfo compareInfo) { Debug.Assert(value.Length != 0); @@ -297,12 +444,10 @@ namespace System { return false; } - return invariantCulture ? - CompareInfo.Invariant.IsSuffix(span, value, CompareOptions.None) : - CultureInfo.CurrentCulture.CompareInfo.IsSuffix(span, value, CompareOptions.None); + return compareInfo.IsSuffix(span, value, CompareOptions.None); } - internal static bool EndsWithCultureIgnoreCaseHelper(ReadOnlySpan span, ReadOnlySpan value, bool invariantCulture = false) + internal static bool EndsWithCultureIgnoreCaseHelper(ReadOnlySpan span, ReadOnlySpan value, CompareInfo compareInfo) { Debug.Assert(value.Length != 0); @@ -314,9 +459,7 @@ namespace System { return false; } - return invariantCulture ? - CompareInfo.Invariant.IsSuffix(span, value, CompareOptions.IgnoreCase) : - CultureInfo.CurrentCulture.CompareInfo.IsSuffix(span, value, CompareOptions.IgnoreCase); + return compareInfo.IsSuffix(span, value, CompareOptions.IgnoreCase); } internal static bool EndsWithOrdinalIgnoreCaseHelper(ReadOnlySpan span, ReadOnlySpan value) @@ -330,7 +473,7 @@ namespace System return (CompareInfo.CompareOrdinalIgnoreCase(span.Slice(span.Length - value.Length), value) == 0); } - internal static unsafe bool EndsWithOrdinalHelper(ReadOnlySpan span, ReadOnlySpan value) + internal static bool EndsWithOrdinalHelper(ReadOnlySpan span, ReadOnlySpan value) { Debug.Assert(value.Length != 0); diff --git a/src/mscorlib/src/System/Globalization/CompareInfo.Unix.cs b/src/mscorlib/src/System/Globalization/CompareInfo.Unix.cs index 9bdf604..ba42ae4 100644 --- a/src/mscorlib/src/System/Globalization/CompareInfo.Unix.cs +++ b/src/mscorlib/src/System/Globalization/CompareInfo.Unix.cs @@ -87,6 +87,47 @@ namespace System.Globalization return -1; } + internal static unsafe int IndexOfOrdinalCore(ReadOnlySpan source, ReadOnlySpan value, bool ignoreCase) + { + Debug.Assert(!GlobalizationMode.Invariant); + + Debug.Assert(source.Length != 0); + Debug.Assert(value.Length != 0); + + if (source.Length < value.Length) + { + return -1; + } + + if (ignoreCase) + { + fixed (char* pSource = &MemoryMarshal.GetReference(source)) + fixed (char* pValue = &MemoryMarshal.GetReference(value)) + { + int index = Interop.Globalization.IndexOfOrdinalIgnoreCase(pValue, value.Length, pSource, source.Length, findLast: false); + return index; + } + } + + int endIndex = source.Length - value.Length; + for (int i = 0; i <= endIndex; i++) + { + int valueIndex, sourceIndex; + + for (valueIndex = 0, sourceIndex = i; + valueIndex < value.Length && source[sourceIndex] == value[valueIndex]; + valueIndex++, sourceIndex++) + ; + + if (valueIndex == value.Length) + { + return i; + } + } + + return -1; + } + internal static unsafe int LastIndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase) { Debug.Assert(!GlobalizationMode.Invariant); @@ -218,6 +259,159 @@ namespace System.Globalization } } + // For now, this method is only called from Span APIs with either options == CompareOptions.None or CompareOptions.IgnoreCase + internal unsafe int IndexOfCore(ReadOnlySpan source, ReadOnlySpan target, CompareOptions options, int* matchLengthPtr) + { + Debug.Assert(!_invariantMode); + Debug.Assert(source.Length != 0); + Debug.Assert(target.Length != 0); + + if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options)) + { + if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase) + { + return IndexOfOrdinalIgnoreCaseHelper(source, target, options, matchLengthPtr); + } + else + { + return IndexOfOrdinalHelper(source, target, options, matchLengthPtr); + } + } + else + { + fixed (char* pSource = &MemoryMarshal.GetReference(source)) + fixed (char* pTarget = &MemoryMarshal.GetReference(target)) + { + return Interop.Globalization.IndexOf(_sortHandle, pTarget, target.Length, pSource, source.Length, options, matchLengthPtr); + } + } + } + + private unsafe int IndexOfOrdinalIgnoreCaseHelper(ReadOnlySpan source, ReadOnlySpan target, CompareOptions options, int* matchLengthPtr) + { + Debug.Assert(!_invariantMode); + + Debug.Assert(!source.IsEmpty); + Debug.Assert(!target.IsEmpty); + Debug.Assert(_isAsciiEqualityOrdinal); + + fixed (char* ap = &MemoryMarshal.GetReference(source)) + fixed (char* bp = &MemoryMarshal.GetReference(target)) + { + char* a = ap; + char* b = bp; + int endIndex = source.Length - target.Length; + + if (endIndex < 0) + goto InteropCall; + + for (int j = 0; j < target.Length; j++) + { + char targetChar = *(b + j); + if (targetChar >= 0x80 || s_highCharTable[targetChar]) + goto InteropCall; + } + + int i = 0; + for (; i <= endIndex; i++) + { + int targetIndex = 0; + int sourceIndex = i; + + for (; targetIndex < target.Length; targetIndex++) + { + char valueChar = *(a + sourceIndex); + char targetChar = *(b + targetIndex); + + if (valueChar == targetChar && valueChar < 0x80 && !s_highCharTable[valueChar]) + { + sourceIndex++; + continue; + } + + // uppercase both chars - notice that we need just one compare per char + if ((uint)(valueChar - 'a') <= ('z' - 'a')) + valueChar = (char)(valueChar - 0x20); + if ((uint)(targetChar - 'a') <= ('z' - 'a')) + targetChar = (char)(targetChar - 0x20); + + if (valueChar >= 0x80 || s_highCharTable[valueChar]) + goto InteropCall; + else if (valueChar != targetChar) + break; + sourceIndex++; + } + + if (targetIndex == target.Length) + { + if (matchLengthPtr != null) + *matchLengthPtr = target.Length; + return i; + } + } + if (i > endIndex) + return -1; + InteropCall: + return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr); + } + } + + private unsafe int IndexOfOrdinalHelper(ReadOnlySpan source, ReadOnlySpan target, CompareOptions options, int* matchLengthPtr) + { + Debug.Assert(!_invariantMode); + + Debug.Assert(!source.IsEmpty); + Debug.Assert(!target.IsEmpty); + Debug.Assert(_isAsciiEqualityOrdinal); + + fixed (char* ap = &MemoryMarshal.GetReference(source)) + fixed (char* bp = &MemoryMarshal.GetReference(target)) + { + char* a = ap; + char* b = bp; + int endIndex = source.Length - target.Length; + + if (endIndex < 0) + goto InteropCall; + + for (int j = 0; j < target.Length; j++) + { + char targetChar = *(b + j); + if (targetChar >= 0x80 || s_highCharTable[targetChar]) + goto InteropCall; + } + + int i = 0; + for (; i <= endIndex; i++) + { + int targetIndex = 0; + int sourceIndex = i; + + for (; targetIndex < target.Length; targetIndex++) + { + char valueChar = *(a + sourceIndex); + char targetChar = *(b + targetIndex); + if (valueChar >= 0x80 || s_highCharTable[valueChar]) + goto InteropCall; + else if (valueChar != targetChar) + break; + sourceIndex++; + } + + if (targetIndex == target.Length) + { + if (matchLengthPtr != null) + *matchLengthPtr = target.Length; + return i; + } + } + if (i > endIndex) + return -1; + InteropCall: + return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr); + } + } + private unsafe int LastIndexOfCore(string source, string target, int startIndex, int count, CompareOptions options) { Debug.Assert(!_invariantMode); diff --git a/src/mscorlib/src/System/Globalization/CompareInfo.Windows.cs b/src/mscorlib/src/System/Globalization/CompareInfo.Windows.cs index e6efa04..1188c21 100644 --- a/src/mscorlib/src/System/Globalization/CompareInfo.Windows.cs +++ b/src/mscorlib/src/System/Globalization/CompareInfo.Windows.cs @@ -56,6 +56,28 @@ namespace System.Globalization } } + private static unsafe int FindStringOrdinal( + uint dwFindStringOrdinalFlags, + ReadOnlySpan source, + ReadOnlySpan value, + bool bIgnoreCase) + { + Debug.Assert(!GlobalizationMode.Invariant); + + fixed (char* pSource = &MemoryMarshal.GetReference(source)) + fixed (char* pValue = &MemoryMarshal.GetReference(value)) + { + int ret = Interop.Kernel32.FindStringOrdinal( + dwFindStringOrdinalFlags, + pSource, + source.Length, + pValue, + value.Length, + bIgnoreCase ? 1 : 0); + return ret; + } + } + internal static int IndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase) { Debug.Assert(!GlobalizationMode.Invariant); @@ -66,6 +88,16 @@ namespace System.Globalization return FindStringOrdinal(FIND_FROMSTART, source, startIndex, count, value, value.Length, ignoreCase); } + internal static int IndexOfOrdinalCore(ReadOnlySpan source, ReadOnlySpan value, bool ignoreCase) + { + Debug.Assert(!GlobalizationMode.Invariant); + + Debug.Assert(source.Length != 0); + Debug.Assert(value.Length != 0); + + return FindStringOrdinal(FIND_FROMSTART, source, value, ignoreCase); + } + internal static int LastIndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase) { Debug.Assert(!GlobalizationMode.Invariant); @@ -290,6 +322,18 @@ namespace System.Globalization return -1; } + internal unsafe int IndexOfCore(ReadOnlySpan source, ReadOnlySpan target, CompareOptions options, int* matchLengthPtr) + { + Debug.Assert(!_invariantMode); + + Debug.Assert(source.Length != 0); + Debug.Assert(target.Length != 0); + Debug.Assert((options == CompareOptions.None || options == CompareOptions.IgnoreCase)); + + int retValue = FindString(FIND_FROMSTART | (uint)GetNativeCompareFlags(options), source, target, matchLengthPtr); + return retValue; + } + private unsafe int LastIndexOfCore(string source, string target, int startIndex, int count, CompareOptions options) { Debug.Assert(!_invariantMode); diff --git a/src/mscorlib/src/System/String.Comparison.cs b/src/mscorlib/src/System/String.Comparison.cs index bb79150..9bd907f 100644 --- a/src/mscorlib/src/System/String.Comparison.cs +++ b/src/mscorlib/src/System/String.Comparison.cs @@ -9,6 +9,14 @@ using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; +using Internal.Runtime.CompilerServices; + +#if BIT64 +using nuint = System.UInt64; +#else +using nuint = System.UInt32; +#endif + namespace System { public partial class String @@ -647,19 +655,36 @@ namespace System // TODO https://github.com/dotnet/corefx/issues/21395: Expose this publicly? internal static int CompareOrdinal(ReadOnlySpan strA, ReadOnlySpan strB) { - // TODO: This needs to be optimized / unrolled. It can't just use CompareOrdinalHelper(str, str) - // (changed to accept spans) because its implementation is based on a string layout, - // in a way that doesn't work when there isn't guaranteed to be a null terminator. + // TODO: Add a vectorized code path, similar to SequenceEqual + // https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/SpanHelpers.byte.cs#L900 int minLength = Math.Min(strA.Length, strB.Length); - for (int i = 0; i < minLength; i++) + ref char first = ref MemoryMarshal.GetReference(strA); + ref char second = ref MemoryMarshal.GetReference(strB); + + int i = 0; + if (minLength >= sizeof(nuint) / sizeof(char)) { - if (strA[i] != strB[i]) + while (i < minLength - sizeof(nuint) / sizeof(char)) { - return strA[i] - strB[i]; + if (Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref first, i))) != + Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref second, i)))) + { + break; + } + i += sizeof(nuint) / sizeof(char); } } - + while (i < minLength) + { + char a = Unsafe.Add(ref first, i); + char b = Unsafe.Add(ref second, i); + if (a != b) + { + return a - b; + } + i++; + } return strA.Length - strB.Length; } -- 2.7.4