From 135c9a3c9e9ccd82f6f75231cdc45d003317ddc4 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 28 Feb 2023 15:12:26 -0500 Subject: [PATCH 1/2] Switch JsonReaderHelper.IndexOfQuoteOrAnyControlOrBackSlash to use IndexOfAnyValues --- .../src/System.Text.Json.csproj | 9 +- .../Text/Json/Reader/JsonReaderHelper.cs | 16 -- .../Text/Json/Reader/JsonReaderHelper.net8.cs | 25 ++ ....sn.cs => JsonReaderHelper.netstandard.cs} | 54 +++-- .../Text/Json/Reader/JsonReaderHelper.sri.cs | 216 ------------------ 5 files changed, 65 insertions(+), 255 deletions(-) create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.net8.cs rename src/libraries/System.Text.Json/src/System/Text/Json/Reader/{JsonReaderHelper.sn.cs => JsonReaderHelper.netstandard.cs} (74%) delete mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.sri.cs diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index ea0c1280f3e95e..f14bab0f7c58b5 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -348,11 +348,14 @@ The System.Text.Json library is built-in as part of the shared framework in .NET - - - + + + + + + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs index 0486afb939ff3b..141d23f5b45999 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs @@ -77,22 +77,6 @@ public static bool IsTokenTypePrimitive(JsonTokenType tokenType) => // Otherwise, return false. public static bool IsHexDigit(byte nextByte) => HexConverter.IsHexChar(nextByte); - // https://tools.ietf.org/html/rfc8259 - // Does the span contain '"', '\', or any control characters (i.e. 0 to 31) - // IndexOfAny(34, 92, < 32) - // Borrowed and modified from SpanHelpers.Byte: - // https://github.com/dotnet/corefx/blob/fc169cddedb6820aaabbdb8b7bece2a3df0fd1a5/src/Common/src/CoreLib/System/SpanHelpers.Byte.cs#L473-L604 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOfQuoteOrAnyControlOrBackSlash(this ReadOnlySpan span) - { - return IndexOfOrLessThan( - ref MemoryMarshal.GetReference(span), - JsonConstants.Quote, - JsonConstants.BackSlash, - lessThan: 32, // Space ' ' - span.Length); - } - public static bool TryGetEscapedDateTime(ReadOnlySpan source, out DateTime value) { Debug.Assert(source.Length <= JsonConstants.MaximumEscapedDateTimeOffsetParseLength); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.net8.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.net8.cs new file mode 100644 index 00000000000000..6aebc26faf2728 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.net8.cs @@ -0,0 +1,25 @@ +// 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.Runtime.CompilerServices; + +namespace System.Text.Json +{ + internal static partial class JsonReaderHelper + { + /// '"', '\', or any control characters (i.e. 0 to 31). + /// https://tools.ietf.org/html/rfc8259 + private static readonly IndexOfAnyValues s_controlQuoteBackslash = IndexOfAnyValues.Create( + // Any Control, < 32 (' ') + "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000A\u000B\u000C\u000D\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F"u8 + + // Quote + "\""u8 + + // Backslash + "\\"u8); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfQuoteOrAnyControlOrBackSlash(this ReadOnlySpan span) => + span.IndexOfAny(s_controlQuoteBackslash); + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.sn.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.netstandard.cs similarity index 74% rename from src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.sn.cs rename to src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.netstandard.cs index 59728a4c01b1ce..550a7696a88d58 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.sn.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.netstandard.cs @@ -4,18 +4,32 @@ using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace System.Text.Json { internal static partial class JsonReaderHelper { - private static unsafe int IndexOfOrLessThan(ref byte searchSpace, byte value0, byte value1, byte lessThan, int length) + /// IndexOfAny('"', '\', less than 32) + /// https://tools.ietf.org/html/rfc8259 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe int IndexOfQuoteOrAnyControlOrBackSlash(this ReadOnlySpan span) { + // Borrowed and modified from SpanHelpers.Byte: + // https://github.com/dotnet/corefx/blob/fc169cddedb6820aaabbdb8b7bece2a3df0fd1a5/src/Common/src/CoreLib/System/SpanHelpers.Byte.cs#L473-L604 + + ref byte searchSpace = ref MemoryMarshal.GetReference(span); + int length = span.Length; Debug.Assert(length >= 0); - uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions - uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions - uint uLessThan = lessThan; // Use uint for comparisons to avoid unnecessary 8->32 extensions + const byte Value0 = JsonConstants.Quote; + const byte Value1 = JsonConstants.BackSlash; + const byte LessThan = JsonConstants.Space; + + const uint UValue0 = Value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions + const uint UValue1 = Value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions + const uint ULessThan = LessThan; // Use uint for comparisons to avoid unnecessary 8->32 extensions + IntPtr index = (IntPtr)0; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations IntPtr nLength = (IntPtr)length; @@ -31,28 +45,28 @@ private static unsafe int IndexOfOrLessThan(ref byte searchSpace, byte value0, b nLength -= 8; lookUp = Unsafe.AddByteOffset(ref searchSpace, index); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) + if (UValue0 == lookUp || UValue1 == lookUp || ULessThan > lookUp) goto Found; lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 1); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) + if (UValue0 == lookUp || UValue1 == lookUp || ULessThan > lookUp) goto Found1; lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 2); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) + if (UValue0 == lookUp || UValue1 == lookUp || ULessThan > lookUp) goto Found2; lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 3); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) + if (UValue0 == lookUp || UValue1 == lookUp || ULessThan > lookUp) goto Found3; lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 4); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) + if (UValue0 == lookUp || UValue1 == lookUp || ULessThan > lookUp) goto Found4; lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 5); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) + if (UValue0 == lookUp || UValue1 == lookUp || ULessThan > lookUp) goto Found5; lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 6); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) + if (UValue0 == lookUp || UValue1 == lookUp || ULessThan > lookUp) goto Found6; lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 7); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) + if (UValue0 == lookUp || UValue1 == lookUp || ULessThan > lookUp) goto Found7; index += 8; @@ -63,16 +77,16 @@ private static unsafe int IndexOfOrLessThan(ref byte searchSpace, byte value0, b nLength -= 4; lookUp = Unsafe.AddByteOffset(ref searchSpace, index); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) + if (UValue0 == lookUp || UValue1 == lookUp || ULessThan > lookUp) goto Found; lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 1); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) + if (UValue0 == lookUp || UValue1 == lookUp || ULessThan > lookUp) goto Found1; lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 2); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) + if (UValue0 == lookUp || UValue1 == lookUp || ULessThan > lookUp) goto Found2; lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 3); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) + if (UValue0 == lookUp || UValue1 == lookUp || ULessThan > lookUp) goto Found3; index += 4; @@ -83,7 +97,7 @@ private static unsafe int IndexOfOrLessThan(ref byte searchSpace, byte value0, b nLength -= 1; lookUp = Unsafe.AddByteOffset(ref searchSpace, index); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) + if (UValue0 == lookUp || UValue1 == lookUp || ULessThan > lookUp) goto Found; index += 1; @@ -94,9 +108,9 @@ private static unsafe int IndexOfOrLessThan(ref byte searchSpace, byte value0, b nLength = (IntPtr)((length - (int)(byte*)index) & ~(Vector.Count - 1)); // Get comparison Vector - Vector values0 = new Vector(value0); - Vector values1 = new Vector(value1); - Vector valuesLessThan = new Vector(lessThan); + Vector values0 = new Vector(Value0); + Vector values1 = new Vector(Value1); + Vector valuesLessThan = new Vector(LessThan); while ((byte*)nLength > (byte*)index) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.sri.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.sri.cs deleted file mode 100644 index ed6abe7ecd6739..00000000000000 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.sri.cs +++ /dev/null @@ -1,216 +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.Intrinsics; -using System.Runtime.CompilerServices; - -namespace System.Text.Json -{ - internal static partial class JsonReaderHelper - { - private static unsafe int IndexOfOrLessThan(ref byte searchSpace, byte value0, byte value1, byte lessThan, int length) - { - Debug.Assert(length >= 0); - - uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions - uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions - uint uLessThan = lessThan; // Use uint for comparisons to avoid unnecessary 8->32 extensions - IntPtr index = (IntPtr)0; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations - IntPtr nLength = (IntPtr)length; - - if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) - { - uint lookUp; - while ((byte*)nLength >= (byte*)8) - { - nLength -= 8; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, index); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) - goto Found; - lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 1); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) - goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 2); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) - goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 3); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) - goto Found3; - lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 4); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) - goto Found4; - lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 5); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) - goto Found5; - lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 6); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) - goto Found6; - lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 7); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) - goto Found7; - - index += 8; - } - - if ((byte*)nLength >= (byte*)4) - { - nLength -= 4; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, index); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) - goto Found; - lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 1); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) - goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 2); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) - goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 3); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) - goto Found3; - - index += 4; - } - - while ((byte*)nLength > (byte*)0) - { - nLength -= 1; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, index); - if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp) - goto Found; - - index += 1; - } - } - else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) - { - // Get comparison Vectors - Vector256 values0 = Vector256.Create(value0); - Vector256 values1 = Vector256.Create(value1); - Vector256 valuesLessThan = Vector256.Create(lessThan); - - ref byte currentSearchSpace = ref searchSpace; - ref byte oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); - - // Loop until either we've finished all elements or there's less than a vector's-worth remaining. - do - { - var vData = Vector256.LoadUnsafe(ref currentSearchSpace); - var vMatches = Vector256.BitwiseOr( - Vector256.BitwiseOr( - Vector256.Equals(vData, values0), - Vector256.Equals(vData, values1)), - Vector256.LessThan(vData, valuesLessThan)); - - if (vMatches == Vector256.Zero) - { - currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector256.Count); - continue; - } - - return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, vMatches); - } - 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) - { - var vData = Vector256.LoadUnsafe(ref oneVectorAwayFromEnd); - var vMatches = Vector256.BitwiseOr( - Vector256.BitwiseOr( - Vector256.Equals(vData, values0), - Vector256.Equals(vData, values1)), - Vector256.LessThan(vData, valuesLessThan)); - - if (vMatches != Vector256.Zero) - { - return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, vMatches); - } - } - } - else - { - // Get comparison Vectors - Vector128 values0 = Vector128.Create(value0); - Vector128 values1 = Vector128.Create(value1); - Vector128 valuesLessThan = Vector128.Create(lessThan); - - ref byte currentSearchSpace = ref searchSpace; - ref byte oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); - - // Loop until either we've finished all elements or there's less than a vector's-worth remaining. - do - { - var vData = Vector128.LoadUnsafe(ref currentSearchSpace); - var vMatches = Vector128.BitwiseOr( - Vector128.BitwiseOr( - Vector128.Equals(vData, values0), - Vector128.Equals(vData, values1)), - Vector128.LessThan(vData, valuesLessThan)); - - if (vMatches == Vector128.Zero) - { - currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); - continue; - } - - return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, vMatches); - } - while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); - - // If any elements remain, process the last vector in the search space. - if ((uint)length % Vector128.Count != 0) - { - var vData = Vector128.LoadUnsafe(ref oneVectorAwayFromEnd); - var vMatches = Vector128.BitwiseOr( - Vector128.BitwiseOr( - Vector128.Equals(vData, values0), - Vector128.Equals(vData, values1)), - Vector128.LessThan(vData, valuesLessThan)); - - if (vMatches != Vector128.Zero) - { - return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, vMatches); - } - } - } - return -1; - Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 - return (int)(byte*)index; - Found1: - return (int)(byte*)(index + 1); - Found2: - return (int)(byte*)(index + 2); - Found3: - return (int)(byte*)(index + 3); - Found4: - return (int)(byte*)(index + 4); - Found5: - return (int)(byte*)(index + 5); - Found6: - return (int)(byte*)(index + 6); - Found7: - return (int)(byte*)(index + 7); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe int ComputeFirstIndex(ref byte searchSpace, ref byte current, Vector256 equals) - { - uint notEqualsElements = equals.ExtractMostSignificantBits(); - int index = BitOperations.TrailingZeroCount(notEqualsElements); - return index + (int)(Unsafe.ByteOffset(ref searchSpace, ref current) / sizeof(byte)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe int ComputeFirstIndex(ref byte searchSpace, ref byte current, Vector128 equals) - { - uint notEqualsElements = equals.ExtractMostSignificantBits(); - int index = BitOperations.TrailingZeroCount(notEqualsElements); - return index + (int)(Unsafe.ByteOffset(ref searchSpace, ref current) / sizeof(byte)); - } - } -} From 236eea9b76ba864b518c63f35d8f293f9448b586 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 28 Feb 2023 17:20:46 -0500 Subject: [PATCH 2/2] Remove aggressive inlining --- .../src/System/Text/Json/Reader/JsonReaderHelper.netstandard.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.netstandard.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.netstandard.cs index 550a7696a88d58..1db19a3d70adec 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.netstandard.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.netstandard.cs @@ -12,7 +12,6 @@ internal static partial class JsonReaderHelper { /// IndexOfAny('"', '\', less than 32) /// https://tools.ietf.org/html/rfc8259 - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe int IndexOfQuoteOrAnyControlOrBackSlash(this ReadOnlySpan span) { // Borrowed and modified from SpanHelpers.Byte: