Add ReadOnlySpan string-like Equals/CompareTo/IndexOf/Contains API with globalization...
authorAhson Khan <ahkha@microsoft.com>
Thu, 22 Feb 2018 21:01:35 +0000 (13:01 -0800)
committerGitHub <noreply@github.com>
Thu, 22 Feb 2018 21:01:35 +0000 (13:01 -0800)
* Add ReadOnlySpan string-like Equals/CompareTo/IndexOf/Contains API with globalization support

* Address PR feedback.

* Fix unix implementation

src/mscorlib/shared/Interop/Unix/System.Globalization.Native/Interop.Collation.cs
src/mscorlib/shared/System/Globalization/CompareInfo.Invariant.cs
src/mscorlib/shared/System/Globalization/CompareInfo.cs
src/mscorlib/shared/System/Span.NonGeneric.cs
src/mscorlib/src/System/Globalization/CompareInfo.Unix.cs
src/mscorlib/src/System/Globalization/CompareInfo.Windows.cs
src/mscorlib/src/System/String.Comparison.cs

index 021f774..08aa611 100644 (file)
@@ -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)]
index 13725bc..29e4f53 100644 (file)
@@ -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<char> source, ReadOnlySpan<char> 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);
index 88af26d..8020fad 100644 (file)
@@ -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<char> string1, string string2, CompareOptions options)
+        internal int Compare(ReadOnlySpan<char> 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<char> string1, ReadOnlySpan<char> string2, CompareOptions options)
+        internal virtual int Compare(ReadOnlySpan<char> string1, ReadOnlySpan<char> 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<char> source, ReadOnlySpan<char> value, bool ignoreCase)
+        {
+            Debug.Assert(!_invariantMode);
+            return IndexOfOrdinalCore(source, value, ignoreCase);
+        }
+
+        internal unsafe virtual int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> 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)
index 6cc801c..2331664 100644 (file)
@@ -23,6 +23,157 @@ namespace System
     /// </summary>
     public static class Span
     {
+        public static bool Contains(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
+        {
+            return (IndexOf(span, value, comparisonType) >= 0);
+        }
+
+        public static bool Equals(this ReadOnlySpan<char> span, ReadOnlySpan<char> 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<char> span, ReadOnlySpan<char> 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<char> span, ReadOnlySpan<char> 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<char> span, ReadOnlySpan<char> 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<char> span, ReadOnlySpan<char> 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<char> span, ReadOnlySpan<char> 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);
+        }
+
         /// <summary>
         /// Copies the characters from the source span into the destination, converting each character to lowercase.
         /// </summary>
@@ -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<char> span, ReadOnlySpan<char> value, bool invariantCulture = false)
+        internal static bool StartsWithCultureHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> 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<char> span, ReadOnlySpan<char> value, bool invariantCulture = false)
+        internal static bool StartsWithCultureIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> 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<char> span, ReadOnlySpan<char> value)
@@ -176,7 +323,7 @@ namespace System
             return CompareInfo.CompareOrdinalIgnoreCase(span.Slice(0, value.Length), value) == 0;
         }
 
-        internal static unsafe bool StartsWithOrdinalHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value)
+        internal static bool StartsWithOrdinalHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> 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<char> span, ReadOnlySpan<char> value, bool invariantCulture = false)
+        internal static bool EndsWithCultureHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> 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<char> span, ReadOnlySpan<char> value, bool invariantCulture = false)
+        internal static bool EndsWithCultureIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> 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<char> span, ReadOnlySpan<char> value)
@@ -330,7 +473,7 @@ namespace System
             return (CompareInfo.CompareOrdinalIgnoreCase(span.Slice(span.Length - value.Length), value) == 0);
         }
 
-        internal static unsafe bool EndsWithOrdinalHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value)
+        internal static bool EndsWithOrdinalHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value)
         {
             Debug.Assert(value.Length != 0);
 
index 9bdf604..ba42ae4 100644 (file)
@@ -87,6 +87,47 @@ namespace System.Globalization
             return -1;
         }
 
+        internal static unsafe int IndexOfOrdinalCore(ReadOnlySpan<char> source, ReadOnlySpan<char> 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<char> source, ReadOnlySpan<char> 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<char> source, ReadOnlySpan<char> 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<char> source, ReadOnlySpan<char> 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);
index e6efa04..1188c21 100644 (file)
@@ -56,6 +56,28 @@ namespace System.Globalization
             }
         }
 
+        private static unsafe int FindStringOrdinal(
+            uint dwFindStringOrdinalFlags,
+            ReadOnlySpan<char> source,
+            ReadOnlySpan<char> 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<char> source, ReadOnlySpan<char> 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<char> source, ReadOnlySpan<char> 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);
index bb79150..9bd907f 100644 (file)
@@ -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<char> strA, ReadOnlySpan<char> 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<nuint>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref first, i))) !=
+                        Unsafe.ReadUnaligned<nuint>(ref Unsafe.As<char, byte>(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;
         }