diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 63534adc023104..fdc17764ac8b7c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -405,12 +405,22 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LastIndexOf(this Span span, ReadOnlySpan value) where T : IEquatable { - if (Unsafe.SizeOf() == sizeof(byte) && RuntimeHelpers.IsBitwiseEquatable()) - return SpanHelpers.LastIndexOf( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - span.Length, - ref Unsafe.As(ref MemoryMarshal.GetReference(value)), - value.Length); + if (RuntimeHelpers.IsBitwiseEquatable()) + { + if (Unsafe.SizeOf() == sizeof(byte)) + return SpanHelpers.LastIndexOf( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + span.Length, + ref Unsafe.As(ref MemoryMarshal.GetReference(value)), + value.Length); + + if (Unsafe.SizeOf() == sizeof(char)) + return SpanHelpers.LastIndexOf( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + span.Length, + ref Unsafe.As(ref MemoryMarshal.GetReference(value)), + value.Length); + } return SpanHelpers.LastIndexOf(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length); } @@ -550,12 +560,22 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LastIndexOf(this ReadOnlySpan span, ReadOnlySpan value) where T : IEquatable { - if (Unsafe.SizeOf() == sizeof(byte) && RuntimeHelpers.IsBitwiseEquatable()) - return SpanHelpers.LastIndexOf( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - span.Length, - ref Unsafe.As(ref MemoryMarshal.GetReference(value)), - value.Length); + if (RuntimeHelpers.IsBitwiseEquatable()) + { + if (Unsafe.SizeOf() == sizeof(byte)) + return SpanHelpers.LastIndexOf( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + span.Length, + ref Unsafe.As(ref MemoryMarshal.GetReference(value)), + value.Length); + + if (Unsafe.SizeOf() == sizeof(char)) + return SpanHelpers.LastIndexOf( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + span.Length, + ref Unsafe.As(ref MemoryMarshal.GetReference(value)), + value.Length); + } return SpanHelpers.LastIndexOf(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length); } diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Char.cs b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Char.cs index 25dc8f6fae4c6f..4f0f2d32ff596a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Char.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Char.cs @@ -871,6 +871,45 @@ public static unsafe int IndexOfAny(ref char searchSpace, char value0, char valu } } + public static int LastIndexOf(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 index = 0; + while (true) + { + 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 = LastIndexOf(ref searchSpace, valueHead, remainingSearchSpaceLength); + if (relativeIndex == -1) + break; + + // Found the first element of "value". See if the tail matches. + if (SequenceEqual( + ref Unsafe.As(ref Unsafe.Add(ref searchSpace, relativeIndex + 1)), + ref Unsafe.As(ref valueTail), + (nuint)valueTailLength * 2)) + { + return relativeIndex; // The tail matched. Return a successful find. + } + + index += remainingSearchSpaceLength - relativeIndex; + } + return -1; + } + [MethodImpl(MethodImplOptions.AggressiveOptimization)] public static unsafe int LastIndexOf(ref char searchSpace, char value, int length) { diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs index 87918217de1db6..98cd686bd6223a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs @@ -456,6 +456,9 @@ public static int IndexOfAny(ref T searchSpace, int searchSpaceLength, ref T public static int LastIndexOf(ref T searchSpace, int searchSpaceLength, ref T value, int valueLength) where T : IEquatable { + // The optimized implementation should be used for these types + Debug.Assert(!RuntimeHelpers.IsBitwiseEquatable() || !(Unsafe.SizeOf() == sizeof(byte) || Unsafe.SizeOf() == sizeof(char))); + Debug.Assert(searchSpaceLength >= 0); Debug.Assert(valueLength >= 0); @@ -574,8 +577,8 @@ public static int LastIndexOf(ref T searchSpace, T value, int length) where T public static int LastIndexOfAny(ref T searchSpace, T value0, T value1, int length) where T : IEquatable { - // The optimized implementation should be used for these types - Debug.Assert(!RuntimeHelpers.IsBitwiseEquatable() || !(Unsafe.SizeOf() == sizeof(byte) || Unsafe.SizeOf() == sizeof(char))); + // The optimized implementation should be used for byte + Debug.Assert(!RuntimeHelpers.IsBitwiseEquatable() || Unsafe.SizeOf() != sizeof(byte)); Debug.Assert(length >= 0); @@ -680,8 +683,8 @@ public static int LastIndexOfAny(ref T searchSpace, T value0, T value1, int l public static int LastIndexOfAny(ref T searchSpace, T value0, T value1, T value2, int length) where T : IEquatable { - // The optimized implementation should be used for these types - Debug.Assert(!RuntimeHelpers.IsBitwiseEquatable() || !(Unsafe.SizeOf() == sizeof(byte) || Unsafe.SizeOf() == sizeof(char))); + // The optimized implementation should be used for byte + Debug.Assert(!RuntimeHelpers.IsBitwiseEquatable() || Unsafe.SizeOf() != sizeof(byte)); Debug.Assert(length >= 0); @@ -786,8 +789,8 @@ public static int LastIndexOfAny(ref T searchSpace, T value0, T value1, T val public static int LastIndexOfAny(ref T searchSpace, int searchSpaceLength, ref T value, int valueLength) where T : IEquatable { - // The optimized implementation should be used for these types - Debug.Assert(!RuntimeHelpers.IsBitwiseEquatable() || !(Unsafe.SizeOf() == sizeof(byte) || Unsafe.SizeOf() == sizeof(char))); + // The optimized implementation should be used for byte + Debug.Assert(!RuntimeHelpers.IsBitwiseEquatable() || Unsafe.SizeOf() != sizeof(byte)); Debug.Assert(searchSpaceLength >= 0); Debug.Assert(valueLength >= 0);