diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index ce3f24f7d6513d..658a464d6d3d2d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -592,6 +592,67 @@ public static int IndexOfAnyExcept(this ReadOnlySpan span, T value0, T val return -1; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyExcept(this ReadOnlySpan span, T value0, T value1, T value2, T value3) where T : IEquatable? + { + if (RuntimeHelpers.IsBitwiseEquatable()) + { + if (Unsafe.SizeOf() == sizeof(byte)) + { + return SpanHelpers.IndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + Unsafe.As(ref value3), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.IndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + Unsafe.As(ref value3), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(int)) + { + return SpanHelpers.IndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + Unsafe.As(ref value3), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(long)) + { + return SpanHelpers.IndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + Unsafe.As(ref value3), + span.Length); + } + } + + for (int i = 0; i < span.Length; i++) + { + if (!EqualityComparer.Default.Equals(span[i], value0) && + !EqualityComparer.Default.Equals(span[i], value1) && + !EqualityComparer.Default.Equals(span[i], value2) && + !EqualityComparer.Default.Equals(span[i], value3)) + { + return i; + } + } + + return -1; + } + /// Searches for the first index of any value other than the specified . /// The type of the span and values. /// The span to search. @@ -619,6 +680,9 @@ public static int IndexOfAnyExcept(this ReadOnlySpan span, ReadOnlySpan case 3: return IndexOfAnyExcept(span, values[0], values[1], values[2]); + case 4: // common for searching whitespaces + return IndexOfAnyExcept(span, values[0], values[1], values[2], values[3]); + default: for (int i = 0; i < span.Length; i++) { 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 ed26ed9d04958b..709a423680af0f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs @@ -1220,19 +1220,72 @@ public static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, int return ComputeIndex(ref searchSpace, ref oneVectorAwayFromEnd, notEquals); } } + } + + return -1; + } + + internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, T value2, T value3, int length) where T : struct, IEquatable + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static int ComputeIndex(ref T searchSpace, ref T current, Vector128 notEquals) + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + for (int i = 0; i < length; i++) { - uint notEqualsElements = notEquals.ExtractMostSignificantBits(); - int index = BitOperations.TrailingZeroCount(notEqualsElements); - return index + (int)(Unsafe.ByteOffset(ref searchSpace, ref current) / Unsafe.SizeOf()); + T current = Unsafe.Add(ref searchSpace, i); + if (!current.Equals(value0) && !current.Equals(value1) && !current.Equals(value2) && !current.Equals(value3)) + { + return i; + } + } + } + else + { + Vector128 notEquals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1), values2 = Vector128.Create(value2), values3 = Vector128.Create(value3); + ref T currentSearchSpace = ref searchSpace; + ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector128.LoadUnsafe(ref currentSearchSpace); + notEquals = ~(Vector128.Equals(values0, current) | Vector128.Equals(values1, current) + | Vector128.Equals(values2, current) | Vector128.Equals(values3, current)); + if (notEquals != Vector128.Zero) + { + return ComputeIndex(ref searchSpace, ref currentSearchSpace, notEquals); + } + + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the last vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + current = Vector128.LoadUnsafe(ref oneVectorAwayFromEnd); + notEquals = ~(Vector128.Equals(values0, current) | Vector128.Equals(values1, current) + | Vector128.Equals(values2, current) | Vector128.Equals(values3, current)); + if (notEquals != Vector128.Zero) + { + return ComputeIndex(ref searchSpace, ref oneVectorAwayFromEnd, notEquals); + } } } return -1; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ComputeIndex(ref T searchSpace, ref T current, Vector128 notEquals) where T : struct, IEquatable + { + uint notEqualsElements = notEquals.ExtractMostSignificantBits(); + int index = BitOperations.TrailingZeroCount(notEqualsElements); + return index + (int)(Unsafe.ByteOffset(ref searchSpace, ref current) / Unsafe.SizeOf()); + } + public static bool SequenceEqual(ref T first, ref T second, int length) where T : IEquatable? { Debug.Assert(length >= 0);