Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce fixed overhead of some Utf8Parser.TryParse methods #33507

Merged
merged 3 commits into from
Mar 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -70,5 +73,23 @@ public static bool TryParseThrowFormatException<T>(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<T>(ReadOnlySpan<byte> source, out T value, out int bytesConsumed) where T : struct
GrabYourPitchforks marked this conversation as resolved.
Show resolved Hide resolved
{
// 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static partial class Utf8Parser
public static bool TryParse(ReadOnlySpan<byte> 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)
{
Expand All @@ -42,7 +42,7 @@ public static bool TryParse(ReadOnlySpan<byte> 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')
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,25 @@ public static partial class Utf8Parser
/// </exceptions>
public static bool TryParse(ReadOnlySpan<byte> source, out Guid value, out int bytesConsumed, char standardFormat = default)
{
FastPath:
tannergooding marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}

Expand Down Expand Up @@ -97,9 +103,9 @@ private static bool TryParseGuidN(ReadOnlySpan<byte> 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<byte> source, bool ends, char begin, char end, out Guid value, out int bytesConsumed)
private static bool TryParseGuidCore(ReadOnlySpan<byte> 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)
{
Expand All @@ -108,16 +114,24 @@ private static bool TryParseGuidCore(ReadOnlySpan<byte> 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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JIT could produce better codegen for this pattern. See #33504.

{
value = default;
bytesConsumed = 0;
return false;
}

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))
Expand Down Expand Up @@ -226,7 +240,7 @@ private static bool TryParseGuidCore(ReadOnlySpan<byte> source, bool ends, char
return false; // 12 digits
}

if (ends && source[justConsumed] != end)
if (ends != 0 && source[justConsumed] != (byte)ends)
{
value = default;
bytesConsumed = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,31 @@ public static partial class Utf8Parser
[CLSCompliant(false)]
public static bool TryParse(ReadOnlySpan<byte> 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<sbyte, byte>(ref value), out bytesConsumed);

default:
return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed);
return ParserHelpers.TryParseThrowFormatException(source, out value, out bytesConsumed);
}
}

Expand All @@ -81,26 +86,31 @@ public static bool TryParse(ReadOnlySpan<byte> source, out sbyte value, out int
/// </exceptions>
public static bool TryParse(ReadOnlySpan<byte> 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<short, ushort>(ref value), out bytesConsumed);

default:
return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed);
return ParserHelpers.TryParseThrowFormatException(source, out value, out bytesConsumed);
}
}

Expand All @@ -127,26 +137,31 @@ public static bool TryParse(ReadOnlySpan<byte> source, out short value, out int
/// </exceptions>
public static bool TryParse(ReadOnlySpan<byte> 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<int, uint>(ref value), out bytesConsumed);

default:
return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed);
return ParserHelpers.TryParseThrowFormatException(source, out value, out bytesConsumed);
}
}

Expand All @@ -173,26 +188,31 @@ public static bool TryParse(ReadOnlySpan<byte> source, out int value, out int by
/// </exceptions>
public static bool TryParse(ReadOnlySpan<byte> 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;
lpereira marked this conversation as resolved.
Show resolved Hide resolved

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<long, ulong>(ref value), out bytesConsumed);

default:
return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed);
return ParserHelpers.TryParseThrowFormatException(source, out value, out bytesConsumed);
}
}
}
Expand Down
Loading