Skip to content

Optimise char to hex conversion #116821

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
56 changes: 31 additions & 25 deletions src/libraries/Common/src/System/HexConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -370,32 +370,38 @@ private static bool TryDecodeFromUtf16_Scalar(ReadOnlySpan<char> chars, Span<byt

// byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern
// is if either byteHi or byteLo was not a hex character.
if ((byteLo | byteHi) == 0xFF)
if ((byteLo | byteHi) < 0)
break;

bytes[j++] = (byte)((byteHi << 4) | byteLo);
i += 2;
}

if (byteLo == 0xFF)
if (byteLo < 0)
i++;

charsProcessed = i;
return (byteLo | byteHi) != 0xFF;
return (byteLo | byteHi) >= 0;
}

/// <summary>Converts a hex character to its integer value.</summary>
/// <returns>The integer value of the hex character, or -1 if the character is not a valid hex character.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int FromChar(int c)
{
return c >= CharToHexLookup.Length ? 0xFF : CharToHexLookup[c];
return CharToHexLookup[Math.Min(c, 0xFF)];
}

/// <summary>Converts an upper hex character to its integer value.</summary>
/// <returns>The integer value of the upper hex character, or -1 if the character is not a valid upper hex character.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int FromUpperChar(int c)
{
return c > 71 ? 0xFF : CharToHexLookup[c];
return CharToHexLookup[Math.Min(c, 'F' + 1)];
}

/// <summary>Converts a lower hex character to its integer value.</summary>
/// <returns>The integer value of the lower hex character, or -1 if the character is not a valid lower hex character.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int FromLowerChar(int c)
{
Expand All @@ -405,7 +411,7 @@ public static int FromLowerChar(int c)
if ((uint)(c - 'a') <= 'f' - 'a')
return c - 'a' + 10;

return 0xFF;
return -1;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down Expand Up @@ -436,7 +442,7 @@ public static bool IsHexChar(int c)
return (long)(shift & mask) < 0 ? true : false;
}

return FromChar(c) != 0xFF;
return FromChar(c) >= 0;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand All @@ -451,25 +457,25 @@ public static bool IsHexLowerChar(int c)
return (uint)(c - '0') <= 9 || (uint)(c - 'a') <= ('f' - 'a');
}

/// <summary>Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit.</summary>
public static ReadOnlySpan<byte> CharToHexLookup =>
/// <summary>Map from an ASCII char to its hex value, e.g. arr['b'] == 11. -1 means it's not a hex digit.</summary>
public static ReadOnlySpan<sbyte> CharToHexLookup =>
[
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63
0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95
0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 15
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 31
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 47
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 63
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 79
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 95
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 111
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 127
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 143
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 159
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 175
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 191
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 207
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 223
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 239
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 255
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,10 @@ internal static unsafe long ParseNonCanonical<TChar>(TChar* name, int start, ref
ch = ToUShort(name[current]);
int digitValue = HexConverter.FromChar(ch);

if (digitValue >= numberBase)
// Unsigned overflow indicates an invalid character or a terminator
if ((uint)digitValue >= (uint)numberBase)
{
break; // Invalid/terminator
break;
}
currentValue = (currentValue * numberBase) + digitValue;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ internal static void Parse<TChar>(ReadOnlySpan<TChar> address, scoped Span<ushor
default:
int characterValue = HexConverter.FromChar(currentCh);

number = number * IPv6AddressHelper.Hex + characterValue;
number = number * IPv6AddressHelper.Hex + (byte)characterValue;
i++;
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2030,7 +2030,7 @@ internal static byte HexByteFromChars(char char1, char char2)
{
int hi = HexConverter.FromLowerChar(char1);
int lo = HexConverter.FromLowerChar(char2);
if ((hi | lo) == 0xFF)
if ((hi | lo) < 0)
{
throw new ArgumentOutOfRangeException("idData");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,8 +390,9 @@ private static bool TryDecodeEscapedByte(ReadOnlySpan<char> span, out byte value
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryDecodeHexDigit(char c, out byte value)
{
value = (byte)HexConverter.FromChar((int)c);
return value != 0xFF; // invalid hex digit
int result = HexConverter.FromChar(c);
value = (byte)result;
return result >= 0;
}

// Allowed baggage key characters:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ private static bool TryReadUnknownPercentEncodedAlpnProtocolName(ReadOnlySpan<ch
private static bool TryReadAlpnHexDigit(char ch, out int nibble)
{
int result = HexConverter.FromUpperChar(ch);
if (result == 0xFF)
if (result < 0)
{
nibble = 0;
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ private static string UrlDecodeStringFromStringInternal(string s, Encoding e)
int h3 = HexConverter.FromChar(s[pos + 4]);
int h4 = HexConverter.FromChar(s[pos + 5]);

if ((h1 | h2 | h3 | h4) != 0xFF)
if ((h1 | h2 | h3 | h4) >= 0)
{ // valid 4 hex chars
ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4);
pos += 5;
Expand All @@ -448,7 +448,7 @@ private static string UrlDecodeStringFromStringInternal(string s, Encoding e)
int h1 = HexConverter.FromChar(s[pos + 1]);
int h2 = HexConverter.FromChar(s[pos + 2]);

if ((h1 | h2) != 0xFF)
if ((h1 | h2) >= 0)
{ // valid 2 hex chars
byte b = (byte)((h1 << 4) | h2);
pos += 2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public static bool TryParse(ReadOnlySpan<char> address, [NotNullWhen(true)] out
{
int character = address[i];
int tmp;
if ((tmp = HexConverter.FromChar(character)) == 0xFF)
if ((tmp = HexConverter.FromChar(character)) < 0)
{
if (delimiter == character && validCount == validSegmentLength)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ private static IPAddress ParseIPv6HexString(ReadOnlySpan<char> hexAddress, bool
private static byte HexToByte(char val)
{
int result = HexConverter.FromChar(val);
if (result == 0xFF)
if (result < 0)
{
throw ExceptionHelper.CreateForParseFailure();
}
Expand Down
Loading
Loading