From 4ac5920f13851d7806d017b960098d8a577e7d04 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Tue, 29 Nov 2022 21:23:46 +0100 Subject: [PATCH 1/2] Add IndexOfAnyValues.Contains --- .../src/System/TypeNameParser.cs | 5 +- .../tests/Span/IndexOfAny.byte.cs | 67 --- .../tests/Span/IndexOfAny.char.cs | 203 -------- .../tests/Span/IndexOfAnyExcept.T.cs | 92 ---- .../tests/Span/IndexOfAnyValues.cs | 471 ++++++++++++++++++ .../tests/Span/LastIndexOfAny.T.cs | 46 -- .../tests/System.Memory.Tests.csproj | 1 + .../IndexOfAnyValues/IndexOfAny1Value.cs | 7 +- .../IndexOfAnyValues/IndexOfAny2Values.cs | 7 +- .../IndexOfAnyValues/IndexOfAny3Values.cs | 7 +- .../IndexOfAnyValues/IndexOfAny4Values.cs | 7 + .../IndexOfAnyValues/IndexOfAny5Values.cs | 8 + .../IndexOfAnyAsciiByteValues.cs | 4 + .../IndexOfAnyAsciiCharValues.cs | 4 + .../IndexOfAnyValues/IndexOfAnyByteValues.cs | 4 + .../IndexOfAnyCharValuesProbabilistic.cs | 4 + .../IndexOfAnyLatin1CharValues.cs | 4 + .../IndexOfAnyValues/IndexOfAnyValues.T.cs | 9 + .../IndexOfAnyValuesInRange.cs | 15 +- .../IndexOfAnyValues/IndexOfEmptyValues.cs | 6 +- .../System.Runtime/ref/System.Runtime.cs | 1 + 21 files changed, 555 insertions(+), 417 deletions(-) create mode 100644 src/libraries/System.Memory/tests/Span/IndexOfAnyValues.cs diff --git a/src/coreclr/System.Private.CoreLib/src/System/TypeNameParser.cs b/src/coreclr/System.Private.CoreLib/src/System/TypeNameParser.cs index 2df15e866885b8..06f37043a5b24b 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/TypeNameParser.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/TypeNameParser.cs @@ -89,8 +89,7 @@ internal sealed partial class TypeNameParser : IDisposable #region Private Data Members private readonly SafeTypeNameParserHandle m_NativeParser; - private const string SpecialChars = ",[]&*+\\"; // see typeparse.h - private static readonly IndexOfAnyValues s_specialChars = IndexOfAnyValues.Create(SpecialChars); + private static readonly IndexOfAnyValues s_specialChars = IndexOfAnyValues.Create(",[]&*+\\"); // see typeparse.h #endregion #region Constructor and Disposer @@ -289,7 +288,7 @@ private static string EscapeTypeName(string name) foreach (char c in name.AsSpan(specialCharIndex)) { - if (SpecialChars.Contains(c)) + if (s_specialChars.Contains(c)) sb.Append('\\'); sb.Append(c); diff --git a/src/libraries/System.Memory/tests/Span/IndexOfAny.byte.cs b/src/libraries/System.Memory/tests/Span/IndexOfAny.byte.cs index 5791f29982095b..cc2e92c8a62b40 100644 --- a/src/libraries/System.Memory/tests/Span/IndexOfAny.byte.cs +++ b/src/libraries/System.Memory/tests/Span/IndexOfAny.byte.cs @@ -530,73 +530,6 @@ public static void MakeSureNoChecksGoOutOfRangeMany_Byte() } } - [Fact] - [OuterLoop("Takes about a second to execute")] - public static void TestIndexOfAny_RandomInputs_Byte() - { - IndexOfAnyCharTestHelper.TestRandomInputs( - expected: IndexOfAnyReferenceImpl, - indexOfAny: (searchSpace, values) => searchSpace.IndexOfAny(values), - indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAny(values)); - - static int IndexOfAnyReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) - { - for (int i = 0; i < searchSpace.Length; i++) - { - if (values.Contains(searchSpace[i])) - { - return i; - } - } - - return -1; - } - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public static void AsciiNeedle_ProperlyHandlesEdgeCases_Byte(bool needleContainsZero) - { - // There is some special handling we have to do for ASCII needles to properly filter out non-ASCII results - ReadOnlySpan needleValues = needleContainsZero ? "AEIOU\0"u8 : "AEIOU!"u8; - IndexOfAnyValues needle = IndexOfAnyValues.Create(needleValues); - - ReadOnlySpan repeatingHaystack = "AaAaAaAaAaAa"u8; - Assert.Equal(0, repeatingHaystack.IndexOfAny(needle)); - Assert.Equal(1, repeatingHaystack.IndexOfAnyExcept(needle)); - Assert.Equal(10, repeatingHaystack.LastIndexOfAny(needle)); - Assert.Equal(11, repeatingHaystack.LastIndexOfAnyExcept(needle)); - - ReadOnlySpan haystackWithZeroes = "Aa\0Aa\0Aa\0"u8; - Assert.Equal(0, haystackWithZeroes.IndexOfAny(needle)); - Assert.Equal(1, haystackWithZeroes.IndexOfAnyExcept(needle)); - Assert.Equal(needleContainsZero ? 8 : 6, haystackWithZeroes.LastIndexOfAny(needle)); - Assert.Equal(needleContainsZero ? 7 : 8, haystackWithZeroes.LastIndexOfAnyExcept(needle)); - - Span haystackWithOffsetNeedle = new byte[100]; - for (int i = 0; i < haystackWithOffsetNeedle.Length; i++) - { - haystackWithOffsetNeedle[i] = (byte)(128 + needleValues[i % needleValues.Length]); - } - - Assert.Equal(-1, haystackWithOffsetNeedle.IndexOfAny(needle)); - Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAnyExcept(needle)); - Assert.Equal(-1, haystackWithOffsetNeedle.LastIndexOfAny(needle)); - Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle)); - - // Mix matching characters back in - for (int i = 0; i < haystackWithOffsetNeedle.Length; i += 3) - { - haystackWithOffsetNeedle[i] = needleValues[i % needleValues.Length]; - } - - Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAny(needle)); - Assert.Equal(1, haystackWithOffsetNeedle.IndexOfAnyExcept(needle)); - Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAny(needle)); - Assert.Equal(haystackWithOffsetNeedle.Length - 2, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle)); - } - private static int IndexOf(Span span, byte value) { int index = span.IndexOf(value); diff --git a/src/libraries/System.Memory/tests/Span/IndexOfAny.char.cs b/src/libraries/System.Memory/tests/Span/IndexOfAny.char.cs index c616ec2adf865d..47ec7bebaa4c93 100644 --- a/src/libraries/System.Memory/tests/Span/IndexOfAny.char.cs +++ b/src/libraries/System.Memory/tests/Span/IndexOfAny.char.cs @@ -4,9 +4,6 @@ using System.Buffers; using System.Linq; using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; using Xunit; namespace System.SpanTests @@ -776,95 +773,6 @@ public static void MakeSureNoChecksGoOutOfRangeMany_Char() } } - [Fact] - [OuterLoop("Takes about a second to execute")] - public static void TestIndexOfAny_RandomInputs_Char() - { - IndexOfAnyCharTestHelper.TestRandomInputs( - expected: IndexOfAnyReferenceImpl, - indexOfAny: (searchSpace, values) => searchSpace.IndexOfAny(values), - indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAny(values)); - - static int IndexOfAnyReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) - { - for (int i = 0; i < searchSpace.Length; i++) - { - if (values.Contains(searchSpace[i])) - { - return i; - } - } - - return -1; - } - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public static void AsciiNeedle_ProperlyHandlesEdgeCases_Char(bool needleContainsZero) - { - // There is some special handling we have to do for ASCII needles to properly filter out non-ASCII results - ReadOnlySpan needleValues = needleContainsZero ? "AEIOU\0" : "AEIOU!"; - IndexOfAnyValues needle = IndexOfAnyValues.Create(needleValues); - - ReadOnlySpan repeatingHaystack = "AaAaAaAaAaAa"; - Assert.Equal(0, repeatingHaystack.IndexOfAny(needle)); - Assert.Equal(1, repeatingHaystack.IndexOfAnyExcept(needle)); - Assert.Equal(10, repeatingHaystack.LastIndexOfAny(needle)); - Assert.Equal(11, repeatingHaystack.LastIndexOfAnyExcept(needle)); - - ReadOnlySpan haystackWithZeroes = "Aa\0Aa\0Aa\0"; - Assert.Equal(0, haystackWithZeroes.IndexOfAny(needle)); - Assert.Equal(1, haystackWithZeroes.IndexOfAnyExcept(needle)); - Assert.Equal(needleContainsZero ? 8 : 6, haystackWithZeroes.LastIndexOfAny(needle)); - Assert.Equal(needleContainsZero ? 7 : 8, haystackWithZeroes.LastIndexOfAnyExcept(needle)); - - Span haystackWithOffsetNeedle = new char[100]; - for (int i = 0; i < haystackWithOffsetNeedle.Length; i++) - { - haystackWithOffsetNeedle[i] = (char)(128 + needleValues[i % needleValues.Length]); - } - - Assert.Equal(-1, haystackWithOffsetNeedle.IndexOfAny(needle)); - Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAnyExcept(needle)); - Assert.Equal(-1, haystackWithOffsetNeedle.LastIndexOfAny(needle)); - Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle)); - - // Mix matching characters back in - for (int i = 0; i < haystackWithOffsetNeedle.Length; i += 3) - { - haystackWithOffsetNeedle[i] = needleValues[i % needleValues.Length]; - } - - Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAny(needle)); - Assert.Equal(1, haystackWithOffsetNeedle.IndexOfAnyExcept(needle)); - Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAny(needle)); - Assert.Equal(haystackWithOffsetNeedle.Length - 2, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle)); - - // With chars, the lower byte could be matching, but we have to check that the higher byte is also 0 - for (int i = 0; i < haystackWithOffsetNeedle.Length; i++) - { - haystackWithOffsetNeedle[i] = (char)(((i + 1) * 256) + needleValues[i % needleValues.Length]); - } - - Assert.Equal(-1, haystackWithOffsetNeedle.IndexOfAny(needle)); - Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAnyExcept(needle)); - Assert.Equal(-1, haystackWithOffsetNeedle.LastIndexOfAny(needle)); - Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle)); - - // Mix matching characters back in - for (int i = 0; i < haystackWithOffsetNeedle.Length; i += 3) - { - haystackWithOffsetNeedle[i] = needleValues[i % needleValues.Length]; - } - - Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAny(needle)); - Assert.Equal(1, haystackWithOffsetNeedle.IndexOfAnyExcept(needle)); - Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAny(needle)); - Assert.Equal(haystackWithOffsetNeedle.Length - 2, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle)); - } - private static int IndexOf(Span span, char value) { int index = span.IndexOf(value); @@ -893,115 +801,4 @@ private static int IndexOfAny(Span span, ReadOnlySpan values) return index; } } - - public static class IndexOfAnyCharTestHelper - { - private const int MaxNeedleLength = 10; - private const int MaxHaystackLength = 40; - - private static readonly char[] s_randomAsciiChars; - private static readonly char[] s_randomLatin1Chars; - private static readonly char[] s_randomChars; - private static readonly byte[] s_randomAsciiBytes; - private static readonly byte[] s_randomBytes; - - static IndexOfAnyCharTestHelper() - { - s_randomAsciiChars = new char[100 * 1024]; - s_randomLatin1Chars = new char[100 * 1024]; - s_randomChars = new char[1024 * 1024]; - s_randomBytes = new byte[100 * 1024]; - - var rng = new Random(42); - - for (int i = 0; i < s_randomAsciiChars.Length; i++) - { - s_randomAsciiChars[i] = (char)rng.Next(0, 128); - } - - for (int i = 0; i < s_randomLatin1Chars.Length; i++) - { - s_randomLatin1Chars[i] = (char)rng.Next(0, 256); - } - - rng.NextBytes(MemoryMarshal.Cast(s_randomChars)); - - s_randomAsciiBytes = Encoding.ASCII.GetBytes(s_randomAsciiChars); - - rng.NextBytes(s_randomBytes); - } - - public delegate int IndexOfAnySearchDelegate(ReadOnlySpan searchSpace, ReadOnlySpan values) where T : IEquatable?; - - public delegate int IndexOfAnyValuesSearchDelegate(ReadOnlySpan searchSpace, IndexOfAnyValues values) where T : IEquatable?; - - public static void TestRandomInputs(IndexOfAnySearchDelegate expected, IndexOfAnySearchDelegate indexOfAny, IndexOfAnyValuesSearchDelegate indexOfAnyValues) - { - var rng = new Random(42); - - for (int iterations = 0; iterations < 1_000_000; iterations++) - { - // There are more interesting corner cases with ASCII needles, test those more. - Test(rng, s_randomBytes, s_randomAsciiBytes, expected, indexOfAny, indexOfAnyValues); - - Test(rng, s_randomBytes, s_randomBytes, expected, indexOfAny, indexOfAnyValues); - } - } - - public static void TestRandomInputs(IndexOfAnySearchDelegate expected, IndexOfAnySearchDelegate indexOfAny, IndexOfAnyValuesSearchDelegate indexOfAnyValues) - { - var rng = new Random(42); - - for (int iterations = 0; iterations < 1_000_000; iterations++) - { - // There are more interesting corner cases with ASCII needles, test those more. - Test(rng, s_randomChars, s_randomAsciiChars, expected, indexOfAny, indexOfAnyValues); - - Test(rng, s_randomChars, s_randomLatin1Chars, expected, indexOfAny, indexOfAnyValues); - - Test(rng, s_randomChars, s_randomChars, expected, indexOfAny, indexOfAnyValues); - } - } - - private static void Test(Random rng, ReadOnlySpan haystackRandom, ReadOnlySpan needleRandom, - IndexOfAnySearchDelegate expected, IndexOfAnySearchDelegate indexOfAny, IndexOfAnyValuesSearchDelegate indexOfAnyValues) - where T : INumber - { - ReadOnlySpan haystack = GetRandomSlice(rng, haystackRandom, MaxHaystackLength); - ReadOnlySpan needle = GetRandomSlice(rng, needleRandom, MaxNeedleLength); - - IndexOfAnyValues indexOfAnyValuesInstance = (IndexOfAnyValues)(object)(typeof(T) == typeof(byte) - ? IndexOfAnyValues.Create(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(needle)), needle.Length)) - : IndexOfAnyValues.Create(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(needle)), needle.Length))); - - int expectedIndex = expected(haystack, needle); - int indexOfAnyIndex = indexOfAny(haystack, needle); - int indexOfAnyValuesIndex = indexOfAnyValues(haystack, indexOfAnyValuesInstance); - - if (expectedIndex != indexOfAnyIndex) - { - AssertionFailed(haystack, needle, expectedIndex, indexOfAnyIndex, nameof(indexOfAny)); - } - - if (expectedIndex != indexOfAnyValuesIndex) - { - AssertionFailed(haystack, needle, expectedIndex, indexOfAnyValuesIndex, nameof(indexOfAnyValues)); - } - } - - private static ReadOnlySpan GetRandomSlice(Random rng, ReadOnlySpan span, int maxLength) - { - ReadOnlySpan slice = span.Slice(rng.Next(span.Length + 1)); - return slice.Slice(0, Math.Min(slice.Length, rng.Next(maxLength + 1))); - } - - private static void AssertionFailed(ReadOnlySpan haystack, ReadOnlySpan needle, int expected, int actual, string approach) - where T : INumber - { - string readableHaystack = string.Join(", ", haystack.ToString().Select(c => int.CreateChecked(c))); - string readableNeedle = string.Join(", ", needle.ToString().Select(c => int.CreateChecked(c))); - - Assert.True(false, $"Expected {expected}, got {approach}={actual} for needle='{readableNeedle}', haystack='{readableHaystack}'"); - } - } } diff --git a/src/libraries/System.Memory/tests/Span/IndexOfAnyExcept.T.cs b/src/libraries/System.Memory/tests/Span/IndexOfAnyExcept.T.cs index 6a5b2dfca408a1..6d9d6fb3795e37 100644 --- a/src/libraries/System.Memory/tests/Span/IndexOfAnyExcept.T.cs +++ b/src/libraries/System.Memory/tests/Span/IndexOfAnyExcept.T.cs @@ -55,98 +55,6 @@ public void SearchingNulls(string[] input, string[] targets, int expected) break; } } - - [Fact] - [OuterLoop("Takes about a second to execute")] - public static void TestIndexOfAnyExcept_RandomInputs_Byte() - { - IndexOfAnyCharTestHelper.TestRandomInputs( - expected: IndexOfAnyExceptReferenceImpl, - indexOfAny: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values), - indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values)); - - static int IndexOfAnyExceptReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) - { - for (int i = 0; i < searchSpace.Length; i++) - { - if (!values.Contains(searchSpace[i])) - { - return i; - } - } - - return -1; - } - } - - [Fact] - [OuterLoop("Takes about a second to execute")] - public static void TestLastIndexOfAnyExcept_RandomInputs_Byte() - { - IndexOfAnyCharTestHelper.TestRandomInputs( - expected: LastIndexOfAnyExceptReferenceImpl, - indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values), - indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values)); - - static int LastIndexOfAnyExceptReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) - { - for (int i = searchSpace.Length - 1; i >= 0; i--) - { - if (!values.Contains(searchSpace[i])) - { - return i; - } - } - - return -1; - } - } - - [Fact] - [OuterLoop("Takes about a second to execute")] - public static void TestIndexOfAnyExcept_RandomInputs_Char() - { - IndexOfAnyCharTestHelper.TestRandomInputs( - expected: IndexOfAnyExceptReferenceImpl, - indexOfAny: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values), - indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values)); - - static int IndexOfAnyExceptReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) - { - for (int i = 0; i < searchSpace.Length; i++) - { - if (!values.Contains(searchSpace[i])) - { - return i; - } - } - - return -1; - } - } - - [Fact] - [OuterLoop("Takes about a second to execute")] - public static void TestLastIndexOfAnyExcept_RandomInputs_Char() - { - IndexOfAnyCharTestHelper.TestRandomInputs( - expected: LastIndexOfAnyExceptReferenceImpl, - indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values), - indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values)); - - static int LastIndexOfAnyExceptReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) - { - for (int i = searchSpace.Length - 1; i >= 0; i--) - { - if (!values.Contains(searchSpace[i])) - { - return i; - } - } - - return -1; - } - } } public record SimpleRecord(int Value); diff --git a/src/libraries/System.Memory/tests/Span/IndexOfAnyValues.cs b/src/libraries/System.Memory/tests/Span/IndexOfAnyValues.cs new file mode 100644 index 00000000000000..8a4266d76cb36d --- /dev/null +++ b/src/libraries/System.Memory/tests/Span/IndexOfAnyValues.cs @@ -0,0 +1,471 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using Xunit; + +namespace System.SpanTests +{ + public static partial class SpanTests + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public static void AsciiNeedle_ProperlyHandlesEdgeCases_Char(bool needleContainsZero) + { + // There is some special handling we have to do for ASCII needles to properly filter out non-ASCII results + ReadOnlySpan needleValues = needleContainsZero ? "AEIOU\0" : "AEIOU!"; + IndexOfAnyValues needle = IndexOfAnyValues.Create(needleValues); + + ReadOnlySpan repeatingHaystack = "AaAaAaAaAaAa"; + Assert.Equal(0, repeatingHaystack.IndexOfAny(needle)); + Assert.Equal(1, repeatingHaystack.IndexOfAnyExcept(needle)); + Assert.Equal(10, repeatingHaystack.LastIndexOfAny(needle)); + Assert.Equal(11, repeatingHaystack.LastIndexOfAnyExcept(needle)); + + ReadOnlySpan haystackWithZeroes = "Aa\0Aa\0Aa\0"; + Assert.Equal(0, haystackWithZeroes.IndexOfAny(needle)); + Assert.Equal(1, haystackWithZeroes.IndexOfAnyExcept(needle)); + Assert.Equal(needleContainsZero ? 8 : 6, haystackWithZeroes.LastIndexOfAny(needle)); + Assert.Equal(needleContainsZero ? 7 : 8, haystackWithZeroes.LastIndexOfAnyExcept(needle)); + + Span haystackWithOffsetNeedle = new char[100]; + for (int i = 0; i < haystackWithOffsetNeedle.Length; i++) + { + haystackWithOffsetNeedle[i] = (char)(128 + needleValues[i % needleValues.Length]); + } + + Assert.Equal(-1, haystackWithOffsetNeedle.IndexOfAny(needle)); + Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAnyExcept(needle)); + Assert.Equal(-1, haystackWithOffsetNeedle.LastIndexOfAny(needle)); + Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle)); + + // Mix matching characters back in + for (int i = 0; i < haystackWithOffsetNeedle.Length; i += 3) + { + haystackWithOffsetNeedle[i] = needleValues[i % needleValues.Length]; + } + + Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAny(needle)); + Assert.Equal(1, haystackWithOffsetNeedle.IndexOfAnyExcept(needle)); + Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAny(needle)); + Assert.Equal(haystackWithOffsetNeedle.Length - 2, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle)); + + // With chars, the lower byte could be matching, but we have to check that the higher byte is also 0 + for (int i = 0; i < haystackWithOffsetNeedle.Length; i++) + { + haystackWithOffsetNeedle[i] = (char)(((i + 1) * 256) + needleValues[i % needleValues.Length]); + } + + Assert.Equal(-1, haystackWithOffsetNeedle.IndexOfAny(needle)); + Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAnyExcept(needle)); + Assert.Equal(-1, haystackWithOffsetNeedle.LastIndexOfAny(needle)); + Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle)); + + // Mix matching characters back in + for (int i = 0; i < haystackWithOffsetNeedle.Length; i += 3) + { + haystackWithOffsetNeedle[i] = needleValues[i % needleValues.Length]; + } + + Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAny(needle)); + Assert.Equal(1, haystackWithOffsetNeedle.IndexOfAnyExcept(needle)); + Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAny(needle)); + Assert.Equal(haystackWithOffsetNeedle.Length - 2, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle)); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public static void AsciiNeedle_ProperlyHandlesEdgeCases_Byte(bool needleContainsZero) + { + // There is some special handling we have to do for ASCII needles to properly filter out non-ASCII results + ReadOnlySpan needleValues = needleContainsZero ? "AEIOU\0"u8 : "AEIOU!"u8; + IndexOfAnyValues needle = IndexOfAnyValues.Create(needleValues); + + ReadOnlySpan repeatingHaystack = "AaAaAaAaAaAa"u8; + Assert.Equal(0, repeatingHaystack.IndexOfAny(needle)); + Assert.Equal(1, repeatingHaystack.IndexOfAnyExcept(needle)); + Assert.Equal(10, repeatingHaystack.LastIndexOfAny(needle)); + Assert.Equal(11, repeatingHaystack.LastIndexOfAnyExcept(needle)); + + ReadOnlySpan haystackWithZeroes = "Aa\0Aa\0Aa\0"u8; + Assert.Equal(0, haystackWithZeroes.IndexOfAny(needle)); + Assert.Equal(1, haystackWithZeroes.IndexOfAnyExcept(needle)); + Assert.Equal(needleContainsZero ? 8 : 6, haystackWithZeroes.LastIndexOfAny(needle)); + Assert.Equal(needleContainsZero ? 7 : 8, haystackWithZeroes.LastIndexOfAnyExcept(needle)); + + Span haystackWithOffsetNeedle = new byte[100]; + for (int i = 0; i < haystackWithOffsetNeedle.Length; i++) + { + haystackWithOffsetNeedle[i] = (byte)(128 + needleValues[i % needleValues.Length]); + } + + Assert.Equal(-1, haystackWithOffsetNeedle.IndexOfAny(needle)); + Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAnyExcept(needle)); + Assert.Equal(-1, haystackWithOffsetNeedle.LastIndexOfAny(needle)); + Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle)); + + // Mix matching characters back in + for (int i = 0; i < haystackWithOffsetNeedle.Length; i += 3) + { + haystackWithOffsetNeedle[i] = needleValues[i % needleValues.Length]; + } + + Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAny(needle)); + Assert.Equal(1, haystackWithOffsetNeedle.IndexOfAnyExcept(needle)); + Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAny(needle)); + Assert.Equal(haystackWithOffsetNeedle.Length - 2, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle)); + } + + [Theory] + [InlineData("\0")] + [InlineData("a")] + [InlineData("ab")] + [InlineData("ac")] + [InlineData("abc")] + [InlineData("aei")] + [InlineData("abcd")] + [InlineData("aeio")] + [InlineData("aeiou")] + [InlineData("abceiou")] + [InlineData("123456789")] + [InlineData("123456789123")] + [InlineData("abcdefgh")] + [InlineData("abcdefghIJK")] + [InlineData("aa")] + [InlineData("aaa")] + [InlineData("aaaa")] + [InlineData("aaaaa")] + [InlineData("\u0000\u0001\u0002\u0003\u0004\u0005")] + [InlineData("\u0001\u0002\u0003\u0004\u0005\u0006")] + [InlineData("\u0001\u0002\u0003\u0004\u0005\u0007")] + [InlineData("\u007E\u007F\u0080\u0081\u0082\u0083")] + [InlineData("\u007E\u007F\u0080\u0081\u0082\u0084")] + [InlineData("\u007E\u007F\u0080\u0081\u0082")] + [InlineData("\u007E\u007F\u0080\u0081\u0083")] + [InlineData("\u00FE\u00FF\u0100\u0101\u0102\u0103")] + [InlineData("\u00FE\u00FF\u0100\u0101\u0102\u0104")] + [InlineData("\u00FE\u00FF\u0100\u0101\u0102")] + [InlineData("\u00FE\u00FF\u0100\u0101\u0103")] + [InlineData("\uFFFF\uFFFE\uFFFD\uFFFC\uFFFB\uFFFA")] + [InlineData("\uFFFF\uFFFE\uFFFD\uFFFC\uFFFB\uFFFB")] + [InlineData("\uFFFF\uFFFE\uFFFD\uFFFC\uFFFB\uFFF9")] + public static void IndexOfAnyValues_Contains(string needle) + { + Test(needle, IndexOfAnyValues.Create(needle)); + + byte[] needleBytes = Encoding.Latin1.GetBytes(needle); + Test(needleBytes, IndexOfAnyValues.Create(needleBytes)); + + static void Test(ReadOnlySpan needle, IndexOfAnyValues values) where T : struct, INumber, IMinMaxValue + { + for (int i = int.CreateChecked(T.MaxValue); i >= 0; i--) + { + T t = T.CreateChecked(i); + Assert.Equal(needle.Contains(t), values.Contains(t)); + } + } + } + + [Fact] + [OuterLoop("Takes about a second to execute")] + public static void TestIndexOfAny_RandomInputs_Byte() + { + IndexOfAnyValuesTestHelper.TestRandomInputs( + expected: IndexOfAnyReferenceImpl, + indexOfAny: (searchSpace, values) => searchSpace.IndexOfAny(values), + indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAny(values)); + + static int IndexOfAnyReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) + { + for (int i = 0; i < searchSpace.Length; i++) + { + if (values.Contains(searchSpace[i])) + { + return i; + } + } + + return -1; + } + } + + [Fact] + [OuterLoop("Takes about a second to execute")] + public static void TestIndexOfAny_RandomInputs_Char() + { + IndexOfAnyValuesTestHelper.TestRandomInputs( + expected: IndexOfAnyReferenceImpl, + indexOfAny: (searchSpace, values) => searchSpace.IndexOfAny(values), + indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAny(values)); + + static int IndexOfAnyReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) + { + for (int i = 0; i < searchSpace.Length; i++) + { + if (values.Contains(searchSpace[i])) + { + return i; + } + } + + return -1; + } + } + + [Fact] + [OuterLoop("Takes about a second to execute")] + public static void TestLastIndexOfAny_RandomInputs_Byte() + { + IndexOfAnyValuesTestHelper.TestRandomInputs( + expected: LastIndexOfAnyReferenceImpl, + indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAny(values), + indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAny(values)); + + static int LastIndexOfAnyReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) + { + for (int i = searchSpace.Length - 1; i >= 0; i--) + { + if (values.Contains(searchSpace[i])) + { + return i; + } + } + + return -1; + } + } + + [Fact] + [OuterLoop("Takes about a second to execute")] + public static void TestLastIndexOfAny_RandomInputs_Char() + { + IndexOfAnyValuesTestHelper.TestRandomInputs( + expected: LastIndexOfAnyReferenceImpl, + indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAny(values), + indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAny(values)); + + static int LastIndexOfAnyReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) + { + for (int i = searchSpace.Length - 1; i >= 0; i--) + { + if (values.Contains(searchSpace[i])) + { + return i; + } + } + + return -1; + } + } + + [Fact] + [OuterLoop("Takes about a second to execute")] + public static void TestIndexOfAnyExcept_RandomInputs_Byte() + { + IndexOfAnyValuesTestHelper.TestRandomInputs( + expected: IndexOfAnyExceptReferenceImpl, + indexOfAny: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values), + indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values)); + + static int IndexOfAnyExceptReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) + { + for (int i = 0; i < searchSpace.Length; i++) + { + if (!values.Contains(searchSpace[i])) + { + return i; + } + } + + return -1; + } + } + + [Fact] + [OuterLoop("Takes about a second to execute")] + public static void TestIndexOfAnyExcept_RandomInputs_Char() + { + IndexOfAnyValuesTestHelper.TestRandomInputs( + expected: IndexOfAnyExceptReferenceImpl, + indexOfAny: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values), + indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values)); + + static int IndexOfAnyExceptReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) + { + for (int i = 0; i < searchSpace.Length; i++) + { + if (!values.Contains(searchSpace[i])) + { + return i; + } + } + + return -1; + } + } + + [Fact] + [OuterLoop("Takes about a second to execute")] + public static void TestLastIndexOfAnyExcept_RandomInputs_Byte() + { + IndexOfAnyValuesTestHelper.TestRandomInputs( + expected: LastIndexOfAnyExceptReferenceImpl, + indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values), + indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values)); + + static int LastIndexOfAnyExceptReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) + { + for (int i = searchSpace.Length - 1; i >= 0; i--) + { + if (!values.Contains(searchSpace[i])) + { + return i; + } + } + + return -1; + } + } + + [Fact] + [OuterLoop("Takes about a second to execute")] + public static void TestLastIndexOfAnyExcept_RandomInputs_Char() + { + IndexOfAnyValuesTestHelper.TestRandomInputs( + expected: LastIndexOfAnyExceptReferenceImpl, + indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values), + indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values)); + + static int LastIndexOfAnyExceptReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) + { + for (int i = searchSpace.Length - 1; i >= 0; i--) + { + if (!values.Contains(searchSpace[i])) + { + return i; + } + } + + return -1; + } + } + + private static class IndexOfAnyValuesTestHelper + { + private const int MaxNeedleLength = 10; + private const int MaxHaystackLength = 40; + + private static readonly char[] s_randomAsciiChars; + private static readonly char[] s_randomLatin1Chars; + private static readonly char[] s_randomChars; + private static readonly byte[] s_randomAsciiBytes; + private static readonly byte[] s_randomBytes; + + static IndexOfAnyValuesTestHelper() + { + s_randomAsciiChars = new char[100 * 1024]; + s_randomLatin1Chars = new char[100 * 1024]; + s_randomChars = new char[1024 * 1024]; + s_randomBytes = new byte[100 * 1024]; + + var rng = new Random(42); + + for (int i = 0; i < s_randomAsciiChars.Length; i++) + { + s_randomAsciiChars[i] = (char)rng.Next(0, 128); + } + + for (int i = 0; i < s_randomLatin1Chars.Length; i++) + { + s_randomLatin1Chars[i] = (char)rng.Next(0, 256); + } + + rng.NextBytes(MemoryMarshal.Cast(s_randomChars)); + + s_randomAsciiBytes = Encoding.ASCII.GetBytes(s_randomAsciiChars); + + rng.NextBytes(s_randomBytes); + } + + public delegate int IndexOfAnySearchDelegate(ReadOnlySpan searchSpace, ReadOnlySpan values) where T : IEquatable?; + + public delegate int IndexOfAnyValuesSearchDelegate(ReadOnlySpan searchSpace, IndexOfAnyValues values) where T : IEquatable?; + + public static void TestRandomInputs(IndexOfAnySearchDelegate expected, IndexOfAnySearchDelegate indexOfAny, IndexOfAnyValuesSearchDelegate indexOfAnyValues) + { + var rng = new Random(42); + + for (int iterations = 0; iterations < 1_000_000; iterations++) + { + // There are more interesting corner cases with ASCII needles, test those more. + Test(rng, s_randomBytes, s_randomAsciiBytes, expected, indexOfAny, indexOfAnyValues); + + Test(rng, s_randomBytes, s_randomBytes, expected, indexOfAny, indexOfAnyValues); + } + } + + public static void TestRandomInputs(IndexOfAnySearchDelegate expected, IndexOfAnySearchDelegate indexOfAny, IndexOfAnyValuesSearchDelegate indexOfAnyValues) + { + var rng = new Random(42); + + for (int iterations = 0; iterations < 1_000_000; iterations++) + { + // There are more interesting corner cases with ASCII needles, test those more. + Test(rng, s_randomChars, s_randomAsciiChars, expected, indexOfAny, indexOfAnyValues); + + Test(rng, s_randomChars, s_randomLatin1Chars, expected, indexOfAny, indexOfAnyValues); + + Test(rng, s_randomChars, s_randomChars, expected, indexOfAny, indexOfAnyValues); + } + } + + private static void Test(Random rng, ReadOnlySpan haystackRandom, ReadOnlySpan needleRandom, + IndexOfAnySearchDelegate expected, IndexOfAnySearchDelegate indexOfAny, IndexOfAnyValuesSearchDelegate indexOfAnyValues) + where T : struct, INumber, IMinMaxValue + { + ReadOnlySpan haystack = GetRandomSlice(rng, haystackRandom, MaxHaystackLength); + ReadOnlySpan needle = GetRandomSlice(rng, needleRandom, MaxNeedleLength); + + IndexOfAnyValues indexOfAnyValuesInstance = (IndexOfAnyValues)(object)(typeof(T) == typeof(byte) + ? IndexOfAnyValues.Create(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(needle)), needle.Length)) + : IndexOfAnyValues.Create(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(needle)), needle.Length))); + + int expectedIndex = expected(haystack, needle); + int indexOfAnyIndex = indexOfAny(haystack, needle); + int indexOfAnyValuesIndex = indexOfAnyValues(haystack, indexOfAnyValuesInstance); + + if (expectedIndex != indexOfAnyIndex) + { + AssertionFailed(haystack, needle, expectedIndex, indexOfAnyIndex, nameof(indexOfAny)); + } + + if (expectedIndex != indexOfAnyValuesIndex) + { + AssertionFailed(haystack, needle, expectedIndex, indexOfAnyValuesIndex, nameof(indexOfAnyValues)); + } + } + + private static ReadOnlySpan GetRandomSlice(Random rng, ReadOnlySpan span, int maxLength) + { + ReadOnlySpan slice = span.Slice(rng.Next(span.Length + 1)); + return slice.Slice(0, Math.Min(slice.Length, rng.Next(maxLength + 1))); + } + + private static void AssertionFailed(ReadOnlySpan haystack, ReadOnlySpan needle, int expected, int actual, string approach) + where T : INumber + { + string readableHaystack = string.Join(", ", haystack.ToString().Select(c => int.CreateChecked(c))); + string readableNeedle = string.Join(", ", needle.ToString().Select(c => int.CreateChecked(c))); + + Assert.True(false, $"Expected {expected}, got {approach}={actual} for needle='{readableNeedle}', haystack='{readableHaystack}'"); + } + } + } +} diff --git a/src/libraries/System.Memory/tests/Span/LastIndexOfAny.T.cs b/src/libraries/System.Memory/tests/Span/LastIndexOfAny.T.cs index afc86a6080dde9..1ae582a1e00029 100644 --- a/src/libraries/System.Memory/tests/Span/LastIndexOfAny.T.cs +++ b/src/libraries/System.Memory/tests/Span/LastIndexOfAny.T.cs @@ -951,51 +951,5 @@ public static void LastIndexOfAnyNullSequence_String(string[] spanInput, string[ } } } - - [Fact] - [OuterLoop("Takes about a second to execute")] - public static void TestLastIndexOfAny_RandomInputs_Byte() - { - IndexOfAnyCharTestHelper.TestRandomInputs( - expected: LastIndexOfAnyReferenceImpl, - indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAny(values), - indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAny(values)); - - static int LastIndexOfAnyReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) - { - for (int i = searchSpace.Length - 1; i >= 0; i--) - { - if (values.Contains(searchSpace[i])) - { - return i; - } - } - - return -1; - } - } - - [Fact] - [OuterLoop("Takes about a second to execute")] - public static void TestLastIndexOfAny_RandomInputs_Char() - { - IndexOfAnyCharTestHelper.TestRandomInputs( - expected: LastIndexOfAnyReferenceImpl, - indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAny(values), - indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAny(values)); - - static int LastIndexOfAnyReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) - { - for (int i = searchSpace.Length - 1; i >= 0; i--) - { - if (values.Contains(searchSpace[i])) - { - return i; - } - } - - return -1; - } - } } } diff --git a/src/libraries/System.Memory/tests/System.Memory.Tests.csproj b/src/libraries/System.Memory/tests/System.Memory.Tests.csproj index e7758562dcb5b5..5179cd1556c7c9 100644 --- a/src/libraries/System.Memory/tests/System.Memory.Tests.csproj +++ b/src/libraries/System.Memory/tests/System.Memory.Tests.csproj @@ -87,6 +87,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny1Value.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny1Value.cs index 0be9ac6f97d9fa..565af9cf42c70c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny1Value.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny1Value.cs @@ -2,12 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Numerics; using System.Runtime.CompilerServices; namespace System.Buffers { internal sealed class IndexOfAny1Value : IndexOfAnyValues - where T : struct, IEquatable + where T : struct, INumber { private readonly T _e0; @@ -19,6 +20,10 @@ public IndexOfAny1Value(ReadOnlySpan values) internal override T[] GetValues() => new[] { _e0 }; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override bool ContainsCore(T value) => + value == _e0; + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal override int IndexOfAny(ReadOnlySpan span) => span.IndexOf(_e0); diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny2Values.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny2Values.cs index bab3007aa27a0d..cb89fcfbc497e6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny2Values.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny2Values.cs @@ -2,12 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Numerics; using System.Runtime.CompilerServices; namespace System.Buffers { internal sealed class IndexOfAny2Values : IndexOfAnyValues - where T : struct, IEquatable + where T : struct, INumber { private readonly T _e0, _e1; @@ -19,6 +20,10 @@ public IndexOfAny2Values(ReadOnlySpan values) internal override T[] GetValues() => new[] { _e0, _e1 }; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override bool ContainsCore(T value) => + value == _e0 || value == _e1; + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal override int IndexOfAny(ReadOnlySpan span) => span.IndexOfAny(_e0, _e1); diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny3Values.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny3Values.cs index 933d87b1f7fa0b..f94509b19f6d6a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny3Values.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny3Values.cs @@ -2,12 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Numerics; using System.Runtime.CompilerServices; namespace System.Buffers { internal sealed class IndexOfAny3Values : IndexOfAnyValues - where T : struct, IEquatable + where T : struct, INumber { private readonly T _e0, _e1, _e2; @@ -19,6 +20,10 @@ public IndexOfAny3Values(ReadOnlySpan values) internal override T[] GetValues() => new[] { _e0, _e1, _e2 }; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override bool ContainsCore(T value) => + value == _e0 || value == _e1 || value == _e2; + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal override int IndexOfAny(ReadOnlySpan span) => span.IndexOfAny(_e0, _e1, _e2); diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny4Values.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny4Values.cs index 0d59ca9f33c759..8af163e768d9de 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny4Values.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny4Values.cs @@ -27,6 +27,13 @@ internal override T[] GetValues() return new[] { Unsafe.As(ref e0), Unsafe.As(ref e1), Unsafe.As(ref e2), Unsafe.As(ref e3) }; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override bool ContainsCore(T value) => + Unsafe.As(ref value) == _e0 || + Unsafe.As(ref value) == _e1 || + Unsafe.As(ref value) == _e2 || + Unsafe.As(ref value) == _e3; + #if MONO // Revert this once https://github.com/dotnet/runtime/pull/78015 is merged internal override int IndexOfAny(ReadOnlySpan span) => span.IndexOfAny(GetValues()); diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny5Values.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny5Values.cs index 5372bd0f1118af..e7b6d09b30a080 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny5Values.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny5Values.cs @@ -27,6 +27,14 @@ internal override T[] GetValues() return new[] { Unsafe.As(ref e0), Unsafe.As(ref e1), Unsafe.As(ref e2), Unsafe.As(ref e3), Unsafe.As(ref e4) }; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override bool ContainsCore(T value) => + Unsafe.As(ref value) == _e0 || + Unsafe.As(ref value) == _e1 || + Unsafe.As(ref value) == _e2 || + Unsafe.As(ref value) == _e3 || + Unsafe.As(ref value) == _e4; + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal override int IndexOfAny(ReadOnlySpan span) => SpanHelpers.IndexOfAnyValueType(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), _e0, _e1, _e2, _e3, _e4, span.Length); diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyAsciiByteValues.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyAsciiByteValues.cs index ac8815ad7660f1..3a214b47d6f781 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyAsciiByteValues.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyAsciiByteValues.cs @@ -21,6 +21,10 @@ public IndexOfAnyAsciiByteValues(Vector128 bitmap, BitVector256 lookup) internal override byte[] GetValues() => _lookup.GetByteValues(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override bool ContainsCore(byte value) => + _lookup.Contains(value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal override int IndexOfAny(ReadOnlySpan span) => IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyAsciiCharValues.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyAsciiCharValues.cs index 671878260c3129..0958f6137aba51 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyAsciiCharValues.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyAsciiCharValues.cs @@ -21,6 +21,10 @@ public IndexOfAnyAsciiCharValues(Vector128 bitmap, BitVector256 lookup) internal override char[] GetValues() => _lookup.GetCharValues(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override bool ContainsCore(char value) => + _lookup.Contains128(value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal override int IndexOfAny(ReadOnlySpan span) => IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyByteValues.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyByteValues.cs index 9dc76d47096719..ffe62195d58678 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyByteValues.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyByteValues.cs @@ -18,6 +18,10 @@ public IndexOfAnyByteValues(ReadOnlySpan values) => internal override byte[] GetValues() => _lookup.GetByteValues(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override bool ContainsCore(byte value) => + _lookup.Contains(value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal override int IndexOfAny(ReadOnlySpan span) => IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyCharValuesProbabilistic.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyCharValuesProbabilistic.cs index d8de8c016d6d7f..0838d480dbeb13 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyCharValuesProbabilistic.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyCharValuesProbabilistic.cs @@ -19,6 +19,10 @@ public unsafe IndexOfAnyCharValuesProbabilistic(ReadOnlySpan values) internal override char[] GetValues() => _values.ToCharArray(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override bool ContainsCore(char value) => + ProbabilisticMap.Contains(ref Unsafe.As(ref _map), _values, value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal override int IndexOfAny(ReadOnlySpan span) => IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyLatin1CharValues.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyLatin1CharValues.cs index e48c000c67b25b..5898493939370b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyLatin1CharValues.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyLatin1CharValues.cs @@ -26,6 +26,10 @@ public IndexOfAnyLatin1CharValues(ReadOnlySpan values) internal override char[] GetValues() => _lookup.GetCharValues(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override bool ContainsCore(char value) => + _lookup.Contains256(value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal override int IndexOfAny(ReadOnlySpan span) => IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValues.T.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValues.T.cs index c96af294e4998c..1873ac4eb107a3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValues.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValues.T.cs @@ -23,6 +23,15 @@ private protected IndexOfAnyValues() { } /// Used by . internal virtual T[] GetValues() => throw new UnreachableException(); + /// + /// Searches for the specified value and returns true if found. If not found, returns false. + /// + /// The value to search for. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T value) => ContainsCore(value); + + internal virtual bool ContainsCore(T value) => throw new UnreachableException(); + internal virtual int IndexOfAny(ReadOnlySpan span) => throw new UnreachableException(); internal virtual int IndexOfAnyExcept(ReadOnlySpan span) => throw new UnreachableException(); internal virtual int LastIndexOfAny(ReadOnlySpan span) => throw new UnreachableException(); diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValuesInRange.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValuesInRange.cs index 19c5c9a40e209d..c9c4f1a1f499a1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValuesInRange.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValuesInRange.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; @@ -10,13 +11,19 @@ internal sealed class IndexOfAnyValuesInRange : IndexOfAnyValues where T : struct, INumber { private readonly T _lowInclusive, _highInclusive; + private readonly uint _lowUint, _highMinusLow; - public IndexOfAnyValuesInRange(T lowInclusive, T highInclusive) => + public IndexOfAnyValuesInRange(T lowInclusive, T highInclusive) + { + Debug.Assert(lowInclusive is byte or char); (_lowInclusive, _highInclusive) = (lowInclusive, highInclusive); + _lowUint = uint.CreateChecked(lowInclusive); + _highMinusLow = uint.CreateChecked(highInclusive - lowInclusive); + } internal override T[] GetValues() { - T[] values = new T[int.CreateChecked(_highInclusive - _lowInclusive)]; + T[] values = new T[_highMinusLow + 1]; T element = _lowInclusive; for (int i = 0; i < values.Length; i++) @@ -28,6 +35,10 @@ internal override T[] GetValues() return values; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override bool ContainsCore(T value) => + uint.CreateChecked(value) - _lowUint <= _highMinusLow; + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal override int IndexOfAny(ReadOnlySpan span) => span.IndexOfAnyInRange(_lowInclusive, _highInclusive); diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfEmptyValues.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfEmptyValues.cs index 91f50a12a5fa8c..d00069c6ef61d4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfEmptyValues.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfEmptyValues.cs @@ -6,7 +6,11 @@ namespace System.Buffers internal sealed class IndexOfEmptyValues : IndexOfAnyValues where T : IEquatable? { - internal override T[] GetValues() => Array.Empty(); + internal override T[] GetValues() => + Array.Empty(); + + internal override bool ContainsCore(T value) => + false; internal override int IndexOfAny(ReadOnlySpan span) => -1; diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 80966d446535bf..d918bc00cff3c7 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -7055,6 +7055,7 @@ public partial interface IMemoryOwner : System.IDisposable public class IndexOfAnyValues where T : System.IEquatable? { internal IndexOfAnyValues() { } + public bool Contains(T value) { throw null; } } public static class IndexOfAnyValues { From 013fa36d3d13c9eaed8053159eba2fa31d934aa0 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Wed, 30 Nov 2022 01:19:38 +0100 Subject: [PATCH 2/2] Unsafe => unsafe --- .../IndexOfAnyValues/IndexOfAny4Values.cs | 16 +++++++++------- .../IndexOfAnyValues/IndexOfAny5Values.cs | 18 ++++++++++-------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny4Values.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny4Values.cs index 8af163e768d9de..1ba4820319e700 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny4Values.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny4Values.cs @@ -6,6 +6,8 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#pragma warning disable 8500 // address of managed types + namespace System.Buffers { internal sealed class IndexOfAny4Values : IndexOfAnyValues @@ -21,18 +23,18 @@ public IndexOfAny4Values(ReadOnlySpan values) (_e0, _e1, _e2, _e3) = (values[0], values[1], values[2], values[3]); } - internal override T[] GetValues() + internal override unsafe T[] GetValues() { TImpl e0 = _e0, e1 = _e1, e2 = _e2, e3 = _e3; - return new[] { Unsafe.As(ref e0), Unsafe.As(ref e1), Unsafe.As(ref e2), Unsafe.As(ref e3) }; + return new[] { *(T*)&e0, *(T*)&e1, *(T*)&e2, *(T*)&e3 }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal override bool ContainsCore(T value) => - Unsafe.As(ref value) == _e0 || - Unsafe.As(ref value) == _e1 || - Unsafe.As(ref value) == _e2 || - Unsafe.As(ref value) == _e3; + internal override unsafe bool ContainsCore(T value) => + *(TImpl*)&value == _e0 || + *(TImpl*)&value == _e1 || + *(TImpl*)&value == _e2 || + *(TImpl*)&value == _e3; #if MONO // Revert this once https://github.com/dotnet/runtime/pull/78015 is merged internal override int IndexOfAny(ReadOnlySpan span) => diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny5Values.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny5Values.cs index e7b6d09b30a080..55660a22b99f12 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny5Values.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny5Values.cs @@ -6,6 +6,8 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#pragma warning disable 8500 // address of managed types + namespace System.Buffers { internal sealed class IndexOfAny5Values : IndexOfAnyValues @@ -21,19 +23,19 @@ public IndexOfAny5Values(ReadOnlySpan values) (_e0, _e1, _e2, _e3, _e4) = (values[0], values[1], values[2], values[3], values[4]); } - internal override T[] GetValues() + internal override unsafe T[] GetValues() { TImpl e0 = _e0, e1 = _e1, e2 = _e2, e3 = _e3, e4 = _e4; - return new[] { Unsafe.As(ref e0), Unsafe.As(ref e1), Unsafe.As(ref e2), Unsafe.As(ref e3), Unsafe.As(ref e4) }; + return new[] { *(T*)&e0, *(T*)&e1, *(T*)&e2, *(T*)&e3, *(T*)&e4 }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal override bool ContainsCore(T value) => - Unsafe.As(ref value) == _e0 || - Unsafe.As(ref value) == _e1 || - Unsafe.As(ref value) == _e2 || - Unsafe.As(ref value) == _e3 || - Unsafe.As(ref value) == _e4; + internal override unsafe bool ContainsCore(T value) => + *(TImpl*)&value == _e0 || + *(TImpl*)&value == _e1 || + *(TImpl*)&value == _e2 || + *(TImpl*)&value == _e3 || + *(TImpl*)&value == _e4; [MethodImpl(MethodImplOptions.AggressiveInlining)] internal override int IndexOfAny(ReadOnlySpan span) =>