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 a149dd46b55177..70404cbd24fd43 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -351,6 +351,11 @@ The System.Text.Json library is built-in as part of the shared framework in .NET + + + + + @@ -368,6 +373,7 @@ 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 33ccb5d3b515dc..95c97bd32cd319 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 @@ -3,7 +3,6 @@ using System.Buffers.Text; using System.Diagnostics; -using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -84,177 +83,6 @@ ref MemoryMarshal.GetReference(span), span.Length); } - 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 (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) - { - int unaligned = (int)Unsafe.AsPointer(ref searchSpace) & (Vector.Count - 1); - nLength = (IntPtr)((Vector.Count - unaligned) & (Vector.Count - 1)); - } - SequentialScan: - 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; - } - - if (Vector.IsHardwareAccelerated && ((int)(byte*)index < length)) - { - 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); - - while ((byte*)nLength > (byte*)index) - { - Vector vData = Unsafe.ReadUnaligned>(ref Unsafe.AddByteOffset(ref searchSpace, index)); - - var vMatches = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(vData, values0), - Vector.Equals(vData, values1)), - Vector.LessThan(vData, valuesLessThan)); - - if (Vector.Zero.Equals(vMatches)) - { - index += Vector.Count; - continue; - } - // Find offset of first match - return (int)(byte*)index + LocateFirstFoundByte(vMatches); - } - - if ((int)(byte*)index < length) - { - nLength = (IntPtr)(length - (int)(byte*)index); - goto SequentialScan; - } - } - 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); - } - - // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int LocateFirstFoundByte(Vector match) - { - var vector64 = Vector.AsVectorUInt64(match); - ulong candidate = 0; - int i = 0; - // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001 - for (; i < Vector.Count; i++) - { - candidate = vector64[i]; - if (candidate != 0) - { - break; - } - } - - // Single LEA instruction with jitted const (using function result) - return i * 8 + LocateFirstFoundByte(candidate); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int LocateFirstFoundByte(ulong match) - { - // Flag least significant power of two bit - var powerOfTwoFlag = match ^ (match - 1); - // Shift all powers of two into the high byte and extract - return (int)((powerOfTwoFlag * XorPowerOfTwoToHighByte) >> 57); - } - - private const ulong XorPowerOfTwoToHighByte = (0x07ul | - 0x06ul << 8 | - 0x05ul << 16 | - 0x04ul << 24 | - 0x03ul << 32 | - 0x02ul << 40 | - 0x01ul << 48) + 1; - 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.sn.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.sn.cs new file mode 100644 index 00000000000000..59728a4c01b1ce --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.sn.cs @@ -0,0 +1,183 @@ +// 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; + +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 (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) + { + int unaligned = (int)Unsafe.AsPointer(ref searchSpace) & (Vector.Count - 1); + nLength = (IntPtr)((Vector.Count - unaligned) & (Vector.Count - 1)); + } + SequentialScan: + 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; + } + + if (Vector.IsHardwareAccelerated && ((int)(byte*)index < length)) + { + 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); + + while ((byte*)nLength > (byte*)index) + { + Vector vData = Unsafe.ReadUnaligned>(ref Unsafe.AddByteOffset(ref searchSpace, index)); + + var vMatches = Vector.BitwiseOr( + Vector.BitwiseOr( + Vector.Equals(vData, values0), + Vector.Equals(vData, values1)), + Vector.LessThan(vData, valuesLessThan)); + + if (Vector.Zero.Equals(vMatches)) + { + index += Vector.Count; + continue; + } + // Find offset of first match + return (int)(byte*)index + LocateFirstFoundByte(vMatches); + } + + if ((int)(byte*)index < length) + { + nLength = (IntPtr)(length - (int)(byte*)index); + goto SequentialScan; + } + } + 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); + } + + // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int LocateFirstFoundByte(Vector match) + { + var vector64 = Vector.AsVectorUInt64(match); + ulong candidate = 0; + int i = 0; + // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001 + for (; i < Vector.Count; i++) + { + candidate = vector64[i]; + if (candidate != 0) + { + break; + } + } + + // Single LEA instruction with jitted const (using function result) + return i * 8 + LocateFirstFoundByte(candidate); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int LocateFirstFoundByte(ulong match) + { + // Flag least significant power of two bit + var powerOfTwoFlag = match ^ (match - 1); + // Shift all powers of two into the high byte and extract + return (int)((powerOfTwoFlag * XorPowerOfTwoToHighByte) >> 57); + } + + private const ulong XorPowerOfTwoToHighByte = (0x07ul | + 0x06ul << 8 | + 0x05ul << 16 | + 0x04ul << 24 | + 0x03ul << 32 | + 0x02ul << 40 | + 0x01ul << 48) + 1; + } +} 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 new file mode 100644 index 00000000000000..ed6abe7ecd6739 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.sri.cs @@ -0,0 +1,216 @@ +// 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)); + } + } +}