diff --git a/src/System.Memory/ref/System.Memory.cs b/src/System.Memory/ref/System.Memory.cs index 467fe01f928c..c06eea658634 100644 --- a/src/System.Memory/ref/System.Memory.cs +++ b/src/System.Memory/ref/System.Memory.cs @@ -97,6 +97,13 @@ public static class SpanExtensions public static int IndexOf(this ReadOnlySpan span, ReadOnlySpan value) where T : struct, IEquatable { throw null; } public static int IndexOf(this ReadOnlySpan span, ReadOnlySpan value) { throw null; } + public static int IndexOfAny(this Span span, byte value0, byte value1) { throw null; } + public static int IndexOfAny(this Span span, byte value0, byte value1, byte value2) { throw null; } + public static int IndexOfAny(this Span span, ReadOnlySpan values) { throw null; } + public static int IndexOfAny(this ReadOnlySpan span, byte value0, byte value1) { throw null; } + public static int IndexOfAny(this ReadOnlySpan span, byte value0, byte value1, byte value2) { throw null; } + public static int IndexOfAny(this ReadOnlySpan span, ReadOnlySpan values) { throw null; } + public static bool SequenceEqual(this Span first, ReadOnlySpan second) where T:struct, IEquatable { throw null; } public static bool SequenceEqual(this Span first, ReadOnlySpan second) { throw null; } diff --git a/src/System.Memory/src/System/SpanExtensions.cs b/src/System.Memory/src/System/SpanExtensions.cs index f8164c3cd56e..1d3d94534790 100644 --- a/src/System.Memory/src/System/SpanExtensions.cs +++ b/src/System.Memory/src/System/SpanExtensions.cs @@ -125,6 +125,79 @@ public static int IndexOf(this ReadOnlySpan span, ReadOnlySpan value return SpanHelpers.IndexOf(ref span.DangerousGetPinnableReference(), span.Length, ref value.DangerousGetPinnableReference(), value.Length); } + + /// + /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1. + /// + /// The span to search. + /// One of the values to search for. + /// One of the values to search for. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfAny(this Span span, byte value0, byte value1) + { + return SpanHelpers.IndexOfAny(ref span.DangerousGetPinnableReference(), value0, value1, span.Length); + } + + /// + /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1. + /// + /// The span to search. + /// One of the values to search for. + /// One of the values to search for. + /// One of the values to search for. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfAny(this Span span, byte value0, byte value1, byte value2) + { + return SpanHelpers.IndexOfAny(ref span.DangerousGetPinnableReference(), value0, value1, value2, span.Length); + } + + /// + /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1. + /// + /// The span to search. + /// The set of values to search for. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfAny(this Span span, ReadOnlySpan values) + { + return SpanHelpers.IndexOfAny(ref span.DangerousGetPinnableReference(), span.Length, ref values.DangerousGetPinnableReference(), values.Length); + } + + /// + /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1. + /// + /// The span to search. + /// One of the values to search for. + /// One of the values to search for. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfAny(this ReadOnlySpan span, byte value0, byte value1) + { + return SpanHelpers.IndexOfAny(ref span.DangerousGetPinnableReference(), value0, value1, span.Length); + } + + /// + /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1. + /// + /// The span to search. + /// One of the values to search for. + /// One of the values to search for. + /// One of the values to search for. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfAny(this ReadOnlySpan span, byte value0, byte value1, byte value2) + { + return SpanHelpers.IndexOfAny(ref span.DangerousGetPinnableReference(), value0, value1, value2, span.Length); + } + + /// + /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1. + /// + /// The span to search. + /// The set of values to search for. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfAny(this ReadOnlySpan span, ReadOnlySpan values) + { + return SpanHelpers.IndexOfAny(ref span.DangerousGetPinnableReference(), span.Length, ref values.DangerousGetPinnableReference(), values.Length); + } + /// /// Determines whether two sequences are equal by comparing the elements using IEquatable<T>.Equals(T). /// diff --git a/src/System.Memory/src/System/SpanHelpers.byte.cs b/src/System.Memory/src/System/SpanHelpers.byte.cs index a702ffca5367..bc98d0b7ff33 100644 --- a/src/System.Memory/src/System/SpanHelpers.byte.cs +++ b/src/System.Memory/src/System/SpanHelpers.byte.cs @@ -48,6 +48,26 @@ public static int IndexOf(ref byte searchSpace, int searchSpaceLength, ref byte return -1; } + public static int IndexOfAny(ref byte searchSpace, int searchSpaceLength, ref byte 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. + + int index = -1; + for (int i = 0; i < valueLength; i++) + { + var tempIndex = IndexOf(ref searchSpace, Unsafe.Add(ref value, i), searchSpaceLength); + if (tempIndex != -1) + { + index = (index == -1 || index > tempIndex) ? tempIndex : index; + } + } + return index; + } + public static unsafe int IndexOf(ref byte searchSpace, byte value, int length) { Debug.Assert(length >= 0); @@ -176,6 +196,307 @@ public static unsafe int IndexOf(ref byte searchSpace, byte value, int length) return (int)(byte*)(index + 7); } + public static unsafe int IndexOfAny(ref byte searchSpace, byte value0, byte value1, int length) + { + Debug.Assert(length >= 0); + + uint uValue0 = value0; // Use uint for comparisions to avoid unnecessary 8->32 extensions + uint uValue1 = value1; // Use uint for comparisions to avoid unnecessary 8->32 extensions + IntPtr index = (IntPtr)0; // Use UIntPtr for arithmetic to avoid unnecessary 64->32->64 truncations + IntPtr nLength = (IntPtr)(uint)length; +#if !netstandard10 + if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) + { + unchecked + { + int unaligned = (int)(byte*)Unsafe.AsPointer(ref searchSpace) & (Vector.Count - 1); + nLength = (IntPtr)(uint)((Vector.Count - unaligned) & (Vector.Count - 1)); + } + } + SequentialScan: +#endif + uint lookUp; + while ((byte*)nLength >= (byte*)8) + { + nLength -= 8; + + lookUp = Unsafe.Add(ref searchSpace, index); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found; + lookUp = Unsafe.Add(ref searchSpace, index + 1); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found1; + lookUp = Unsafe.Add(ref searchSpace, index + 2); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found2; + lookUp = Unsafe.Add(ref searchSpace, index + 3); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found3; + lookUp = Unsafe.Add(ref searchSpace, index + 4); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found4; + lookUp = Unsafe.Add(ref searchSpace, index + 5); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found5; + lookUp = Unsafe.Add(ref searchSpace, index + 6); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found6; + lookUp = Unsafe.Add(ref searchSpace, index + 7); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found7; + + index += 8; + } + + if ((byte*)nLength >= (byte*)4) + { + nLength -= 4; + + lookUp = Unsafe.Add(ref searchSpace, index); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found; + lookUp = Unsafe.Add(ref searchSpace, index + 1); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found1; + lookUp = Unsafe.Add(ref searchSpace, index + 2); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found2; + lookUp = Unsafe.Add(ref searchSpace, index + 3); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found3; + + index += 4; + } + + while ((byte*)nLength > (byte*)0) + { + nLength -= 1; + + lookUp = Unsafe.Add(ref searchSpace, index); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found; + + index += 1; + } +#if !netstandard10 + if (Vector.IsHardwareAccelerated) + { + if ((int)(byte*)index >= length - 1) + { + goto NotFound; + } + nLength = (IntPtr)(uint)((length - (uint)index) & ~(Vector.Count - 1)); + // Get comparision Vector + Vector values0 = GetVector(value0); + Vector values1 = GetVector(value1); + do + { + var vData = Unsafe.ReadUnaligned>(ref Unsafe.AddByteOffset(ref searchSpace, index)); + var vMatches = Vector.BitwiseOr( + Vector.Equals(vData, values0), + Vector.Equals(vData, values1)); + + if (!vMatches.Equals(Vector.Zero)) + { + // Found match, reuse Vector values0 to keep register pressure low + values0 = vMatches; + break; + } + index += Vector.Count; + } while ((byte*)nLength > (byte*)index); + + // Found match? Perform secondary search outside out of loop, so above loop body is small + if ((byte*)nLength > (byte*)index) + { + // Find offset of first match + index += LocateFirstFoundByte(values0); + // goto rather than inline return to keep function smaller + goto Found; + } + + if ((int)(byte*)index <= length - 1) + { + unchecked + { + nLength = (IntPtr)(length - (int)(byte*)index); + } + goto SequentialScan; + } + } + NotFound: // Workaround for https://github.com/dotnet/coreclr/issues/9692 +#endif + return -1; + Found: // Workaround for https://github.com/dotnet/coreclr/issues/9692 + return (int)(byte*)index; + Found1: + return (int)(byte*)(index + 1); + Found2: + return (int)(byte*)(index + 2); + Found3: + return (int)(byte*)(index + 3); + Found4: + return (int)(byte*)(index + 4); + Found5: + return (int)(byte*)(index + 5); + Found6: + return (int)(byte*)(index + 6); + Found7: + return (int)(byte*)(index + 7); + } + + public static unsafe int IndexOfAny(ref byte searchSpace, byte value0, byte value1, byte value2, int length) + { + Debug.Assert(length >= 0); + + uint uValue0 = value0; // Use uint for comparisions to avoid unnecessary 8->32 extensions + uint uValue1 = value1; // Use uint for comparisions to avoid unnecessary 8->32 extensions + uint uValue2 = value2; // Use uint for comparisions to avoid unnecessary 8->32 extensions + IntPtr index = (IntPtr)0; // Use UIntPtr for arithmetic to avoid unnecessary 64->32->64 truncations + IntPtr nLength = (IntPtr)(uint)length; +#if !netstandard10 + if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) + { + unchecked + { + int unaligned = (int)(byte*)Unsafe.AsPointer(ref searchSpace) & (Vector.Count - 1); + nLength = (IntPtr)(uint)((Vector.Count - unaligned) & (Vector.Count - 1)); + } + } + SequentialScan: +#endif + uint lookUp; + while ((byte*)nLength >= (byte*)8) + { + nLength -= 8; + + lookUp = Unsafe.Add(ref searchSpace, index); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found; + lookUp = Unsafe.Add(ref searchSpace, index + 1); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found1; + lookUp = Unsafe.Add(ref searchSpace, index + 2); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found2; + lookUp = Unsafe.Add(ref searchSpace, index + 3); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found3; + lookUp = Unsafe.Add(ref searchSpace, index + 4); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found4; + lookUp = Unsafe.Add(ref searchSpace, index + 5); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found5; + lookUp = Unsafe.Add(ref searchSpace, index + 6); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found6; + lookUp = Unsafe.Add(ref searchSpace, index + 7); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found7; + + index += 8; + } + + if ((byte*)nLength >= (byte*)4) + { + nLength -= 4; + + lookUp = Unsafe.Add(ref searchSpace, index); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found; + lookUp = Unsafe.Add(ref searchSpace, index + 1); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found1; + lookUp = Unsafe.Add(ref searchSpace, index + 2); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found2; + lookUp = Unsafe.Add(ref searchSpace, index + 3); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found3; + + index += 4; + } + + while ((byte*)nLength > (byte*)0) + { + nLength -= 1; + + lookUp = Unsafe.Add(ref searchSpace, index); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found; + + index += 1; + } +#if !netstandard10 + if (Vector.IsHardwareAccelerated) + { + if ((int)(byte*)index >= length - 2) + { + goto NotFound; + } + nLength = (IntPtr)(uint)((length - (uint)index) & ~(Vector.Count - 1)); + // Get comparision Vector + Vector values0 = GetVector(value0); + Vector values1 = GetVector(value1); + Vector values2 = GetVector(value2); + do + { + var vData = Unsafe.ReadUnaligned>(ref Unsafe.AddByteOffset(ref searchSpace, index)); + + var vMatches = Vector.BitwiseOr( + Vector.BitwiseAnd( + Vector.Equals(vData, values0), + Vector.Equals(vData, values1)), + Vector.Equals(vData, values2)); + + if (!vMatches.Equals(Vector.Zero)) + { + // Found match, reuse Vector values0 to keep register pressure low + values0 = vMatches; + break; + } + index += Vector.Count; + } while ((byte*)nLength > (byte*)index); + + // Found match? Perform secondary search outside out of loop, so above loop body is small + if ((byte*)nLength > (byte*)index) + { + // Find offset of first match + index += LocateFirstFoundByte(values0); + // goto rather than inline return to keep function smaller + goto Found; + } + + if ((int)(byte*)index <= length - 2) + { + unchecked + { + nLength = (IntPtr)(length - (int)(byte*)index); + } + goto SequentialScan; + } + } + NotFound: // Workaround for https://github.com/dotnet/coreclr/issues/9692 +#endif + return -1; + Found: // Workaround for https://github.com/dotnet/coreclr/issues/9692 + return (int)(byte*)index; + Found1: + return (int)(byte*)(index + 1); + Found2: + return (int)(byte*)(index + 2); + Found3: + return (int)(byte*)(index + 3); + Found4: + return (int)(byte*)(index + 4); + Found5: + return (int)(byte*)(index + 5); + Found6: + return (int)(byte*)(index + 6); + Found7: + return (int)(byte*)(index + 7); + } + public static unsafe bool SequenceEqual(ref byte first, ref byte second, int length) { Debug.Assert(length >= 0); diff --git a/src/System.Memory/tests/ReadOnlySpan/IndexOf.byte.cs b/src/System.Memory/tests/ReadOnlySpan/IndexOf.byte.cs index a52b81698db3..31caef6d19dd 100644 --- a/src/System.Memory/tests/ReadOnlySpan/IndexOf.byte.cs +++ b/src/System.Memory/tests/ReadOnlySpan/IndexOf.byte.cs @@ -70,5 +70,476 @@ public static void MakeSureNoChecksGoOutOfRange_Byte() Assert.Equal(-1, index); } } + + [Fact] + public static void ZeroLengthIndexOfTwo_Byte() + { + ReadOnlySpan sp = new ReadOnlySpan(Array.Empty()); + int idx = sp.IndexOfAny(0, 0); + Assert.Equal(-1, idx); + } + + [Fact] + public static void DefaultFilledIndexOfTwo_Byte() + { + Random rnd = new Random(42); + + for (int length = 0; length < 32; length++) + { + byte[] a = new byte[length]; + ReadOnlySpan span = new ReadOnlySpan(a); + + byte[] targets = { default(byte), 99 }; + + for (int i = 0; i < length; i++) + { + int index; + index = rnd.Next(0, 2) == 0 ? 0 : 1; + byte target0 = targets[index]; + byte target1 = targets[(index + 1) % 2]; + int idx = span.IndexOfAny(target0, target1); + Assert.Equal(0, idx); + } + } + } + + [Fact] + public static void TestMatchTwo_Byte() + { + for (int length = 0; length < 32; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + ReadOnlySpan span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + byte target0 = a[targetIndex]; + byte target1 = 99; + int idx = span.IndexOfAny(target0, target1); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + byte target0 = a[targetIndex]; + byte target1 = a[targetIndex + 1]; + int idx = span.IndexOfAny(target0, target1); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + byte target0 = 99; + byte target1 = a[targetIndex + 1]; + int idx = span.IndexOfAny(target0, target1); + Assert.Equal(targetIndex + 1, idx); + } + } + } + + [Fact] + public static void TestNoMatchTwo_Byte() + { + for (int length = 0; length < 32; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + ReadOnlySpan span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + byte target0 = 98; + byte target1 = 99; + int idx = span.IndexOfAny(target0, target1); + Assert.Equal(-1, idx); + } + } + } + + [Fact] + public static void TestMultipleMatchTwo_Byte() + { + for (int length = 3; length < 32; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + + a[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + + ReadOnlySpan span = new ReadOnlySpan(a); + int idx = span.IndexOfAny(200, 200); + Assert.Equal(length - 3, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeTwo_Byte() + { + for (int length = 1; length < 100; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 98; + ReadOnlySpan span = new ReadOnlySpan(a, 1, length - 1); + int index = span.IndexOfAny(99, 98); + Assert.Equal(-1, index); + } + + for (int length = 1; length < 100; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 99; + ReadOnlySpan span = new ReadOnlySpan(a, 1, length - 1); + int index = span.IndexOfAny(99, 99); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthIndexOfThree_Byte() + { + ReadOnlySpan sp = new ReadOnlySpan(Array.Empty()); + int idx = sp.IndexOfAny(0, 0, 0); + Assert.Equal(-1, idx); + } + + [Fact] + public static void DefaultFilledIndexOfThree_Byte() + { + Random rnd = new Random(42); + + for (int length = 0; length < 32; length++) + { + byte[] a = new byte[length]; + ReadOnlySpan span = new ReadOnlySpan(a); + + byte[] targets = { default(byte), 99, 98 }; + + for (int i = 0; i < length; i++) + { + int index; + index = rnd.Next(0, 3); + byte target0 = targets[index]; + byte target1 = targets[(index + 1) % 2]; + byte traget2 = targets[(index + 1) % 3]; + int idx = span.IndexOfAny(target0, target1, traget2); + Assert.Equal(0, idx); + } + } + } + + [Fact] + public static void TestMatchThree_Byte() + { + for (int length = 0; length < 32; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + ReadOnlySpan span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + byte target0 = a[targetIndex]; + byte target1 = 99; + byte target2 = 99; + int idx = span.IndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + byte target0 = a[targetIndex]; + byte target1 = a[targetIndex + 1]; + byte target2 = a[targetIndex + 2]; + int idx = span.IndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + byte target0 = 99; + byte target1 = 99; + byte target2 = a[targetIndex + 2]; + int idx = span.IndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex + 2, idx); + } + } + } + + [Fact] + public static void TestNoMatchThree_Byte() + { + for (int length = 0; length < 32; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + ReadOnlySpan span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + byte target0 = 98; + byte target1 = 99; + byte target2 = 100; + int idx = span.IndexOfAny(target0, target1, target2); + Assert.Equal(-1, idx); + } + } + } + + [Fact] + public static void TestMultipleMatchThree_Byte() + { + for (int length = 4; length < 32; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + + a[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + a[length - 4] = 200; + + ReadOnlySpan span = new ReadOnlySpan(a); + int idx = span.IndexOfAny(200, 200, 200); + Assert.Equal(length - 4, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeThree_Byte() + { + for (int length = 1; length < 100; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 98; + ReadOnlySpan span = new ReadOnlySpan(a, 1, length - 1); + int index = span.IndexOfAny(99, 98, 99); + Assert.Equal(-1, index); + } + + for (int length = 1; length < 100; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 99; + ReadOnlySpan span = new ReadOnlySpan(a, 1, length - 1); + int index = span.IndexOfAny(99, 99, 99); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthIndexOfMany_Byte() + { + ReadOnlySpan sp = new ReadOnlySpan(Array.Empty()); + var values = new ReadOnlySpan(new byte[] { 0, 0, 0, 0 }); + int idx = sp.IndexOfAny(values); + Assert.Equal(-1, idx); + + values = new ReadOnlySpan(new byte[] { }); + idx = sp.IndexOfAny(values); + Assert.Equal(0, idx); + } + + [Fact] + public static void DefaultFilledIndexOfMany_Byte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + ReadOnlySpan span = new ReadOnlySpan(a); + + var values = new ReadOnlySpan(new byte[] { default(byte), 99, 98, 0 }); + + for (int i = 0; i < length; i++) + { + int idx = span.IndexOfAny(values); + Assert.Equal(0, idx); + } + } + } + + [Fact] + public static void TestMatchMany_Byte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + ReadOnlySpan span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + var values = new ReadOnlySpan(new byte[] { a[targetIndex], 0, 0, 0 }); + int idx = span.IndexOfAny(values); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 3; targetIndex++) + { + var values = new ReadOnlySpan(new byte[] { a[targetIndex], a[targetIndex + 1], a[targetIndex + 2], a[targetIndex + 3] }); + int idx = span.IndexOfAny(values); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 3; targetIndex++) + { + var values = new ReadOnlySpan(new byte[] { 0, 0, 0, a[targetIndex + 3] }); + int idx = span.IndexOfAny(values); + Assert.Equal(targetIndex + 3, idx); + } + } + } + + [Fact] + public static void TestMatchValuesLargerMany_Byte() + { + var rnd = new Random(42); + for (int length = 2; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + int expectedIndex = length / 2; + for (int i = 0; i < length; i++) + { + if (i == expectedIndex) + { + continue; + } + a[i] = 255; + } + ReadOnlySpan span = new ReadOnlySpan(a); + + byte[] targets = new byte[length * 2]; + for (int i = 0; i < targets.Length; i++) + { + if (i == length + 1) + { + continue; + } + targets[i] = (byte)rnd.Next(1, 255); + } + + var values = new ReadOnlySpan(targets); + int idx = span.IndexOfAny(values); + Assert.Equal(expectedIndex, idx); + } + } + + [Fact] + public static void TestNoMatchMany_Byte() + { + var rnd = new Random(42); + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + byte[] targets = new byte[length]; + for (int i = 0; i < targets.Length; i++) + { + targets[i] = (byte)rnd.Next(1, 256); + } + ReadOnlySpan span = new ReadOnlySpan(a); + var values = new ReadOnlySpan(targets); + + int idx = span.IndexOfAny(values); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestNoMatchValuesLargerMany_Byte() + { + var rnd = new Random(42); + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + byte[] targets = new byte[length * 2]; + for (int i = 0; i < targets.Length; i++) + { + targets[i] = (byte)rnd.Next(1, 256); + } + ReadOnlySpan span = new ReadOnlySpan(a); + var values = new ReadOnlySpan(targets); + + int idx = span.IndexOfAny(values); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchMany_Byte() + { + for (int length = 5; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + byte val = (byte)(i + 1); + a[i] = val == 200 ? (byte)201 : val; + } + + a[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + a[length - 4] = 200; + a[length - 5] = 200; + + ReadOnlySpan span = new ReadOnlySpan(a); + var values = new ReadOnlySpan(new byte[] { 200, 200, 200, 200, 200, 200, 200, 200, 200 }); + int idx = span.IndexOfAny(values); + Assert.Equal(length - 5, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeMany_Byte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 98; + ReadOnlySpan span = new ReadOnlySpan(a, 1, length - 1); + var values = new ReadOnlySpan(new byte[] { 99, 98, 99, 98, 99, 98 }); + int index = span.IndexOfAny(values); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 99; + ReadOnlySpan span = new ReadOnlySpan(a, 1, length - 1); + var values = new ReadOnlySpan(new byte[] { 99, 99, 99, 99, 99, 99 }); + int index = span.IndexOfAny(values); + Assert.Equal(-1, index); + } + } } } diff --git a/src/System.Memory/tests/Span/IndexOf.byte.cs b/src/System.Memory/tests/Span/IndexOf.byte.cs index aca94dbcbc5a..f2e7b399f327 100644 --- a/src/System.Memory/tests/Span/IndexOf.byte.cs +++ b/src/System.Memory/tests/Span/IndexOf.byte.cs @@ -70,5 +70,476 @@ public static void MakeSureNoChecksGoOutOfRange_Byte() Assert.Equal(-1, index); } } + + [Fact] + public static void ZeroLengthIndexOfTwo_Byte() + { + Span sp = new Span(Array.Empty()); + int idx = sp.IndexOfAny(0, 0); + Assert.Equal(-1, idx); + } + + [Fact] + public static void DefaultFilledIndexOfTwo_Byte() + { + Random rnd = new Random(42); + + for (int length = 0; length < 32; length++) + { + byte[] a = new byte[length]; + Span span = new Span(a); + + byte[] targets = { default(byte), 99 }; + + for (int i = 0; i < length; i++) + { + int index; + index = rnd.Next(0, 2) == 0 ? 0 : 1; + byte target0 = targets[index]; + byte target1 = targets[(index + 1) % 2]; + int idx = span.IndexOfAny(target0, target1); + Assert.Equal(0, idx); + } + } + } + + [Fact] + public static void TestMatchTwo_Byte() + { + for (int length = 0; length < 32; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + Span span = new Span(a); + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + byte target0 = a[targetIndex]; + byte target1 = 99; + int idx = span.IndexOfAny(target0, target1); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + byte target0 = a[targetIndex]; + byte target1 = a[targetIndex + 1]; + int idx = span.IndexOfAny(target0, target1); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + byte target0 = 99; + byte target1 = a[targetIndex + 1]; + int idx = span.IndexOfAny(target0, target1); + Assert.Equal(targetIndex + 1, idx); + } + } + } + + [Fact] + public static void TestNoMatchTwo_Byte() + { + for (int length = 0; length < 32; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + Span span = new Span(a); + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + byte target0 = 98; + byte target1 = 99; + int idx = span.IndexOfAny(target0, target1); + Assert.Equal(-1, idx); + } + } + } + + [Fact] + public static void TestMultipleMatchTwo_Byte() + { + for (int length = 3; length < 32; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + + a[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + + Span span = new Span(a); + int idx = span.IndexOfAny(200, 200); + Assert.Equal(length - 3, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeTwo_Byte() + { + for (int length = 1; length < 100; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 98; + Span span = new Span(a, 1, length - 1); + int index = span.IndexOfAny(99, 98); + Assert.Equal(-1, index); + } + + for (int length = 1; length < 100; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 99; + Span span = new Span(a, 1, length - 1); + int index = span.IndexOfAny(99, 99); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthIndexOfThree_Byte() + { + Span sp = new Span(Array.Empty()); + int idx = sp.IndexOfAny(0, 0, 0); + Assert.Equal(-1, idx); + } + + [Fact] + public static void DefaultFilledIndexOfThree_Byte() + { + Random rnd = new Random(42); + + for (int length = 0; length < 32; length++) + { + byte[] a = new byte[length]; + Span span = new Span(a); + + byte[] targets = { default(byte), 99, 98 }; + + for (int i = 0; i < length; i++) + { + int index; + index = rnd.Next(0, 3); + byte target0 = targets[index]; + byte target1 = targets[(index + 1) % 2]; + byte traget2 = targets[(index + 1) % 3]; + int idx = span.IndexOfAny(target0, target1, traget2); + Assert.Equal(0, idx); + } + } + } + + [Fact] + public static void TestMatchThree_Byte() + { + for (int length = 0; length < 32; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + Span span = new Span(a); + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + byte target0 = a[targetIndex]; + byte target1 = 99; + byte target2 = 99; + int idx = span.IndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + byte target0 = a[targetIndex]; + byte target1 = a[targetIndex + 1]; + byte target2 = a[targetIndex + 2]; + int idx = span.IndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + byte target0 = 99; + byte target1 = 99; + byte target2 = a[targetIndex + 2]; + int idx = span.IndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex + 2, idx); + } + } + } + + [Fact] + public static void TestNoMatchThree_Byte() + { + for (int length = 0; length < 32; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + Span span = new Span(a); + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + byte target0 = 98; + byte target1 = 99; + byte target2 = 100; + int idx = span.IndexOfAny(target0, target1, target2); + Assert.Equal(-1, idx); + } + } + } + + [Fact] + public static void TestMultipleMatchThree_Byte() + { + for (int length = 4; length < 32; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + + a[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + a[length - 4] = 200; + + Span span = new Span(a); + int idx = span.IndexOfAny(200, 200, 200); + Assert.Equal(length - 4, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeThree_Byte() + { + for (int length = 1; length < 100; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 98; + Span span = new Span(a, 1, length - 1); + int index = span.IndexOfAny(99, 98, 99); + Assert.Equal(-1, index); + } + + for (int length = 1; length < 100; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 99; + Span span = new Span(a, 1, length - 1); + int index = span.IndexOfAny(99, 99, 99); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthIndexOfMany_Byte() + { + Span sp = new Span(Array.Empty()); + var values = new ReadOnlySpan(new byte[] {0, 0, 0, 0}); + int idx = sp.IndexOfAny(values); + Assert.Equal(-1, idx); + + values = new ReadOnlySpan(new byte[] {}); + idx = sp.IndexOfAny(values); + Assert.Equal(0, idx); + } + + [Fact] + public static void DefaultFilledIndexOfMany_Byte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + Span span = new Span(a); + + var values = new ReadOnlySpan(new byte[] { default(byte), 99, 98, 0 }); + + for (int i = 0; i < length; i++) + { + int idx = span.IndexOfAny(values); + Assert.Equal(0, idx); + } + } + } + + [Fact] + public static void TestMatchMany_Byte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + Span span = new Span(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + var values = new ReadOnlySpan(new byte[] { a[targetIndex], 0, 0, 0 }); + int idx = span.IndexOfAny(values); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 3; targetIndex++) + { + var values = new ReadOnlySpan(new byte[] { a[targetIndex], a[targetIndex + 1], a[targetIndex + 2], a[targetIndex + 3] }); + int idx = span.IndexOfAny(values); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 3; targetIndex++) + { + var values = new ReadOnlySpan(new byte[] { 0, 0, 0, a[targetIndex + 3] }); + int idx = span.IndexOfAny(values); + Assert.Equal(targetIndex + 3, idx); + } + } + } + + [Fact] + public static void TestMatchValuesLargerMany_Byte() + { + var rnd = new Random(42); + for (int length = 2; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + int expectedIndex = length/2; + for (int i = 0; i < length; i++) + { + if (i == expectedIndex) + { + continue; + } + a[i] = 255; + } + Span span = new Span(a); + + byte[] targets = new byte[length*2]; + for (int i = 0; i < targets.Length; i++) + { + if (i == length + 1) + { + continue; + } + targets[i] = (byte)rnd.Next(1, 255); + } + + var values = new ReadOnlySpan(targets); + int idx = span.IndexOfAny(values); + Assert.Equal(expectedIndex, idx); + } + } + + [Fact] + public static void TestNoMatchMany_Byte() + { + var rnd = new Random(42); + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + byte[] targets = new byte[length]; + for (int i = 0; i < targets.Length; i++) + { + targets[i] = (byte) rnd.Next(1, 256); + } + Span span = new Span(a); + var values = new ReadOnlySpan(targets); + + int idx = span.IndexOfAny(values); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestNoMatchValuesLargerMany_Byte() + { + var rnd = new Random(42); + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + byte[] targets = new byte[length*2]; + for (int i = 0; i < targets.Length; i++) + { + targets[i] = (byte)rnd.Next(1, 256); + } + Span span = new Span(a); + var values = new ReadOnlySpan(targets); + + int idx = span.IndexOfAny(values); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchMany_Byte() + { + for (int length = 5; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + byte val = (byte)(i + 1); + a[i] = val == 200 ? (byte)201 : val; + } + + a[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + a[length - 4] = 200; + a[length - 5] = 200; + + Span span = new Span(a); + var values = new ReadOnlySpan(new byte[] { 200, 200, 200, 200, 200, 200, 200, 200, 200 }); + int idx = span.IndexOfAny(values); + Assert.Equal(length - 5, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeMany_Byte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 98; + Span span = new Span(a, 1, length - 1); + var values = new ReadOnlySpan(new byte[] { 99, 98, 99, 98, 99, 98 }); + int index = span.IndexOfAny(values); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 99; + Span span = new Span(a, 1, length - 1); + var values = new ReadOnlySpan(new byte[] { 99, 99, 99, 99, 99, 99 }); + int index = span.IndexOfAny(values); + Assert.Equal(-1, index); + } + } } }