diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Unix.cs b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Unix.cs index a55b966f4596..be33668d447d 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Unix.cs +++ b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Unix.cs @@ -8,6 +8,8 @@ using System.Runtime.InteropServices; using System.Security; +using Internal.Runtime.CompilerServices; + namespace System.Globalization { public partial class CompareInfo @@ -240,31 +242,12 @@ internal unsafe int IndexOfCore(string source, string target, int startIndex, in Debug.Assert(!string.IsNullOrEmpty(source)); Debug.Assert(target != null); Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0); - - int index; - - if (target.Length == 0) - { - if (matchLengthPtr != null) - *matchLengthPtr = 0; - return startIndex; - } - - if (options == CompareOptions.Ordinal) - { - index = IndexOfOrdinal(source, target, startIndex, count, ignoreCase: false); - if (index != -1) - { - if (matchLengthPtr != null) - *matchLengthPtr = target.Length; - } - return index; - } + Debug.Assert((options & CompareOptions.Ordinal) == 0); #if CORECLR if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && target.IsFastSort()) { - index = IndexOf(source, target, startIndex, count, GetOrdinalCompareOptions(options)); + int index = IndexOf(source, target, startIndex, count, GetOrdinalCompareOptions(options)); if (index != -1) { if (matchLengthPtr != null) @@ -277,7 +260,7 @@ internal unsafe int IndexOfCore(string source, string target, int startIndex, in fixed (char* pSource = source) fixed (char* pTarget = target) { - index = Interop.Globalization.IndexOf(_sortHandle, pTarget, target.Length, pSource + startIndex, count, options, matchLengthPtr); + int index = Interop.Globalization.IndexOf(_sortHandle, pTarget, target.Length, pSource + startIndex, count, options, matchLengthPtr); return index != -1 ? index + startIndex : -1; } diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Windows.cs b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Windows.cs index 6aeed0f22b78..749e2ad654d8 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Windows.cs +++ b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Windows.cs @@ -4,10 +4,10 @@ using System.Buffers; using System.Diagnostics; -using System.Security; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Internal.Runtime.CompilerServices; + namespace System.Globalization { public partial class CompareInfo @@ -327,37 +327,13 @@ internal unsafe int IndexOfCore(string source, string target, int startIndex, in Debug.Assert(source != null); Debug.Assert(target != null); Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0); + Debug.Assert((options & CompareOptions.Ordinal) == 0); - if (target.Length == 0) - { - if (matchLengthPtr != null) - *matchLengthPtr = 0; - return startIndex; - } - - if (source.Length == 0) - { - return -1; - } - - if ((options & CompareOptions.Ordinal) != 0) - { - int retValue = FastIndexOfString(source, target, startIndex, count, target.Length, findLastIndex: false); - if (retValue >= 0) - { - if (matchLengthPtr != null) - *matchLengthPtr = target.Length; - } - return retValue; - } - else + int retValue = FindString(FIND_FROMSTART | (uint)GetNativeCompareFlags(options), source, startIndex, count, + target, 0, target.Length, matchLengthPtr); + if (retValue >= 0) { - int retValue = FindString(FIND_FROMSTART | (uint)GetNativeCompareFlags(options), source, startIndex, count, - target, 0, target.Length, matchLengthPtr); - if (retValue >= 0) - { - return retValue + startIndex; - } + return retValue + startIndex; } return -1; @@ -388,7 +364,7 @@ private unsafe int LastIndexOfCore(string source, string target, int startIndex, if ((options & CompareOptions.Ordinal) != 0) { - return FastIndexOfString(source, target, startIndex, count, target.Length, findLastIndex: true); + return FastLastIndexOfString(source, target, startIndex, count, target.Length); } else { @@ -462,75 +438,44 @@ private unsafe bool EndsWith(ReadOnlySpan source, ReadOnlySpan suffi private const int FIND_FROMSTART = 0x00400000; private const int FIND_FROMEND = 0x00800000; - // TODO: Instead of this method could we just have upstack code call IndexOfOrdinal with ignoreCase = false? - private static unsafe int FastIndexOfString(string source, string target, int startIndex, int sourceCount, int targetCount, bool findLastIndex) + // TODO: Instead of this method could we just have upstack code call LastIndexOfOrdinal with ignoreCase = false? + private static unsafe int FastLastIndexOfString(string source, string target, int startIndex, int sourceCount, int targetCount) { int retValue = -1; - int sourceStartIndex = findLastIndex ? startIndex - sourceCount + 1 : startIndex; + int sourceStartIndex = startIndex - sourceCount + 1; fixed (char* pSource = source, spTarget = target) { char* spSubSource = pSource + sourceStartIndex; - if (findLastIndex) + int endPattern = sourceCount - targetCount; + if (endPattern < 0) + return -1; + + Debug.Assert(target.Length >= 1); + char patternChar0 = spTarget[0]; + for (int ctrSrc = endPattern; ctrSrc >= 0; ctrSrc--) { - int startPattern = (sourceCount - 1) - targetCount + 1; - if (startPattern < 0) - return -1; + if (spSubSource[ctrSrc] != patternChar0) + continue; - char patternChar0 = spTarget[0]; - for (int ctrSrc = startPattern; ctrSrc >= 0; ctrSrc--) + int ctrPat; + for (ctrPat = 1; ctrPat < targetCount; ctrPat++) { - if (spSubSource[ctrSrc] != patternChar0) - continue; - - int ctrPat; - for (ctrPat = 1; ctrPat < targetCount; ctrPat++) - { - if (spSubSource[ctrSrc + ctrPat] != spTarget[ctrPat]) - break; - } - if (ctrPat == targetCount) - { - retValue = ctrSrc; + if (spSubSource[ctrSrc + ctrPat] != spTarget[ctrPat]) break; - } } - - if (retValue >= 0) + if (ctrPat == targetCount) { - retValue += startIndex - sourceCount + 1; + retValue = ctrSrc; + break; } } - else - { - int endPattern = (sourceCount - 1) - targetCount + 1; - if (endPattern < 0) - return -1; - - char patternChar0 = spTarget[0]; - for (int ctrSrc = 0; ctrSrc <= endPattern; ctrSrc++) - { - if (spSubSource[ctrSrc] != patternChar0) - continue; - int ctrPat; - for (ctrPat = 1; ctrPat < targetCount; ctrPat++) - { - if (spSubSource[ctrSrc + ctrPat] != spTarget[ctrPat]) - break; - } - if (ctrPat == targetCount) - { - retValue = ctrSrc; - break; - } - } - if (retValue >= 0) - { - retValue += startIndex; - } + if (retValue >= 0) + { + retValue += startIndex - sourceCount + 1; } } diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs index 6cd8c8049484..b31e7493c4e7 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs +++ b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs @@ -972,20 +972,12 @@ public unsafe virtual int IndexOf(string source, char value, int startIndex, int return -1; } - if (options == CompareOptions.OrdinalIgnoreCase) - { - return source.IndexOf(value.ToString(), startIndex, count, StringComparison.OrdinalIgnoreCase); - } - // Validate CompareOptions // Ordinal can't be selected with other flags - if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal)) + if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal && options != CompareOptions.OrdinalIgnoreCase)) throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options)); - if (GlobalizationMode.Invariant) - return IndexOfOrdinal(source, new string(value, 1), startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0); - - return IndexOfCore(source, new string(value, 1), startIndex, count, options, null); + return IndexOf(source, new string(value, 1), startIndex, count, options, null); } public unsafe virtual int IndexOf(string source, string value, int startIndex, int count, CompareOptions options) @@ -1020,28 +1012,21 @@ public unsafe virtual int IndexOf(string source, string value, int startIndex, i if (count < 0 || startIndex > source.Length - count) throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count); - if (options == CompareOptions.OrdinalIgnoreCase) - { - return IndexOfOrdinal(source, value, startIndex, count, ignoreCase: true); - } - // Validate CompareOptions // Ordinal can't be selected with other flags - if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal)) + if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal && options != CompareOptions.OrdinalIgnoreCase)) throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options)); - if (GlobalizationMode.Invariant) - return IndexOfOrdinal(source, value, startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0); - - return IndexOfCore(source, value, startIndex, count, options, null); + return IndexOf(source, value, startIndex, count, options, null); } - internal int IndexOfOrdinal(ReadOnlySpan source, ReadOnlySpan value, bool ignoreCase) + internal int IndexOfOrdinalIgnoreCase(ReadOnlySpan source, ReadOnlySpan value) { Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(!source.IsEmpty); Debug.Assert(!value.IsEmpty); - return IndexOfOrdinalCore(source, value, ignoreCase, fromBeginning: true); + + return IndexOfOrdinalCore(source, value, ignoreCase: true, fromBeginning: true); } internal int LastIndexOfOrdinal(ReadOnlySpan source, ReadOnlySpan value, bool ignoreCase) @@ -1075,16 +1060,15 @@ internal unsafe int IndexOf(string source, string value, int startIndex, int cou Debug.Assert(source != null); Debug.Assert(value != null); Debug.Assert(startIndex >= 0); - Debug.Assert(matchLengthPtr != null); - *matchLengthPtr = 0; - if (source.Length == 0) + if (matchLengthPtr != null) { - if (value.Length == 0) - { - return 0; - } - return -1; + *matchLengthPtr = 0; + } + + if (value.Length == 0) + { + return startIndex; } if (startIndex >= source.Length) @@ -1095,7 +1079,7 @@ internal unsafe int IndexOf(string source, string value, int startIndex, int cou if (options == CompareOptions.OrdinalIgnoreCase) { int res = IndexOfOrdinal(source, value, startIndex, count, ignoreCase: true); - if (res >= 0) + if (res >= 0 && matchLengthPtr != null) { *matchLengthPtr = value.Length; } @@ -1105,18 +1089,49 @@ internal unsafe int IndexOf(string source, string value, int startIndex, int cou if (GlobalizationMode.Invariant) { int res = IndexOfOrdinal(source, value, startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0); - if (res >= 0) + if (res >= 0 && matchLengthPtr != null) { *matchLengthPtr = value.Length; } return res; } - return IndexOfCore(source, value, startIndex, count, options, matchLengthPtr); + if (options == CompareOptions.Ordinal) + { + int retValue = SpanHelpers.IndexOf( + ref Unsafe.Add(ref source.GetRawStringData(), startIndex), + count, + ref value.GetRawStringData(), + value.Length); + + if (retValue >= 0) + { + retValue += startIndex; + if (matchLengthPtr != null) + *matchLengthPtr = value.Length; + } + + return retValue; + } + else + { + return IndexOfCore(source, value, startIndex, count, options, matchLengthPtr); + } } internal int IndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase) { + if (!ignoreCase) + { + int result = SpanHelpers.IndexOf( + ref Unsafe.Add(ref source.GetRawStringData(), startIndex), + count, + ref value.GetRawStringData(), + value.Length); + + return (result >= 0 ? startIndex : 0) + result; + } + if (GlobalizationMode.Invariant) { return InvariantIndexOf(source, value, startIndex, count, ignoreCase); diff --git a/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs b/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs index 99fb83e635c7..11980fbcacb9 100644 --- a/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs +++ b/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs @@ -141,6 +141,15 @@ public static int IndexOf(this ReadOnlySpan span, ReadOnlySpan value return -1; } + if (comparisonType == StringComparison.Ordinal) + { + return SpanHelpers.IndexOf( + ref MemoryMarshal.GetReference(span), + span.Length, + ref MemoryMarshal.GetReference(value), + value.Length); + } + if (GlobalizationMode.Invariant) { return CompareInfo.InvariantIndexOf(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType) != CompareOptions.None); @@ -157,8 +166,8 @@ public static int IndexOf(this ReadOnlySpan span, ReadOnlySpan value return CompareInfo.Invariant.IndexOf(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType)); default: - Debug.Assert(comparisonType == StringComparison.Ordinal || comparisonType == StringComparison.OrdinalIgnoreCase); - return CompareInfo.Invariant.IndexOfOrdinal(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType) != CompareOptions.None); + Debug.Assert(comparisonType == StringComparison.OrdinalIgnoreCase); + return CompareInfo.Invariant.IndexOfOrdinalIgnoreCase(span, value); } } diff --git a/src/System.Private.CoreLib/shared/System/MemoryExtensions.cs b/src/System.Private.CoreLib/shared/System/MemoryExtensions.cs index 6145801faff3..64a99d0e5541 100644 --- a/src/System.Private.CoreLib/shared/System/MemoryExtensions.cs +++ b/src/System.Private.CoreLib/shared/System/MemoryExtensions.cs @@ -296,6 +296,12 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length, ref Unsafe.As(ref MemoryMarshal.GetReference(value)), value.Length); + if (typeof(T) == typeof(char)) + return SpanHelpers.IndexOf( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + span.Length, + ref Unsafe.As(ref MemoryMarshal.GetReference(value)), + value.Length); return SpanHelpers.IndexOf(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length); } @@ -424,6 +430,12 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length, ref Unsafe.As(ref MemoryMarshal.GetReference(value)), value.Length); + if (typeof(T) == typeof(char)) + return SpanHelpers.IndexOf( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + span.Length, + ref Unsafe.As(ref MemoryMarshal.GetReference(value)), + value.Length); return SpanHelpers.IndexOf(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length); } diff --git a/src/System.Private.CoreLib/shared/System/SpanHelpers.Byte.cs b/src/System.Private.CoreLib/shared/System/SpanHelpers.Byte.cs index 373065363b24..118c82be2811 100644 --- a/src/System.Private.CoreLib/shared/System/SpanHelpers.Byte.cs +++ b/src/System.Private.CoreLib/shared/System/SpanHelpers.Byte.cs @@ -29,25 +29,27 @@ public static int IndexOf(ref byte searchSpace, int searchSpaceLength, ref byte byte valueHead = value; ref byte valueTail = ref Unsafe.Add(ref value, 1); int valueTailLength = valueLength - 1; + int remainingSearchSpaceLength = searchSpaceLength - valueTailLength; int index = 0; - for (; ; ) + while (remainingSearchSpaceLength > 0) { - Debug.Assert(0 <= index && index <= searchSpaceLength); // Ensures no deceptive underflows in the computation of "remainingSearchSpaceLength". - int remainingSearchSpaceLength = searchSpaceLength - index - valueTailLength; - if (remainingSearchSpaceLength <= 0) - break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there. - // Do a quick search for the first element of "value". int relativeIndex = IndexOf(ref Unsafe.Add(ref searchSpace, index), valueHead, remainingSearchSpaceLength); if (relativeIndex == -1) break; + + remainingSearchSpaceLength -= relativeIndex; index += relativeIndex; + if (remainingSearchSpaceLength <= 0) + break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there. + // Found the first element of "value". See if the tail matches. if (SequenceEqual(ref Unsafe.Add(ref searchSpace, index + 1), ref valueTail, valueTailLength)) return index; // The tail matched. Return a successful find. + remainingSearchSpaceLength--; index++; } return -1; diff --git a/src/System.Private.CoreLib/shared/System/SpanHelpers.Char.cs b/src/System.Private.CoreLib/shared/System/SpanHelpers.Char.cs index 40d05759fb54..47011963dfdc 100644 --- a/src/System.Private.CoreLib/shared/System/SpanHelpers.Char.cs +++ b/src/System.Private.CoreLib/shared/System/SpanHelpers.Char.cs @@ -10,10 +10,58 @@ using Internal.Runtime.CompilerServices; #endif +#if BIT64 +using nuint = System.UInt64; +#else +using nuint = System.UInt32; +#endif + namespace System { internal static partial class SpanHelpers // .Char { + public static int IndexOf(ref char searchSpace, int searchSpaceLength, ref char value, int valueLength) + { + Debug.Assert(searchSpaceLength >= 0); + Debug.Assert(valueLength >= 0); + + if (valueLength == 0) + return 0; // A zero-length sequence is always treated as "found" at the start of the search space. + + char valueHead = value; + ref char valueTail = ref Unsafe.Add(ref value, 1); + int valueTailLength = valueLength - 1; + int remainingSearchSpaceLength = searchSpaceLength - valueTailLength; + + int index = 0; + while (remainingSearchSpaceLength > 0) + { + // Do a quick search for the first element of "value". + int relativeIndex = IndexOf(ref Unsafe.Add(ref searchSpace, index), valueHead, remainingSearchSpaceLength); + if (relativeIndex == -1) + break; + + remainingSearchSpaceLength -= relativeIndex; + index += relativeIndex; + + if (remainingSearchSpaceLength <= 0) + break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there. + + // Found the first element of "value". See if the tail matches. + if (SequenceEqual( + ref Unsafe.As(ref Unsafe.Add(ref searchSpace, index + 1)), + ref Unsafe.As(ref valueTail), + (nuint)valueTailLength * 2)) + { + return index; // The tail matched. Return a successful find. + } + + remainingSearchSpaceLength--; + index++; + } + return -1; + } + public static unsafe int SequenceCompareTo(ref char first, int firstLength, ref char second, int secondLength) { Debug.Assert(firstLength >= 0); diff --git a/src/System.Private.CoreLib/shared/System/String.Searching.cs b/src/System.Private.CoreLib/shared/System/String.Searching.cs index e288b2c34283..893c2c7448fe 100644 --- a/src/System.Private.CoreLib/shared/System/String.Searching.cs +++ b/src/System.Private.CoreLib/shared/System/String.Searching.cs @@ -261,6 +261,17 @@ public int IndexOf(string value, int startIndex, int count, StringComparison com if (count < 0 || startIndex > this.Length - count) throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count); + if (comparisonType == StringComparison.Ordinal) + { + var result = SpanHelpers.IndexOf( + ref Unsafe.Add(ref this._firstChar, startIndex), + count, + ref value._firstChar, + value.Length); + + return (result >= 0 ? startIndex : 0) + result; + } + switch (comparisonType) { case StringComparison.CurrentCulture: @@ -271,7 +282,6 @@ public int IndexOf(string value, int startIndex, int count, StringComparison com case StringComparison.InvariantCultureIgnoreCase: return CompareInfo.Invariant.IndexOf(this, value, startIndex, count, GetCaseCompareOfComparisonCulture(comparisonType)); - case StringComparison.Ordinal: case StringComparison.OrdinalIgnoreCase: return CompareInfo.Invariant.IndexOfOrdinal(this, value, startIndex, count, GetCaseCompareOfComparisonCulture(comparisonType) != CompareOptions.None);