From e4471c1238499c752ccd06381c7179c10696441d Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 26 Oct 2022 03:25:48 -0400 Subject: [PATCH] Add {Last}IndexOfAny{Except}InRange (#76803) * Add {Last}IndexOfAny{Exception}InRange * Address PR feedback * Address PR feedback --- .../src/System/Net/HttpValidationHelpers.cs | 13 +- .../System.Memory/ref/System.Memory.cs | 8 + .../tests/Span/IndexOfAnyInRange.cs | 202 +++++++++++++ .../tests/System.Memory.Tests.csproj | 1 + .../Net/Http/Headers/UriHeaderParser.cs | 49 ++-- .../src/System.Net.WebHeaderCollection.csproj | 1 + .../src/System/Environment.OSVersion.Unix.cs | 9 +- .../src/System/Globalization/TextInfo.cs | 90 +----- .../src/System/Globalization/TimeSpanParse.cs | 27 +- .../src/System/MemoryExtensions.cs | 273 ++++++++++++++++++ .../src/System/SpanHelpers.T.cs | 240 ++++++++++++++- .../src/System/Xml/XmlCharType.cs | 11 +- 12 files changed, 759 insertions(+), 165 deletions(-) create mode 100644 src/libraries/System.Memory/tests/Span/IndexOfAnyInRange.cs diff --git a/src/libraries/Common/src/System/Net/HttpValidationHelpers.cs b/src/libraries/Common/src/System/Net/HttpValidationHelpers.cs index 4ea758adcc525..2af2721d0fb88 100644 --- a/src/libraries/Common/src/System/Net/HttpValidationHelpers.cs +++ b/src/libraries/Common/src/System/Net/HttpValidationHelpers.cs @@ -21,17 +21,8 @@ internal static string CheckBadHeaderNameChars(string name) return name; } - internal static bool ContainsNonAsciiChars(string token) - { - for (int i = 0; i < token.Length; ++i) - { - if ((token[i] < 0x20) || (token[i] > 0x7e)) - { - return true; - } - } - return false; - } + internal static bool ContainsNonAsciiChars(string token) => + token.AsSpan().IndexOfAnyExceptInRange((char)0x20, (char)0x7e) >= 0; internal static bool IsValidToken(string token) { diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs index b9fadb244beb8..9836bb6eb3742 100644 --- a/src/libraries/System.Memory/ref/System.Memory.cs +++ b/src/libraries/System.Memory/ref/System.Memory.cs @@ -257,10 +257,14 @@ public static void CopyTo(this T[]? source, System.Span destination) { } 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.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; } public static int IndexOf(this System.ReadOnlySpan span, System.ReadOnlySpan value) where T : System.IEquatable? { throw null; } public static int IndexOf(this System.ReadOnlySpan span, T value) where T : System.IEquatable? { throw null; } public static int IndexOf(this System.Span span, System.ReadOnlySpan value) where T : System.IEquatable? { throw null; } public static int IndexOf(this System.Span span, T value) where T : System.IEquatable? { throw null; } + public static int IndexOfAnyInRange(this System.ReadOnlySpan span, T lowInclusive, T highInclusive) where T : System.IComparable { throw null; } + 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.ReadOnlySpan values) where T : System.IEquatable? { throw null; } @@ -277,10 +281,14 @@ public static void CopyTo(this T[]? source, System.Span destination) { } 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.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; } public static int LastIndexOf(this System.ReadOnlySpan span, System.ReadOnlySpan value) where T : System.IEquatable? { throw null; } public static int LastIndexOf(this System.ReadOnlySpan span, T value) where T : System.IEquatable? { throw null; } public static int LastIndexOf(this System.Span span, System.ReadOnlySpan value) where T : System.IEquatable { throw null; } public static int LastIndexOf(this System.Span span, T value) where T : System.IEquatable? { throw null; } + public static int LastIndexOfAnyInRange(this System.ReadOnlySpan span, T lowInclusive, T highInclusive) where T : System.IComparable { throw null; } + public static int LastIndexOfAnyInRange(this System.Span span, T lowInclusive, T highInclusive) where T : System.IComparable { throw null; } public static bool Overlaps(this System.ReadOnlySpan span, System.ReadOnlySpan other) { throw null; } public static bool Overlaps(this System.ReadOnlySpan span, System.ReadOnlySpan other, out int elementOffset) { throw null; } public static bool Overlaps(this System.Span span, System.ReadOnlySpan other) { throw null; } diff --git a/src/libraries/System.Memory/tests/Span/IndexOfAnyInRange.cs b/src/libraries/System.Memory/tests/Span/IndexOfAnyInRange.cs new file mode 100644 index 0000000000000..1814e10949c2e --- /dev/null +++ b/src/libraries/System.Memory/tests/Span/IndexOfAnyInRange.cs @@ -0,0 +1,202 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using Xunit; + +namespace System.SpanTests +{ + public class IndexOfAnyInRangeTests_Byte : IndexOfAnyInRangeTests { protected override byte Create(int value) => (byte)value; } + public class IndexOfAnyInRangeTests_SByte : IndexOfAnyInRangeTests { protected override sbyte Create(int value) => (sbyte)value; } + public class IndexOfAnyInRangeTests_Char : IndexOfAnyInRangeTests { protected override char Create(int value) => (char)value; } + public class IndexOfAnyInRangeTests_Int16 : IndexOfAnyInRangeTests { protected override short Create(int value) => (short)value; } + public class IndexOfAnyInRangeTests_UInt16 : IndexOfAnyInRangeTests { protected override ushort Create(int value) => (ushort)value; } + public class IndexOfAnyInRangeTests_Int32 : IndexOfAnyInRangeTests { protected override int Create(int value) => value; } + public class IndexOfAnyInRangeTests_UInt32 : IndexOfAnyInRangeTests { protected override uint Create(int value) => (uint)value; } + public class IndexOfAnyInRangeTests_Int64 : IndexOfAnyInRangeTests { protected override long Create(int value) => value; } + public class IndexOfAnyInRangeTests_UInt64 : IndexOfAnyInRangeTests { protected override ulong Create(int value) => (ulong)value; } + public class IndexOfAnyInRangeTests_IntPtr : IndexOfAnyInRangeTests { protected override nint Create(int value) => (nint)value; } + public class IndexOfAnyInRangeTests_UIntPtr : IndexOfAnyInRangeTests { protected override nuint Create(int value) => (nuint)value; } + public class IndexOfAnyInRangeTests_TimeSpan : IndexOfAnyInRangeTests { protected override TimeSpan Create(int value) => TimeSpan.FromTicks(value); } + + public class IndexOfAnyInRangeTests_RefType : IndexOfAnyInRangeTests + { + protected override RefType Create(int value) => new RefType { Value = value }; + + [Fact] + public void NullBound_Throws() + { + foreach ((RefType low, RefType high) in new (RefType,RefType)[] { (null, new RefType { Value = 42 }), (null, null), (new RefType { Value = 42 }, null) }) + { + string argName = low is null ? "lowInclusive" : "highInclusive"; + + AssertExtensions.Throws(argName, () => MemoryExtensions.IndexOfAnyInRange(Span.Empty, low, high)); + AssertExtensions.Throws(argName, () => MemoryExtensions.IndexOfAnyInRange(ReadOnlySpan.Empty, low, high)); + + AssertExtensions.Throws(argName, () => MemoryExtensions.LastIndexOfAnyInRange(Span.Empty, low, high)); + AssertExtensions.Throws(argName, () => MemoryExtensions.LastIndexOfAnyInRange(ReadOnlySpan.Empty, low, high)); + + AssertExtensions.Throws(argName, () => MemoryExtensions.IndexOfAnyExceptInRange(Span.Empty, low, high)); + AssertExtensions.Throws(argName, () => MemoryExtensions.IndexOfAnyExceptInRange(ReadOnlySpan.Empty, low, high)); + + AssertExtensions.Throws(argName, () => MemoryExtensions.LastIndexOfAnyExceptInRange(Span.Empty, low, high)); + AssertExtensions.Throws(argName, () => MemoryExtensions.LastIndexOfAnyExceptInRange(ReadOnlySpan.Empty, low, high)); + } + } + + [Fact] + public void NullPermittedInInput() + { + RefType[] array = new[] { null, new RefType { Value = 1 }, null, null, new RefType { Value = 42 }, null }; + RefType low = new RefType { Value = 41 }, high = new RefType { Value = 43 }; + + Assert.Equal(4, IndexOfAnyInRange(array, low, high)); + Assert.Equal(4, LastIndexOfAnyInRange(array, low, high)); + + Assert.Equal(0, IndexOfAnyExceptInRange(array, low, high)); + Assert.Equal(5, LastIndexOfAnyExceptInRange(array, low, high)); + } + } + + public class RefType : IComparable + { + public int Value { get; set; } + public int CompareTo(RefType? other) => other is null ? 1 : Value.CompareTo(other.Value); + } + + public abstract class IndexOfAnyInRangeTests + where T : IComparable + { + protected abstract T Create(int value); + + [Fact] + public void EmptyInput_ReturnsMinus1() + { + T[] array = Array.Empty(); + T low = Create(0); + T high = Create(255); + + Assert.Equal(-1, IndexOfAnyInRange(array, low, high)); + Assert.Equal(-1, LastIndexOfAnyInRange(array, low, high)); + Assert.Equal(-1, IndexOfAnyExceptInRange(array, low, high)); + Assert.Equal(-1, LastIndexOfAnyExceptInRange(array, low, high)); + } + + [Fact] + public void NotFound_ReturnsMinus1() + { + T fillValue = Create(42); + for (int length = 0; length < 128; length++) + { + T[] array = Enumerable.Repeat(fillValue, length).ToArray(); + + Assert.Equal(-1, IndexOfAnyInRange(array, Create(43), Create(44))); + Assert.Equal(-1, LastIndexOfAnyInRange(array, Create(43), Create(44))); + Assert.Equal(-1, IndexOfAnyExceptInRange(array, Create(42), Create(44))); + Assert.Equal(-1, LastIndexOfAnyExceptInRange(array, Create(42), Create(44))); + } + } + + [Fact] + public void FindValueInSequence_ReturnsFoundIndexOrMinus1() + { + for (int length = 1; length <= 64; length++) + { + T[] array = Enumerable.Range(0, length).Select(Create).ToArray(); + + Assert.Equal(0, IndexOfAnyInRange(array, Create(0), Create(length - 1))); + Assert.Equal(length - 1, LastIndexOfAnyInRange(array, Create(0), Create(length - 1))); + Assert.Equal(-1, IndexOfAnyExceptInRange(array, Create(0), Create(length - 1))); + Assert.Equal(-1, LastIndexOfAnyExceptInRange(array, Create(0), Create(length - 1))); + + if (length > 4) + { + Assert.Equal(length / 4, IndexOfAnyInRange(array, Create(length / 4), Create(length * 3 / 4))); + Assert.Equal(length * 3 / 4, LastIndexOfAnyInRange(array, Create(length / 4), Create(length * 3 / 4))); + Assert.Equal(0, IndexOfAnyExceptInRange(array, Create(length / 4), Create(length * 3 / 4))); + Assert.Equal(length - 1, LastIndexOfAnyExceptInRange(array, Create(length / 4), Create(length * 3 / 4))); + } + } + } + + [Fact] + public void MatrixOfLengthsAndOffsets_Any_FindsValueAtExpectedIndex() + { + foreach (int length in Enumerable.Range(1, 40)) + { + for (int offset = 0; offset < length; offset++) + { + int target = -42; + T[] array = Enumerable.Repeat(Create(target - 2), length).ToArray(); + array[offset] = Create(target); + + Assert.Equal(offset, IndexOfAnyInRange(array, Create(target), Create(target))); + Assert.Equal(offset, IndexOfAnyInRange(array, Create(target - 1), Create(target))); + Assert.Equal(offset, IndexOfAnyInRange(array, Create(target), Create(target + 1))); + Assert.Equal(offset, IndexOfAnyInRange(array, Create(target - 1), Create(target + 1))); + + Assert.Equal(offset, LastIndexOfAnyInRange(array, Create(target), Create(target))); + Assert.Equal(offset, LastIndexOfAnyInRange(array, Create(target - 1), Create(target))); + Assert.Equal(offset, LastIndexOfAnyInRange(array, Create(target), Create(target + 1))); + Assert.Equal(offset, LastIndexOfAnyInRange(array, Create(target - 1), Create(target + 1))); + } + } + } + + [Fact] + public void MatrixOfLengthsAndOffsets_AnyExcept_FindsValueAtExpectedIndex() + { + foreach (int length in Enumerable.Range(1, 40)) + { + for (int offset = 0; offset < length; offset++) + { + int target = -42; + T[] array = Enumerable.Repeat(Create(target), length).ToArray(); + array[offset] = Create(target - 2); + + Assert.Equal(offset, IndexOfAnyExceptInRange(array, Create(target), Create(target))); + Assert.Equal(offset, IndexOfAnyExceptInRange(array, Create(target - 1), Create(target))); + Assert.Equal(offset, IndexOfAnyExceptInRange(array, Create(target), Create(target + 1))); + Assert.Equal(offset, IndexOfAnyExceptInRange(array, Create(target - 1), Create(target + 1))); + + Assert.Equal(offset, LastIndexOfAnyExceptInRange(array, Create(target), Create(target))); + Assert.Equal(offset, LastIndexOfAnyExceptInRange(array, Create(target - 1), Create(target))); + Assert.Equal(offset, LastIndexOfAnyExceptInRange(array, Create(target), Create(target + 1))); + Assert.Equal(offset, LastIndexOfAnyExceptInRange(array, Create(target - 1), Create(target + 1))); + } + } + } + + // Wrappers for {Last}IndexOfAny{Except}InRange that invoke both the Span and ReadOnlySpan overloads, + // ensuring they both produce the same result, and returning that result. + // This avoids needing to code the same call sites twice in all the above tests. + + protected static int IndexOfAnyInRange(Span span, T lowInclusive, T highInclusive) + { + int result = MemoryExtensions.IndexOfAnyInRange(span, lowInclusive, highInclusive); + Assert.Equal(result, MemoryExtensions.IndexOfAnyInRange((ReadOnlySpan)span, lowInclusive, highInclusive)); + return result; + } + + protected static int LastIndexOfAnyInRange(Span span, T lowInclusive, T highInclusive) + { + int result = MemoryExtensions.LastIndexOfAnyInRange(span, lowInclusive, highInclusive); + Assert.Equal(result, MemoryExtensions.LastIndexOfAnyInRange((ReadOnlySpan)span, lowInclusive, highInclusive)); + return result; + } + + protected static int IndexOfAnyExceptInRange(Span span, T lowInclusive, T highInclusive) + { + int result = MemoryExtensions.IndexOfAnyExceptInRange(span, lowInclusive, highInclusive); + Assert.Equal(result, MemoryExtensions.IndexOfAnyExceptInRange((ReadOnlySpan)span, lowInclusive, highInclusive)); + return result; + } + + protected static int LastIndexOfAnyExceptInRange(Span span, T lowInclusive, T highInclusive) + { + int result = MemoryExtensions.LastIndexOfAnyExceptInRange(span, lowInclusive, highInclusive); + Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExceptInRange((ReadOnlySpan)span, lowInclusive, highInclusive)); + return result; + } + } +} diff --git a/src/libraries/System.Memory/tests/System.Memory.Tests.csproj b/src/libraries/System.Memory/tests/System.Memory.Tests.csproj index 5e0b857be6a71..fa93b4b69343b 100644 --- a/src/libraries/System.Memory/tests/System.Memory.Tests.csproj +++ b/src/libraries/System.Memory/tests/System.Memory.Tests.csproj @@ -86,6 +86,7 @@ + diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/UriHeaderParser.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/UriHeaderParser.cs index 08d15634942ec..7a29df1494691 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/UriHeaderParser.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/UriHeaderParser.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Text; namespace System.Net.Http.Headers { @@ -59,44 +60,28 @@ public override bool TryParseValue([NotNullWhen(true)] string? value, object? st // If not, just return the input value. internal static string DecodeUtf8FromString(string input) { - if (string.IsNullOrWhiteSpace(input)) + if (!string.IsNullOrWhiteSpace(input)) { - return input; - } - - bool possibleUtf8 = false; - for (int i = 0; i < input.Length; i++) - { - if (input[i] > (char)255) - { - return input; // This couldn't have come from the wire, someone assigned it directly. - } - else if (input[i] > (char)127) + int possibleUtf8Pos = input.AsSpan().IndexOfAnyExceptInRange((char)0, (char)127); + if (possibleUtf8Pos >= 0 && + input.AsSpan(possibleUtf8Pos).IndexOfAnyExceptInRange((char)0, (char)255) < 0) { - possibleUtf8 = true; - break; - } - } - if (possibleUtf8) - { - byte[] rawBytes = new byte[input.Length]; - for (int i = 0; i < input.Length; i++) - { - if (input[i] > (char)255) + Span rawBytes = input.Length <= 256 ? stackalloc byte[input.Length] : new byte[input.Length]; + for (int i = 0; i < input.Length; i++) { - return input; // This couldn't have come from the wire, someone assigned it directly. + rawBytes[i] = (byte)input[i]; } - rawBytes[i] = (byte)input[i]; - } - try - { - // We don't want '?' replacement characters, just fail. - System.Text.Encoding decoder = System.Text.Encoding.GetEncoding("utf-8", System.Text.EncoderFallback.ExceptionFallback, - System.Text.DecoderFallback.ExceptionFallback); - return decoder.GetString(rawBytes, 0, rawBytes.Length); + + try + { + // We don't want '?' replacement characters, just fail. + Encoding decoder = Encoding.GetEncoding("utf-8", EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); + return decoder.GetString(rawBytes); + } + catch (ArgumentException) { } // Not actually Utf-8 } - catch (ArgumentException) { } // Not actually Utf-8 } + return input; } diff --git a/src/libraries/System.Net.WebHeaderCollection/src/System.Net.WebHeaderCollection.csproj b/src/libraries/System.Net.WebHeaderCollection/src/System.Net.WebHeaderCollection.csproj index 940cc70611fd3..b13d0aa2f6c32 100644 --- a/src/libraries/System.Net.WebHeaderCollection/src/System.Net.WebHeaderCollection.csproj +++ b/src/libraries/System.Net.WebHeaderCollection/src/System.Net.WebHeaderCollection.csproj @@ -22,6 +22,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.Unix.cs index 086cdd4726baf..8d99f650e9458 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.Unix.cs @@ -30,13 +30,8 @@ private static OperatingSystem GetOperatingSystem(string release) private static int FindAndParseNextNumber(string text, ref int pos) { // Move to the beginning of the number - for (; (uint)pos < (uint)text.Length; pos++) - { - if (char.IsAsciiDigit(text[pos])) - { - break; - } - } + int numberPos = text.AsSpan(pos).IndexOfAnyInRange('0', '9'); + pos = numberPos >= 0 ? pos + numberPos : text.Length; // Parse the number; int num = 0; diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.cs index 547601d74d7de..8949e5530b9c1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.cs @@ -426,93 +426,25 @@ internal static unsafe string ToLowerAsciiInvariant(string s) return string.Empty; } - fixed (char* pSource = s) + int i = s.AsSpan().IndexOfAnyInRange('A', 'Z'); + if (i < 0) { - int i = 0; - while (i < s.Length) - { - if (char.IsAsciiLetterUpper(pSource[i])) - { - break; - } - i++; - } - - if (i >= s.Length) - { - return s; - } - - string result = string.FastAllocateString(s.Length); - fixed (char* pResult = result) - { - for (int j = 0; j < i; j++) - { - pResult[j] = pSource[j]; - } - - pResult[i] = (char)(pSource[i] | 0x20); - i++; - - while (i < s.Length) - { - pResult[i] = ToLowerAsciiInvariant(pSource[i]); - i++; - } - } - - return result; - } - } - - internal static void ToLowerAsciiInvariant(ReadOnlySpan source, Span destination) - { - Debug.Assert(destination.Length >= source.Length); - - for (int i = 0; i < source.Length; i++) - { - destination[i] = ToLowerAsciiInvariant(source[i]); - } - } - - private static unsafe string ToUpperAsciiInvariant(string s) - { - if (s.Length == 0) - { - return string.Empty; + return s; } fixed (char* pSource = s) { - int i = 0; - while (i < s.Length) - { - if (char.IsAsciiLetterLower(pSource[i])) - { - break; - } - i++; - } - - if (i >= s.Length) - { - return s; - } - string result = string.FastAllocateString(s.Length); fixed (char* pResult = result) { - for (int j = 0; j < i; j++) - { - pResult[j] = pSource[j]; - } + s.AsSpan(0, i).CopyTo(new Span(pResult, result.Length)); - pResult[i] = (char)(pSource[i] & ~0x20); + pResult[i] = (char)(pSource[i] | 0x20); i++; while (i < s.Length) { - pResult[i] = ToUpperAsciiInvariant(pSource[i]); + pResult[i] = ToLowerAsciiInvariant(pSource[i]); i++; } } @@ -521,16 +453,6 @@ private static unsafe string ToUpperAsciiInvariant(string s) } } - internal static void ToUpperAsciiInvariant(ReadOnlySpan source, Span destination) - { - Debug.Assert(destination.Length >= source.Length); - - for (int i = 0; i < source.Length; i++) - { - destination[i] = ToUpperAsciiInvariant(source[i]); - } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static char ToLowerAsciiInvariant(char c) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs index b504009715f35..3b27881e64460 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs @@ -1467,34 +1467,26 @@ private ref struct StringParser private ReadOnlySpan _str; private char _ch; private int _pos; - private int _len; internal void NextChar() { - if (_pos < _len) + ReadOnlySpan str = _str; + + if (_pos < str.Length) { _pos++; } - _ch = _pos < _len ? - _str[_pos] : + int pos = _pos; + _ch = (uint)pos < (uint)str.Length ? + str[pos] : (char)0; } internal char NextNonDigit() { - int i = _pos; - while (i < _len) - { - char ch = _str[i]; - if (!char.IsAsciiDigit(ch)) - { - return ch; - } - i++; - } - - return (char)0; + int i = _str.Slice(_pos).IndexOfAnyExceptInRange('0', '9'); + return i < 0 ? (char)0 : _str[_pos + i]; } internal bool TryParse(ReadOnlySpan input, ref TimeSpanResult result) @@ -1502,7 +1494,6 @@ internal bool TryParse(ReadOnlySpan input, ref TimeSpanResult result) result.parsedTimeSpan = default; _str = input; - _len = input.Length; _pos = -1; NextChar(); SkipBlanks(); @@ -1561,7 +1552,7 @@ internal bool TryParse(ReadOnlySpan input, ref TimeSpanResult result) SkipBlanks(); - if (_pos < _len) + if (_pos < _str.Length) { return result.SetBadTimeSpanFailure(); } diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index d14051ec622e7..6e6a209c14255 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -958,6 +959,278 @@ public static int LastIndexOfAnyExcept(this ReadOnlySpan span, ReadOnlySpa } } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfAnyInRange(this Span span, T lowInclusive, T highInclusive) + where T : IComparable => + IndexOfAnyInRange((ReadOnlySpan)span, lowInclusive, highInclusive); + + /// Searches for the first index of any value in the range between and , inclusive. + /// The type of the span and values. + /// The span to search. + /// A lower bound, inclusive, of the range for which to search. + /// A upper bound, inclusive, of the range for which to search. + /// + /// The index in the span of the first occurrence of any value in the specified range. + /// If all of the values are outside of the specified range, returns -1. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfAnyInRange(this ReadOnlySpan span, T lowInclusive, T highInclusive) + where T : IComparable + { + if (lowInclusive is null || highInclusive is null) + { + ThrowNullLowHighInclusive(lowInclusive, highInclusive); + } + + if (Vector128.IsHardwareAccelerated) + { + if (lowInclusive is byte or sbyte) + { + return SpanHelpers.IndexOfAnyInRangeUnsignedNumber( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref lowInclusive), + Unsafe.As(ref highInclusive), + span.Length); + } + + if (lowInclusive is short or ushort or char) + { + return SpanHelpers.IndexOfAnyInRangeUnsignedNumber( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref lowInclusive), + Unsafe.As(ref highInclusive), + span.Length); + } + + if (lowInclusive is int or uint || (IntPtr.Size == 4 && (lowInclusive is nint or nuint))) + { + return SpanHelpers.IndexOfAnyInRangeUnsignedNumber( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref lowInclusive), + Unsafe.As(ref highInclusive), + span.Length); + } + + if (lowInclusive is long or ulong || (IntPtr.Size == 8 && (lowInclusive is nint or nuint))) + { + return SpanHelpers.IndexOfAnyInRangeUnsignedNumber( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref lowInclusive), + Unsafe.As(ref highInclusive), + span.Length); + } + } + + return SpanHelpers.IndexOfAnyInRange(ref MemoryMarshal.GetReference(span), lowInclusive, highInclusive, span.Length); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfAnyExceptInRange(this Span span, T lowInclusive, T highInclusive) + where T : IComparable => + IndexOfAnyExceptInRange((ReadOnlySpan)span, lowInclusive, highInclusive); + + /// Searches for the first index of any value outside of the range between and , inclusive. + /// The type of the span and values. + /// The span to search. + /// A lower bound, inclusive, of the excluded range. + /// A upper bound, inclusive, of the excluded range. + /// + /// The index in the span of the first occurrence of any value outside of the specified range. + /// If all of the values are inside of the specified range, returns -1. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfAnyExceptInRange(this ReadOnlySpan span, T lowInclusive, T highInclusive) + where T : IComparable + { + if (lowInclusive is null || highInclusive is null) + { + ThrowNullLowHighInclusive(lowInclusive, highInclusive); + } + + if (Vector128.IsHardwareAccelerated) + { + if (lowInclusive is byte or sbyte) + { + return SpanHelpers.IndexOfAnyExceptInRangeUnsignedNumber( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref lowInclusive), + Unsafe.As(ref highInclusive), + span.Length); + } + + if (lowInclusive is short or ushort or char) + { + return SpanHelpers.IndexOfAnyExceptInRangeUnsignedNumber( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref lowInclusive), + Unsafe.As(ref highInclusive), + span.Length); + } + + if (lowInclusive is int or uint || (IntPtr.Size == 4 && (lowInclusive is nint or nuint))) + { + return SpanHelpers.IndexOfAnyExceptInRangeUnsignedNumber( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref lowInclusive), + Unsafe.As(ref highInclusive), + span.Length); + } + + if (lowInclusive is long or ulong || (IntPtr.Size == 8 && (lowInclusive is nint or nuint))) + { + return SpanHelpers.IndexOfAnyExceptInRangeUnsignedNumber( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref lowInclusive), + Unsafe.As(ref highInclusive), + span.Length); + } + } + + return SpanHelpers.IndexOfAnyExceptInRange(ref MemoryMarshal.GetReference(span), lowInclusive, highInclusive, span.Length); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOfAnyInRange(this Span span, T lowInclusive, T highInclusive) + where T : IComparable => + LastIndexOfAnyInRange((ReadOnlySpan)span, lowInclusive, highInclusive); + + /// Searches for the last index of any value in the range between and , inclusive. + /// The type of the span and values. + /// The span to search. + /// A lower bound, inclusive, of the range for which to search. + /// A upper bound, inclusive, of the range for which to search. + /// + /// The index in the span of the last occurrence of any value in the specified range. + /// If all of the values are outside of the specified range, returns -1. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOfAnyInRange(this ReadOnlySpan span, T lowInclusive, T highInclusive) + where T : IComparable + { + if (lowInclusive is null || highInclusive is null) + { + ThrowNullLowHighInclusive(lowInclusive, highInclusive); + } + + if (Vector128.IsHardwareAccelerated) + { + if (lowInclusive is byte or sbyte) + { + return SpanHelpers.LastIndexOfAnyInRangeUnsignedNumber( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref lowInclusive), + Unsafe.As(ref highInclusive), + span.Length); + } + + if (lowInclusive is short or ushort or char) + { + return SpanHelpers.LastIndexOfAnyInRangeUnsignedNumber( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref lowInclusive), + Unsafe.As(ref highInclusive), + span.Length); + } + + if (lowInclusive is int or uint || (IntPtr.Size == 4 && (lowInclusive is nint or nuint))) + { + return SpanHelpers.LastIndexOfAnyInRangeUnsignedNumber( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref lowInclusive), + Unsafe.As(ref highInclusive), + span.Length); + } + + if (lowInclusive is long or ulong || (IntPtr.Size == 8 && (lowInclusive is nint or nuint))) + { + return SpanHelpers.LastIndexOfAnyInRangeUnsignedNumber( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref lowInclusive), + Unsafe.As(ref highInclusive), + span.Length); + } + } + + return SpanHelpers.LastIndexOfAnyInRange(ref MemoryMarshal.GetReference(span), lowInclusive, highInclusive, span.Length); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOfAnyExceptInRange(this Span span, T lowInclusive, T highInclusive) + where T : IComparable => + LastIndexOfAnyExceptInRange((ReadOnlySpan)span, lowInclusive, highInclusive); + + /// Searches for the last index of any value outside of the range between and , inclusive. + /// The type of the span and values. + /// The span to search. + /// A lower bound, inclusive, of the excluded range. + /// A upper bound, inclusive, of the excluded range. + /// + /// The index in the span of the last occurrence of any value outside of the specified range. + /// If all of the values are inside of the specified range, returns -1. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOfAnyExceptInRange(this ReadOnlySpan span, T lowInclusive, T highInclusive) + where T : IComparable + { + if (lowInclusive is null || highInclusive is null) + { + ThrowNullLowHighInclusive(lowInclusive, highInclusive); + } + + if (Vector128.IsHardwareAccelerated) + { + if (lowInclusive is byte or sbyte) + { + return SpanHelpers.LastIndexOfAnyExceptInRangeUnsignedNumber( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref lowInclusive), + Unsafe.As(ref highInclusive), + span.Length); + } + + if (lowInclusive is short or ushort or char) + { + return SpanHelpers.LastIndexOfAnyExceptInRangeUnsignedNumber( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref lowInclusive), + Unsafe.As(ref highInclusive), + span.Length); + } + + if (lowInclusive is int or uint || (IntPtr.Size == 4 && (lowInclusive is nint or nuint))) + { + return SpanHelpers.LastIndexOfAnyExceptInRangeUnsignedNumber( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref lowInclusive), + Unsafe.As(ref highInclusive), + span.Length); + } + + if (lowInclusive is long or ulong || (IntPtr.Size == 8 && (lowInclusive is nint or nuint))) + { + return SpanHelpers.LastIndexOfAnyExceptInRangeUnsignedNumber( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref lowInclusive), + Unsafe.As(ref highInclusive), + span.Length); + } + } + + return SpanHelpers.LastIndexOfAnyExceptInRange(ref MemoryMarshal.GetReference(span), lowInclusive, highInclusive, span.Length); + } + + /// Throws an for or being null. + [DoesNotReturn] + private static void ThrowNullLowHighInclusive(T? lowInclusive, T? highInclusive) + { + Debug.Assert(lowInclusive is null || highInclusive is null); + throw new ArgumentNullException(lowInclusive is null ? nameof(lowInclusive) : nameof(highInclusive)); + } + /// /// 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 5a4f52b4e679c..254087cd7e750 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs @@ -1382,7 +1382,7 @@ internal static bool ContainsValueType(ref T searchSpace, T value, int length { Vector256 equals, values = Vector256.Create(value); ref T currentSearchSpace = ref searchSpace; - ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); + ref T 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 @@ -1412,7 +1412,7 @@ internal static bool ContainsValueType(ref T searchSpace, T value, int length { Vector128 equals, values = Vector128.Create(value); ref T currentSearchSpace = ref searchSpace; - ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + ref T 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 @@ -2032,7 +2032,7 @@ internal static int IndexOfAnyValueType(ref T searchSpace, T value0, T 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, length - Vector256.Count); + ref T 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 @@ -2067,7 +2067,7 @@ internal static int IndexOfAnyValueType(ref T searchSpace, T value0, T 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, length - Vector128.Count); + ref T 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 @@ -2670,5 +2670,237 @@ private interface INegator where T : struct public static Vector128 NegateIfNeeded(Vector128 equals) => ~equals; public static Vector256 NegateIfNeeded(Vector256 equals) => ~equals; } + + internal static int IndexOfAnyInRange(ref T searchSpace, T lowInclusive, T highInclusive, int length) + where T : IComparable + { + for (int i = 0; i < length; i++) + { + ref T current = ref Unsafe.Add(ref searchSpace, i); + if ((lowInclusive.CompareTo(current) <= 0) && (highInclusive.CompareTo(current) >= 0)) + { + return i; + } + } + + return -1; + } + + internal static int IndexOfAnyExceptInRange(ref T searchSpace, T lowInclusive, T highInclusive, int length) + where T : IComparable + { + for (int i = 0; i < length; i++) + { + ref T current = ref Unsafe.Add(ref searchSpace, i); + if ((lowInclusive.CompareTo(current) > 0) || (highInclusive.CompareTo(current) < 0)) + { + return i; + } + } + + return -1; + } + + internal static int IndexOfAnyInRangeUnsignedNumber(ref T searchSpace, T lowInclusive, T highInclusive, int length) + where T : struct, IUnsignedNumber, IComparisonOperators => + IndexOfAnyInRangeUnsignedNumber>(ref searchSpace, lowInclusive, highInclusive, length); + + internal static int IndexOfAnyExceptInRangeUnsignedNumber(ref T searchSpace, T lowInclusive, T highInclusive, int length) + where T : struct, IUnsignedNumber, IComparisonOperators => + IndexOfAnyInRangeUnsignedNumber>(ref searchSpace, lowInclusive, highInclusive, length); + + private static int IndexOfAnyInRangeUnsignedNumber(ref T searchSpace, T lowInclusive, T highInclusive, int length) + where T : struct, IUnsignedNumber, IComparisonOperators + where TNegator : struct, INegator + { + // T must be a type whose comparison operator semantics match that of Vector128/256. + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + T rangeInclusive = highInclusive - lowInclusive; + for (int i = 0; i < length; i++) + { + ref T current = ref Unsafe.Add(ref searchSpace, i); + if (TNegator.NegateIfNeeded((current - lowInclusive) <= rangeInclusive)) + { + return i; + } + } + } + else if (!Vector256.IsHardwareAccelerated || length < Vector256.Count) + { + Vector128 lowVector = Vector128.Create(lowInclusive); + Vector128 rangeVector = Vector128.Create(highInclusive - lowInclusive); + Vector128 inRangeVector; + + ref T current = ref searchSpace; + ref T 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 + { + inRangeVector = TNegator.NegateIfNeeded(Vector128.LessThanOrEqual(Vector128.LoadUnsafe(ref current) - lowVector, rangeVector)); + if (inRangeVector != Vector128.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref current, inRangeVector); + } + + current = ref Unsafe.Add(ref current, Vector128.Count); + } + while (Unsafe.IsAddressLessThan(ref current, ref oneVectorAwayFromEnd)); + + // Process the last vector in the search space (which might overlap with already processed elements). + inRangeVector = TNegator.NegateIfNeeded(Vector128.LessThanOrEqual(Vector128.LoadUnsafe(ref oneVectorAwayFromEnd) - lowVector, rangeVector)); + if (inRangeVector != Vector128.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, inRangeVector); + } + } + else + { + Vector256 lowVector = Vector256.Create(lowInclusive); + Vector256 rangeVector = Vector256.Create(highInclusive - lowInclusive); + Vector256 inRangeVector; + + ref T current = ref searchSpace; + ref T 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 + { + inRangeVector = TNegator.NegateIfNeeded(Vector256.LessThanOrEqual(Vector256.LoadUnsafe(ref current) - lowVector, rangeVector)); + if (inRangeVector != Vector256.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref current, inRangeVector); + } + + current = ref Unsafe.Add(ref current, Vector256.Count); + } + while (Unsafe.IsAddressLessThan(ref current, ref oneVectorAwayFromEnd)); + + // Process the last vector in the search space (which might overlap with already processed elements). + inRangeVector = TNegator.NegateIfNeeded(Vector256.LessThanOrEqual(Vector256.LoadUnsafe(ref oneVectorAwayFromEnd) - lowVector, rangeVector)); + if (inRangeVector != Vector256.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, inRangeVector); + } + } + + return -1; + } + + internal static int LastIndexOfAnyInRange(ref T searchSpace, T lowInclusive, T highInclusive, int length) + where T : IComparable + { + for (int i = length - 1; i >= 0; i--) + { + ref T current = ref Unsafe.Add(ref searchSpace, i); + if ((lowInclusive.CompareTo(current) <= 0) && (highInclusive.CompareTo(current) >= 0)) + { + return i; + } + } + + return -1; + } + + internal static int LastIndexOfAnyExceptInRange(ref T searchSpace, T lowInclusive, T highInclusive, int length) + where T : IComparable + { + for (int i = length - 1; i >= 0; i--) + { + ref T current = ref Unsafe.Add(ref searchSpace, i); + if ((lowInclusive.CompareTo(current) > 0) || (highInclusive.CompareTo(current) < 0)) + { + return i; + } + } + + return -1; + } + + internal static int LastIndexOfAnyInRangeUnsignedNumber(ref T searchSpace, T lowInclusive, T highInclusive, int length) + where T : struct, IUnsignedNumber, IComparisonOperators => + LastIndexOfAnyInRangeUnsignedNumber>(ref searchSpace, lowInclusive, highInclusive, length); + + internal static int LastIndexOfAnyExceptInRangeUnsignedNumber(ref T searchSpace, T lowInclusive, T highInclusive, int length) + where T : struct, IUnsignedNumber, IComparisonOperators => + LastIndexOfAnyInRangeUnsignedNumber>(ref searchSpace, lowInclusive, highInclusive, length); + + private static int LastIndexOfAnyInRangeUnsignedNumber(ref T searchSpace, T lowInclusive, T highInclusive, int length) + where T : struct, IUnsignedNumber, IComparisonOperators + where TNegator : struct, INegator + { + // T must be a type whose comparison operator semantics match that of Vector128/256. + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + T rangeInclusive = highInclusive - lowInclusive; + for (int i = length - 1; i >= 0; i--) + { + ref T current = ref Unsafe.Add(ref searchSpace, i); + if (TNegator.NegateIfNeeded((current - lowInclusive) <= rangeInclusive)) + { + return i; + } + } + } + else if (!Vector256.IsHardwareAccelerated || length < Vector256.Count) + { + Vector128 lowVector = Vector128.Create(lowInclusive); + Vector128 rangeVector = Vector128.Create(highInclusive - lowInclusive); + Vector128 inRangeVector; + + nint offset = length - Vector128.Count; + + // Loop until either we've finished all elements or there's a vector's-worth or less remaining. + while (offset > 0) + { + inRangeVector = TNegator.NegateIfNeeded(Vector128.LessThanOrEqual(Vector128.LoadUnsafe(ref searchSpace, (nuint)offset) - lowVector, rangeVector)); + if (inRangeVector != Vector128.Zero) + { + return ComputeLastIndex(offset, inRangeVector); + } + + offset -= Vector128.Count; + } + + // Process the first vector in the search space. + inRangeVector = TNegator.NegateIfNeeded(Vector128.LessThanOrEqual(Vector128.LoadUnsafe(ref searchSpace) - lowVector, rangeVector)); + if (inRangeVector != Vector128.Zero) + { + return ComputeLastIndex(offset: 0, inRangeVector); + } + } + else + { + Vector256 lowVector = Vector256.Create(lowInclusive); + Vector256 rangeVector = Vector256.Create(highInclusive - lowInclusive); + Vector256 inRangeVector; + + nint offset = length - Vector256.Count; + + // Loop until either we've finished all elements or there's a vector's-worth or less remaining. + while (offset > 0) + { + inRangeVector = TNegator.NegateIfNeeded(Vector256.LessThanOrEqual(Vector256.LoadUnsafe(ref searchSpace, (nuint)offset) - lowVector, rangeVector)); + if (inRangeVector != Vector256.Zero) + { + return ComputeLastIndex(offset, inRangeVector); + } + + offset -= Vector256.Count; + } + + // Process the first vector in the search space. + inRangeVector = TNegator.NegateIfNeeded(Vector256.LessThanOrEqual(Vector256.LoadUnsafe(ref searchSpace) - lowVector, rangeVector)); + if (inRangeVector != Vector256.Zero) + { + return ComputeLastIndex(offset: 0, inRangeVector); + } + } + + return -1; + } } } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/XmlCharType.cs b/src/libraries/System.Private.Xml/src/System/Xml/XmlCharType.cs index 4a4ae14dd09e1..21233b44632c6 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/XmlCharType.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/XmlCharType.cs @@ -155,17 +155,10 @@ internal static int IsOnlyCharData(string str) internal static bool IsOnlyDigits(string str, int startPos, int len) { Debug.Assert(str != null); - Debug.Assert(startPos + len <= str.Length); Debug.Assert(startPos <= str.Length); + Debug.Assert(startPos + len <= str.Length); - for (int i = startPos; i < startPos + len; i++) - { - if (!char.IsAsciiDigit(str[i])) - { - return false; - } - } - return true; + return str.AsSpan(startPos, len).IndexOfAnyExceptInRange('0', '9') < 0; } internal static int IsPublicId(string str)