diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs index 6c8f777bcb2ad..c67966a60604e 100644 --- a/src/libraries/System.Memory/ref/System.Memory.cs +++ b/src/libraries/System.Memory/ref/System.Memory.cs @@ -243,19 +243,23 @@ public static void CopyTo(this T[]? source, System.Span destination) { } public static System.Text.SpanRuneEnumerator EnumerateRunes(this System.Span span) { throw null; } public static bool Equals(this System.ReadOnlySpan span, System.ReadOnlySpan other, System.StringComparison comparisonType) { throw null; } public static int IndexOf(this System.ReadOnlySpan span, System.ReadOnlySpan value, System.StringComparison comparisonType) { throw null; } + public static int IndexOfAny(this System.ReadOnlySpan span, System.Buffers.IndexOfAnyValues values) where T : System.IEquatable? { throw null; } public static int IndexOfAny(this System.ReadOnlySpan span, System.ReadOnlySpan values) where T : System.IEquatable? { throw null; } public static int IndexOfAny(this System.ReadOnlySpan span, T value0, T value1) where T : System.IEquatable? { throw null; } public static int IndexOfAny(this System.ReadOnlySpan span, T value0, T value1, T value2) where T : System.IEquatable? { throw null; } + public static int IndexOfAny(this System.Span span, System.Buffers.IndexOfAnyValues values) where T : System.IEquatable? { throw null; } public static int IndexOfAny(this System.Span span, System.ReadOnlySpan values) where T : System.IEquatable? { throw null; } public static int IndexOfAny(this System.Span span, T value0, T value1) where T : System.IEquatable? { throw null; } public static int IndexOfAny(this System.Span span, T value0, T value1, T value2) where T : System.IEquatable? { throw null; } public static int IndexOfAnyExcept(this System.Span span, T value) where T : System.IEquatable? { throw null; } public static int IndexOfAnyExcept(this System.Span span, T value0, T value1) where T : System.IEquatable? { throw null; } public static int IndexOfAnyExcept(this System.Span span, T value0, T value1, T value2) where T : System.IEquatable? { throw null; } + public static int IndexOfAnyExcept(this System.Span span, System.Buffers.IndexOfAnyValues values) where T : System.IEquatable? { throw null; } public static int IndexOfAnyExcept(this System.Span span, System.ReadOnlySpan values) where T : System.IEquatable? { throw null; } public static int IndexOfAnyExcept(this System.ReadOnlySpan span, T value) where T : System.IEquatable? { throw null; } public static int IndexOfAnyExcept(this System.ReadOnlySpan span, T value0, T value1) where T : System.IEquatable? { throw null; } public static int IndexOfAnyExcept(this System.ReadOnlySpan span, T value0, T value1, T value2) where T : System.IEquatable? { throw null; } + public static int IndexOfAnyExcept(this System.ReadOnlySpan span, System.Buffers.IndexOfAnyValues values) where T : System.IEquatable? { throw null; } public static int IndexOfAnyExcept(this System.ReadOnlySpan span, System.ReadOnlySpan values) where T : System.IEquatable? { throw null; } public static int IndexOfAnyExceptInRange(this System.ReadOnlySpan span, T lowInclusive, T highInclusive) where T : System.IComparable { throw null; } public static int IndexOfAnyExceptInRange(this System.Span span, T lowInclusive, T highInclusive) where T : System.IComparable { throw null; } @@ -267,19 +271,23 @@ public static void CopyTo(this T[]? source, System.Span destination) { } public static int IndexOfAnyInRange(this System.Span span, T lowInclusive, T highInclusive) where T : System.IComparable { throw null; } public static bool IsWhiteSpace(this System.ReadOnlySpan span) { throw null; } public static int LastIndexOf(this System.ReadOnlySpan span, System.ReadOnlySpan value, System.StringComparison comparisonType) { throw null; } + public static int LastIndexOfAny(this System.ReadOnlySpan span, System.Buffers.IndexOfAnyValues values) where T : System.IEquatable? { throw null; } public static int LastIndexOfAny(this System.ReadOnlySpan span, System.ReadOnlySpan values) where T : System.IEquatable? { throw null; } public static int LastIndexOfAny(this System.ReadOnlySpan span, T value0, T value1) where T : System.IEquatable? { throw null; } public static int LastIndexOfAny(this System.ReadOnlySpan span, T value0, T value1, T value2) where T : System.IEquatable? { throw null; } + public static int LastIndexOfAny(this System.Span span, System.Buffers.IndexOfAnyValues values) where T : System.IEquatable? { throw null; } public static int LastIndexOfAny(this System.Span span, System.ReadOnlySpan values) where T : System.IEquatable? { throw null; } public static int LastIndexOfAny(this System.Span span, T value0, T value1) where T : System.IEquatable? { throw null; } public static int LastIndexOfAny(this System.Span span, T value0, T value1, T value2) where T : System.IEquatable? { throw null; } public static int LastIndexOfAnyExcept(this System.Span span, T value) where T : System.IEquatable? { throw null; } public static int LastIndexOfAnyExcept(this System.Span span, T value0, T value1) where T : System.IEquatable? { throw null; } public static int LastIndexOfAnyExcept(this System.Span span, T value0, T value1, T value2) where T : System.IEquatable? { throw null; } + public static int LastIndexOfAnyExcept(this System.Span span, System.Buffers.IndexOfAnyValues values) where T : System.IEquatable? { throw null; } public static int LastIndexOfAnyExcept(this System.Span span, System.ReadOnlySpan values) where T : System.IEquatable? { throw null; } public static int LastIndexOfAnyExcept(this System.ReadOnlySpan span, T value) where T : System.IEquatable? { throw null; } public static int LastIndexOfAnyExcept(this System.ReadOnlySpan span, T value0, T value1) where T : System.IEquatable? { throw null; } public static int LastIndexOfAnyExcept(this System.ReadOnlySpan span, T value0, T value1, T value2) where T : System.IEquatable? { throw null; } + public static int LastIndexOfAnyExcept(this System.ReadOnlySpan span, System.Buffers.IndexOfAnyValues values) where T : System.IEquatable? { throw null; } public static int LastIndexOfAnyExcept(this System.ReadOnlySpan span, System.ReadOnlySpan values) where T : System.IEquatable? { throw null; } public static int LastIndexOfAnyExceptInRange(this System.ReadOnlySpan span, T lowInclusive, T highInclusive) where T : System.IComparable { throw null; } public static int LastIndexOfAnyExceptInRange(this System.Span span, T lowInclusive, T highInclusive) where T : System.IComparable { throw null; } diff --git a/src/libraries/System.Memory/tests/Span/IndexOfAny.byte.cs b/src/libraries/System.Memory/tests/Span/IndexOfAny.byte.cs index d29735101d476..5690c54941ba4 100644 --- a/src/libraries/System.Memory/tests/Span/IndexOfAny.byte.cs +++ b/src/libraries/System.Memory/tests/Span/IndexOfAny.byte.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.Buffers; using System.Text; using Xunit; @@ -35,18 +36,18 @@ public static void IndexOfAnyStrings_Byte(string raw, string search, char expect char[] searchFor = search.ToCharArray(); byte[] searchForBytes = Encoding.UTF8.GetBytes(searchFor); - var index = span.IndexOfAny(new ReadOnlySpan(searchForBytes)); + var index = IndexOfAny(span, new ReadOnlySpan(searchForBytes)); if (searchFor.Length == 1) { - Assert.Equal(index, span.IndexOf((byte)searchFor[0])); + Assert.Equal(index, IndexOf(span, (byte)searchFor[0])); } else if (searchFor.Length == 2) { - Assert.Equal(index, span.IndexOfAny((byte)searchFor[0], (byte)searchFor[1])); + Assert.Equal(index, IndexOfAny(span, (byte)searchFor[0], (byte)searchFor[1])); } else if (searchFor.Length == 3) { - Assert.Equal(index, span.IndexOfAny((byte)searchFor[0], (byte)searchFor[1], (byte)searchFor[2])); + Assert.Equal(index, IndexOfAny(span, (byte)searchFor[0], (byte)searchFor[1], (byte)searchFor[2])); } var found = span[index]; @@ -59,8 +60,8 @@ public static void ZeroLengthIndexOfTwo_Byte() { Span span = new Span(Array.Empty()); - Assert.Equal(-1, span.IndexOfAny(0, 0)); - Assert.Equal(-1, span.IndexOfAny(new byte[2])); + Assert.Equal(-1, IndexOfAny(span, 0, 0)); + Assert.Equal(-1, IndexOfAny(span, new byte[2])); } [Fact] @@ -81,8 +82,8 @@ public static void DefaultFilledIndexOfTwo_Byte() byte target0 = targets[index]; byte target1 = targets[(index + 1) % 2]; - Assert.Equal(0, span.IndexOfAny(target0, target1)); - Assert.Equal(0, span.IndexOfAny(new[] { target0, target1 })); + Assert.Equal(0, IndexOfAny(span, target0, target1)); + Assert.Equal(0, IndexOfAny(span, new[] { target0, target1 })); } } } @@ -104,8 +105,8 @@ public static void TestMatchTwo_Byte() byte target0 = a[targetIndex]; byte target1 = 0; - Assert.Equal(targetIndex, span.IndexOfAny(target0, target1)); - Assert.Equal(targetIndex, span.IndexOfAny(new[] { target0, target1 })); + Assert.Equal(targetIndex, IndexOfAny(span, target0, target1)); + Assert.Equal(targetIndex, IndexOfAny(span, new[] { target0, target1 })); } for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) @@ -113,8 +114,8 @@ public static void TestMatchTwo_Byte() byte target0 = a[targetIndex]; byte target1 = a[targetIndex + 1]; - Assert.Equal(targetIndex, span.IndexOfAny(target0, target1)); - Assert.Equal(targetIndex, span.IndexOfAny(new[] { target0, target1 })); + Assert.Equal(targetIndex, IndexOfAny(span, target0, target1)); + Assert.Equal(targetIndex, IndexOfAny(span, new[] { target0, target1 })); } for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) @@ -122,8 +123,8 @@ public static void TestMatchTwo_Byte() byte target0 = 0; byte target1 = a[targetIndex + 1]; - Assert.Equal(targetIndex + 1, span.IndexOfAny(target0, target1)); - Assert.Equal(targetIndex + 1, span.IndexOfAny(new[] { target0, target1 })); + Assert.Equal(targetIndex + 1, IndexOfAny(span, target0, target1)); + Assert.Equal(targetIndex + 1, IndexOfAny(span, new[] { target0, target1 })); } } } @@ -139,8 +140,8 @@ public static void TestNoMatchTwo_Byte() byte target1 = (byte)rnd.Next(1, 256); Span span = new Span(a); - Assert.Equal(-1, span.IndexOfAny(target0, target1)); - Assert.Equal(-1, span.IndexOfAny(new[] { target0, target1 })); + Assert.Equal(-1, IndexOfAny(span, target0, target1)); + Assert.Equal(-1, IndexOfAny(span, new[] { target0, target1 })); } } @@ -162,8 +163,8 @@ public static void TestMultipleMatchTwo_Byte() Span span = new Span(a); - Assert.Equal(length - 3, span.IndexOfAny(200, 200)); - Assert.Equal(length - 3, span.IndexOfAny(new byte[] { 200, 200 })); + Assert.Equal(length - 3, IndexOfAny(span, 200, 200)); + Assert.Equal(length - 3, IndexOfAny(span, new byte[] { 200, 200 })); } } @@ -177,8 +178,8 @@ public static void MakeSureNoChecksGoOutOfRangeTwo_Byte() a[length + 1] = 98; Span span = new Span(a, 1, length - 1); - Assert.Equal(-1, span.IndexOfAny(99, 98)); - Assert.Equal(-1, span.IndexOfAny(new byte[] { 99, 98 })); + Assert.Equal(-1, IndexOfAny(span, 99, 98)); + Assert.Equal(-1, IndexOfAny(span, new byte[] { 99, 98 })); } for (int length = 1; length < byte.MaxValue; length++) @@ -188,8 +189,8 @@ public static void MakeSureNoChecksGoOutOfRangeTwo_Byte() a[length + 1] = 99; Span span = new Span(a, 1, length - 1); - Assert.Equal(-1, span.IndexOfAny(99, 99)); - Assert.Equal(-1, span.IndexOfAny(new byte[] { 99, 99 })); + Assert.Equal(-1, IndexOfAny(span, 99, 99)); + Assert.Equal(-1, IndexOfAny(span, new byte[] { 99, 99 })); } } @@ -198,8 +199,8 @@ public static void ZeroLengthIndexOfThree_Byte() { Span span = new Span(Array.Empty()); - Assert.Equal(-1, span.IndexOfAny(0, 0, 0)); - Assert.Equal(-1, span.IndexOfAny(new byte[3])); + Assert.Equal(-1, IndexOfAny(span, 0, 0, 0)); + Assert.Equal(-1, IndexOfAny(span, new byte[3])); } [Fact] @@ -221,8 +222,8 @@ public static void DefaultFilledIndexOfThree_Byte() byte target1 = targets[(index + 1) % 2]; byte target2 = targets[(index + 1) % 3]; - Assert.Equal(0, span.IndexOfAny(target0, target1, target2)); - Assert.Equal(0, span.IndexOfAny(new[] { target0, target1, target2 })); + Assert.Equal(0, IndexOfAny(span, target0, target1, target2)); + Assert.Equal(0, IndexOfAny(span, new[] { target0, target1, target2 })); } } } @@ -245,8 +246,8 @@ public static void TestMatchThree_Byte() byte target1 = 0; byte target2 = 0; - Assert.Equal(targetIndex, span.IndexOfAny(target0, target1, target2)); - Assert.Equal(targetIndex, span.IndexOfAny(new[] { target0, target1, target2 })); + Assert.Equal(targetIndex, IndexOfAny(span, target0, target1, target2)); + Assert.Equal(targetIndex, IndexOfAny(span, new[] { target0, target1, target2 })); } for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) @@ -255,8 +256,8 @@ public static void TestMatchThree_Byte() byte target1 = a[targetIndex + 1]; byte target2 = a[targetIndex + 2]; - Assert.Equal(targetIndex, span.IndexOfAny(target0, target1, target2)); - Assert.Equal(targetIndex, span.IndexOfAny(new[] { target0, target1, target2 })); + Assert.Equal(targetIndex, IndexOfAny(span, target0, target1, target2)); + Assert.Equal(targetIndex, IndexOfAny(span, new[] { target0, target1, target2 })); } for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) @@ -265,8 +266,8 @@ public static void TestMatchThree_Byte() byte target1 = 0; byte target2 = a[targetIndex + 2]; - Assert.Equal(targetIndex + 2, span.IndexOfAny(target0, target1, target2)); - Assert.Equal(targetIndex + 2, span.IndexOfAny(new[] { target0, target1, target2 })); + Assert.Equal(targetIndex + 2, IndexOfAny(span, target0, target1, target2)); + Assert.Equal(targetIndex + 2, IndexOfAny(span, new[] { target0, target1, target2 })); } } } @@ -283,8 +284,8 @@ public static void TestNoMatchThree_Byte() byte target2 = (byte)rnd.Next(1, 256); Span span = new Span(a); - Assert.Equal(-1, span.IndexOfAny(target0, target1, target2)); - Assert.Equal(-1, span.IndexOfAny(new[] { target0, target1, target2 })); + Assert.Equal(-1, IndexOfAny(span, target0, target1, target2)); + Assert.Equal(-1, IndexOfAny(span, new[] { target0, target1, target2 })); } } @@ -307,8 +308,8 @@ public static void TestMultipleMatchThree_Byte() Span span = new Span(a); - Assert.Equal(length - 4, span.IndexOfAny(200, 200, 200)); - Assert.Equal(length - 4, span.IndexOfAny(new byte[] { 200, 200, 200 })); + Assert.Equal(length - 4, IndexOfAny(span, 200, 200, 200)); + Assert.Equal(length - 4, IndexOfAny(span, new byte[] { 200, 200, 200 })); } } @@ -322,8 +323,8 @@ public static void MakeSureNoChecksGoOutOfRangeThree_Byte() a[length + 1] = 98; Span span = new Span(a, 1, length - 1); - Assert.Equal(-1, span.IndexOfAny(99, 98, 99)); - Assert.Equal(-1, span.IndexOfAny(new byte[] { 99, 98, 99 })); + Assert.Equal(-1, IndexOfAny(span, 99, 98, 99)); + Assert.Equal(-1, IndexOfAny(span, new byte[] { 99, 98, 99 })); } for (int length = 1; length < byte.MaxValue; length++) @@ -333,21 +334,21 @@ public static void MakeSureNoChecksGoOutOfRangeThree_Byte() a[length + 1] = 99; Span span = new Span(a, 1, length - 1); - Assert.Equal(-1, span.IndexOfAny(99, 99, 99)); - Assert.Equal(-1, span.IndexOfAny(new byte[] { 99, 99, 99 })); + Assert.Equal(-1, IndexOfAny(span, 99, 99, 99)); + Assert.Equal(-1, IndexOfAny(span, new byte[] { 99, 99, 99 })); } } [Fact] public static void ZeroLengthIndexOfMany_Byte() { - Span sp = new Span(Array.Empty()); + Span span = new Span(Array.Empty()); var values = new ReadOnlySpan(new byte[] { 0, 0, 0, 0 }); - int idx = sp.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(-1, idx); values = new ReadOnlySpan(new byte[] { }); - idx = sp.IndexOfAny(values); + idx = IndexOfAny(span, values); Assert.Equal(-1, idx); } @@ -363,7 +364,7 @@ public static void DefaultFilledIndexOfMany_Byte() for (int i = 0; i < length; i++) { - int idx = span.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(0, idx); } } @@ -384,21 +385,21 @@ public static void TestMatchMany_Byte() for (int targetIndex = 0; targetIndex < length; targetIndex++) { var values = new ReadOnlySpan(new byte[] { a[targetIndex], 0, 0, 0 }); - int idx = span.IndexOfAny(values); + int idx = IndexOfAny(span, 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); + int idx = IndexOfAny(span, 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); + int idx = IndexOfAny(span, values); Assert.Equal(targetIndex + 3, idx); } } @@ -433,7 +434,7 @@ public static void TestMatchValuesLargerMany_Byte() } var values = new ReadOnlySpan(targets); - int idx = span.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(expectedIndex, idx); } } @@ -453,7 +454,7 @@ public static void TestNoMatchMany_Byte() Span span = new Span(a); var values = new ReadOnlySpan(targets); - int idx = span.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(-1, idx); } } @@ -473,7 +474,7 @@ public static void TestNoMatchValuesLargerMany_Byte() Span span = new Span(a); var values = new ReadOnlySpan(targets); - int idx = span.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(-1, idx); } } @@ -498,7 +499,7 @@ public static void TestMultipleMatchMany_Byte() 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); + int idx = IndexOfAny(span, values); Assert.Equal(length - 5, idx); } } @@ -513,7 +514,7 @@ public static void MakeSureNoChecksGoOutOfRangeMany_Byte() 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); + int index = IndexOfAny(span, values); Assert.Equal(-1, index); } @@ -524,9 +525,60 @@ public static void MakeSureNoChecksGoOutOfRangeMany_Byte() 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); + int index = IndexOfAny(span, values); Assert.Equal(-1, index); } } + + [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; + } + } + + private static int IndexOf(Span span, byte value) + { + int index = span.IndexOf(value); + Assert.Equal(index, span.IndexOfAny(IndexOfAnyValues.Create(stackalloc byte[] { value }))); + return index; + } + + private static int IndexOfAny(Span span, byte value0, byte value1) + { + int index = span.IndexOfAny(value0, value1); + Assert.Equal(index, span.IndexOfAny(IndexOfAnyValues.Create(stackalloc byte[] { value0, value1 }))); + return index; + } + + private static int IndexOfAny(Span span, byte value0, byte value1, byte value2) + { + int index = span.IndexOfAny(value0, value1, value2); + Assert.Equal(index, span.IndexOfAny(IndexOfAnyValues.Create(stackalloc byte[] { value0, value1, value2 }))); + return index; + } + + private static int IndexOfAny(Span span, ReadOnlySpan values) + { + int index = span.IndexOfAny(values); + Assert.Equal(index, span.IndexOfAny(IndexOfAnyValues.Create(values))); + return index; + } } } diff --git a/src/libraries/System.Memory/tests/Span/IndexOfAny.char.cs b/src/libraries/System.Memory/tests/Span/IndexOfAny.char.cs index d03ab6c10478b..7189231af38cd 100644 --- a/src/libraries/System.Memory/tests/Span/IndexOfAny.char.cs +++ b/src/libraries/System.Memory/tests/Span/IndexOfAny.char.cs @@ -1,8 +1,10 @@ // 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; @@ -37,18 +39,18 @@ public static void IndexOfAnyStrings_Char(string raw, string search, char expect Span span = new Span(buffers); char[] searchFor = search.ToCharArray(); - int index = span.IndexOfAny(searchFor); + int index = IndexOfAny(span, searchFor); if (searchFor.Length == 1) { - Assert.Equal(index, span.IndexOf(searchFor[0])); + Assert.Equal(index, IndexOf(span, searchFor[0])); } else if (searchFor.Length == 2) { - Assert.Equal(index, span.IndexOfAny(searchFor[0], searchFor[1])); + Assert.Equal(index, IndexOfAny(span, searchFor[0], searchFor[1])); } else if (searchFor.Length == 3) { - Assert.Equal(index, span.IndexOfAny(searchFor[0], searchFor[1], searchFor[2])); + Assert.Equal(index, IndexOfAny(span, searchFor[0], searchFor[1], searchFor[2])); } char found = span[index]; @@ -59,8 +61,8 @@ public static void IndexOfAnyStrings_Char(string raw, string search, char expect [Fact] public static void ZeroLengthIndexOfTwo_Char() { - Span sp = new Span(Array.Empty()); - int idx = sp.IndexOfAny((char)0, (char)0); + Span span = new Span(Array.Empty()); + int idx = IndexOfAny(span, (char)0, (char)0); Assert.Equal(-1, idx); } @@ -81,7 +83,7 @@ public static void DefaultFilledIndexOfTwo_Char() int index = rnd.Next(0, targets.Length) == 0 ? 0 : 1; char target0 = targets[index]; char target1 = targets[(index + 1) % 2]; - int idx = span.IndexOfAny(target0, target1); + int idx = IndexOfAny(span, target0, target1); Assert.Equal(0, idx); } } @@ -102,7 +104,7 @@ public static void TestMatchTwo_Char() { char target0 = a[targetIndex + i]; char target1 = (char)0; - int idx = span.IndexOfAny(target0, target1); + int idx = IndexOfAny(span, target0, target1); Assert.Equal(targetIndex, idx); } @@ -110,7 +112,7 @@ public static void TestMatchTwo_Char() { char target0 = a[targetIndex + i]; char target1 = a[targetIndex + i + 1]; - int idx = span.IndexOfAny(target0, target1); + int idx = IndexOfAny(span, target0, target1); Assert.Equal(targetIndex, idx); } @@ -118,7 +120,7 @@ public static void TestMatchTwo_Char() { char target0 = (char)0; char target1 = a[targetIndex + i + 1]; - int idx = span.IndexOfAny(target0, target1); + int idx = IndexOfAny(span, target0, target1); Assert.Equal(targetIndex + 1, idx); } } @@ -136,7 +138,7 @@ public static void TestNoMatchTwo_Char() char target1 = (char)rnd.Next(1, 256); Span span = new Span(a); - int idx = span.IndexOfAny(target0, target1); + int idx = IndexOfAny(span, target0, target1); Assert.Equal(-1, idx); } } @@ -158,7 +160,7 @@ public static void TestMultipleMatchTwo_Char() a[length - 3] = (char)200; Span span = new Span(a); - int idx = span.IndexOfAny((char)200, (char)200); + int idx = IndexOfAny(span, (char)200, (char)200); Assert.Equal(length - 3, idx); } } @@ -172,7 +174,7 @@ public static void MakeSureNoChecksGoOutOfRangeTwo_Char() a[0] = (char)99; a[length + 1] = (char)98; Span span = new Span(a, 1, length - 1); - int index = span.IndexOfAny((char)99, (char)98); + int index = IndexOfAny(span, (char)99, (char)98); Assert.Equal(-1, index); } @@ -182,7 +184,7 @@ public static void MakeSureNoChecksGoOutOfRangeTwo_Char() a[0] = (char)99; a[length + 1] = (char)99; Span span = new Span(a, 1, length - 1); - int index = span.IndexOfAny((char)99, (char)99); + int index = IndexOfAny(span, (char)99, (char)99); Assert.Equal(-1, index); } } @@ -190,8 +192,8 @@ public static void MakeSureNoChecksGoOutOfRangeTwo_Char() [Fact] public static void ZeroLengthIndexOfThree_Char() { - Span sp = new Span(Array.Empty()); - int idx = sp.IndexOfAny((char)0, (char)0, (char)0); + Span span = new Span(Array.Empty()); + int idx = IndexOfAny(span, (char)0, (char)0, (char)0); Assert.Equal(-1, idx); } @@ -213,7 +215,7 @@ public static void DefaultFilledIndexOfThree_Char() char target0 = targets[index]; char target1 = targets[(index + 1) % 2]; char target2 = targets[(index + 1) % 3]; - int idx = span.IndexOfAny(target0, target1, target2); + int idx = IndexOfAny(span, target0, target1, target2); Assert.Equal(0, idx); } } @@ -234,7 +236,7 @@ public static void TestMatchThree_Char() char target0 = a[targetIndex + i]; char target1 = (char)0; char target2 = (char)0; - int idx = span.IndexOfAny(target0, target1, target2); + int idx = IndexOfAny(span, target0, target1, target2); Assert.Equal(targetIndex, idx); } @@ -243,7 +245,7 @@ public static void TestMatchThree_Char() char target0 = a[targetIndex + i]; char target1 = a[targetIndex + i + 1]; char target2 = a[targetIndex + i + 2]; - int idx = span.IndexOfAny(target0, target1, target2); + int idx = IndexOfAny(span, target0, target1, target2); Assert.Equal(targetIndex, idx); } @@ -252,7 +254,7 @@ public static void TestMatchThree_Char() char target0 = (char)0; char target1 = (char)0; char target2 = a[targetIndex + i + 2]; - int idx = span.IndexOfAny(target0, target1, target2); + int idx = IndexOfAny(span, target0, target1, target2); Assert.Equal(targetIndex + 2, idx); } } @@ -271,7 +273,7 @@ public static void TestNoMatchThree_Char() char target2 = (char)rnd.Next(1, 256); Span span = new Span(a); - int idx = span.IndexOfAny(target0, target1, target2); + int idx = IndexOfAny(span, target0, target1, target2); Assert.Equal(-1, idx); } } @@ -294,7 +296,7 @@ public static void TestMultipleMatchThree_Char() a[length - 4] = (char)200; Span span = new Span(a); - int idx = span.IndexOfAny((char)200, (char)200, (char)200); + int idx = IndexOfAny(span, (char)200, (char)200, (char)200); Assert.Equal(length - 4, idx); } } @@ -308,7 +310,7 @@ public static void MakeSureNoChecksGoOutOfRangeThree_Char() a[0] = (char)99; a[length + 1] = (char)98; Span span = new Span(a, 1, length - 1); - int index = span.IndexOfAny((char)99, (char)98, (char)99); + int index = IndexOfAny(span, (char)99, (char)98, (char)99); Assert.Equal(-1, index); } @@ -318,7 +320,7 @@ public static void MakeSureNoChecksGoOutOfRangeThree_Char() a[0] = (char)99; a[length + 1] = (char)99; Span span = new Span(a, 1, length - 1); - int index = span.IndexOfAny((char)99, (char)99, (char)99); + int index = IndexOfAny(span, (char)99, (char)99, (char)99); Assert.Equal(-1, index); } } @@ -326,9 +328,9 @@ public static void MakeSureNoChecksGoOutOfRangeThree_Char() [Fact] public static void ZeroLengthIndexOfFour_Char() { - Span sp = new Span(Array.Empty()); + Span span = new Span(Array.Empty()); ReadOnlySpan values = new char[] { (char)0, (char)0, (char)0, (char)0 }; - int idx = sp.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(-1, idx); } @@ -347,8 +349,8 @@ public static void DefaultFilledIndexOfFour_Char() for (int i = 0; i < length; i++) { int index = rnd.Next(0, targets.Length); - ReadOnlySpan values = new char[] { (char)targets[index], (char)targets[(index + 1) % 2], (char)targets[(index + 1) % 3], (char)targets[(index + 1) % 4] }; - int idx = span.IndexOfAny(values); + ReadOnlySpan values = new char[] { targets[index], targets[(index + 1) % 2], targets[(index + 1) % 3], targets[(index + 1) % 4] }; + int idx = IndexOfAny(span, values); Assert.Equal(0, idx); } } @@ -367,22 +369,22 @@ public static void TestMatchFour_Char() for (int targetIndex = 0; targetIndex < length - Vector.Count; targetIndex++) { - ReadOnlySpan values = new char[] { (char)a[targetIndex + i], (char)0, (char)0, (char)0 }; - int idx = span.IndexOfAny(values); + ReadOnlySpan values = new char[] { a[targetIndex + i], (char)0, (char)0, (char)0 }; + int idx = IndexOfAny(span, values); Assert.Equal(targetIndex, idx); } for (int targetIndex = 0; targetIndex < length - 3 - Vector.Count; targetIndex++) { - ReadOnlySpan values = new char[] { (char)a[targetIndex + i], (char)a[targetIndex + i + 1], (char)a[targetIndex + i + 2], (char)a[targetIndex + i + 3] }; - int idx = span.IndexOfAny(values); + ReadOnlySpan values = new char[] { a[targetIndex + i], a[targetIndex + i + 1], a[targetIndex + i + 2], a[targetIndex + i + 3] }; + int idx = IndexOfAny(span, values); Assert.Equal(targetIndex, idx); } for (int targetIndex = 0; targetIndex < length - 3 - Vector.Count; targetIndex++) { - ReadOnlySpan values = new char[] { (char)0, (char)0, (char)0, (char)a[targetIndex + i + 3] }; - int idx = span.IndexOfAny(values); + ReadOnlySpan values = new char[] { (char)0, (char)0, (char)0, a[targetIndex + i + 3] }; + int idx = IndexOfAny(span, values); Assert.Equal(targetIndex + 3, idx); } } @@ -399,7 +401,7 @@ public static void TestNoMatchFour_Char() ReadOnlySpan values = new char[] { (char)rnd.Next(1, 256), (char)rnd.Next(1, 256), (char)rnd.Next(1, 256), (char)rnd.Next(1, 256) }; Span span = new Span(a); - int idx = span.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(-1, idx); } } @@ -424,7 +426,7 @@ public static void TestMultipleMatchFour_Char() Span span = new Span(a); ReadOnlySpan values = new char[] { (char)200, (char)200, (char)200, (char)200 }; - int idx = span.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(length - 5, idx); } } @@ -439,7 +441,7 @@ public static void MakeSureNoChecksGoOutOfRangeFour_Char() a[length + 1] = (char)98; Span span = new Span(a, 1, length - 1); ReadOnlySpan values = new char[] { (char)99, (char)98, (char)99, (char)99 }; - int index = span.IndexOfAny(values); + int index = IndexOfAny(span, values); Assert.Equal(-1, index); } @@ -450,7 +452,7 @@ public static void MakeSureNoChecksGoOutOfRangeFour_Char() a[length + 1] = (char)99; Span span = new Span(a, 1, length - 1); ReadOnlySpan values = new char[] { (char)99, (char)99, (char)99, (char)99 }; - int index = span.IndexOfAny(values); + int index = IndexOfAny(span, values); Assert.Equal(-1, index); } } @@ -458,9 +460,9 @@ public static void MakeSureNoChecksGoOutOfRangeFour_Char() [Fact] public static void ZeroLengthIndexOfFive_Char() { - Span sp = new Span(Array.Empty()); + Span span = new Span(Array.Empty()); ReadOnlySpan values = new char[] { (char)0, (char)0, (char)0, (char)0, (char)0 }; - int idx = sp.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(-1, idx); } @@ -479,8 +481,8 @@ public static void DefaultFilledIndexOfFive_Char() for (int i = 0; i < length; i++) { int index = rnd.Next(0, targets.Length); - ReadOnlySpan values = new char[] { (char)targets[index], (char)targets[(index + 1) % 2], (char)targets[(index + 1) % 3], (char)targets[(index + 1) % 4], (char)targets[(index + 1) % 5] }; - int idx = span.IndexOfAny(values); + ReadOnlySpan values = new char[] { targets[index], targets[(index + 1) % 2], targets[(index + 1) % 3], targets[(index + 1) % 4], targets[(index + 1) % 5] }; + int idx = IndexOfAny(span, values); Assert.Equal(0, idx); } } @@ -498,22 +500,22 @@ public static void TestMatchFive_Char() for (int targetIndex = 0; targetIndex < length - Vector.Count; targetIndex++) { - ReadOnlySpan values = new char[] { (char)a[targetIndex + i], (char)0, (char)0, (char)0, (char)0 }; - int idx = span.IndexOfAny(values); + ReadOnlySpan values = new char[] { a[targetIndex + i], (char)0, (char)0, (char)0, (char)0 }; + int idx = IndexOfAny(span, values); Assert.Equal(targetIndex, idx); } for (int targetIndex = 0; targetIndex < length - 4 - Vector.Count; targetIndex++) { - ReadOnlySpan values = new char[] { (char)a[targetIndex + i], (char)a[targetIndex + i + 1], (char)a[targetIndex + i + 2], (char)a[targetIndex + i + 3], (char)a[targetIndex + i + 4] }; - int idx = span.IndexOfAny(values); + ReadOnlySpan values = new char[] { a[targetIndex + i], a[targetIndex + i + 1], a[targetIndex + i + 2], a[targetIndex + i + 3], a[targetIndex + i + 4] }; + int idx = IndexOfAny(span, values); Assert.Equal(targetIndex, idx); } for (int targetIndex = 0; targetIndex < length - 4 - Vector.Count; targetIndex++) { - ReadOnlySpan values = new char[] { (char)0, (char)0, (char)0, (char)0, (char)a[targetIndex + i + 4] }; - int idx = span.IndexOfAny(values); + ReadOnlySpan values = new char[] { (char)0, (char)0, (char)0, (char)0, a[targetIndex + i + 4] }; + int idx = IndexOfAny(span, values); Assert.Equal(targetIndex + 4, idx); } } @@ -530,7 +532,7 @@ public static void TestNoMatchFive_Char() ReadOnlySpan values = new char[] { (char)rnd.Next(1, 256), (char)rnd.Next(1, 256), (char)rnd.Next(1, 256), (char)rnd.Next(1, 256), (char)rnd.Next(1, 256) }; Span span = new Span(a); - int idx = span.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(-1, idx); } } @@ -556,7 +558,7 @@ public static void TestMultipleMatchFive_Char() Span span = new Span(a); ReadOnlySpan values = new char[] { (char)200, (char)200, (char)200, (char)200, (char)200 }; - int idx = span.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(length - 6, idx); } } @@ -571,7 +573,7 @@ public static void MakeSureNoChecksGoOutOfRangeFive_Char() a[length + 1] = (char)98; Span span = new Span(a, 1, length - 1); ReadOnlySpan values = new char[] { (char)99, (char)98, (char)99, (char)99, (char)99 }; - int index = span.IndexOfAny(values); + int index = IndexOfAny(span, values); Assert.Equal(-1, index); } @@ -582,7 +584,7 @@ public static void MakeSureNoChecksGoOutOfRangeFive_Char() a[length + 1] = (char)99; Span span = new Span(a, 1, length - 1); ReadOnlySpan values = new char[] { (char)99, (char)99, (char)99, (char)99, (char)99 }; - int index = span.IndexOfAny(values); + int index = IndexOfAny(span, values); Assert.Equal(-1, index); } } @@ -590,13 +592,13 @@ public static void MakeSureNoChecksGoOutOfRangeFive_Char() [Fact] public static void ZeroLengthIndexOfMany_Char() { - Span sp = new Span(Array.Empty()); + Span span = new Span(Array.Empty()); ReadOnlySpan values = new ReadOnlySpan(new char[] { (char)0, (char)0, (char)0, (char)0, (char)0, (char)0 }); - int idx = sp.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(-1, idx); values = new ReadOnlySpan(new char[] { }); - idx = sp.IndexOfAny(values); + idx = IndexOfAny(span, values); Assert.Equal(-1, idx); } @@ -612,7 +614,7 @@ public static void DefaultFilledIndexOfMany_Char() for (int i = 0; i < length; i++) { - int idx = span.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(0, idx); } } @@ -629,21 +631,21 @@ public static void TestMatchMany_Char() for (int targetIndex = 0; targetIndex < length; targetIndex++) { ReadOnlySpan values = new ReadOnlySpan(new char[] { a[targetIndex], (char)0, (char)0, (char)0, (char)0, (char)0 }); - int idx = span.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(targetIndex, idx); } for (int targetIndex = 0; targetIndex < length - 5; targetIndex++) { ReadOnlySpan values = new ReadOnlySpan(new char[] { a[targetIndex], a[targetIndex + 1], a[targetIndex + 2], a[targetIndex + 3], a[targetIndex + 4], a[targetIndex + 5] }); - int idx = span.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(targetIndex, idx); } for (int targetIndex = 0; targetIndex < length - 5; targetIndex++) { ReadOnlySpan values = new ReadOnlySpan(new char[] { (char)0, (char)0, (char)0, (char)0, (char)0, a[targetIndex + 5] }); - int idx = span.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(targetIndex + 5, idx); } } @@ -678,7 +680,7 @@ public static void TestMatchValuesLargerMany_Char() } ReadOnlySpan values = new ReadOnlySpan(targets); - int idx = span.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(expectedIndex, idx); } } @@ -698,7 +700,7 @@ public static void TestNoMatchMany_Char() Span span = new Span(a); ReadOnlySpan values = new ReadOnlySpan(targets); - int idx = span.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(-1, idx); } } @@ -718,7 +720,7 @@ public static void TestNoMatchValuesLargerMany_Char() Span span = new Span(a); ReadOnlySpan values = new ReadOnlySpan(targets); - int idx = span.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(-1, idx); } } @@ -743,7 +745,7 @@ public static void TestMultipleMatchMany_Char() Span span = new Span(a); ReadOnlySpan values = new ReadOnlySpan(new char[] { (char)200, (char)200, (char)200, (char)200, (char)200, (char)200, (char)200, (char)200, (char)200 }); - int idx = span.IndexOfAny(values); + int idx = IndexOfAny(span, values); Assert.Equal(length - 5, idx); } } @@ -758,7 +760,7 @@ public static void MakeSureNoChecksGoOutOfRangeMany_Char() a[length + 1] = (char)98; Span span = new Span(a, 1, length - 1); ReadOnlySpan values = new ReadOnlySpan(new char[] { (char)99, (char)98, (char)99, (char)98, (char)99, (char)98 }); - int index = span.IndexOfAny(values); + int index = IndexOfAny(span, values); Assert.Equal(-1, index); } @@ -769,7 +771,7 @@ public static void MakeSureNoChecksGoOutOfRangeMany_Char() a[length + 1] = (char)99; Span span = new Span(a, 1, length - 1); ReadOnlySpan values = new ReadOnlySpan(new char[] { (char)99, (char)99, (char)99, (char)99, (char)99, (char)99 }); - int index = span.IndexOfAny(values); + int index = IndexOfAny(span, values); Assert.Equal(-1, index); } } @@ -780,7 +782,8 @@ public static void TestIndexOfAny_RandomInputs_Char() { IndexOfAnyCharTestHelper.TestRandomInputs( expected: IndexOfAnyReferenceImpl, - actual: (searchSpace, values) => searchSpace.IndexOfAny(values)); + indexOfAny: (searchSpace, values) => searchSpace.IndexOfAny(values), + indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAny(values)); static int IndexOfAnyReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) { @@ -795,17 +798,53 @@ static int IndexOfAnyReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan< return -1; } } + + private static int IndexOf(Span span, char value) + { + int index = span.IndexOf(value); + Assert.Equal(index, span.IndexOfAny(IndexOfAnyValues.Create(stackalloc char[] { value }))); + return index; + } + + private static int IndexOfAny(Span span, char value0, char value1) + { + int index = span.IndexOfAny(value0, value1); + Assert.Equal(index, span.IndexOfAny(IndexOfAnyValues.Create(stackalloc char[] { value0, value1 }))); + return index; + } + + private static int IndexOfAny(Span span, char value0, char value1, char value2) + { + int index = span.IndexOfAny(value0, value1, value2); + Assert.Equal(index, span.IndexOfAny(IndexOfAnyValues.Create(stackalloc char[] { value0, value1, value2 }))); + return index; + } + + private static int IndexOfAny(Span span, ReadOnlySpan values) + { + int index = span.IndexOfAny(values); + Assert.Equal(index, span.IndexOfAny(IndexOfAnyValues.Create(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[10 * 1024]; + 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); @@ -814,45 +853,89 @@ static IndexOfAnyCharTestHelper() 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); + 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 actual) + 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, stress those more. - Test(s_randomChars, s_randomAsciiChars); + // There are more interesting corner cases with ASCII needles, test those more. + Test(rng, s_randomBytes, s_randomAsciiBytes, expected, indexOfAny, indexOfAnyValues); - Test(s_randomChars, s_randomChars); + Test(rng, s_randomBytes, s_randomBytes, expected, indexOfAny, indexOfAnyValues); } + } - void Test(ReadOnlySpan haystackRandom, ReadOnlySpan needleRandom) + public static void TestRandomInputs(IndexOfAnySearchDelegate expected, IndexOfAnySearchDelegate indexOfAny, IndexOfAnyValuesSearchDelegate indexOfAnyValues) + { + var rng = new Random(42); + + for (int iterations = 0; iterations < 1_000_000; iterations++) { - const int MaxNeedleLength = 8; - const int MaxHaystackLength = 40; + // There are more interesting corner cases with ASCII needles, test those more. + Test(rng, s_randomChars, s_randomAsciiChars, expected, indexOfAny, indexOfAnyValues); - ReadOnlySpan haystack = haystackRandom.Slice(rng.Next(haystackRandom.Length + 1)); - haystack = haystack.Slice(0, Math.Min(haystack.Length, rng.Next(MaxHaystackLength))); + Test(rng, s_randomChars, s_randomLatin1Chars, expected, indexOfAny, indexOfAnyValues); + + Test(rng, s_randomChars, s_randomChars, expected, indexOfAny, indexOfAnyValues); + } + } - ReadOnlySpan needle = needleRandom.Slice(rng.Next(needleRandom.Length + 1)); - needle = needle.Slice(0, Math.Min(needle.Length, rng.Next(MaxNeedleLength))); + 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); - int expectedIndex = expected(haystack, needle); - int actualIndex = actual(haystack, needle); + 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))); - if (expectedIndex != actualIndex) - { - string readableNeedle = string.Join(", ", needle.ToString().Select(c => (int)c)); - string readableHaystack = string.Join(", ", haystack.ToString().Select(c => (int)c)); + int expectedIndex = expected(haystack, needle); + int indexOfAnyIndex = indexOfAny(haystack, needle); + int indexOfAnyValuesIndex = indexOfAnyValues(haystack, indexOfAnyValuesInstance); - Assert.True(false, $"Expected {expectedIndex}, got {actualIndex} for needle='{readableNeedle}', haystack='{readableHaystack}'"); - } + 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 5f9a06d3c4152..6a5b2dfca408a 100644 --- a/src/libraries/System.Memory/tests/Span/IndexOfAnyExcept.T.cs +++ b/src/libraries/System.Memory/tests/Span/IndexOfAnyExcept.T.cs @@ -1,8 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections; +using System.Buffers; using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Xunit; namespace System.SpanTests @@ -54,13 +56,60 @@ public void SearchingNulls(string[] input, string[] targets, int expected) } } + [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, - actual: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values)); + indexOfAny: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values), + indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values)); static int IndexOfAnyExceptReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) { @@ -82,7 +131,8 @@ public static void TestLastIndexOfAnyExcept_RandomInputs_Char() { IndexOfAnyCharTestHelper.TestRandomInputs( expected: LastIndexOfAnyExceptReferenceImpl, - actual: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values)); + indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values), + indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values)); static int LastIndexOfAnyExceptReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) { @@ -225,6 +275,8 @@ private static int IndexOfAnyExcept(Span span, T value) Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan)span, value)); Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((Span)span, new[] { value })); Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan)span, new[] { value })); + if (typeof(T) == typeof(byte)) Assert.Equal(result, Cast(span).IndexOfAnyExcept(IndexOfAnyValues.Create(Cast(new T[] { value })))); + if (typeof(T) == typeof(char)) Assert.Equal(result, Cast(span).IndexOfAnyExcept(IndexOfAnyValues.Create(Cast(new T[] { value })))); return result; } private static int IndexOfAnyExcept(Span span, T value0, T value1) @@ -233,6 +285,8 @@ private static int IndexOfAnyExcept(Span span, T value0, T value1) Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan)span, value0, value1)); Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((Span)span, new[] { value0, value1 })); Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan)span, new[] { value0, value1 })); + if (typeof(T) == typeof(byte)) Assert.Equal(result, Cast(span).IndexOfAnyExcept(IndexOfAnyValues.Create(Cast(new T[] { value0, value1 })))); + if (typeof(T) == typeof(char)) Assert.Equal(result, Cast(span).IndexOfAnyExcept(IndexOfAnyValues.Create(Cast(new T[] { value0, value1 })))); return result; } private static int IndexOfAnyExcept(Span span, T value0, T value1, T value2) @@ -241,12 +295,16 @@ private static int IndexOfAnyExcept(Span span, T value0, T value1, T value2) Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan)span, value0, value1, value2)); Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((Span)span, new[] { value0, value1, value2 })); Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan)span, new[] { value0, value1, value2 })); + if (typeof(T) == typeof(byte)) Assert.Equal(result, Cast(span).IndexOfAnyExcept(IndexOfAnyValues.Create(Cast(new T[] { value0, value1, value2 })))); + if (typeof(T) == typeof(char)) Assert.Equal(result, Cast(span).IndexOfAnyExcept(IndexOfAnyValues.Create(Cast(new T[] { value0, value1, value2 })))); return result; } private static int IndexOfAnyExcept(Span span, params T[] values) { int result = MemoryExtensions.IndexOfAnyExcept(span, values); Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan)span, values)); + if (typeof(T) == typeof(byte)) Assert.Equal(result, Cast(span).IndexOfAnyExcept(IndexOfAnyValues.Create(Cast(values)))); + if (typeof(T) == typeof(char)) Assert.Equal(result, Cast(span).IndexOfAnyExcept(IndexOfAnyValues.Create(Cast(values)))); return result; } private static int LastIndexOfAnyExcept(Span span, T value) @@ -255,6 +313,8 @@ private static int LastIndexOfAnyExcept(Span span, T value) Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan)span, value)); Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((Span)span, new[] { value })); Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan)span, new[] { value })); + if (typeof(T) == typeof(byte)) Assert.Equal(result, Cast(span).LastIndexOfAnyExcept(IndexOfAnyValues.Create(Cast(new T[] { value })))); + if (typeof(T) == typeof(char)) Assert.Equal(result, Cast(span).LastIndexOfAnyExcept(IndexOfAnyValues.Create(Cast(new T[] { value })))); return result; } private static int LastIndexOfAnyExcept(Span span, T value0, T value1) @@ -263,6 +323,8 @@ private static int LastIndexOfAnyExcept(Span span, T value0, T value1) Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan)span, value0, value1)); Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((Span)span, new[] { value0, value1 })); Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan)span, new[] { value0, value1 })); + if (typeof(T) == typeof(byte)) Assert.Equal(result, Cast(span).LastIndexOfAnyExcept(IndexOfAnyValues.Create(Cast(new T[] { value0, value1 })))); + if (typeof(T) == typeof(char)) Assert.Equal(result, Cast(span).LastIndexOfAnyExcept(IndexOfAnyValues.Create(Cast(new T[] { value0, value1 })))); return result; } private static int LastIndexOfAnyExcept(Span span, T value0, T value1, T value2) @@ -271,13 +333,20 @@ private static int LastIndexOfAnyExcept(Span span, T value0, T value1, T valu Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan)span, value0, value1, value2)); Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((Span)span, new[] { value0, value1, value2 })); Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan)span, new[] { value0, value1, value2 })); + if (typeof(T) == typeof(byte)) Assert.Equal(result, Cast(span).LastIndexOfAnyExcept(IndexOfAnyValues.Create(Cast(new T[] { value0, value1, value2 })))); + if (typeof(T) == typeof(char)) Assert.Equal(result, Cast(span).LastIndexOfAnyExcept(IndexOfAnyValues.Create(Cast(new T[] { value0, value1, value2 })))); return result; } private static int LastIndexOfAnyExcept(Span span, params T[] values) { int result = MemoryExtensions.LastIndexOfAnyExcept(span, values); Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan)span, values)); + if (typeof(T) == typeof(byte)) Assert.Equal(result, Cast(span).LastIndexOfAnyExcept(IndexOfAnyValues.Create(Cast(values)))); + if (typeof(T) == typeof(char)) Assert.Equal(result, Cast(span).LastIndexOfAnyExcept(IndexOfAnyValues.Create(Cast(values)))); return result; } + + private static ReadOnlySpan Cast(ReadOnlySpan span) => + MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length); } } diff --git a/src/libraries/System.Memory/tests/Span/LastIndexOfAny.T.cs b/src/libraries/System.Memory/tests/Span/LastIndexOfAny.T.cs index 72060aa20aacc..afc86a6080dde 100644 --- a/src/libraries/System.Memory/tests/Span/LastIndexOfAny.T.cs +++ b/src/libraries/System.Memory/tests/Span/LastIndexOfAny.T.cs @@ -952,13 +952,37 @@ 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, - actual: (searchSpace, values) => searchSpace.LastIndexOfAny(values)); + indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAny(values), + indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAny(values)); static int LastIndexOfAnyReferenceImpl(ReadOnlySpan searchSpace, ReadOnlySpan values) { diff --git a/src/libraries/System.Memory/tests/Span/LastIndexOfAny.byte.cs b/src/libraries/System.Memory/tests/Span/LastIndexOfAny.byte.cs index b78e981acdd3b..7bcb48da70d0e 100644 --- a/src/libraries/System.Memory/tests/Span/LastIndexOfAny.byte.cs +++ b/src/libraries/System.Memory/tests/Span/LastIndexOfAny.byte.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.Buffers; using System.Text; using Xunit; @@ -38,19 +39,19 @@ public static void LastIndexOfAnyStrings_Byte(string raw, string search, char ex var index = -1; if (searchFor.Length == 1) { - index = span.LastIndexOf((byte)searchFor[0]); + index = LastIndexOf(span, (byte)searchFor[0]); } else if (searchFor.Length == 2) { - index = span.LastIndexOfAny((byte)searchFor[0], (byte)searchFor[1]); + index = LastIndexOfAny(span, (byte)searchFor[0], (byte)searchFor[1]); } else if (searchFor.Length == 3) { - index = span.LastIndexOfAny((byte)searchFor[0], (byte)searchFor[1], (byte)searchFor[2]); + index = LastIndexOfAny(span, (byte)searchFor[0], (byte)searchFor[1], (byte)searchFor[2]); } else { - index = span.LastIndexOfAny(new ReadOnlySpan(searchForBytes)); + index = LastIndexOfAny(span, new ReadOnlySpan(searchForBytes)); } var found = span[index]; @@ -61,8 +62,8 @@ public static void LastIndexOfAnyStrings_Byte(string raw, string search, char ex [Fact] public static void ZeroLengthLastIndexOfAny_Byte_TwoByte() { - Span sp = new Span(Array.Empty()); - int idx = sp.LastIndexOfAny(0, 0); + Span span = new Span(Array.Empty()); + int idx = LastIndexOfAny(span, 0, 0); Assert.Equal(-1, idx); } @@ -83,7 +84,7 @@ public static void DefaultFilledLastIndexOfAny_Byte_TwoByte() int index = rnd.Next(0, 2) == 0 ? 0 : 1; byte target0 = targets[index]; byte target1 = targets[(index + 1) % 2]; - int idx = span.LastIndexOfAny(target0, target1); + int idx = LastIndexOfAny(span, target0, target1); Assert.Equal(span.Length - 1, idx); } } @@ -105,7 +106,7 @@ public static void TestMatchLastIndexOfAny_Byte_TwoByte() { byte target0 = a[targetIndex]; byte target1 = 0; - int idx = span.LastIndexOfAny(target0, target1); + int idx = LastIndexOfAny(span, target0, target1); Assert.Equal(targetIndex, idx); } @@ -113,7 +114,7 @@ public static void TestMatchLastIndexOfAny_Byte_TwoByte() { byte target0 = a[targetIndex]; byte target1 = a[targetIndex + 1]; - int idx = span.LastIndexOfAny(target0, target1); + int idx = LastIndexOfAny(span, target0, target1); Assert.Equal(targetIndex + 1, idx); } @@ -121,7 +122,7 @@ public static void TestMatchLastIndexOfAny_Byte_TwoByte() { byte target0 = 0; byte target1 = a[targetIndex + 1]; - int idx = span.LastIndexOfAny(target0, target1); + int idx = LastIndexOfAny(span, target0, target1); Assert.Equal(targetIndex + 1, idx); } } @@ -138,7 +139,7 @@ public static void TestNoMatchLastIndexOfAny_Byte_TwoByte() byte target1 = (byte)rnd.Next(1, 256); Span span = new Span(a); - int idx = span.LastIndexOfAny(target0, target1); + int idx = LastIndexOfAny(span, target0, target1); Assert.Equal(-1, idx); } } @@ -160,7 +161,7 @@ public static void TestMultipleMatchLastIndexOfAny_Byte_TwoByte() a[length - 3] = 200; Span span = new Span(a); - int idx = span.LastIndexOfAny(200, 200); + int idx = LastIndexOfAny(span, 200, 200); Assert.Equal(length - 1, idx); } } @@ -174,7 +175,7 @@ public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_Byte_TwoByte() a[0] = 99; a[length + 1] = 98; Span span = new Span(a, 1, length - 1); - int index = span.LastIndexOfAny(99, 98); + int index = LastIndexOfAny(span, 99, 98); Assert.Equal(-1, index); } @@ -184,7 +185,7 @@ public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_Byte_TwoByte() a[0] = 99; a[length + 1] = 99; Span span = new Span(a, 1, length - 1); - int index = span.LastIndexOfAny(99, 99); + int index = LastIndexOfAny(span, 99, 99); Assert.Equal(-1, index); } } @@ -192,8 +193,8 @@ public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_Byte_TwoByte() [Fact] public static void ZeroLengthIndexOf_Byte_ThreeByte() { - Span sp = new Span(Array.Empty()); - int idx = sp.LastIndexOfAny(0, 0, 0); + Span span = new Span(Array.Empty()); + int idx = LastIndexOfAny(span, 0, 0, 0); Assert.Equal(-1, idx); } @@ -215,7 +216,7 @@ public static void DefaultFilledLastIndexOfAny_Byte_ThreeByte() byte target0 = targets[index]; byte target1 = targets[(index + 1) % 2]; byte target2 = targets[(index + 1) % 3]; - int idx = span.LastIndexOfAny(target0, target1, target2); + int idx = LastIndexOfAny(span, target0, target1, target2); Assert.Equal(span.Length - 1, idx); } } @@ -238,7 +239,7 @@ public static void TestMatchLastIndexOfAny_Byte_ThreeByte() byte target0 = a[targetIndex]; byte target1 = 0; byte target2 = 0; - int idx = span.LastIndexOfAny(target0, target1, target2); + int idx = LastIndexOfAny(span, target0, target1, target2); Assert.Equal(targetIndex, idx); } @@ -247,7 +248,7 @@ public static void TestMatchLastIndexOfAny_Byte_ThreeByte() byte target0 = a[targetIndex]; byte target1 = a[targetIndex + 1]; byte target2 = a[targetIndex + 2]; - int idx = span.LastIndexOfAny(target0, target1, target2); + int idx = LastIndexOfAny(span, target0, target1, target2); Assert.Equal(targetIndex + 2, idx); } @@ -256,7 +257,7 @@ public static void TestMatchLastIndexOfAny_Byte_ThreeByte() byte target0 = 0; byte target1 = 0; byte target2 = a[targetIndex + 2]; - int idx = span.LastIndexOfAny(target0, target1, target2); + int idx = LastIndexOfAny(span, target0, target1, target2); Assert.Equal(targetIndex + 2, idx); } } @@ -274,7 +275,7 @@ public static void TestNoMatchLastIndexOfAny_Byte_ThreeByte() byte target2 = (byte)rnd.Next(1, 256); Span span = new Span(a); - int idx = span.LastIndexOfAny(target0, target1, target2); + int idx = LastIndexOfAny(span, target0, target1, target2); Assert.Equal(-1, idx); } } @@ -297,7 +298,7 @@ public static void TestMultipleMatchLastIndexOfAny_Byte_ThreeByte() a[length - 4] = 200; Span span = new Span(a); - int idx = span.LastIndexOfAny(200, 200, 200); + int idx = LastIndexOfAny(span, 200, 200, 200); Assert.Equal(length - 1, idx); } } @@ -311,7 +312,7 @@ public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_Byte_ThreeByte() a[0] = 99; a[length + 1] = 98; Span span = new Span(a, 1, length - 1); - int index = span.LastIndexOfAny(99, 98, 99); + int index = LastIndexOfAny(span, 99, 98, 99); Assert.Equal(-1, index); } @@ -321,7 +322,7 @@ public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_Byte_ThreeByte() a[0] = 99; a[length + 1] = 99; Span span = new Span(a, 1, length - 1); - int index = span.LastIndexOfAny(99, 99, 99); + int index = LastIndexOfAny(span, 99, 99, 99); Assert.Equal(-1, index); } } @@ -329,13 +330,13 @@ public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_Byte_ThreeByte() [Fact] public static void ZeroLengthLastIndexOfAny_Byte_ManyByte() { - Span sp = new Span(Array.Empty()); + Span span = new Span(Array.Empty()); var values = new ReadOnlySpan(new byte[] { 0, 0, 0, 0 }); - int idx = sp.LastIndexOfAny(values); + int idx = LastIndexOfAny(span, values); Assert.Equal(-1, idx); values = new ReadOnlySpan(new byte[] { }); - idx = sp.LastIndexOfAny(values); + idx = LastIndexOfAny(span, values); Assert.Equal(-1, idx); } @@ -351,7 +352,7 @@ public static void DefaultFilledLastIndexOfAny_Byte_ManyByte() for (int i = 0; i < length; i++) { - int idx = span.LastIndexOfAny(values); + int idx = LastIndexOfAny(span, values); Assert.Equal(span.Length - 1, idx); } } @@ -372,21 +373,21 @@ public static void TestMatchLastIndexOfAny_Byte_ManyByte() for (int targetIndex = 0; targetIndex < length; targetIndex++) { var values = new ReadOnlySpan(new byte[] { a[targetIndex], 0, 0, 0 }); - int idx = span.LastIndexOfAny(values); + int idx = LastIndexOfAny(span, 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.LastIndexOfAny(values); + int idx = LastIndexOfAny(span, values); Assert.Equal(targetIndex + 3, idx); } for (int targetIndex = 0; targetIndex < length - 3; targetIndex++) { var values = new ReadOnlySpan(new byte[] { 0, 0, 0, a[targetIndex + 3] }); - int idx = span.LastIndexOfAny(values); + int idx = LastIndexOfAny(span, values); Assert.Equal(targetIndex + 3, idx); } } @@ -421,7 +422,7 @@ public static void TestMatchValuesLargerLastIndexOfAny_Byte_ManyByte() } var values = new ReadOnlySpan(targets); - int idx = span.LastIndexOfAny(values); + int idx = LastIndexOfAny(span, values); Assert.Equal(expectedIndex, idx); } } @@ -441,7 +442,7 @@ public static void TestNoMatchLastIndexOfAny_Byte_ManyByte() Span span = new Span(a); var values = new ReadOnlySpan(targets); - int idx = span.LastIndexOfAny(values); + int idx = LastIndexOfAny(span, values); Assert.Equal(-1, idx); } } @@ -461,7 +462,7 @@ public static void TestNoMatchValuesLargerLastIndexOfAny_Byte_ManyByte() Span span = new Span(a); var values = new ReadOnlySpan(targets); - int idx = span.LastIndexOfAny(values); + int idx = LastIndexOfAny(span, values); Assert.Equal(-1, idx); } } @@ -486,7 +487,7 @@ public static void TestMultipleMatchLastIndexOfAny_Byte_ManyByte() Span span = new Span(a); var values = new ReadOnlySpan(new byte[] { 200, 200, 200, 200, 200, 200, 200, 200, 200 }); - int idx = span.LastIndexOfAny(values); + int idx = LastIndexOfAny(span, values); Assert.Equal(length - 1, idx); } } @@ -501,7 +502,7 @@ public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_Byte_ManyByte() 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.LastIndexOfAny(values); + int index = LastIndexOfAny(span, values); Assert.Equal(-1, index); } @@ -512,9 +513,37 @@ public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_Byte_ManyByte() 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.LastIndexOfAny(values); + int index = LastIndexOfAny(span, values); Assert.Equal(-1, index); } } + + private static int LastIndexOf(Span span, byte value) + { + int index = span.LastIndexOf(value); + Assert.Equal(index, span.LastIndexOfAny(IndexOfAnyValues.Create(stackalloc byte[] { value }))); + return index; + } + + private static int LastIndexOfAny(Span span, byte value0, byte value1) + { + int index = span.LastIndexOfAny(value0, value1); + Assert.Equal(index, span.LastIndexOfAny(IndexOfAnyValues.Create(stackalloc byte[] { value0, value1 }))); + return index; + } + + private static int LastIndexOfAny(Span span, byte value0, byte value1, byte value2) + { + int index = span.LastIndexOfAny(value0, value1, value2); + Assert.Equal(index, span.LastIndexOfAny(IndexOfAnyValues.Create(stackalloc byte[] { value0, value1, value2 }))); + return index; + } + + private static int LastIndexOfAny(Span span, ReadOnlySpan values) + { + int index = span.LastIndexOfAny(values); + Assert.Equal(index, span.LastIndexOfAny(IndexOfAnyValues.Create(values))); + return index; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index f1070474198a3..566f008d65939 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -416,7 +416,24 @@ - + + + + + + + + + + + + + + + + + + @@ -555,7 +572,6 @@ - @@ -1279,13 +1295,13 @@ Common\System\SR.cs - System\Collections\Concurrent\IProducerConsumerQueue.cs + System\Collections\Concurrent\IProducerConsumerQueue.cs - System\Collections\Concurrent\MultiProducerMultiConsumerQueue.cs + System\Collections\Concurrent\MultiProducerMultiConsumerQueue.cs - System\Collections\Concurrent\SingleProducerSingleConsumerQueue.cs + System\Collections\Concurrent\SingleProducerSingleConsumerQueue.cs Common\System\Collections\Generic\EnumerableHelpers.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyAsciiSearcher.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyAsciiSearcher.cs deleted file mode 100644 index d104cc839dec0..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyAsciiSearcher.cs +++ /dev/null @@ -1,355 +0,0 @@ -// 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; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; - -namespace System -{ - internal static class IndexOfAnyAsciiSearcher - { - private static bool IsSupported => Ssse3.IsSupported || AdvSimd.Arm64.IsSupported; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe bool TryComputeBitmap(ReadOnlySpan values, byte* bitmap, out bool needleContainsZero) - { - byte* bitmapLocal = bitmap; // https://github.com/dotnet/runtime/issues/9040 - - foreach (char c in values) - { - if (c > 127) - { - needleContainsZero = false; - return false; - } - - int highNibble = c >> 4; - int lowNibble = c & 0xF; - - bitmapLocal[(uint)lowNibble] |= (byte)(1 << highNibble); - } - - needleContainsZero = (bitmap[0] & 1) != 0; - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryIndexOfAny(ref char searchSpace, int searchSpaceLength, ReadOnlySpan asciiValues, out int index) => - TryIndexOfAny(ref Unsafe.As(ref searchSpace), searchSpaceLength, asciiValues, out index); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryIndexOfAnyExcept(ref char searchSpace, int searchSpaceLength, ReadOnlySpan asciiValues, out int index) => - TryIndexOfAny(ref Unsafe.As(ref searchSpace), searchSpaceLength, asciiValues, out index); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryLastIndexOfAny(ref char searchSpace, int searchSpaceLength, ReadOnlySpan asciiValues, out int index) => - TryLastIndexOfAny(ref Unsafe.As(ref searchSpace), searchSpaceLength, asciiValues, out index); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryLastIndexOfAnyExcept(ref char searchSpace, int searchSpaceLength, ReadOnlySpan asciiValues, out int index) => - TryLastIndexOfAny(ref Unsafe.As(ref searchSpace), searchSpaceLength, asciiValues, out index); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe bool TryIndexOfAny(ref short searchSpace, int searchSpaceLength, ReadOnlySpan asciiValues, out int index) - where TNegator : struct, INegator - { - Debug.Assert(searchSpaceLength >= Vector128.Count); - - if (IsSupported) - { - Vector128 bitmap = default; - if (TryComputeBitmap(asciiValues, (byte*)&bitmap, out bool needleContainsZero)) - { - index = Ssse3.IsSupported && needleContainsZero - ? IndexOfAnyVectorized(ref searchSpace, searchSpaceLength, bitmap) - : IndexOfAnyVectorized(ref searchSpace, searchSpaceLength, bitmap); - return true; - } - } - - index = default; - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe bool TryLastIndexOfAny(ref short searchSpace, int searchSpaceLength, ReadOnlySpan asciiValues, out int index) - where TNegator : struct, INegator - { - Debug.Assert(searchSpaceLength >= Vector128.Count); - - if (IsSupported) - { - Vector128 bitmap = default; - if (TryComputeBitmap(asciiValues, (byte*)&bitmap, out bool needleContainsZero)) - { - index = Ssse3.IsSupported && needleContainsZero - ? LastIndexOfAnyVectorized(ref searchSpace, searchSpaceLength, bitmap) - : LastIndexOfAnyVectorized(ref searchSpace, searchSpaceLength, bitmap); - return true; - } - } - - index = default; - return false; - } - - private static int IndexOfAnyVectorized(ref short searchSpace, int searchSpaceLength, Vector128 bitmap) - where TNegator : struct, INegator - where TOptimizations : struct, IOptimizations - { - ref short currentSearchSpace = ref searchSpace; - - if (searchSpaceLength > 2 * Vector128.Count) - { - // Process the input in chunks of 16 characters (2 * Vector128). - // We're mainly interested in a single byte of each character, and the core lookup operates on a Vector128. - // As packing two Vector128s into a Vector128 is cheap compared to the lookup, we can effectively double the throughput. - // If the input length is a multiple of 16, don't consume the last 16 characters in this loop. - // Let the fallback below handle it instead. This is why the condition is - // ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "!IsAddressGreaterThan". - ref short twoVectorsAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - (2 * Vector128.Count)); - - do - { - Vector128 source0 = Vector128.LoadUnsafe(ref currentSearchSpace); - Vector128 source1 = Vector128.LoadUnsafe(ref currentSearchSpace, (nuint)Vector128.Count); - - Vector128 result = IndexOfAnyLookup(source0, source1, bitmap); - if (result != Vector128.Zero) - { - return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, result); - } - - currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, 2 * Vector128.Count); - } - while (Unsafe.IsAddressLessThan(ref currentSearchSpace, ref twoVectorsAwayFromEnd)); - } - - // We have 1-16 characters remaining. Process the first and last vector in the search space. - // They may overlap, but we'll handle that in the index calculation if we do get a match. - Debug.Assert(searchSpaceLength >= Vector128.Count, "We expect that the input is long enough for us to load a whole vector."); - { - ref short oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector128.Count); - - ref short firstVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd) - ? ref oneVectorAwayFromEnd - : ref currentSearchSpace; - - Vector128 source0 = Vector128.LoadUnsafe(ref firstVector); - Vector128 source1 = Vector128.LoadUnsafe(ref oneVectorAwayFromEnd); - - Vector128 result = IndexOfAnyLookup(source0, source1, bitmap); - if (result != Vector128.Zero) - { - return ComputeFirstIndexOverlapped(ref searchSpace, ref firstVector, ref oneVectorAwayFromEnd, result); - } - } - - return -1; - } - - private static int LastIndexOfAnyVectorized(ref short searchSpace, int searchSpaceLength, Vector128 bitmap) - where TNegator : struct, INegator - where TOptimizations : struct, IOptimizations - { - ref short currentSearchSpace = ref Unsafe.Add(ref searchSpace, searchSpaceLength); - - if (searchSpaceLength > 2 * Vector128.Count) - { - // Process the input in chunks of 16 characters (2 * Vector128). - // We're mainly interested in a single byte of each character, and the core lookup operates on a Vector128. - // As packing two Vector128s into a Vector128 is cheap compared to the lookup, we can effectively double the throughput. - // If the input length is a multiple of 16, don't consume the last 16 characters in this loop. - // Let the fallback below handle it instead. This is why the condition is - // ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "!IsAddressLessThan". - ref short twoVectorsAfterStart = ref Unsafe.Add(ref searchSpace, 2 * Vector128.Count); - - do - { - currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, 2 * Vector128.Count); - - Vector128 source0 = Vector128.LoadUnsafe(ref currentSearchSpace); - Vector128 source1 = Vector128.LoadUnsafe(ref currentSearchSpace, (nuint)Vector128.Count); - - Vector128 result = IndexOfAnyLookup(source0, source1, bitmap); - if (result != Vector128.Zero) - { - return ComputeLastIndex(ref searchSpace, ref currentSearchSpace, result); - } - } - while (Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref twoVectorsAfterStart)); - } - - // We have 1-16 characters remaining. Process the first and last vector in the search space. - // They may overlap, but we'll handle that in the index calculation if we do get a match. - Debug.Assert(searchSpaceLength >= Vector128.Count, "We expect that the input is long enough for us to load a whole vector."); - { - ref short oneVectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector128.Count); - - ref short secondVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAfterStart) - ? ref Unsafe.Subtract(ref currentSearchSpace, Vector128.Count) - : ref searchSpace; - - Vector128 source0 = Vector128.LoadUnsafe(ref searchSpace); - Vector128 source1 = Vector128.LoadUnsafe(ref secondVector); - - Vector128 result = IndexOfAnyLookup(source0, source1, bitmap); - if (result != Vector128.Zero) - { - return ComputeLastIndexOverlapped(ref searchSpace, ref secondVector, result); - } - } - - return -1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 IndexOfAnyLookup(Vector128 source0, Vector128 source1, Vector128 bitmapLookup) - where TNegator : struct, INegator - where TOptimizations : struct, IOptimizations - { - // Pack two vectors of characters into bytes. While the type is Vector128, these are really UInt16 characters. - // X86: Downcast every character using saturation. - // - Values <= 32767 result in min(value, 255). - // - Values > 32767 result in 0. Because of this we must do more work to handle needles that contain 0. - // ARM64: Take the low byte of each character. - // - All values result in (value & 0xFF). - Vector128 source = Sse2.IsSupported - ? Sse2.PackUnsignedSaturate(source0, source1) - : AdvSimd.Arm64.UnzipEven(source0.AsByte(), source1.AsByte()); - - // On X86, the Ssse3.Shuffle instruction will already perform an implicit 'AND 0xF' on the indices, so we can skip it. - // For values above 127, Ssse3.Shuffle will also set the result to 0. This saves us from explicitly checking whether the input was ascii below. - Vector128 lowerNibbles = Ssse3.IsSupported - ? source - : source & Vector128.Create((byte)0xF); - - Vector128 highNibbles = Vector128.ShiftRightLogical(source.AsInt32(), 4).AsByte() & Vector128.Create((byte)0xF); - - // The bitmapLookup represents a 8x16 table of bits, indicating whether a character is present in the needle. - // Lookup the rows via the lower nibble and the column via the higher nibble. - Vector128 bitMask = Shuffle(bitmapLookup, lowerNibbles); - Vector128 bitPositions = Shuffle(Vector128.Create(0x8040201008040201).AsByte(), highNibbles); - - Vector128 result = bitMask & bitPositions; - - // On ARM64, we ignored the high byte of every character when packing (see above). - // The 'result' can therefore contain false positives - e.g. 0x141 would match 0x41 ('A'). - // On X86, PackUnsignedSaturate resulted in values becoming 0 for inputs above 32767. - // Any value above 32767 would therefore match against 0. If 0 is present in the needle, we must clear the false positives. - // In both cases, we can correct the result by clearing any bits that matched with a non-ascii source character. - if (AdvSimd.Arm64.IsSupported || TOptimizations.NeedleContainsZero) - { - Vector128 ascii0 = Vector128.LessThan(source0.AsUInt16(), Vector128.Create((ushort)128)).AsInt16(); - Vector128 ascii1 = Vector128.LessThan(source1.AsUInt16(), Vector128.Create((ushort)128)).AsInt16(); - Vector128 ascii = Sse2.IsSupported - ? Sse2.PackSignedSaturate(ascii0, ascii1).AsByte() - : AdvSimd.Arm64.UnzipEven(ascii0.AsByte(), ascii1.AsByte()); - result &= ascii; - } - - return TNegator.NegateIfNeeded(result); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 Shuffle(Vector128 vector, Vector128 indices) - { - // We're not using Vector128.Shuffle as the caller already accounts for and relies on differences in behavior between platforms. - return Ssse3.IsSupported - ? Ssse3.Shuffle(vector, indices) - : AdvSimd.Arm64.VectorTableLookup(vector, indices); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int ComputeFirstIndex(ref short searchSpace, ref short current, Vector128 result) - where TNegator : struct, INegator - { - uint mask = TNegator.ExtractMask(result); - int offsetInVector = BitOperations.TrailingZeroCount(mask); - return offsetInVector + (int)(Unsafe.ByteOffset(ref searchSpace, ref current) / sizeof(short)); - } - -#pragma warning disable IDE0060 // https://github.com/dotnet/roslyn-analyzers/issues/6228 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int ComputeFirstIndexOverlapped(ref short searchSpace, ref short current0, ref short current1, Vector128 result) - where TNegator : struct, INegator - { - uint mask = TNegator.ExtractMask(result); - int offsetInVector = BitOperations.TrailingZeroCount(mask); - if (offsetInVector >= Vector128.Count) - { - // We matched within the second vector - current0 = ref current1; - offsetInVector -= Vector128.Count; - } - return offsetInVector + (int)(Unsafe.ByteOffset(ref searchSpace, ref current0) / sizeof(short)); - } -#pragma warning restore IDE0060 // https://github.com/dotnet/roslyn-analyzers/issues/6228 - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int ComputeLastIndex(ref short searchSpace, ref short current, Vector128 result) - where TNegator : struct, INegator - { - uint mask = TNegator.ExtractMask(result) & 0xFFFF; - int offsetInVector = 31 - BitOperations.LeadingZeroCount(mask); - return offsetInVector + (int)(Unsafe.ByteOffset(ref searchSpace, ref current) / sizeof(char)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int ComputeLastIndexOverlapped(ref short searchSpace, ref short secondVector, Vector128 result) - where TNegator : struct, INegator - { - uint mask = TNegator.ExtractMask(result) & 0xFFFF; - int offsetInVector = 31 - BitOperations.LeadingZeroCount(mask); - if (offsetInVector < Vector128.Count) - { - return offsetInVector; - } - - // We matched within the second vector - return offsetInVector - Vector128.Count + (int)(Unsafe.ByteOffset(ref searchSpace, ref secondVector) / sizeof(char)); - } - - private interface INegator - { - static abstract bool NegateIfNeeded(bool result); - static abstract Vector128 NegateIfNeeded(Vector128 result); - static abstract uint ExtractMask(Vector128 result); - } - - private readonly struct DontNegate : INegator - { - public static bool NegateIfNeeded(bool result) => result; - public static Vector128 NegateIfNeeded(Vector128 result) => result; - public static uint ExtractMask(Vector128 result) => ~Vector128.Equals(result, Vector128.Zero).ExtractMostSignificantBits(); - } - - private readonly struct Negate : INegator - { - public static bool NegateIfNeeded(bool result) => !result; - // This is intentionally testing for equality with 0 instead of "~result". - // We want to know if any character didn't match, as that means it should be treated as a match for the -Except method. - public static Vector128 NegateIfNeeded(Vector128 result) => Vector128.Equals(result, Vector128.Zero); - public static uint ExtractMask(Vector128 result) => result.ExtractMostSignificantBits(); - } - - private interface IOptimizations - { - static abstract bool NeedleContainsZero { get; } - } - - private readonly struct Ssse3HandleZeroInNeedle : IOptimizations - { - public static bool NeedleContainsZero => true; - } - - private readonly struct Default : IOptimizations - { - public static bool NeedleContainsZero => false; - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/BitVector256.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/BitVector256.cs new file mode 100644 index 0000000000000..a276d0dc2996a --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/BitVector256.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System.Buffers +{ + internal unsafe struct BitVector256 + { + private fixed uint _values[8]; + + public void Set(int c) + { + Debug.Assert(c < 256); + uint offset = (uint)(c >> 5); + uint significantBit = 1u << c; + _values[offset] |= significantBit; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Contains128(char c) => + c < 128 && ContainsUnchecked(c); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Contains256(char c) => + c < 256 && ContainsUnchecked(c); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Contains(byte b) => + ContainsUnchecked(b); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private readonly bool ContainsUnchecked(int b) + { + Debug.Assert(b < 256); + uint offset = (uint)(b >> 5); + uint significantBit = 1u << b; + return (_values[offset] & significantBit) != 0; + } + + public readonly char[] GetCharValues() + { + var chars = new List(); + for (int i = 0; i < 256; i++) + { + if (ContainsUnchecked(i)) + { + chars.Add((char)i); + } + } + return chars.ToArray(); + } + + public readonly byte[] GetByteValues() + { + var bytes = new List(); + for (int i = 0; i < 256; i++) + { + if (ContainsUnchecked(i)) + { + bytes.Add((byte)i); + } + } + return bytes.ToArray(); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny1Value.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny1Value.cs new file mode 100644 index 0000000000000..0be9ac6f97d9f --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny1Value.cs @@ -0,0 +1,38 @@ +// 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.Runtime.CompilerServices; + +namespace System.Buffers +{ + internal sealed class IndexOfAny1Value : IndexOfAnyValues + where T : struct, IEquatable + { + private readonly T _e0; + + public IndexOfAny1Value(ReadOnlySpan values) + { + Debug.Assert(values.Length == 1); + _e0 = values[0]; + } + + internal override T[] GetValues() => new[] { _e0 }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAny(ReadOnlySpan span) => + span.IndexOf(_e0); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAnyExcept(ReadOnlySpan span) => + span.IndexOfAnyExcept(_e0); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAny(ReadOnlySpan span) => + span.LastIndexOf(_e0); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAnyExcept(ReadOnlySpan span) => + span.LastIndexOfAnyExcept(_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 new file mode 100644 index 0000000000000..bab3007aa27a0 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny2Values.cs @@ -0,0 +1,38 @@ +// 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.Runtime.CompilerServices; + +namespace System.Buffers +{ + internal sealed class IndexOfAny2Values : IndexOfAnyValues + where T : struct, IEquatable + { + private readonly T _e0, _e1; + + public IndexOfAny2Values(ReadOnlySpan values) + { + Debug.Assert(values.Length == 2); + (_e0, _e1) = (values[0], values[1]); + } + + internal override T[] GetValues() => new[] { _e0, _e1 }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAny(ReadOnlySpan span) => + span.IndexOfAny(_e0, _e1); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAnyExcept(ReadOnlySpan span) => + span.IndexOfAnyExcept(_e0, _e1); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAny(ReadOnlySpan span) => + span.LastIndexOfAny(_e0, _e1); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAnyExcept(ReadOnlySpan span) => + span.LastIndexOfAnyExcept(_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 new file mode 100644 index 0000000000000..933d87b1f7fa0 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny3Values.cs @@ -0,0 +1,38 @@ +// 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.Runtime.CompilerServices; + +namespace System.Buffers +{ + internal sealed class IndexOfAny3Values : IndexOfAnyValues + where T : struct, IEquatable + { + private readonly T _e0, _e1, _e2; + + public IndexOfAny3Values(ReadOnlySpan values) + { + Debug.Assert(values.Length == 3); + (_e0, _e1, _e2) = (values[0], values[1], values[2]); + } + + internal override T[] GetValues() => new[] { _e0, _e1, _e2 }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAny(ReadOnlySpan span) => + span.IndexOfAny(_e0, _e1, _e2); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAnyExcept(ReadOnlySpan span) => + span.IndexOfAnyExcept(_e0, _e1, _e2); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAny(ReadOnlySpan span) => + span.LastIndexOfAny(_e0, _e1, _e2); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAnyExcept(ReadOnlySpan span) => + span.LastIndexOfAnyExcept(_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 new file mode 100644 index 0000000000000..0d59ca9f33c75 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny4Values.cs @@ -0,0 +1,60 @@ +// 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; +using System.Runtime.InteropServices; + +namespace System.Buffers +{ + internal sealed class IndexOfAny4Values : IndexOfAnyValues + where T : struct, IEquatable + where TImpl : struct, INumber + { + private readonly TImpl _e0, _e1, _e2, _e3; + + public IndexOfAny4Values(ReadOnlySpan values) + { + Debug.Assert(Unsafe.SizeOf() == Unsafe.SizeOf()); + Debug.Assert(values.Length == 4); + (_e0, _e1, _e2, _e3) = (values[0], values[1], values[2], values[3]); + } + + internal override 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) }; + } + +#if MONO // Revert this once https://github.com/dotnet/runtime/pull/78015 is merged + internal override int IndexOfAny(ReadOnlySpan span) => + span.IndexOfAny(GetValues()); + + internal override int IndexOfAnyExcept(ReadOnlySpan span) => + span.IndexOfAnyExcept(GetValues()); + + internal override int LastIndexOfAny(ReadOnlySpan span) => + span.LastIndexOfAny(GetValues()); + + internal override int LastIndexOfAnyExcept(ReadOnlySpan span) => + span.LastIndexOfAnyExcept(GetValues()); +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAny(ReadOnlySpan span) => + SpanHelpers.IndexOfAnyValueType(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), _e0, _e1, _e2, _e3, span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAnyExcept(ReadOnlySpan span) => + SpanHelpers.IndexOfAnyExceptValueType(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), _e0, _e1, _e2, _e3, span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAny(ReadOnlySpan span) => + SpanHelpers.LastIndexOfAnyValueType(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), _e0, _e1, _e2, _e3, span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAnyExcept(ReadOnlySpan span) => + SpanHelpers.LastIndexOfAnyExceptValueType(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), _e0, _e1, _e2, _e3, span.Length); +#endif + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny5Values.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny5Values.cs new file mode 100644 index 0000000000000..5372bd0f1118a --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny5Values.cs @@ -0,0 +1,46 @@ +// 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; +using System.Runtime.InteropServices; + +namespace System.Buffers +{ + internal sealed class IndexOfAny5Values : IndexOfAnyValues + where T : struct, IEquatable + where TImpl : struct, INumber + { + private readonly TImpl _e0, _e1, _e2, _e3, _e4; + + public IndexOfAny5Values(ReadOnlySpan values) + { + Debug.Assert(Unsafe.SizeOf() == Unsafe.SizeOf()); + Debug.Assert(values.Length == 5); + (_e0, _e1, _e2, _e3, _e4) = (values[0], values[1], values[2], values[3], values[4]); + } + + internal override 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) }; + } + + [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); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAnyExcept(ReadOnlySpan span) => + SpanHelpers.IndexOfAnyExceptValueType(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), _e0, _e1, _e2, _e3, _e4, span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAny(ReadOnlySpan span) => + SpanHelpers.LastIndexOfAnyValueType(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), _e0, _e1, _e2, _e3, _e4, span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAnyExcept(ReadOnlySpan span) => + SpanHelpers.LastIndexOfAnyExceptValueType(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 new file mode 100644 index 0000000000000..ac8815ad7660f --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyAsciiByteValues.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace System.Buffers +{ + internal sealed class IndexOfAnyAsciiByteValues : IndexOfAnyValues + where TOptimizations : struct, IndexOfAnyAsciiSearcher.IOptimizations + { + private readonly Vector128 _bitmap; + private readonly BitVector256 _lookup; + + public IndexOfAnyAsciiByteValues(Vector128 bitmap, BitVector256 lookup) + { + _bitmap = bitmap; + _lookup = lookup; + } + + internal override byte[] GetValues() => _lookup.GetByteValues(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAny(ReadOnlySpan span) => + IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAnyExcept(ReadOnlySpan span) => + IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAny(ReadOnlySpan span) => + LastIndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAnyExcept(ReadOnlySpan span) => + LastIndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int IndexOfAny(ref byte searchSpace, int searchSpaceLength) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator + { + return IndexOfAnyAsciiSearcher.IsVectorizationSupported && searchSpaceLength >= sizeof(ulong) + ? IndexOfAnyAsciiSearcher.IndexOfAnyVectorized(ref searchSpace, searchSpaceLength, _bitmap) + : IndexOfAnyScalar(ref searchSpace, searchSpaceLength); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int LastIndexOfAny(ref byte searchSpace, int searchSpaceLength) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator + { + return IndexOfAnyAsciiSearcher.IsVectorizationSupported && searchSpaceLength >= sizeof(ulong) + ? IndexOfAnyAsciiSearcher.LastIndexOfAnyVectorized(ref searchSpace, searchSpaceLength, _bitmap) + : LastIndexOfAnyScalar(ref searchSpace, searchSpaceLength); + } + + private int IndexOfAnyScalar(ref byte searchSpace, int searchSpaceLength) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator + { + ref byte searchSpaceEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength); + ref byte cur = ref searchSpace; + + while (!Unsafe.AreSame(ref cur, ref searchSpaceEnd)) + { + byte b = cur; + if (TNegator.NegateIfNeeded(_lookup.Contains(b))) + { + return (int)Unsafe.ByteOffset(ref searchSpace, ref cur); + } + + cur = ref Unsafe.Add(ref cur, 1); + } + + return -1; + } + + private int LastIndexOfAnyScalar(ref byte searchSpace, int searchSpaceLength) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator + { + for (int i = searchSpaceLength - 1; i >= 0; i--) + { + byte b = Unsafe.Add(ref searchSpace, i); + if (TNegator.NegateIfNeeded(_lookup.Contains(b))) + { + return i; + } + } + + return -1; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyAsciiCharValues.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyAsciiCharValues.cs new file mode 100644 index 0000000000000..671878260c312 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyAsciiCharValues.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace System.Buffers +{ + internal sealed class IndexOfAnyAsciiCharValues : IndexOfAnyValues + where TOptimizations : struct, IndexOfAnyAsciiSearcher.IOptimizations + { + private readonly Vector128 _bitmap; + private readonly BitVector256 _lookup; + + public IndexOfAnyAsciiCharValues(Vector128 bitmap, BitVector256 lookup) + { + _bitmap = bitmap; + _lookup = lookup; + } + + internal override char[] GetValues() => _lookup.GetCharValues(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAny(ReadOnlySpan span) => + IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAnyExcept(ReadOnlySpan span) => + IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAny(ReadOnlySpan span) => + LastIndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAnyExcept(ReadOnlySpan span) => + LastIndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int IndexOfAny(ref char searchSpace, int searchSpaceLength) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator + { + return IndexOfAnyAsciiSearcher.IsVectorizationSupported && searchSpaceLength >= Vector128.Count + ? IndexOfAnyAsciiSearcher.IndexOfAnyVectorized(ref Unsafe.As(ref searchSpace), searchSpaceLength, _bitmap) + : IndexOfAnyScalar(ref searchSpace, searchSpaceLength); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int LastIndexOfAny(ref char searchSpace, int searchSpaceLength) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator + { + return IndexOfAnyAsciiSearcher.IsVectorizationSupported && searchSpaceLength >= Vector128.Count + ? IndexOfAnyAsciiSearcher.LastIndexOfAnyVectorized(ref Unsafe.As(ref searchSpace), searchSpaceLength, _bitmap) + : LastIndexOfAnyScalar(ref searchSpace, searchSpaceLength); + } + + private int IndexOfAnyScalar(ref char searchSpace, int searchSpaceLength) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator + { + ref char searchSpaceEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength); + ref char cur = ref searchSpace; + + while (!Unsafe.AreSame(ref cur, ref searchSpaceEnd)) + { + char c = cur; + if (TNegator.NegateIfNeeded(_lookup.Contains128(c))) + { + return (int)(Unsafe.ByteOffset(ref searchSpace, ref cur) / sizeof(char)); + } + + cur = ref Unsafe.Add(ref cur, 1); + } + + return -1; + } + + private int LastIndexOfAnyScalar(ref char searchSpace, int searchSpaceLength) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator + { + for (int i = searchSpaceLength - 1; i >= 0; i--) + { + char c = Unsafe.Add(ref searchSpace, i); + if (TNegator.NegateIfNeeded(_lookup.Contains128(c))) + { + return i; + } + } + + return -1; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyAsciiSearcher.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyAsciiSearcher.cs new file mode 100644 index 0000000000000..c33807fd33a18 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyAsciiSearcher.cs @@ -0,0 +1,675 @@ +// 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; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; + +namespace System.Buffers +{ + internal static class IndexOfAnyAsciiSearcher + { + internal static bool IsVectorizationSupported => Ssse3.IsSupported || AdvSimd.Arm64.IsSupported; + + internal static unsafe void ComputeBitmap256(ReadOnlySpan values, out Vector128 bitmap0, out Vector128 bitmap1, out BitVector256 lookup) + { + // The exact format of these bitmaps differs from the other ComputeBitmap overloads as it's meant for the full [0, 255] range algorithm. + // See http://0x80.pl/articles/simd-byte-lookup.html#universal-algorithm + + Vector128 bitmapSpace0 = default; + Vector128 bitmapSpace1 = default; + byte* bitmapLocal0 = (byte*)&bitmapSpace0; + byte* bitmapLocal1 = (byte*)&bitmapSpace1; + BitVector256 lookupLocal = default; + + foreach (byte b in values) + { + lookupLocal.Set(b); + + int highNibble = b >> 4; + int lowNibble = b & 0xF; + + if (highNibble < 8) + { + bitmapLocal0[(uint)lowNibble] |= (byte)(1 << highNibble); + } + else + { + bitmapLocal1[(uint)lowNibble] |= (byte)(1 << (highNibble - 8)); + } + } + + bitmap0 = bitmapSpace0; + bitmap1 = bitmapSpace1; + lookup = lookupLocal; + } + + internal static unsafe void ComputeBitmap(ReadOnlySpan values, out Vector128 bitmap, out BitVector256 lookup) + where T : struct, IUnsignedNumber + { + Debug.Assert(typeof(T) == typeof(byte) || typeof(T) == typeof(char)); + + Vector128 bitmapSpace = default; + byte* bitmapLocal = (byte*)&bitmapSpace; + BitVector256 lookupLocal = default; + + foreach (T tValue in values) + { + int value = int.CreateChecked(tValue); + + if (value > 127) + { + // The values were modified concurrent with the call to IndexOfAnyValues.Create + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); + } + + lookupLocal.Set(value); + + int highNibble = value >> 4; + int lowNibble = value & 0xF; + + bitmapLocal[(uint)lowNibble] |= (byte)(1 << highNibble); + } + + bitmap = bitmapSpace; + lookup = lookupLocal; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe bool TryComputeBitmap(ReadOnlySpan values, byte* bitmap, out bool needleContainsZero) + { + byte* bitmapLocal = bitmap; // https://github.com/dotnet/runtime/issues/9040 + + foreach (char c in values) + { + if (c > 127) + { + needleContainsZero = false; + return false; + } + + int highNibble = c >> 4; + int lowNibble = c & 0xF; + + bitmapLocal[(uint)lowNibble] |= (byte)(1 << highNibble); + } + + needleContainsZero = (bitmap[0] & 1) != 0; + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryIndexOfAny(ref char searchSpace, int searchSpaceLength, ReadOnlySpan asciiValues, out int index) => + TryIndexOfAny(ref Unsafe.As(ref searchSpace), searchSpaceLength, asciiValues, out index); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryIndexOfAnyExcept(ref char searchSpace, int searchSpaceLength, ReadOnlySpan asciiValues, out int index) => + TryIndexOfAny(ref Unsafe.As(ref searchSpace), searchSpaceLength, asciiValues, out index); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryLastIndexOfAny(ref char searchSpace, int searchSpaceLength, ReadOnlySpan asciiValues, out int index) => + TryLastIndexOfAny(ref Unsafe.As(ref searchSpace), searchSpaceLength, asciiValues, out index); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryLastIndexOfAnyExcept(ref char searchSpace, int searchSpaceLength, ReadOnlySpan asciiValues, out int index) => + TryLastIndexOfAny(ref Unsafe.As(ref searchSpace), searchSpaceLength, asciiValues, out index); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe bool TryIndexOfAny(ref short searchSpace, int searchSpaceLength, ReadOnlySpan asciiValues, out int index) + where TNegator : struct, INegator + { + Debug.Assert(searchSpaceLength >= Vector128.Count); + + if (IsVectorizationSupported) + { + Vector128 bitmap = default; + if (TryComputeBitmap(asciiValues, (byte*)&bitmap, out bool needleContainsZero)) + { + index = Ssse3.IsSupported && needleContainsZero + ? IndexOfAnyVectorized(ref searchSpace, searchSpaceLength, bitmap) + : IndexOfAnyVectorized(ref searchSpace, searchSpaceLength, bitmap); + return true; + } + } + + index = default; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe bool TryLastIndexOfAny(ref short searchSpace, int searchSpaceLength, ReadOnlySpan asciiValues, out int index) + where TNegator : struct, INegator + { + Debug.Assert(searchSpaceLength >= Vector128.Count); + + if (IsVectorizationSupported) + { + Vector128 bitmap = default; + if (TryComputeBitmap(asciiValues, (byte*)&bitmap, out bool needleContainsZero)) + { + index = Ssse3.IsSupported && needleContainsZero + ? LastIndexOfAnyVectorized(ref searchSpace, searchSpaceLength, bitmap) + : LastIndexOfAnyVectorized(ref searchSpace, searchSpaceLength, bitmap); + return true; + } + } + + index = default; + return false; + } + + internal static int IndexOfAnyVectorized(ref short searchSpace, int searchSpaceLength, Vector128 bitmap) + where TNegator : struct, INegator + where TOptimizations : struct, IOptimizations + { + ref short currentSearchSpace = ref searchSpace; + + if (searchSpaceLength > 2 * Vector128.Count) + { + // Process the input in chunks of 16 characters (2 * Vector128). + // We're mainly interested in a single byte of each character, and the core lookup operates on a Vector128. + // As packing two Vector128s into a Vector128 is cheap compared to the lookup, we can effectively double the throughput. + // If the input length is a multiple of 16, don't consume the last 16 characters in this loop. + // Let the fallback below handle it instead. This is why the condition is + // ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "!IsAddressGreaterThan". + ref short twoVectorsAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - (2 * Vector128.Count)); + + do + { + Vector128 source0 = Vector128.LoadUnsafe(ref currentSearchSpace); + Vector128 source1 = Vector128.LoadUnsafe(ref currentSearchSpace, (nuint)Vector128.Count); + + Vector128 result = IndexOfAnyLookup(source0, source1, bitmap); + if (result != Vector128.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, result); + } + + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, 2 * Vector128.Count); + } + while (Unsafe.IsAddressLessThan(ref currentSearchSpace, ref twoVectorsAwayFromEnd)); + } + + // We have 1-16 characters remaining. Process the first and last vector in the search space. + // They may overlap, but we'll handle that in the index calculation if we do get a match. + Debug.Assert(searchSpaceLength >= Vector128.Count, "We expect that the input is long enough for us to load a whole vector."); + { + ref short oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector128.Count); + + ref short firstVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd) + ? ref oneVectorAwayFromEnd + : ref currentSearchSpace; + + Vector128 source0 = Vector128.LoadUnsafe(ref firstVector); + Vector128 source1 = Vector128.LoadUnsafe(ref oneVectorAwayFromEnd); + + Vector128 result = IndexOfAnyLookup(source0, source1, bitmap); + if (result != Vector128.Zero) + { + return ComputeFirstIndexOverlapped(ref searchSpace, ref firstVector, ref oneVectorAwayFromEnd, result); + } + } + + return -1; + } + + internal static int LastIndexOfAnyVectorized(ref short searchSpace, int searchSpaceLength, Vector128 bitmap) + where TNegator : struct, INegator + where TOptimizations : struct, IOptimizations + { + ref short currentSearchSpace = ref Unsafe.Add(ref searchSpace, searchSpaceLength); + + if (searchSpaceLength > 2 * Vector128.Count) + { + // Process the input in chunks of 16 characters (2 * Vector128). + // We're mainly interested in a single byte of each character, and the core lookup operates on a Vector128. + // As packing two Vector128s into a Vector128 is cheap compared to the lookup, we can effectively double the throughput. + // If the input length is a multiple of 16, don't consume the last 16 characters in this loop. + // Let the fallback below handle it instead. This is why the condition is + // ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "!IsAddressLessThan". + ref short twoVectorsAfterStart = ref Unsafe.Add(ref searchSpace, 2 * Vector128.Count); + + do + { + currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, 2 * Vector128.Count); + + Vector128 source0 = Vector128.LoadUnsafe(ref currentSearchSpace); + Vector128 source1 = Vector128.LoadUnsafe(ref currentSearchSpace, (nuint)Vector128.Count); + + Vector128 result = IndexOfAnyLookup(source0, source1, bitmap); + if (result != Vector128.Zero) + { + return ComputeLastIndex(ref searchSpace, ref currentSearchSpace, result); + } + } + while (Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref twoVectorsAfterStart)); + } + + // We have 1-16 characters remaining. Process the first and last vector in the search space. + // They may overlap, but we'll handle that in the index calculation if we do get a match. + Debug.Assert(searchSpaceLength >= Vector128.Count, "We expect that the input is long enough for us to load a whole vector."); + { + ref short oneVectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector128.Count); + + ref short secondVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAfterStart) + ? ref Unsafe.Subtract(ref currentSearchSpace, Vector128.Count) + : ref searchSpace; + + Vector128 source0 = Vector128.LoadUnsafe(ref searchSpace); + Vector128 source1 = Vector128.LoadUnsafe(ref secondVector); + + Vector128 result = IndexOfAnyLookup(source0, source1, bitmap); + if (result != Vector128.Zero) + { + return ComputeLastIndexOverlapped(ref searchSpace, ref secondVector, result); + } + } + + return -1; + } + + internal static int IndexOfAnyVectorized(ref byte searchSpace, int searchSpaceLength, Vector128 bitmap) + where TNegator : struct, INegator + where TOptimizations : struct, IOptimizations + { + ref byte currentSearchSpace = ref searchSpace; + + if (searchSpaceLength > Vector128.Count) + { + // Process the input in chunks of 16 bytes. + // If the input length is a multiple of 16, don't consume the last 16 characters in this loop. + // Let the fallback below handle it instead. This is why the condition is + // ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "!IsAddressGreaterThan". + ref byte vectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector128.Count); + + do + { + Vector128 source = Vector128.LoadUnsafe(ref currentSearchSpace); + + Vector128 result = IndexOfAnyLookup(source, bitmap); + if (result != Vector128.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, result); + } + + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + } + while (Unsafe.IsAddressLessThan(ref currentSearchSpace, ref vectorAwayFromEnd)); + } + + // We have 1-16 bytes remaining. Process the first and last half vectors in the search space. + // They may overlap, but we'll handle that in the index calculation if we do get a match. + Debug.Assert(searchSpaceLength >= sizeof(ulong), "We expect that the input is long enough for us to load a ulong."); + { + ref byte halfVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector128.Count / 2); + + ref byte firstVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref halfVectorAwayFromEnd) + ? ref halfVectorAwayFromEnd + : ref currentSearchSpace; + + ulong source0 = Unsafe.ReadUnaligned(ref firstVector); + ulong source1 = Unsafe.ReadUnaligned(ref halfVectorAwayFromEnd); + Vector128 source = Vector128.Create(source0, source1).AsByte(); + + Vector128 result = IndexOfAnyLookup(source, bitmap); + if (result != Vector128.Zero) + { + return ComputeFirstIndexOverlapped(ref searchSpace, ref firstVector, ref halfVectorAwayFromEnd, result); + } + } + + return -1; + } + + internal static int LastIndexOfAnyVectorized(ref byte searchSpace, int searchSpaceLength, Vector128 bitmap) + where TNegator : struct, INegator + where TOptimizations : struct, IOptimizations + { + ref byte currentSearchSpace = ref Unsafe.Add(ref searchSpace, searchSpaceLength); + + if (searchSpaceLength > Vector128.Count) + { + // Process the input in chunks of 16 bytes. + // If the input length is a multiple of 16, don't consume the last 16 characters in this loop. + // Let the fallback below handle it instead. This is why the condition is + // ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "!IsAddressLessThan". + ref byte vectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector128.Count); + + do + { + currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, Vector128.Count); + + Vector128 source = Vector128.LoadUnsafe(ref currentSearchSpace); + + Vector128 result = IndexOfAnyLookup(source, bitmap); + if (result != Vector128.Zero) + { + return ComputeLastIndex(ref searchSpace, ref currentSearchSpace, result); + } + } + while (Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref vectorAfterStart)); + } + + // We have 1-16 bytes remaining. Process the first and last half vectors in the search space. + // They may overlap, but we'll handle that in the index calculation if we do get a match. + Debug.Assert(searchSpaceLength >= sizeof(ulong), "We expect that the input is long enough for us to load a ulong."); + { + ref byte halfVectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector128.Count / 2); + + ref byte secondVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref halfVectorAfterStart) + ? ref Unsafe.Subtract(ref currentSearchSpace, Vector128.Count) + : ref searchSpace; + + ulong source0 = Unsafe.ReadUnaligned(ref searchSpace); + ulong source1 = Unsafe.ReadUnaligned(ref secondVector); + Vector128 source = Vector128.Create(source0, source1).AsByte(); + + Vector128 result = IndexOfAnyLookup(source, bitmap); + if (result != Vector128.Zero) + { + return ComputeLastIndexOverlapped(ref searchSpace, ref secondVector, result); + } + } + + return -1; + } + + internal static int IndexOfAnyVectorized(ref byte searchSpace, int searchSpaceLength, Vector128 bitmap0, Vector128 bitmap1) + where TNegator : struct, INegator + { + ref byte currentSearchSpace = ref searchSpace; + + if (searchSpaceLength > Vector128.Count) + { + // Process the input in chunks of 16 bytes. + // If the input length is a multiple of 16, don't consume the last 16 characters in this loop. + // Let the fallback below handle it instead. This is why the condition is + // ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "!IsAddressGreaterThan". + ref byte vectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector128.Count); + + do + { + Vector128 source = Vector128.LoadUnsafe(ref currentSearchSpace); + + Vector128 result = IndexOfAnyLookup(source, bitmap0, bitmap1); + if (result != Vector128.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, result); + } + + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + } + while (Unsafe.IsAddressLessThan(ref currentSearchSpace, ref vectorAwayFromEnd)); + } + + // We have 1-16 bytes remaining. Process the first and last half vectors in the search space. + // They may overlap, but we'll handle that in the index calculation if we do get a match. + Debug.Assert(searchSpaceLength >= sizeof(ulong), "We expect that the input is long enough for us to load a ulong."); + { + ref byte halfVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector128.Count / 2); + + ref byte firstVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref halfVectorAwayFromEnd) + ? ref halfVectorAwayFromEnd + : ref currentSearchSpace; + + ulong source0 = Unsafe.ReadUnaligned(ref firstVector); + ulong source1 = Unsafe.ReadUnaligned(ref halfVectorAwayFromEnd); + Vector128 source = Vector128.Create(source0, source1).AsByte(); + + Vector128 result = IndexOfAnyLookup(source, bitmap0, bitmap1); + if (result != Vector128.Zero) + { + return ComputeFirstIndexOverlapped(ref searchSpace, ref firstVector, ref halfVectorAwayFromEnd, result); + } + } + + return -1; + } + + internal static int LastIndexOfAnyVectorized(ref byte searchSpace, int searchSpaceLength, Vector128 bitmap0, Vector128 bitmap1) + where TNegator : struct, INegator + { + ref byte currentSearchSpace = ref Unsafe.Add(ref searchSpace, searchSpaceLength); + + if (searchSpaceLength > Vector128.Count) + { + // Process the input in chunks of 16 bytes. + // If the input length is a multiple of 16, don't consume the last 16 characters in this loop. + // Let the fallback below handle it instead. This is why the condition is + // ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "!IsAddressLessThan". + ref byte vectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector128.Count); + + do + { + currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, Vector128.Count); + + Vector128 source = Vector128.LoadUnsafe(ref currentSearchSpace); + + Vector128 result = IndexOfAnyLookup(source, bitmap0, bitmap1); + if (result != Vector128.Zero) + { + return ComputeLastIndex(ref searchSpace, ref currentSearchSpace, result); + } + } + while (Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref vectorAfterStart)); + } + + // We have 1-16 bytes remaining. Process the first and last half vectors in the search space. + // They may overlap, but we'll handle that in the index calculation if we do get a match. + Debug.Assert(searchSpaceLength >= sizeof(ulong), "We expect that the input is long enough for us to load a ulong."); + { + ref byte halfVectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector128.Count / 2); + + ref byte secondVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref halfVectorAfterStart) + ? ref Unsafe.Subtract(ref currentSearchSpace, Vector128.Count) + : ref searchSpace; + + ulong source0 = Unsafe.ReadUnaligned(ref searchSpace); + ulong source1 = Unsafe.ReadUnaligned(ref secondVector); + Vector128 source = Vector128.Create(source0, source1).AsByte(); + + Vector128 result = IndexOfAnyLookup(source, bitmap0, bitmap1); + if (result != Vector128.Zero) + { + return ComputeLastIndexOverlapped(ref searchSpace, ref secondVector, result); + } + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 IndexOfAnyLookup(Vector128 source0, Vector128 source1, Vector128 bitmapLookup) + where TNegator : struct, INegator + where TOptimizations : struct, IOptimizations + { + // Pack two vectors of characters into bytes. While the type is Vector128, these are really UInt16 characters. + // X86: Downcast every character using saturation. + // - Values <= 32767 result in min(value, 255). + // - Values > 32767 result in 0. Because of this we must do more work to handle needles that contain 0. + // ARM64: Take the low byte of each character. + // - All values result in (value & 0xFF). + Vector128 source = Sse2.IsSupported + ? Sse2.PackUnsignedSaturate(source0, source1) + : AdvSimd.Arm64.UnzipEven(source0.AsByte(), source1.AsByte()); + + Vector128 result = IndexOfAnyLookupCore(source, bitmapLookup); + + // On ARM64, we ignored the high byte of every character when packing (see above). + // The 'result' can therefore contain false positives - e.g. 0x141 would match 0x41 ('A'). + // On X86, PackUnsignedSaturate resulted in values becoming 0 for inputs above 32767. + // Any value above 32767 would therefore match against 0. If 0 is present in the needle, we must clear the false positives. + // In both cases, we can correct the result by clearing any bits that matched with a non-ascii source character. + if (AdvSimd.Arm64.IsSupported || TOptimizations.NeedleContainsZero) + { + Vector128 ascii0 = Vector128.LessThan(source0.AsUInt16(), Vector128.Create((ushort)128)).AsInt16(); + Vector128 ascii1 = Vector128.LessThan(source1.AsUInt16(), Vector128.Create((ushort)128)).AsInt16(); + Vector128 ascii = Sse2.IsSupported + ? Sse2.PackSignedSaturate(ascii0, ascii1).AsByte() + : AdvSimd.Arm64.UnzipEven(ascii0.AsByte(), ascii1.AsByte()); + result &= ascii; + } + + return TNegator.NegateIfNeeded(result); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 IndexOfAnyLookup(Vector128 source, Vector128 bitmapLookup) + where TNegator : struct, INegator + where TOptimizations : struct, IOptimizations + { + Vector128 result = IndexOfAnyLookupCore(source, bitmapLookup); + + // On X86, values above 127 will map to 0. If 0 is present in the needle, we must clear the false positives. + if (TOptimizations.NeedleContainsZero) + { + Vector128 ascii = Vector128.LessThan(source, Vector128.Create((byte)128)); + result &= ascii; + } + + return TNegator.NegateIfNeeded(result); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 IndexOfAnyLookupCore(Vector128 source, Vector128 bitmapLookup) + { + // On X86, the Ssse3.Shuffle instruction will already perform an implicit 'AND 0xF' on the indices, so we can skip it. + // For values above 127, Ssse3.Shuffle will also set the result to 0. This saves us from explicitly checking whether the input was ascii. + Vector128 lowNibbles = Ssse3.IsSupported + ? source + : source & Vector128.Create((byte)0xF); + + Vector128 highNibbles = Vector128.ShiftRightLogical(source.AsInt32(), 4).AsByte() & Vector128.Create((byte)0xF); + + // The bitmapLookup represents a 8x16 table of bits, indicating whether a character is present in the needle. + // Lookup the rows via the lower nibble and the column via the higher nibble. + Vector128 bitMask = Shuffle(bitmapLookup, lowNibbles); + Vector128 bitPositions = Shuffle(Vector128.Create(0x8040201008040201).AsByte(), highNibbles); + + Vector128 result = bitMask & bitPositions; + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 IndexOfAnyLookup(Vector128 source, Vector128 bitmapLookup0, Vector128 bitmapLookup1) + where TNegator : struct, INegator + { + // http://0x80.pl/articles/simd-byte-lookup.html#universal-algorithm + + Vector128 lowNibbles = source & Vector128.Create((byte)0xF); + Vector128 highNibbles = Vector128.ShiftRightLogical(source.AsInt32(), 4).AsByte() & Vector128.Create((byte)0xF); + + Vector128 row0 = Shuffle(bitmapLookup0, lowNibbles); + Vector128 row1 = Shuffle(bitmapLookup1, lowNibbles); + + Vector128 bitmask = Shuffle(Vector128.Create(0x8040201008040201).AsByte(), highNibbles); + + Vector128 mask = Vector128.LessThan(highNibbles, Vector128.Create((byte)0x8)); + Vector128 bitsets = Vector128.ConditionalSelect(mask, row0, row1); + + Vector128 result = Vector128.Equals(bitsets & bitmask, bitmask); + + return TNegator.NegateIfNeeded(result); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 Shuffle(Vector128 vector, Vector128 indices) + { + // We're not using Vector128.Shuffle as the caller already accounts for and relies on differences in behavior between platforms. + return Ssse3.IsSupported + ? Ssse3.Shuffle(vector, indices) + : AdvSimd.Arm64.VectorTableLookup(vector, indices); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ComputeFirstIndex(ref T searchSpace, ref T current, Vector128 result) + where TNegator : struct, INegator + { + uint mask = TNegator.ExtractMask(result); + int offsetInVector = BitOperations.TrailingZeroCount(mask); + return offsetInVector + (int)(Unsafe.ByteOffset(ref searchSpace, ref current) / Unsafe.SizeOf()); + } + +#pragma warning disable IDE0060 // https://github.com/dotnet/roslyn-analyzers/issues/6228 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ComputeFirstIndexOverlapped(ref T searchSpace, ref T current0, ref T current1, Vector128 result) + where TNegator : struct, INegator + { + uint mask = TNegator.ExtractMask(result); + int offsetInVector = BitOperations.TrailingZeroCount(mask); + if (offsetInVector >= Vector128.Count) + { + // We matched within the second vector + current0 = ref current1; + offsetInVector -= Vector128.Count; + } + return offsetInVector + (int)(Unsafe.ByteOffset(ref searchSpace, ref current0) / Unsafe.SizeOf()); + } +#pragma warning restore IDE0060 // https://github.com/dotnet/roslyn-analyzers/issues/6228 + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ComputeLastIndex(ref T searchSpace, ref T current, Vector128 result) + where TNegator : struct, INegator + { + uint mask = TNegator.ExtractMask(result) & 0xFFFF; + int offsetInVector = 31 - BitOperations.LeadingZeroCount(mask); + return offsetInVector + (int)(Unsafe.ByteOffset(ref searchSpace, ref current) / Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ComputeLastIndexOverlapped(ref T searchSpace, ref T secondVector, Vector128 result) + where TNegator : struct, INegator + { + uint mask = TNegator.ExtractMask(result) & 0xFFFF; + int offsetInVector = 31 - BitOperations.LeadingZeroCount(mask); + if (offsetInVector < Vector128.Count) + { + return offsetInVector; + } + + // We matched within the second vector + return offsetInVector - Vector128.Count + (int)(Unsafe.ByteOffset(ref searchSpace, ref secondVector) / Unsafe.SizeOf()); + } + + internal interface INegator + { + static abstract bool NegateIfNeeded(bool result); + static abstract Vector128 NegateIfNeeded(Vector128 result); + static abstract uint ExtractMask(Vector128 result); + } + + internal readonly struct DontNegate : INegator + { + public static bool NegateIfNeeded(bool result) => result; + public static Vector128 NegateIfNeeded(Vector128 result) => result; + public static uint ExtractMask(Vector128 result) => ~Vector128.Equals(result, Vector128.Zero).ExtractMostSignificantBits(); + } + + internal readonly struct Negate : INegator + { + public static bool NegateIfNeeded(bool result) => !result; + // This is intentionally testing for equality with 0 instead of "~result". + // We want to know if any character didn't match, as that means it should be treated as a match for the -Except method. + public static Vector128 NegateIfNeeded(Vector128 result) => Vector128.Equals(result, Vector128.Zero); + public static uint ExtractMask(Vector128 result) => result.ExtractMostSignificantBits(); + } + + internal interface IOptimizations + { + static abstract bool NeedleContainsZero { get; } + } + + internal readonly struct Ssse3HandleZeroInNeedle : IOptimizations + { + public static bool NeedleContainsZero => true; + } + + internal readonly struct Default : IOptimizations + { + public static bool NeedleContainsZero => false; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyByteValues.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyByteValues.cs new file mode 100644 index 0000000000000..9dc76d4709671 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyByteValues.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace System.Buffers +{ + internal sealed class IndexOfAnyByteValues : IndexOfAnyValues + { + private readonly Vector128 _bitmap0; + private readonly Vector128 _bitmap1; + private readonly BitVector256 _lookup; + + public IndexOfAnyByteValues(ReadOnlySpan values) => + IndexOfAnyAsciiSearcher.ComputeBitmap256(values, out _bitmap0, out _bitmap1, out _lookup); + + internal override byte[] GetValues() => _lookup.GetByteValues(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAny(ReadOnlySpan span) => + IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAnyExcept(ReadOnlySpan span) => + IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAny(ReadOnlySpan span) => + LastIndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAnyExcept(ReadOnlySpan span) => + LastIndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int IndexOfAny(ref byte searchSpace, int searchSpaceLength) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator + { + return IndexOfAnyAsciiSearcher.IsVectorizationSupported && searchSpaceLength >= sizeof(ulong) + ? IndexOfAnyAsciiSearcher.IndexOfAnyVectorized(ref searchSpace, searchSpaceLength, _bitmap0, _bitmap1) + : IndexOfAnyScalar(ref searchSpace, searchSpaceLength); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int LastIndexOfAny(ref byte searchSpace, int searchSpaceLength) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator + { + return IndexOfAnyAsciiSearcher.IsVectorizationSupported && searchSpaceLength >= sizeof(ulong) + ? IndexOfAnyAsciiSearcher.LastIndexOfAnyVectorized(ref searchSpace, searchSpaceLength, _bitmap0, _bitmap1) + : LastIndexOfAnyScalar(ref searchSpace, searchSpaceLength); + } + + private int IndexOfAnyScalar(ref byte searchSpace, int searchSpaceLength) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator + { + ref byte searchSpaceEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength); + ref byte cur = ref searchSpace; + + while (!Unsafe.AreSame(ref cur, ref searchSpaceEnd)) + { + byte b = cur; + if (TNegator.NegateIfNeeded(_lookup.Contains(b))) + { + return (int)(Unsafe.ByteOffset(ref searchSpace, ref cur)); + } + + cur = ref Unsafe.Add(ref cur, 1); + } + + return -1; + } + + private int LastIndexOfAnyScalar(ref byte searchSpace, int searchSpaceLength) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator + { + for (int i = searchSpaceLength - 1; i >= 0; i--) + { + byte b = Unsafe.Add(ref searchSpace, i); + if (TNegator.NegateIfNeeded(_lookup.Contains(b))) + { + return i; + } + } + + return -1; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyCharValuesProbabilistic.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyCharValuesProbabilistic.cs new file mode 100644 index 0000000000000..d8de8c016d6d7 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyCharValuesProbabilistic.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.Buffers +{ + internal sealed class IndexOfAnyCharValuesProbabilistic : IndexOfAnyValues + { + private ProbabilisticMap _map; + private readonly string _values; + + public unsafe IndexOfAnyCharValuesProbabilistic(ReadOnlySpan values) + { + _values = new string(values); + _map = new ProbabilisticMap(_values); + } + + internal override char[] GetValues() => _values.ToCharArray(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAny(ReadOnlySpan span) => + IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAnyExcept(ReadOnlySpan span) => + IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAny(ReadOnlySpan span) => + LastIndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAnyExcept(ReadOnlySpan span) => + LastIndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.NoInlining)] + private int IndexOfAny(ref char searchSpace, int searchSpaceLength) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator => + ProbabilisticMap.IndexOfAny(ref Unsafe.As(ref _map), ref searchSpace, searchSpaceLength, _values); + + [MethodImpl(MethodImplOptions.NoInlining)] + private int LastIndexOfAny(ref char searchSpace, int searchSpaceLength) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator => + ProbabilisticMap.LastIndexOfAny(ref Unsafe.As(ref _map), ref searchSpace, searchSpaceLength, _values); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyLatin1CharValues.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyLatin1CharValues.cs new file mode 100644 index 0000000000000..e48c000c67b25 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyLatin1CharValues.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.Buffers +{ + internal sealed class IndexOfAnyLatin1CharValues : IndexOfAnyValues + { + private readonly BitVector256 _lookup; + + public IndexOfAnyLatin1CharValues(ReadOnlySpan values) + { + foreach (char c in values) + { + if (c > 255) + { + // The values were modified concurrent with the call to IndexOfAnyValues.Create + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); + } + + _lookup.Set(c); + } + } + + internal override char[] GetValues() => _lookup.GetCharValues(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAny(ReadOnlySpan span) => + IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAnyExcept(ReadOnlySpan span) => + IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAny(ReadOnlySpan span) => + LastIndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAnyExcept(ReadOnlySpan span) => + LastIndexOfAny(ref MemoryMarshal.GetReference(span), span.Length); + + private int IndexOfAny(ref char searchSpace, int searchSpaceLength) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator + { + ref char searchSpaceEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength); + ref char cur = ref searchSpace; + + while (!Unsafe.AreSame(ref cur, ref searchSpaceEnd)) + { + char c = cur; + if (TNegator.NegateIfNeeded(_lookup.Contains256(c))) + { + return (int)(Unsafe.ByteOffset(ref searchSpace, ref cur) / sizeof(char)); + } + + cur = ref Unsafe.Add(ref cur, 1); + } + + return -1; + } + + private int LastIndexOfAny(ref char searchSpace, int searchSpaceLength) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator + { + for (int i = searchSpaceLength - 1; i >= 0; i--) + { + char c = Unsafe.Add(ref searchSpace, i); + if (TNegator.NegateIfNeeded(_lookup.Contains256(c))) + { + return i; + } + } + + return -1; + } + } +} 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 new file mode 100644 index 0000000000000..c96af294e4998 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValues.T.cs @@ -0,0 +1,75 @@ +// 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.Runtime.CompilerServices; + +namespace System.Buffers +{ + /// + /// Provides an immutable, read-only set of values optimized for efficient searching. + /// Instances are created by or . + /// + /// The type of the values to search for. + /// + /// are optimized for situations where the same set of values is frequently used for searching at runtime. + /// + [DebuggerTypeProxy(typeof(IndexOfAnyValuesDebugView<>))] + public class IndexOfAnyValues where T : IEquatable? + { + // Only CoreLib can create derived types + private protected IndexOfAnyValues() { } + + /// Used by . + internal virtual T[] GetValues() => 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(); + internal virtual int LastIndexOfAnyExcept(ReadOnlySpan span) => throw new UnreachableException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAny(ReadOnlySpan span, IndexOfAnyValues values) + { + if (values is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.values); + } + + return values.IndexOfAny(span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyExcept(ReadOnlySpan span, IndexOfAnyValues values) + { + if (values is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.values); + } + + return values.IndexOfAnyExcept(span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAny(ReadOnlySpan span, IndexOfAnyValues values) + { + if (values is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.values); + } + + return values.LastIndexOfAny(span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyExcept(ReadOnlySpan span, IndexOfAnyValues values) + { + if (values is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.values); + } + + return values.LastIndexOfAnyExcept(span); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValues.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValues.cs new file mode 100644 index 0000000000000..b67b27b0b2643 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValues.cs @@ -0,0 +1,171 @@ +// 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; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace System.Buffers +{ + /// + /// Provides a set of initialization methods for instances of the class. + /// + /// + /// IndexOfAnyValues are optimized for situations where the same set of values is frequently used for searching at runtime. + /// + public static class IndexOfAnyValues + { + /// + /// Creates an optimized representation of used for efficient searching. + /// + /// The set of values. + public static IndexOfAnyValues Create(ReadOnlySpan values) + { + if (values.IsEmpty) + { + return new IndexOfEmptyValues(); + } + + if (values.Length == 1) + { + return new IndexOfAny1Value(values); + } + + // IndexOfAnyValuesInRange is slower than IndexOfAny1Value, but faster than IndexOfAny2Values + if (TryGetSingleRange(values, out byte maxInclusive) is IndexOfAnyValues range) + { + return range; + } + + if (values.Length <= 5) + { + Debug.Assert(values.Length is 2 or 3 or 4 or 5); + return values.Length switch + { + 2 => new IndexOfAny2Values(values), + 3 => new IndexOfAny3Values(values), + 4 => new IndexOfAny4Values(values), + _ => new IndexOfAny5Values(values), + }; + } + + if (IndexOfAnyAsciiSearcher.IsVectorizationSupported && maxInclusive < 128) + { + IndexOfAnyAsciiSearcher.ComputeBitmap(values, out Vector128 bitmap, out BitVector256 lookup); + + return Sse3.IsSupported && lookup.Contains(0) + ? new IndexOfAnyAsciiByteValues(bitmap, lookup) + : new IndexOfAnyAsciiByteValues(bitmap, lookup); + } + + return new IndexOfAnyByteValues(values); + } + + /// + /// Creates an optimized representation of used for efficient searching. + /// + /// The set of values. + public static IndexOfAnyValues Create(ReadOnlySpan values) + { + if (values.IsEmpty) + { + return new IndexOfEmptyValues(); + } + + if (values.Length == 1) + { + return new IndexOfAny1Value(values); + } + + // IndexOfAnyValuesInRange is slower than IndexOfAny1Value, but faster than IndexOfAny2Values + if (TryGetSingleRange(values, out char maxInclusive) is IndexOfAnyValues charRange) + { + return charRange; + } + + if (values.Length == 2) + { + return new IndexOfAny2Values(values); + } + + if (values.Length == 3) + { + return new IndexOfAny3Values(values); + } + + // IndexOfAnyAsciiSearcher for chars is slower than IndexOfAny3Values, but faster than IndexOfAny4Values + if (IndexOfAnyAsciiSearcher.IsVectorizationSupported && maxInclusive < 128) + { + IndexOfAnyAsciiSearcher.ComputeBitmap(values, out Vector128 bitmap, out BitVector256 lookup); + + return Sse3.IsSupported && lookup.Contains(0) + ? new IndexOfAnyAsciiCharValues(bitmap, lookup) + : new IndexOfAnyAsciiCharValues(bitmap, lookup); + } + + // Vector128 isn't valid. Treat the values as shorts instead. + ReadOnlySpan shortValues = MemoryMarshal.CreateReadOnlySpan( + ref Unsafe.As(ref MemoryMarshal.GetReference(values)), + values.Length); + + if (values.Length == 4) + { + return new IndexOfAny4Values(shortValues); + } + + if (values.Length == 5) + { + return new IndexOfAny5Values(shortValues); + } + + if (maxInclusive < 256) + { + // This will also match ASCII values when IndexOfAnyAsciiSearcher is not supported + return new IndexOfAnyLatin1CharValues(values); + } + + return new IndexOfAnyCharValuesProbabilistic(values); + } + + private static IndexOfAnyValues? TryGetSingleRange(ReadOnlySpan values, out T maxInclusive) + where T : struct, INumber, IMinMaxValue + { + T min = T.MaxValue; + T max = T.MinValue; + + foreach (T value in values) + { + min = T.Min(min, value); + max = T.Max(max, value); + } + + maxInclusive = max; + + uint range = uint.CreateChecked(max - min) + 1; + if (range > values.Length) + { + return null; + } + + Span seenValues = range <= 256 ? stackalloc bool[256] : new bool[range]; + seenValues = seenValues.Slice(0, (int)range); + seenValues.Clear(); + + foreach (T value in values) + { + int offset = int.CreateChecked(value - min); + seenValues[offset] = true; + } + + if (seenValues.Contains(false)) + { + return null; + } + + return (IndexOfAnyValues)(object)new IndexOfAnyValuesInRange(min, max); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValuesDebugView.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValuesDebugView.cs new file mode 100644 index 0000000000000..986ac148001ee --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValuesDebugView.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Buffers +{ + internal sealed class IndexOfAnyValuesDebugView where T : IEquatable? + { + private readonly IndexOfAnyValues _values; + + public IndexOfAnyValuesDebugView(IndexOfAnyValues values) + { + ArgumentNullException.ThrowIfNull(values); + _values = values; + } + + public T[] Values => _values.GetValues(); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValuesInRange.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValuesInRange.cs new file mode 100644 index 0000000000000..19c5c9a40e209 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValuesInRange.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace System.Buffers +{ + internal sealed class IndexOfAnyValuesInRange : IndexOfAnyValues + where T : struct, INumber + { + private readonly T _lowInclusive, _highInclusive; + + public IndexOfAnyValuesInRange(T lowInclusive, T highInclusive) => + (_lowInclusive, _highInclusive) = (lowInclusive, highInclusive); + + internal override T[] GetValues() + { + T[] values = new T[int.CreateChecked(_highInclusive - _lowInclusive)]; + + T element = _lowInclusive; + for (int i = 0; i < values.Length; i++) + { + values[i] = element; + element += T.One; + } + + return values; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAny(ReadOnlySpan span) => + span.IndexOfAnyInRange(_lowInclusive, _highInclusive); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int IndexOfAnyExcept(ReadOnlySpan span) => + span.IndexOfAnyExceptInRange(_lowInclusive, _highInclusive); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAny(ReadOnlySpan span) => + span.LastIndexOfAnyInRange(_lowInclusive, _highInclusive); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override int LastIndexOfAnyExcept(ReadOnlySpan span) => + span.LastIndexOfAnyExceptInRange(_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 new file mode 100644 index 0000000000000..91f50a12a5fa8 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfEmptyValues.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Buffers +{ + internal sealed class IndexOfEmptyValues : IndexOfAnyValues + where T : IEquatable? + { + internal override T[] GetValues() => Array.Empty(); + + internal override int IndexOfAny(ReadOnlySpan span) => + -1; + + internal override int IndexOfAnyExcept(ReadOnlySpan span) => + span.IsEmpty ? -1 : 0; + + internal override int LastIndexOfAny(ReadOnlySpan span) => + -1; + + internal override int LastIndexOfAnyExcept(ReadOnlySpan span) => + span.Length - 1; + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/ProbabilisticMap.cs b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/ProbabilisticMap.cs similarity index 65% rename from src/libraries/System.Private.CoreLib/src/System/ProbabilisticMap.cs rename to src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/ProbabilisticMap.cs index 51f804e314f98..d0a0a7821913e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ProbabilisticMap.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/ProbabilisticMap.cs @@ -1,12 +1,13 @@ // 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.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -namespace System +#pragma warning disable IDE0060 // https://github.com/dotnet/roslyn-analyzers/issues/6228 + +namespace System.Buffers { /// Data structure used to optimize checks for whether a char is in a set of chars. /// @@ -19,33 +20,25 @@ namespace System /// character is used to index into this map to get the right block, the value of /// the remaining 5 msb are used as the bit position inside this block. /// - [StructLayout(LayoutKind.Explicit, Size = Size * sizeof(uint))] - internal struct ProbabilisticMap + [StructLayout(LayoutKind.Sequential)] + internal readonly struct ProbabilisticMap { - private const int Size = 0x8; private const int IndexMask = 0x7; private const int IndexShift = 0x3; - /// Initializes the map based on the specified values. - /// A pointer to the beginning of a . - /// The values to set in the map. - public static unsafe void Initialize(uint* charMap, ReadOnlySpan values) + private readonly uint _e0, _e1, _e2, _e3, _e4, _e5, _e6, _e7; + + public ProbabilisticMap(ReadOnlySpan values) { -#if DEBUG - for (int i = 0; i < Size; i++) - { - Debug.Assert(charMap[i] == 0, "Expected charMap to be zero-initialized."); - } -#endif bool hasAscii = false; - uint* charMapLocal = charMap; // https://github.com/dotnet/runtime/issues/9040 + ref uint charMap = ref _e0; for (int i = 0; i < values.Length; ++i) { int c = values[i]; // Map low bit - SetCharBit(charMapLocal, (byte)c); + SetCharBit(ref charMap, (byte)c); // Map high bit c >>= 8; @@ -56,22 +49,30 @@ public static unsafe void Initialize(uint* charMap, ReadOnlySpan values) } else { - SetCharBit(charMapLocal, (byte)c); + SetCharBit(ref charMap, (byte)c); } } if (hasAscii) { // Common to search for ASCII symbols. Just set the high value once. - charMapLocal[0] |= 1u; + charMap |= 1u; } } - public static unsafe bool IsCharBitSet(uint* charMap, byte value) => - (charMap[(uint)value & IndexMask] & (1u << (value >> IndexShift))) != 0; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void SetCharBit(ref uint charMap, byte value) => + Unsafe.Add(ref charMap, (uint)value & IndexMask) |= 1u << (value >> IndexShift); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsCharBitSet(ref uint charMap, byte value) => + (Unsafe.Add(ref charMap, (uint)value & IndexMask) & (1u << (value >> IndexShift))) != 0; - private static unsafe void SetCharBit(uint* charMap, byte value) => - charMap[(uint)value & IndexMask] |= 1u << (value >> IndexShift); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool Contains(ref uint charMap, ReadOnlySpan values, int ch) => + IsCharBitSet(ref charMap, (byte)ch) && + IsCharBitSet(ref charMap, (byte)(ch >> 8)) && + values.Contains((char)ch); [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool ShouldUseSimpleLoop(int searchSpaceLength, int valuesLength) @@ -103,7 +104,7 @@ public static int LastIndexOfAnyExcept(ref char searchSpace, int searchSpaceLeng private static int IndexOfAny(ref char searchSpace, int searchSpaceLength, ref char values, int valuesLength) where TNegator : struct, SpanHelpers.INegator { - ReadOnlySpan valuesSpan = new ReadOnlySpan(ref values, valuesLength); + var valuesSpan = new ReadOnlySpan(ref values, valuesLength); // If the search space is relatively short compared to the needle, do a simple O(n * m) search. if (ShouldUseSimpleLoop(searchSpaceLength, valuesLength)) @@ -113,7 +114,8 @@ private static int IndexOfAny(ref char searchSpace, int searchSpaceLen while (!Unsafe.AreSame(ref cur, ref searchSpaceEnd)) { - if (TNegator.NegateIfNeeded(valuesSpan.Contains(cur))) + char c = cur; + if (TNegator.NegateIfNeeded(valuesSpan.Contains(c))) { return (int)(Unsafe.ByteOffset(ref searchSpace, ref cur) / sizeof(char)); } @@ -142,15 +144,12 @@ private static int LastIndexOfAny(ref char searchSpace, int searchSpac // If the search space is relatively short compared to the needle, do a simple O(n * m) search. if (ShouldUseSimpleLoop(searchSpaceLength, valuesLength)) { - ref char cur = ref Unsafe.Add(ref searchSpace, searchSpaceLength); - - while (!Unsafe.AreSame(ref searchSpace, ref cur)) + for (int i = searchSpaceLength - 1; i >= 0; i--) { - cur = ref Unsafe.Subtract(ref cur, 1); - - if (TNegator.NegateIfNeeded(valuesSpan.Contains(cur))) + char c = Unsafe.Add(ref searchSpace, i); + if (TNegator.NegateIfNeeded(valuesSpan.Contains(c))) { - return (int)(Unsafe.ByteOffset(ref searchSpace, ref cur) / sizeof(char)); + return i; } } @@ -167,25 +166,45 @@ private static int LastIndexOfAny(ref char searchSpace, int searchSpac return ProbabilisticLastIndexOfAny(ref searchSpace, searchSpaceLength, ref values, valuesLength); } - private static unsafe int ProbabilisticIndexOfAny(ref char searchSpace, int searchSpaceLength, ref char values, int valuesLength) + [MethodImpl(MethodImplOptions.NoInlining)] + private static int ProbabilisticIndexOfAny(ref char searchSpace, int searchSpaceLength, ref char values, int valuesLength) + where TNegator : struct, SpanHelpers.INegator + { + var valuesSpan = new ReadOnlySpan(ref values, valuesLength); + + var map = new ProbabilisticMap(valuesSpan); + ref uint charMap = ref Unsafe.As(ref map); + + return typeof(TNegator) == typeof(SpanHelpers.DontNegate) + ? IndexOfAny(ref charMap, ref searchSpace, searchSpaceLength, valuesSpan) + : IndexOfAny(ref charMap, ref searchSpace, searchSpaceLength, valuesSpan); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int ProbabilisticLastIndexOfAny(ref char searchSpace, int searchSpaceLength, ref char values, int valuesLength) where TNegator : struct, SpanHelpers.INegator { var valuesSpan = new ReadOnlySpan(ref values, valuesLength); - ProbabilisticMap map = default; - uint* charMap = (uint*)↦ - Initialize(charMap, valuesSpan); + var map = new ProbabilisticMap(valuesSpan); + ref uint charMap = ref Unsafe.As(ref map); + + return typeof(TNegator) == typeof(SpanHelpers.DontNegate) + ? LastIndexOfAny(ref charMap, ref searchSpace, searchSpaceLength, valuesSpan) + : LastIndexOfAny(ref charMap, ref searchSpace, searchSpaceLength, valuesSpan); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAny(ref uint charMap, ref char searchSpace, int searchSpaceLength, ReadOnlySpan values) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator + { ref char searchSpaceEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength); ref char cur = ref searchSpace; while (!Unsafe.AreSame(ref cur, ref searchSpaceEnd)) { int ch = cur; - if (TNegator.NegateIfNeeded( - IsCharBitSet(charMap, (byte)ch) && - IsCharBitSet(charMap, (byte)(ch >> 8)) && - valuesSpan.Contains((char)ch))) + if (TNegator.NegateIfNeeded(Contains(ref charMap, values, ch))) { return (int)(Unsafe.ByteOffset(ref searchSpace, ref cur) / sizeof(char)); } @@ -196,28 +215,16 @@ private static unsafe int ProbabilisticIndexOfAny(ref char searchSpace return -1; } - private static unsafe int ProbabilisticLastIndexOfAny(ref char searchSpace, int searchSpaceLength, ref char values, int valuesLength) - where TNegator : struct, SpanHelpers.INegator + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAny(ref uint charMap, ref char searchSpace, int searchSpaceLength, ReadOnlySpan values) + where TNegator : struct, IndexOfAnyAsciiSearcher.INegator { - var valuesSpan = new ReadOnlySpan(ref values, valuesLength); - - ProbabilisticMap map = default; - uint* charMap = (uint*)↦ - Initialize(charMap, valuesSpan); - - ref char cur = ref Unsafe.Add(ref searchSpace, searchSpaceLength); - - while (!Unsafe.AreSame(ref searchSpace, ref cur)) + for (int i = searchSpaceLength - 1; i >= 0; i--) { - cur = ref Unsafe.Subtract(ref cur, 1); - - int ch = cur; - if (TNegator.NegateIfNeeded( - IsCharBitSet(charMap, (byte)ch) && - IsCharBitSet(charMap, (byte)(ch >> 8)) && - valuesSpan.Contains((char)ch))) + int ch = Unsafe.Add(ref searchSpace, i); + if (TNegator.NegateIfNeeded(Contains(ref charMap, values, ch))) { - return (int)(Unsafe.ByteOffset(ref searchSpace, ref cur) / sizeof(char)); + return i; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 9bb51d939f255..b218550bd2720 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.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.Buffers; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; @@ -532,6 +533,18 @@ public static int IndexOfAnyExcept(this Span span, T value0, T value1, T v public static int IndexOfAnyExcept(this Span span, ReadOnlySpan values) where T : IEquatable? => IndexOfAnyExcept((ReadOnlySpan)span, values); + /// Searches for the first index of any value other than the specified . + /// The type of the span and values. + /// The span to search. + /// The values to avoid. + /// + /// The index in the span of the first occurrence of any value other than those in . + /// If all of the values are in , returns -1. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfAnyExcept(this Span span, IndexOfAnyValues values) where T : IEquatable? => + IndexOfAnyExcept((ReadOnlySpan)span, values); + /// Searches for the first index of any value other than the specified . /// The type of the span and values. /// The span to search. @@ -716,6 +729,36 @@ public static int IndexOfAnyExcept(this ReadOnlySpan span, ReadOnlySpan return IndexOfAnyExcept(span, values[0], values[1], values[2], values[3]); default: + if (RuntimeHelpers.IsBitwiseEquatable()) + { + if (Unsafe.SizeOf() == sizeof(byte) && values.Length == 5) + { + ref byte valuesRef = ref Unsafe.As(ref MemoryMarshal.GetReference(values)); + + return SpanHelpers.IndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + valuesRef, + Unsafe.Add(ref valuesRef, 1), + Unsafe.Add(ref valuesRef, 2), + Unsafe.Add(ref valuesRef, 3), + Unsafe.Add(ref valuesRef, 4), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short) && values.Length == 5) + { + ref short valuesRef = ref Unsafe.As(ref MemoryMarshal.GetReference(values)); + + return SpanHelpers.IndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + valuesRef, + Unsafe.Add(ref valuesRef, 1), + Unsafe.Add(ref valuesRef, 2), + Unsafe.Add(ref valuesRef, 3), + Unsafe.Add(ref valuesRef, 4), + span.Length); + } + } + if (RuntimeHelpers.IsBitwiseEquatable() && Unsafe.SizeOf() == sizeof(char)) { return ProbabilisticMap.IndexOfAnyExcept( @@ -737,6 +780,18 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(values)), } } + /// Searches for the first index of any value other than the specified . + /// The type of the span and values. + /// The span to search. + /// The values to avoid. + /// + /// The index in the span of the first occurrence of any value other than those in . + /// If all of the values are in , returns -1. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfAnyExcept(this ReadOnlySpan span, IndexOfAnyValues values) where T : IEquatable? => + IndexOfAnyValues.IndexOfAnyExcept(span, values); + /// Searches for the last index of any value other than the specified . /// The type of the span and values. /// The span to search. @@ -784,6 +839,18 @@ public static int LastIndexOfAnyExcept(this Span span, T value0, T value1, public static int LastIndexOfAnyExcept(this Span span, ReadOnlySpan values) where T : IEquatable? => LastIndexOfAnyExcept((ReadOnlySpan)span, values); + /// Searches for the last index of any value other than the specified . + /// The type of the span and values. + /// The span to search. + /// The values to avoid. + /// + /// The index in the span of the first occurrence of any value other than those in . + /// If all of the values are in , returns -1. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOfAnyExcept(this Span span, IndexOfAnyValues values) where T : IEquatable? => + LastIndexOfAnyExcept((ReadOnlySpan)span, values); + /// Searches for the last index of any value other than the specified . /// The type of the span and values. /// The span to search. @@ -969,6 +1036,36 @@ public static int LastIndexOfAnyExcept(this ReadOnlySpan span, ReadOnlySpa return LastIndexOfAnyExcept(span, values[0], values[1], values[2], values[3]); default: + if (RuntimeHelpers.IsBitwiseEquatable()) + { + if (Unsafe.SizeOf() == sizeof(byte) && values.Length == 5) + { + ref byte valuesRef = ref Unsafe.As(ref MemoryMarshal.GetReference(values)); + + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + valuesRef, + Unsafe.Add(ref valuesRef, 1), + Unsafe.Add(ref valuesRef, 2), + Unsafe.Add(ref valuesRef, 3), + Unsafe.Add(ref valuesRef, 4), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short) && values.Length == 5) + { + ref short valuesRef = ref Unsafe.As(ref MemoryMarshal.GetReference(values)); + + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + valuesRef, + Unsafe.Add(ref valuesRef, 1), + Unsafe.Add(ref valuesRef, 2), + Unsafe.Add(ref valuesRef, 3), + Unsafe.Add(ref valuesRef, 4), + span.Length); + } + } + if (RuntimeHelpers.IsBitwiseEquatable() && Unsafe.SizeOf() == sizeof(char)) { return ProbabilisticMap.LastIndexOfAnyExcept( @@ -990,6 +1087,18 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(values)), } } + /// Searches for the last index of any value other than the specified . + /// The type of the span and values. + /// The span to search. + /// The values to avoid. + /// + /// The index in the span of the first occurrence of any value other than those in . + /// If all of the values are in , returns -1. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOfAnyExcept(this ReadOnlySpan span, IndexOfAnyValues values) where T : IEquatable? => + IndexOfAnyValues.LastIndexOfAnyExcept(span, values); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexOfAnyInRange(this Span span, T lowInclusive, T highInclusive) @@ -1526,6 +1635,15 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), public static int IndexOfAny(this Span span, ReadOnlySpan values) where T : IEquatable? => IndexOfAny((ReadOnlySpan)span, values); + /// + /// 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, IndexOfAnyValues values) where T : IEquatable? => + IndexOfAny((ReadOnlySpan)span, values); + /// /// 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. /// @@ -1707,6 +1825,15 @@ public static int IndexOfAny(this ReadOnlySpan span, ReadOnlySpan value return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(values), 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. + /// The set of values to search for. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfAny(this ReadOnlySpan span, IndexOfAnyValues values) where T : IEquatable? => + IndexOfAnyValues.IndexOfAny(span, values); + /// /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1. /// @@ -1783,6 +1910,15 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), public static int LastIndexOfAny(this Span span, ReadOnlySpan values) where T : IEquatable? => LastIndexOfAny((ReadOnlySpan)span, values); + /// + /// Searches for the last index of any of the specified values similar to calling LastIndexOf 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 LastIndexOfAny(this Span span, IndexOfAnyValues values) where T : IEquatable? => + LastIndexOfAny((ReadOnlySpan)span, values); + /// /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1. /// @@ -1897,6 +2033,16 @@ public static int LastIndexOfAny(this ReadOnlySpan span, ReadOnlySpan v Unsafe.Add(ref valueRef, 3), span.Length); #endif + + case 5: + return SpanHelpers.LastIndexOfAnyValueType( + ref spanRef, + valueRef, + Unsafe.Add(ref valueRef, 1), + Unsafe.Add(ref valueRef, 2), + Unsafe.Add(ref valueRef, 3), + Unsafe.Add(ref valueRef, 4), + span.Length); } } @@ -1938,6 +2084,16 @@ public static int LastIndexOfAny(this ReadOnlySpan span, ReadOnlySpan v span.Length); #endif + case 5: + return SpanHelpers.LastIndexOfAnyValueType( + ref spanRef, + valueRef, + Unsafe.Add(ref valueRef, 1), + Unsafe.Add(ref valueRef, 2), + Unsafe.Add(ref valueRef, 3), + Unsafe.Add(ref valueRef, 4), + span.Length); + default: return ProbabilisticMap.LastIndexOfAny(ref Unsafe.As(ref spanRef), span.Length, ref Unsafe.As(ref valueRef), values.Length); } @@ -1947,6 +2103,15 @@ public static int LastIndexOfAny(this ReadOnlySpan span, ReadOnlySpan v return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(values), values.Length); } + /// + /// Searches for the last index of any of the specified values similar to calling LastIndexOf 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 LastIndexOfAny(this ReadOnlySpan span, IndexOfAnyValues values) where T : IEquatable? => + IndexOfAnyValues.LastIndexOfAny(span, values); + /// /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T). /// 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 ea734fd5e289a..0102f8c6751d7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs @@ -1990,31 +1990,40 @@ private static int IndexOfAnyValueType(ref TValue searchSpace, return -1; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyValueType(ref T searchSpace, T value0, T value1, T value2, T value3, T value4, int length) where T : struct, INumber + => IndexOfAnyValueType>(ref searchSpace, value0, value1, value2, value3, value4, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, T value2, T value3, T value4, int length) where T : struct, INumber + => IndexOfAnyValueType>(ref searchSpace, value0, value1, value2, value3, value4, length); + [MethodImpl(MethodImplOptions.AggressiveOptimization)] - internal static int IndexOfAnyValueType(ref T searchSpace, T value0, T value1, T value2, T value3, T value4, int length) - where T : struct, INumber + private static int IndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, TValue value2, TValue value3, TValue value4, int length) + where TValue : struct, INumber + where TNegator : struct, INegator { 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"); - if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) { nuint offset = 0; - T lookUp; + TValue lookUp; while (length >= 4) { length -= 4; - ref T current = ref Unsafe.Add(ref searchSpace, offset); + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); lookUp = current; - if (lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4) return (int)offset; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4)) return (int)offset; lookUp = Unsafe.Add(ref current, 1); - if (lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4) return (int)offset + 1; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4)) return (int)offset + 1; lookUp = Unsafe.Add(ref current, 2); - if (lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4) return (int)offset + 2; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4)) return (int)offset + 2; lookUp = Unsafe.Add(ref current, 3); - if (lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4) return (int)offset + 3; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4)) return (int)offset + 3; offset += 4; } @@ -2024,27 +2033,27 @@ internal static int IndexOfAnyValueType(ref T searchSpace, T value0, T value1 length -= 1; lookUp = Unsafe.Add(ref searchSpace, offset); - if (lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4) return (int)offset; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4)) return (int)offset; offset += 1; } } - else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) { - Vector256 equals, current, values0 = Vector256.Create(value0), values1 = Vector256.Create(value1), + Vector256 equals, current, values0 = Vector256.Create(value0), values1 = Vector256.Create(value1), values2 = Vector256.Create(value2), values3 = Vector256.Create(value3), values4 = Vector256.Create(value4); - ref T currentSearchSpace = ref searchSpace; - ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, (uint)(length - Vector256.Count)); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, (uint)(length - Vector256.Count)); // Loop until either we've finished all elements or there's less than a vector's-worth remaining. do { current = Vector256.LoadUnsafe(ref currentSearchSpace); - equals = Vector256.Equals(values0, current) | Vector256.Equals(values1, current) | Vector256.Equals(values2, current) - | Vector256.Equals(values3, current) | Vector256.Equals(values4, current); - if (equals == Vector256.Zero) + equals = TNegator.NegateIfNeeded(Vector256.Equals(values0, current) | Vector256.Equals(values1, current) | Vector256.Equals(values2, current) + | Vector256.Equals(values3, current) | Vector256.Equals(values4, current)); + if (equals == Vector256.Zero) { - currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector256.Count); + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector256.Count); continue; } @@ -2053,12 +2062,12 @@ internal static int IndexOfAnyValueType(ref T searchSpace, T value0, T value1 while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); // If any elements remain, process the last vector in the search space. - if ((uint)length % Vector256.Count != 0) + if ((uint)length % Vector256.Count != 0) { current = Vector256.LoadUnsafe(ref oneVectorAwayFromEnd); - equals = Vector256.Equals(values0, current) | Vector256.Equals(values1, current) | Vector256.Equals(values2, current) - | Vector256.Equals(values3, current) | Vector256.Equals(values4, current); - if (equals != Vector256.Zero) + equals = TNegator.NegateIfNeeded(Vector256.Equals(values0, current) | Vector256.Equals(values1, current) | Vector256.Equals(values2, current) + | Vector256.Equals(values3, current) | Vector256.Equals(values4, current)); + if (equals != Vector256.Zero) { return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); } @@ -2066,20 +2075,20 @@ internal static int IndexOfAnyValueType(ref T searchSpace, T value0, T value1 } else { - Vector128 equals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1), + Vector128 equals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1), values2 = Vector128.Create(value2), values3 = Vector128.Create(value3), values4 = Vector128.Create(value4); - ref T currentSearchSpace = ref searchSpace; - ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, (uint)(length - Vector128.Count)); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, (uint)(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); - equals = Vector128.Equals(values0, current) | Vector128.Equals(values1, current) | Vector128.Equals(values2, current) - | Vector128.Equals(values3, current) | Vector128.Equals(values4, current); - if (equals == Vector128.Zero) + equals = TNegator.NegateIfNeeded(Vector128.Equals(values0, current) | Vector128.Equals(values1, current) | Vector128.Equals(values2, current) + | Vector128.Equals(values3, current) | Vector128.Equals(values4, current)); + if (equals == Vector128.Zero) { - currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); continue; } @@ -2088,12 +2097,12 @@ internal static int IndexOfAnyValueType(ref T searchSpace, T value0, T value1 while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); // If any elements remain, process the first vector in the search space. - if ((uint)length % Vector128.Count != 0) + if ((uint)length % Vector128.Count != 0) { current = Vector128.LoadUnsafe(ref oneVectorAwayFromEnd); - equals = Vector128.Equals(values0, current) | Vector128.Equals(values1, current) | Vector128.Equals(values2, current) - | Vector128.Equals(values3, current) | Vector128.Equals(values4, current); - if (equals != Vector128.Zero) + equals = TNegator.NegateIfNeeded(Vector128.Equals(values0, current) | Vector128.Equals(values1, current) | Vector128.Equals(values2, current) + | Vector128.Equals(values3, current) | Vector128.Equals(values4, current)); + if (equals != Vector128.Zero) { return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); } @@ -2717,6 +2726,123 @@ public static void ReplaceValueType(ref T src, ref T dst, T oldValue, T newVa } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyValueType(ref T searchSpace, T value0, T value1, T value2, T value3, T value4, int length) where T : struct, INumber + => LastIndexOfAnyValueType>(ref searchSpace, value0, value1, value2, value3, value4, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, T value2, T value3, T value4, int length) where T : struct, INumber + => LastIndexOfAnyValueType>(ref searchSpace, value0, value1, value2, value3, value4, length); + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static int LastIndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, TValue value2, TValue value3, TValue value4, int length) + where TValue : struct, INumber + where TNegator : struct, INegator + { + 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"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = (nuint)length - 1; + TValue lookUp; + + while (length >= 4) + { + length -= 4; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4)) return (int)offset; + lookUp = Unsafe.Add(ref current, -1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4)) return (int)offset - 1; + lookUp = Unsafe.Add(ref current, -2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4)) return (int)offset - 2; + lookUp = Unsafe.Add(ref current, -3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4)) return (int)offset - 3; + + offset -= 4; + } + + while (length > 0) + { + length -= 1; + + lookUp = Unsafe.Add(ref searchSpace, offset); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4)) return (int)offset; + + offset -= 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, current, values0 = Vector256.Create(value0), values1 = Vector256.Create(value1), + values2 = Vector256.Create(value2), values3 = Vector256.Create(value3), values4 = Vector256.Create(value4); + nint offset = length - Vector256.Count; + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + while (offset > 0) + { + current = Vector256.LoadUnsafe(ref searchSpace, (nuint)(offset)); + equals = TNegator.NegateIfNeeded(Vector256.Equals(current, values0) | Vector256.Equals(current, values1) | Vector256.Equals(current, values2) + | Vector256.Equals(current, values3) | Vector256.Equals(current, values4)); + if (equals == Vector256.Zero) + { + offset -= Vector256.Count; + continue; + } + + return ComputeLastIndex(offset, equals); + } + + // Process the first vector in the search space. + + current = Vector256.LoadUnsafe(ref searchSpace); + equals = TNegator.NegateIfNeeded(Vector256.Equals(current, values0) | Vector256.Equals(current, values1) | Vector256.Equals(current, values2) + | Vector256.Equals(current, values3) | Vector256.Equals(current, values4)); + + if (equals != Vector256.Zero) + { + return ComputeLastIndex(offset: 0, equals); + } + } + else + { + Vector128 equals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1), + values2 = Vector128.Create(value2), values3 = Vector128.Create(value3), values4 = Vector128.Create(value4); + nint offset = length - Vector128.Count; + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + while (offset > 0) + { + current = Vector128.LoadUnsafe(ref searchSpace, (nuint)(offset)); + equals = TNegator.NegateIfNeeded(Vector128.Equals(current, values0) | Vector128.Equals(current, values1) | Vector128.Equals(current, values2) + | Vector128.Equals(current, values3) | Vector128.Equals(current, values4)); + + if (equals == Vector128.Zero) + { + offset -= Vector128.Count; + continue; + } + + return ComputeLastIndex(offset, equals); + } + + // Process the first vector in the search space. + + current = Vector128.LoadUnsafe(ref searchSpace); + equals = TNegator.NegateIfNeeded(Vector128.Equals(current, values0) | Vector128.Equals(current, values1) | Vector128.Equals(current, values2) + | Vector128.Equals(current, values3) | Vector128.Equals(current, values4)); + + if (equals != Vector128.Zero) + { + return ComputeLastIndex(offset: 0, equals); + } + } + + return -1; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int ComputeFirstIndex(ref T searchSpace, ref T current, Vector128 equals) where T : struct { diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs index a2524d0d3436d..58a2a92ef1919 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.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.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -1639,16 +1640,13 @@ private void MakeSeparatorList(ReadOnlySpan separators, ref ValueListBuild { unsafe { - ProbabilisticMap map = default; - uint* charMap = (uint*)↦ - ProbabilisticMap.Initialize(charMap, separators); + var map = new ProbabilisticMap(separators); + ref uint charMap = ref Unsafe.As(ref map); for (int i = 0; i < Length; i++) { char c = this[i]; - if (ProbabilisticMap.IsCharBitSet(charMap, (byte)c) && - ProbabilisticMap.IsCharBitSet(charMap, (byte)(c >> 8)) && - separators.Contains(c)) + if (ProbabilisticMap.Contains(ref charMap, separators, c)) { sepListBuilder.Append(i); } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 8406052aab3b5..713acb7e8120b 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -7052,6 +7052,15 @@ public partial interface IMemoryOwner : System.IDisposable { System.Memory Memory { get; } } + public class IndexOfAnyValues where T : System.IEquatable? + { + internal IndexOfAnyValues() { } + } + public static class IndexOfAnyValues + { + public static System.Buffers.IndexOfAnyValues Create(System.ReadOnlySpan values) { throw null; } + public static System.Buffers.IndexOfAnyValues Create(System.ReadOnlySpan values) { throw null; } + } public partial interface IPinnable { System.Buffers.MemoryHandle Pin(int elementIndex);