Skip to content

Commit

Permalink
Reduce fixed overhead of some Utf8Parser.TryParse methods (#33507)
Browse files Browse the repository at this point in the history
  • Loading branch information
GrabYourPitchforks authored Mar 13, 2020
1 parent 36ee0ef commit 099bc4b
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 80 deletions.
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
{
// 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:
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)
{
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;

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

0 comments on commit 099bc4b

Please sign in to comment.