* Removed (uint) cast workaround in CoreLib, annotated missing places with TODOs
* Fixed the bug I introduced in TimeSpanParse.TimeSpanTokenizer
* Use uint-cast on both sided for clarity
Cf. https://github.com/dotnet/runtime/pull/67448#discussion_r841082992
* Use uint-division in BitHelper as the JIT can produce more efficient code
Cf. https://github.com/dotnet/runtime/pull/67448#discussion_r841239729
* Don't have struct local in ValueStringBuilder due to hitting JIT optimization limits
Cf. https://github.com/dotnet/runtime/pull/67448#discussion_r841129465
* BitHelper with uint division and without Math.DivRem for even better codegen
AggressiveInlining isn't needed anymore, also the JIT recognizes the _span field, so no local Span is needed here.
* BitHelper needs local Span due to regression in jit-diff otherwise
* Added Debug.Asserts to BitHelper's method that take bitPosition
* BitHelper: comment for workaround to tracking issue
// 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;
+
namespace System.Collections.Generic
{
internal ref struct BitHelper
internal void MarkBit(int bitPosition)
{
- int bitArrayIndex = bitPosition / IntSize;
- if ((uint)bitArrayIndex < (uint)_span.Length)
+ Debug.Assert(bitPosition >= 0);
+
+ uint bitArrayIndex = (uint)bitPosition / IntSize;
+
+ // Workaround for https://github.com/dotnet/runtime/issues/72004
+ Span<int> span = _span;
+ if (bitArrayIndex < (uint)span.Length)
{
- _span[bitArrayIndex] |= (1 << (bitPosition % IntSize));
+ span[(int)bitArrayIndex] |= (1 << (int)((uint)bitPosition % IntSize));
}
}
internal bool IsMarked(int bitPosition)
{
- int bitArrayIndex = bitPosition / IntSize;
+ Debug.Assert(bitPosition >= 0);
+
+ uint bitArrayIndex = (uint)bitPosition / IntSize;
+
+ // Workaround for https://github.com/dotnet/runtime/issues/72004
+ Span<int> span = _span;
return
- (uint)bitArrayIndex < (uint)_span.Length &&
- (_span[bitArrayIndex] & (1 << (bitPosition % IntSize))) != 0;
+ bitArrayIndex < (uint)span.Length &&
+ (span[(int)bitArrayIndex] & (1 << ((int)((uint)bitPosition % IntSize)))) != 0;
}
/// <summary>How many ints must be allocated to represent n bits. Returns (n+31)/32, but avoids overflow.</summary>
{
if (m_value)
{
- if ((uint)destination.Length > 3) // uint cast, per https://github.com/dotnet/runtime/issues/10596
+ if (destination.Length > 3)
{
ulong true_val = BitConverter.IsLittleEndian ? 0x65007500720054ul : 0x54007200750065ul; // "True"
MemoryMarshal.Write<ulong>(MemoryMarshal.AsBytes(destination), ref true_val);
}
else
{
- if ((uint)destination.Length > 4)
+ if (destination.Length > 4)
{
ulong fals_val = BitConverter.IsLittleEndian ? 0x73006C00610046ul : 0x460061006C0073ul; // "Fals"
MemoryMarshal.Write<ulong>(MemoryMarshal.AsBytes(destination), ref fals_val);
int count = 0;
char symbol = Symbol;
- if (symbol != default &&
- (uint)destination.Length == FormatStringLength) // to eliminate bounds checks
+ if (symbol != default && destination.Length == FormatStringLength)
{
destination[0] = symbol;
count = 1;
{
// This check can't be performed earlier because we need to throw if an invalid symbol is
// provided, even if the buffer is too small.
- if ((uint)4 >= (uint)destination.Length)
+ if (destination.Length <= 4)
{
goto BufferTooSmall;
}
}
else if (symbol == 'l')
{
- if ((uint)4 >= (uint)destination.Length)
+ if (destination.Length <= 4)
{
goto BufferTooSmall;
}
//
private static bool TryFormatDateTimeL(DateTime value, Span<byte> destination, out int bytesWritten)
{
- // Writing the check in this fashion elides all bounds checks on 'buffer'
- // for the remainder of the method.
- if ((uint)28 >= (uint)destination.Length)
+ if (destination.Length <= 28)
{
bytesWritten = 0;
return false;
//
private static bool TryFormatDateTimeR(DateTime value, Span<byte> destination, out int bytesWritten)
{
- // Writing the check in this fashion elides all bounds checks on 'buffer'
- // for the remainder of the method.
- if ((uint)28 >= (uint)destination.Length)
+ if (destination.Length <= 28)
{
bytesWritten = 0;
return false;
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
}
- if (((uint)index) >= ((uint)s.Length))
+ if ((uint)index >= (uint)s.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
}
- if (((uint)index) >= ((uint)s.Length))
+ if ((uint)index >= (uint)s.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
}
- if (((uint)index) >= ((uint)s.Length))
+ if ((uint)index >= (uint)s.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
}
- if (((uint)index) >= ((uint)s.Length))
+ if ((uint)index >= (uint)s.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
}
-
- if (((uint)index) >= ((uint)s.Length))
+ if ((uint)index >= (uint)s.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
}
- if (((uint)index) >= ((uint)s.Length))
+ if ((uint)index >= (uint)s.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
}
- if (((uint)index) >= ((uint)s.Length))
+ if ((uint)index >= (uint)s.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
}
- if (((uint)index) >= ((uint)s.Length))
+ if ((uint)index >= (uint)s.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
}
- if (((uint)index) >= ((uint)s.Length))
+ if ((uint)index >= (uint)s.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
}
- if (((uint)index) >= ((uint)s.Length))
+ if ((uint)index >= (uint)s.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
}
- if (((uint)index) >= ((uint)s.Length))
+ if ((uint)index >= (uint)s.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
}
- if (((uint)index) >= ((uint)s.Length))
+ if ((uint)index >= (uint)s.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
}
- if (((uint)index) >= ((uint)s.Length))
+ if ((uint)index >= (uint)s.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
}
- if (((uint)index) >= ((uint)s.Length))
+ if ((uint)index >= (uint)s.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
}
- if (((uint)index) >= ((uint)s.Length))
+ if ((uint)index >= (uint)s.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
}
- if (((uint)index) >= ((uint)s.Length))
+ if ((uint)index >= (uint)s.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
}
- if (((uint)index) >= ((uint)s.Length))
+ if ((uint)index >= (uint)s.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
public void Append(T item)
{
int pos = _pos;
- if ((uint)pos < (uint)_span.Length)
+
+ // Workaround for https://github.com/dotnet/runtime/issues/72004
+ Span<T> span = _span;
+ if ((uint)pos < (uint)span.Length)
{
- _span[pos] = item;
+ span[pos] = item;
_pos = pos + 1;
}
else
/// <exception cref="ArgumentException">The destination span was not long enough to store the binary representation.</exception>
public static int GetBits(decimal d, Span<int> destination)
{
- if ((uint)destination.Length <= 3)
+ if (destination.Length <= 3)
{
ThrowHelper.ThrowArgumentException_DestinationTooShort();
}
/// <returns>true if the decimal's binary representation was written to the destination; false if the destination wasn't long enough.</returns>
public static bool TryGetBits(decimal d, Span<int> destination, out int valuesWritten)
{
- if ((uint)destination.Length <= 3)
+ if (destination.Length <= 3)
{
valuesWritten = 0;
return false;
// Tue, 03 Jan 2017 08:08:05 GMT
// The format is exactly 29 characters.
- if ((uint)source.Length != 29)
+ if (source.Length != 29)
{
result.SetBadDateTimeFailure();
return false;
// 2017-06-12T05:30:45.7680000-7:00 (special-case of one-digit offset hour)
// 2017-06-12T05:30:45.7680000-07:00
- if ((uint)source.Length < 27 ||
+ if (source.Length < 27 ||
source[4] != '-' ||
source[7] != '-' ||
source[10] != 'T' ||
return false;
}
- if ((uint)source.Length > 27)
+ if (source.Length > 27)
{
char offsetChar = source[27];
switch (offsetChar)
case '-':
int offsetHours, colonIndex;
- if ((uint)source.Length == 33)
+ if (source.Length == 33)
{
uint oh1 = (uint)(source[28] - '0'), oh2 = (uint)(source[29] - '0');
offsetHours = (int)(oh1 * 10 + oh2);
colonIndex = 30;
}
- else if ((uint)source.Length == 32) // special-case allowed for compat: only one offset hour digit
+ else if (source.Length == 32) // special-case allowed for compat: only one offset hour digit
{
offsetHours = source[28] - '0';
{
// Get the position of the next character to be processed. If there is no
// next character, we're at the end.
- int pos = _pos;
+ int startPos = _pos;
+ int pos = startPos;
+ ReadOnlySpan<char> value = _value;
Debug.Assert(pos > -1);
- if (pos >= _value.Length)
+ if ((uint)pos >= (uint)value.Length)
{
return new TimeSpanToken(TTT.End);
}
// Now retrieve that character. If it's a digit, we're processing a number.
- int num = _value[pos] - '0';
+ int num = value[pos] - '0';
if ((uint)num <= 9)
{
int zeroes = 0;
while (true)
{
int digit;
- if (++_pos >= _value.Length || (uint)(digit = _value[_pos] - '0') > 9)
+ if ((uint)++pos >= (uint)value.Length || (uint)(digit = value[pos] - '0') > 9)
{
+ _pos = pos;
return new TimeSpanToken(TTT.Num, 0, zeroes, default);
}
num = digit;
break;
}
+
+ _pos = pos;
}
// Continue to read as long as we're reading digits.
- while (++_pos < _value.Length)
+ while ((uint)++pos < (uint)value.Length)
{
- int digit = _value[_pos] - '0';
+ int digit = value[pos] - '0';
if ((uint)digit > 9)
{
break;
num = num * 10 + digit;
if ((num & 0xF0000000) != 0) // Max limit we can support 268435455 which is FFFFFFF
{
+ _pos = pos;
return new TimeSpanToken(TTT.NumOverflow);
}
}
+ _pos = pos;
return new TimeSpanToken(TTT.Num, num, zeroes, default);
}
// Otherwise, we're processing a separator, and we've already processed the first
// character of it. Continue processing characters as long as they're not digits.
int length = 1;
- while (true)
+ while ((uint)++pos < (uint)value.Length && !char.IsAsciiDigit(value[pos]))
{
- if (++_pos >= _value.Length || char.IsAsciiDigit(_value[_pos]))
- {
- break;
- }
length++;
}
+ _pos = pos;
// Return the separator.
- return new TimeSpanToken(TTT.Sep, 0, 0, _value.Slice(pos, length));
+ return new TimeSpanToken(TTT.Sep, 0, 0, _value.Slice(startPos, length));
}
internal bool EOL => _pos >= (_value.Length - 1);
internal char NextChar()
{
int pos = ++_pos;
- return (uint)pos < (uint)_value.Length?
+ return (uint)pos < (uint)_value.Length ?
_value[pos] :
(char)0;
}
// Creates a new guid from a read-only span.
public Guid(ReadOnlySpan<byte> b)
{
- if ((uint)b.Length != 16)
+ if (b.Length != 16)
{
throw new ArgumentException(SR.Format(SR.Arg_GuidArrayCtor, "16"), nameof(b));
}
{
// e.g. "{d85b1407-351d-4694-9392-03acc5870eb1}"
- if ((uint)guidString.Length != 38 || guidString[0] != '{' || guidString[37] != '}')
+ if (guidString.Length != 38 || guidString[0] != '{' || guidString[37] != '}')
{
result.SetFailure(overflow: false, nameof(SR.Format_GuidInvLen));
return false;
{
// e.g. "d85b1407-351d-4694-9392-03acc5870eb1"
- if ((uint)guidString.Length != 36 || guidString[8] != '-' || guidString[13] != '-' || guidString[18] != '-' || guidString[23] != '-')
+ if (guidString.Length != 36 || guidString[8] != '-' || guidString[13] != '-' || guidString[18] != '-' || guidString[23] != '-')
{
result.SetFailure(overflow: false, guidString.Length != 36 ? nameof(SR.Format_GuidInvLen) : nameof(SR.Format_GuidDashes));
return false;
{
// e.g. "d85b1407351d4694939203acc5870eb1"
- if ((uint)guidString.Length != 32)
+ if (guidString.Length != 32)
{
result.SetFailure(overflow: false, nameof(SR.Format_GuidInvLen));
return false;
{
// e.g. "(d85b1407-351d-4694-9392-03acc5870eb1)"
- if ((uint)guidString.Length != 38 || guidString[0] != '(' || guidString[37] != ')')
+ if (guidString.Length != 38 || guidString[0] != '(' || guidString[37] != ')')
{
result.SetFailure(overflow: false, nameof(SR.Format_GuidInvLen));
return false;
guidString = EatAllWhitespace(guidString);
// Check for leading '{'
- if ((uint)guidString.Length == 0 || guidString[0] != '{')
+ if (guidString.Length == 0 || guidString[0] != '{')
{
result.SetFailure(overflow: false, nameof(SR.Format_GuidBrace));
return false;
private static bool TryParseHex(ReadOnlySpan<char> guidString, out uint result, ref bool overflow)
{
- if ((uint)guidString.Length > 0)
+ if (guidString.Length > 0)
{
if (guidString[0] == '+')
{
guidString = guidString.Slice(1);
}
- if ((uint)guidString.Length > 1 && guidString[0] == '0' && (guidString[1] | 0x20) == 'x')
+ if (guidString.Length > 1 && guidString[0] == '0' && (guidString[1] | 0x20) == 'x')
{
guidString = guidString.Slice(2);
}
if (value.Length == 1)
{
- Span<char> chars = _chars;
int pos = _pos;
+ Span<char> chars = _chars;
if ((uint)pos < (uint)chars.Length)
{
chars[pos] = value[0];
if (value.Length == 2)
{
- Span<char> chars = _chars;
int pos = _pos;
- if ((uint)pos < chars.Length - 1)
+ if ((uint)pos < _chars.Length - 1)
{
Unsafe.WriteUnaligned(
- ref Unsafe.As<char, byte>(ref Unsafe.Add(ref MemoryMarshal.GetReference(chars), pos)),
+ ref Unsafe.As<char, byte>(ref Unsafe.Add(ref MemoryMarshal.GetReference(_chars), pos)),
Unsafe.ReadUnaligned<int>(ref Unsafe.As<char, byte>(ref value.GetRawStringData())));
_pos = pos + 2;
}
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
}
- if ((uint)index >= (uint)input!.Length)
+ if ((uint)index >= (uint)input.Length)
{
ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException();
}
charsWritten = 1;
return true;
}
- else if (1 < (uint)destination.Length)
+ else if (destination.Length > 1)
{
UnicodeUtility.GetUtf16SurrogatesFromSupplementaryPlaneScalar((uint)value._value, out destination[0], out destination[1]);
charsWritten = 2;
return true;
}
- if (1 < (uint)destination.Length)
+ if (destination.Length > 1)
{
if (value.Value <= 0x7FFu)
{
return true;
}
- if (2 < (uint)destination.Length)
+ if (destination.Length > 2)
{
if (value.Value <= 0xFFFFu)
{
return true;
}
- if (3 < (uint)destination.Length)
+ if (destination.Length > 3)
{
// Scalar 000uuuuu zzzzyyyy yyxxxxxx -> bytes [ 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx ]
destination[0] = (byte)((value._value + (0b11110 << 21)) >> 18);
int nextCharIndex = m_ChunkLength;
char[] chars = m_ChunkChars;
- if ((uint)chars.Length > (uint)nextCharIndex)
+ if ((uint)nextCharIndex < (uint)chars.Length)
{
chars[nextCharIndex] = value;
- m_ChunkLength++;
+ m_ChunkLength = nextCharIndex + 1;
}
else
{