diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/ParserHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/ParserHelpers.cs index d5f50473f24458..5d0562e9d04b60 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/ParserHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/ParserHelpers.cs @@ -2,7 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using Internal.Runtime.CompilerServices; namespace System.Buffers.Text { @@ -70,5 +73,23 @@ public static bool TryParseThrowFormatException(out T value, out int bytesCon value = default; return TryParseThrowFormatException(out bytesConsumed); } + + // + // Enable use of ThrowHelper from TryParse() routines without introducing dozens of non-code-coveraged "value= default; bytesConsumed = 0; return false" boilerplate. + // + [DoesNotReturn] + [StackTraceHidden] + public static bool TryParseThrowFormatException(ReadOnlySpan source, out T value, out int bytesConsumed) where T : struct + { + // The parameters to this method are ordered the same as our callers' parameters + // allowing the JIT to avoid unnecessary register swapping or spilling. + + Unsafe.SkipInit(out value); // bypass language initialization rules since we're about to throw + Unsafe.SkipInit(out bytesConsumed); + ThrowHelper.ThrowFormatException_BadFormatSpecifier(); + + Debug.Fail("Control should never reach this point."); + return false; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Boolean.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Boolean.cs index 1c5e4f243e12dd..ae771e9b7631a7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Boolean.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Boolean.cs @@ -30,7 +30,7 @@ public static partial class Utf8Parser public static bool TryParse(ReadOnlySpan source, out bool value, out int bytesConsumed, char standardFormat = default) { if (!(standardFormat == default(char) || standardFormat == 'G' || standardFormat == 'l')) - return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); + ThrowHelper.ThrowFormatException_BadFormatSpecifier(); if (source.Length >= 4) { @@ -42,7 +42,7 @@ public static bool TryParse(ReadOnlySpan source, out bool value, out int b return true; } - if (source.Length >= 5) + if (4 < (uint)source.Length) { if (dw == 0x534c4146 /* 'SLAF' */ && (source[4] & ~0x20) == 'E') { diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Guid.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Guid.cs index cdc2f5e5c35ae1..e5b48387d431ad 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Guid.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Guid.cs @@ -29,19 +29,25 @@ public static partial class Utf8Parser /// public static bool TryParse(ReadOnlySpan source, out Guid value, out int bytesConsumed, char standardFormat = default) { + FastPath: + if (standardFormat == default) + { + return TryParseGuidCore(source, out value, out bytesConsumed, ends: 0); + } + switch (standardFormat) { - case default(char): case 'D': - return TryParseGuidCore(source, false, ' ', ' ', out value, out bytesConsumed); + standardFormat = default; + goto FastPath; case 'B': - return TryParseGuidCore(source, true, '{', '}', out value, out bytesConsumed); + return TryParseGuidCore(source, out value, out bytesConsumed, ends: '{' | ('}' << 8)); case 'P': - return TryParseGuidCore(source, true, '(', ')', out value, out bytesConsumed); + return TryParseGuidCore(source, out value, out bytesConsumed, ends: '(' | (')' << 8)); case 'N': return TryParseGuidN(source, out value, out bytesConsumed); default: - return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(source, out value, out bytesConsumed); } } @@ -97,9 +103,9 @@ private static bool TryParseGuidN(ReadOnlySpan text, out Guid value, out i } // {8-4-4-4-12}, where number is the number of hex digits, and {/} are ends. - private static bool TryParseGuidCore(ReadOnlySpan source, bool ends, char begin, char end, out Guid value, out int bytesConsumed) + private static bool TryParseGuidCore(ReadOnlySpan source, out Guid value, out int bytesConsumed, int ends) { - int expectedCodingUnits = 36 + (ends ? 2 : 0); // 32 hex digits + 4 delimiters + 2 optional ends + int expectedCodingUnits = 36 + ((ends != 0) ? 2 : 0); // 32 hex digits + 4 delimiters + 2 optional ends if (source.Length < expectedCodingUnits) { @@ -108,9 +114,16 @@ private static bool TryParseGuidCore(ReadOnlySpan source, bool ends, char return false; } - if (ends) + // The 'ends' parameter is a 16-bit value where the byte denoting the starting + // brace is at byte position 0 and the byte denoting the closing brace is at + // byte position 1. If no braces are expected, has value 0. + // Default: ends = 0 + // Braces: ends = "}{" + // Parens: ends = ")(" + + if (ends != 0) { - if (source[0] != begin) + if (source[0] != (byte)ends) { value = default; bytesConsumed = 0; @@ -118,6 +131,7 @@ private static bool TryParseGuidCore(ReadOnlySpan source, bool ends, char } source = source.Slice(1); // skip beginning + ends >>= 8; // shift the closing brace to byte position 0 } if (!TryParseUInt32X(source, out uint i1, out int justConsumed)) @@ -226,7 +240,7 @@ private static bool TryParseGuidCore(ReadOnlySpan source, bool ends, char return false; // 12 digits } - if (ends && source[justConsumed] != end) + if (ends != 0 && source[justConsumed] != (byte)ends) { value = default; bytesConsumed = 0; diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.cs index d6cc01b8a70533..10e8bd0a2f2660 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.cs @@ -35,26 +35,31 @@ public static partial class Utf8Parser [CLSCompliant(false)] public static bool TryParse(ReadOnlySpan source, out sbyte value, out int bytesConsumed, char standardFormat = default) { - switch (standardFormat) + FastPath: + if (standardFormat == default) + { + return TryParseSByteD(source, out value, out bytesConsumed); + } + + // There's small but measurable overhead when entering the switch block below. + // We optimize for the default case by hoisting it above the switch block. + + switch (standardFormat | 0x20) // convert to lowercase { - case default(char): case 'g': - case 'G': case 'd': - case 'D': - return TryParseSByteD(source, out value, out bytesConsumed); + standardFormat = default; + goto FastPath; case 'n': - case 'N': return TryParseSByteN(source, out value, out bytesConsumed); case 'x': - case 'X': - value = default; + Unsafe.SkipInit(out value); // will be populated by TryParseByteX return TryParseByteX(source, out Unsafe.As(ref value), out bytesConsumed); default: - return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(source, out value, out bytesConsumed); } } @@ -81,26 +86,31 @@ public static bool TryParse(ReadOnlySpan source, out sbyte value, out int /// public static bool TryParse(ReadOnlySpan source, out short value, out int bytesConsumed, char standardFormat = default) { - switch (standardFormat) + FastPath: + if (standardFormat == default) + { + return TryParseInt16D(source, out value, out bytesConsumed); + } + + // There's small but measurable overhead when entering the switch block below. + // We optimize for the default case by hoisting it above the switch block. + + switch (standardFormat | 0x20) // convert to lowercase { - case default(char): case 'g': - case 'G': case 'd': - case 'D': - return TryParseInt16D(source, out value, out bytesConsumed); + standardFormat = default; + goto FastPath; case 'n': - case 'N': return TryParseInt16N(source, out value, out bytesConsumed); case 'x': - case 'X': - value = default; + Unsafe.SkipInit(out value); // will be populated by TryParseUInt16X return TryParseUInt16X(source, out Unsafe.As(ref value), out bytesConsumed); default: - return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(source, out value, out bytesConsumed); } } @@ -127,26 +137,31 @@ public static bool TryParse(ReadOnlySpan source, out short value, out int /// public static bool TryParse(ReadOnlySpan source, out int value, out int bytesConsumed, char standardFormat = default) { - switch (standardFormat) + FastPath: + if (standardFormat == default) + { + return TryParseInt32D(source, out value, out bytesConsumed); + } + + // There's small but measurable overhead when entering the switch block below. + // We optimize for the default case by hoisting it above the switch block. + + switch (standardFormat | 0x20) // convert to lowercase { - case default(char): case 'g': - case 'G': case 'd': - case 'D': - return TryParseInt32D(source, out value, out bytesConsumed); + standardFormat = default; + goto FastPath; case 'n': - case 'N': return TryParseInt32N(source, out value, out bytesConsumed); case 'x': - case 'X': - value = default; + Unsafe.SkipInit(out value); // will be populated by TryParseUInt32X return TryParseUInt32X(source, out Unsafe.As(ref value), out bytesConsumed); default: - return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(source, out value, out bytesConsumed); } } @@ -173,26 +188,31 @@ public static bool TryParse(ReadOnlySpan source, out int value, out int by /// public static bool TryParse(ReadOnlySpan source, out long value, out int bytesConsumed, char standardFormat = default) { - switch (standardFormat) + FastPath: + if (standardFormat == default) + { + return TryParseInt64D(source, out value, out bytesConsumed); + } + + // There's small but measurable overhead when entering the switch block below. + // We optimize for the default case by hoisting it above the switch block. + + switch (standardFormat | 0x20) // convert to lowercase { - case default(char): case 'g': - case 'G': case 'd': - case 'D': - return TryParseInt64D(source, out value, out bytesConsumed); + standardFormat = default; + goto FastPath; case 'n': - case 'N': return TryParseInt64N(source, out value, out bytesConsumed); case 'x': - case 'X': - value = default; + Unsafe.SkipInit(out value); // will be populated by TryParseUInt64X return TryParseUInt64X(source, out Unsafe.As(ref value), out bytesConsumed); default: - return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(source, out value, out bytesConsumed); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.cs index 476889c1fb0543..5d3237f199c7d8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.cs @@ -29,25 +29,30 @@ public static partial class Utf8Parser /// public static bool TryParse(ReadOnlySpan source, out byte value, out int bytesConsumed, char standardFormat = default) { - switch (standardFormat) + FastPath: + if (standardFormat == default) + { + return TryParseByteD(source, out value, out bytesConsumed); + } + + // There's small but measurable overhead when entering the switch block below. + // We optimize for the default case by hoisting it above the switch block. + + switch (standardFormat | 0x20) // convert to lowercase { - case default(char): case 'g': - case 'G': case 'd': - case 'D': - return TryParseByteD(source, out value, out bytesConsumed); + standardFormat = default; + goto FastPath; case 'n': - case 'N': return TryParseByteN(source, out value, out bytesConsumed); case 'x': - case 'X': return TryParseByteX(source, out value, out bytesConsumed); default: - return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(source, out value, out bytesConsumed); } } @@ -75,25 +80,30 @@ public static bool TryParse(ReadOnlySpan source, out byte value, out int b [CLSCompliant(false)] public static bool TryParse(ReadOnlySpan source, out ushort value, out int bytesConsumed, char standardFormat = default) { - switch (standardFormat) + FastPath: + if (standardFormat == default) + { + return TryParseUInt16D(source, out value, out bytesConsumed); + } + + // There's small but measurable overhead when entering the switch block below. + // We optimize for the default case by hoisting it above the switch block. + + switch (standardFormat | 0x20) // convert to lowercase { - case default(char): case 'g': - case 'G': case 'd': - case 'D': - return TryParseUInt16D(source, out value, out bytesConsumed); + standardFormat = default; + goto FastPath; case 'n': - case 'N': return TryParseUInt16N(source, out value, out bytesConsumed); case 'x': - case 'X': return TryParseUInt16X(source, out value, out bytesConsumed); default: - return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(source, out value, out bytesConsumed); } } @@ -121,25 +131,30 @@ public static bool TryParse(ReadOnlySpan source, out ushort value, out int [CLSCompliant(false)] public static bool TryParse(ReadOnlySpan source, out uint value, out int bytesConsumed, char standardFormat = default) { - switch (standardFormat) + FastPath: + if (standardFormat == default) + { + return TryParseUInt32D(source, out value, out bytesConsumed); + } + + // There's small but measurable overhead when entering the switch block below. + // We optimize for the default case by hoisting it above the switch block. + + switch (standardFormat | 0x20) // convert to lowercase { - case default(char): case 'g': - case 'G': case 'd': - case 'D': - return TryParseUInt32D(source, out value, out bytesConsumed); + standardFormat = default; + goto FastPath; case 'n': - case 'N': return TryParseUInt32N(source, out value, out bytesConsumed); case 'x': - case 'X': return TryParseUInt32X(source, out value, out bytesConsumed); default: - return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(source, out value, out bytesConsumed); } } @@ -167,25 +182,30 @@ public static bool TryParse(ReadOnlySpan source, out uint value, out int b [CLSCompliant(false)] public static bool TryParse(ReadOnlySpan source, out ulong value, out int bytesConsumed, char standardFormat = default) { - switch (standardFormat) + FastPath: + if (standardFormat == default) + { + return TryParseUInt64D(source, out value, out bytesConsumed); + } + + // There's small but measurable overhead when entering the switch block below. + // We optimize for the default case by hoisting it above the switch block. + + switch (standardFormat | 0x20) // convert to lowercase { - case default(char): case 'g': - case 'G': case 'd': - case 'D': - return TryParseUInt64D(source, out value, out bytesConsumed); + standardFormat = default; + goto FastPath; case 'n': - case 'N': return TryParseUInt64N(source, out value, out bytesConsumed); case 'x': - case 'X': return TryParseUInt64X(source, out value, out bytesConsumed); default: - return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(source, out value, out bytesConsumed); } } }