Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
796 changes: 500 additions & 296 deletions src/libraries/Common/src/System/Number.Formatting.Common.cs

Large diffs are not rendered by default.

12 changes: 1 addition & 11 deletions src/libraries/Common/src/System/Number.NumberBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;

namespace System
Expand All @@ -25,22 +23,14 @@ internal static partial class Number
internal const int Decimal64NumberBufferLength = 16 + 1 + 1; // 16 for the longest input + 1 for rounding
internal const int Decimal128NumberBufferLength = 34 + 1 + 1; // 34 for the longest input + 1 for rounding

internal unsafe ref struct NumberBuffer
internal ref struct NumberBuffer
{
public int DigitsCount;
public int Scale;
public bool IsNegative;
public bool HasNonZeroTail;
public NumberBufferKind Kind;
public Span<byte> Digits;
/// <safety>Converts the ref to Digits into a pointer value via Unsafe.AsPointer and returns it without dereferencing; the result is not GC-tracked, so any use must be in an unsafe context that establishes Digits still refers to unmovable memory.</safety>
public readonly byte* DigitsPtr => (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(Digits)); // safe since constructor expects Digits to refer to unmovable memory

public NumberBuffer(NumberBufferKind kind, byte* digits, int digitsLength) : this(kind, new Span<byte>(digits, digitsLength))
{
Debug.Assert(digits != null);
}

/// <summary>Initializes the NumberBuffer.</summary>
/// <param name="kind">The kind of the buffer.</param>
/// <param name="digits">The digits scratch space. The referenced memory must not be moveable, e.g. stack memory, pinned array, etc.</param>
Expand Down
179 changes: 92 additions & 87 deletions src/libraries/Common/src/System/Number.Parsing.Common.cs

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4089,7 +4089,7 @@ private static bool ParseJapaneseEraStart(ref __DTString str, DateTimeFormatInfo

// Given a specified format character, parse and update the parsing result.
//
private static unsafe bool ParseByFormat(
private static bool ParseByFormat(
ref __DTString str,
ref __DTString format,
scoped ref ParsingInfo parseInfo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;

namespace System.Globalization
Expand Down Expand Up @@ -103,7 +102,7 @@ private static ReadOnlySpan<byte> GetUtf8Span(ref byte[]? utf8Cache, string valu
{
byte[] utf8 = utf8Cache ?? CreateUtf8Cache(ref utf8Cache, value);
Debug.Assert(utf8.Length > 0);
return MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetArrayDataReference(utf8), utf8.Length - 1);
return utf8.AsSpan(0, utf8.Length - 1);
}

[MethodImpl(MethodImplOptions.NoInlining)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal static class TimeSpanFormat
internal static readonly FormatLiterals NegativeInvariantFormatLiterals = FormatLiterals.InitInvariant(isNegative: true);

/// <summary>Main method called from TimeSpan.ToString.</summary>
internal static unsafe string Format(TimeSpan value, string? format, IFormatProvider? formatProvider)
internal static string Format(TimeSpan value, string? format, IFormatProvider? formatProvider)
{
if (string.IsNullOrEmpty(format))
{
Expand Down Expand Up @@ -48,7 +48,7 @@ internal static unsafe string Format(TimeSpan value, string? format, IFormatProv
}

/// <summary>Main method called from TimeSpan.TryFormat.</summary>
internal static unsafe bool TryFormat<TChar>(TimeSpan value, Span<TChar> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? formatProvider) where TChar : unmanaged, IUtfChar<TChar>
internal static bool TryFormat<TChar>(TimeSpan value, Span<TChar> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? formatProvider) where TChar : unmanaged, IUtfChar<TChar>
{
Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));

Expand Down Expand Up @@ -81,14 +81,14 @@ internal static unsafe bool TryFormat<TChar>(TimeSpan value, Span<TChar> destina
return result;
}

internal static unsafe string FormatC(TimeSpan value)
internal static string FormatC(TimeSpan value)
{
Span<char> destination = stackalloc char[26]; // large enough for any "c" TimeSpan
TryFormatStandard(value, StandardFormat.C, null, destination, out int charsWritten);
return new string(destination.Slice(0, charsWritten));
}

private static unsafe string FormatG(TimeSpan value, DateTimeFormatInfo dtfi, StandardFormat format)
private static string FormatG(TimeSpan value, DateTimeFormatInfo dtfi, StandardFormat format)
{
string decimalSeparator = dtfi.DecimalSeparator;
int maxLength = checked(25 + decimalSeparator.Length); // large enough for any "g"/"G" TimeSpan
Expand All @@ -106,7 +106,7 @@ internal enum StandardFormat
g
}

internal static unsafe bool TryFormatStandard<TChar>(TimeSpan value, StandardFormat format, ReadOnlySpan<TChar> decimalSeparator, Span<TChar> destination, out int written) where TChar : unmanaged, IUtfChar<TChar>
internal static bool TryFormatStandard<TChar>(TimeSpan value, StandardFormat format, ReadOnlySpan<TChar> decimalSeparator, Span<TChar> destination, out int written) where TChar : unmanaged, IUtfChar<TChar>
{
Debug.Assert(format == StandardFormat.C || format == StandardFormat.G || format == StandardFormat.g);

Expand Down Expand Up @@ -229,66 +229,114 @@ internal static unsafe bool TryFormatStandard<TChar>(TimeSpan value, StandardFor
return false;
}

fixed (TChar* dest = &MemoryMarshal.GetReference(destination))
int pos = 0;

// Write leading '-' if necessary
if (value.Ticks < 0)
{
destination[pos++] = TChar.CastFrom('-');
}

// Write day and separator, if necessary
if (dayDigits != 0)
{
TChar* p = dest;
Number.WriteDigits(days, destination.Slice(pos, dayDigits));
pos += dayDigits;
destination[pos++] = TChar.CastFrom(format == StandardFormat.C ? '.' : ':');
}

// Write leading '-' if necessary
if (value.Ticks < 0)
// After writing the variable-length prefix into destination[0..pos), write the
// fixed "[h]h:mm:ss[.fraction]" suffix. We branch on hourDigits (1 or 2) and
// initialize suffixLen to the minimum length (8 or 7) before conditionally adding
// the fraction part, giving the JIT a concrete lower bound to hoist bounds checks
// out of the inner writes.
Debug.Assert(hourDigits == 1 || hourDigits == 2);
int suffixLen;
if (hourDigits == 2)
{
int decSepLen = 0;
suffixLen = 8; // hh:mm:ss
if (fractionDigits != 0)
{
*p++ = TChar.CastFrom('-');
decSepLen = format == StandardFormat.C ? 1 : decimalSeparator.Length;
suffixLen += decSepLen + fractionDigits;
}

// Write day and separator, if necessary
if (dayDigits != 0)
// Invariant: suffixLen >= 8 by construction; this check is unreachable but lets
// the JIT prove that all suffix writes at constant offsets 0..7 are in bounds.
if ((uint)suffixLen < 8u)
{
Number.WriteDigits(days, p, dayDigits);
p += dayDigits;
*p++ = TChar.CastFrom(format == StandardFormat.C ? '.' : ':');
ThrowHelper.ThrowArgumentOutOfRangeException();
}
Comment thread
tannergooding marked this conversation as resolved.
Span<TChar> suffix = destination.Slice(pos, suffixLen);

Number.WriteTwoDigits(hours, suffix.Slice(0, 2));
suffix[2] = TChar.CastFrom(':');
Number.WriteTwoDigits((uint)minutes, suffix.Slice(3, 2));
suffix[5] = TChar.CastFrom(':');
Number.WriteTwoDigits((uint)seconds, suffix.Slice(6, 2));

if (fractionDigits != 0)
{
if (format == StandardFormat.C)
{
suffix[8] = TChar.CastFrom('.');
}
else if (decSepLen == 1)
{
suffix[8] = decimalSeparator[0];
}
else
{
decimalSeparator.CopyTo(suffix.Slice(8, decSepLen));
}

// Write "[h]h:mm:ss
Debug.Assert(hourDigits == 1 || hourDigits == 2);
if (hourDigits == 2)
Number.WriteDigits(fraction, suffix.Slice(8 + decSepLen, fractionDigits));
}
}
else
{
int decSepLen = 0;
suffixLen = 7; // h:mm:ss
if (fractionDigits != 0)
{
Number.WriteTwoDigits(hours, p);
p += 2;
decSepLen = format == StandardFormat.C ? 1 : decimalSeparator.Length;
suffixLen += decSepLen + fractionDigits;
}
else
// Invariant: suffixLen >= 7 by construction; this check is unreachable but lets
// the JIT prove that all suffix writes at constant offsets 0..6 are in bounds.
if ((uint)suffixLen < 7u)
{
*p++ = TChar.CastFrom('0' + hours);
ThrowHelper.ThrowArgumentOutOfRangeException();
}
*p++ = TChar.CastFrom(':');
Number.WriteTwoDigits((uint)minutes, p);
p += 2;
*p++ = TChar.CastFrom(':');
Number.WriteTwoDigits((uint)seconds, p);
p += 2;

// Write fraction and separator, if necessary
Span<TChar> suffix = destination.Slice(pos, suffixLen);

suffix[0] = TChar.CastFrom('0' + (int)hours);
suffix[1] = TChar.CastFrom(':');
Number.WriteTwoDigits((uint)minutes, suffix.Slice(2, 2));
suffix[4] = TChar.CastFrom(':');
Number.WriteTwoDigits((uint)seconds, suffix.Slice(5, 2));

if (fractionDigits != 0)
{
if (format == StandardFormat.C)
{
*p++ = TChar.CastFrom('.');
suffix[7] = TChar.CastFrom('.');
}
else if (decimalSeparator.Length == 1)
else if (decSepLen == 1)
{
*p++ = decimalSeparator[0];
suffix[7] = decimalSeparator[0];
}
else
{
decimalSeparator.CopyTo(new Span<TChar>(p, decimalSeparator.Length));
p += decimalSeparator.Length;
decimalSeparator.CopyTo(suffix.Slice(7, decSepLen));
}

Number.WriteDigits(fraction, p, fractionDigits);
p += fractionDigits;
Number.WriteDigits(fraction, suffix.Slice(7 + decSepLen, fractionDigits));
}

Debug.Assert(p - dest == requiredOutputLength);
}

Debug.Assert(pos + suffixLen == requiredOutputLength);

written = requiredOutputLength;
return true;
}
Expand Down Expand Up @@ -496,7 +544,7 @@ internal static FormatLiterals InitInvariant(bool isNegative)
// the constants guaranteed to include DHMSF ordered greatest to least significant.
// Once the data becomes more complex than this we will need to write a proper tokenizer for
// parsing and formatting
internal unsafe void Init(ReadOnlySpan<char> format, bool useInvariantFieldLengths)
internal void Init(ReadOnlySpan<char> format, bool useInvariantFieldLengths)
{
dd = hh = mm = ss = ff = 0;
_literals = new string[6];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,11 @@ internal ref struct BigInteger
private int _length;
private BlocksBuffer _blocks;

// _blocks spans MaxBlockCount uints and only the first _length of them are ever read, so the
// out parameters below use Unsafe.SkipInit rather than `= default` to avoid zero-filling the
// whole buffer. Measured on the double.Parse slow path, `= default` costs 10-30%
// (267ns -> 340ns for 50 digits, 1456ns -> 1650ns for 408 digits).

public static void Add(scoped ref BigInteger lhs, scoped ref BigInteger rhs, out BigInteger result)
{
Unsafe.SkipInit(out result);
Expand Down Expand Up @@ -512,7 +517,11 @@ public static void DivRem(scoped ref BigInteger lhs, scoped ref BigInteger rhs,
else
{
int quoLength = lhsLength - rhsLength + 1;
SetValue(out rem, ref lhs);

// rem is an out parameter and so is not scoped, which means it cannot be passed
// by ref alongside the scoped rhs below. Compute into a local and copy out at the
// end instead. This was only a warning while Number was an unsafe context.
SetValue(out BigInteger remValue, ref lhs);
int remLength = lhsLength;

// Executes the "grammar-school" algorithm for computing q = a / b.
Expand Down Expand Up @@ -544,10 +553,10 @@ public static void DivRem(scoped ref BigInteger lhs, scoped ref BigInteger rhs,
for (int i = lhsLength; i >= rhsLength; i--)
{
int n = i - rhsLength;
uint t = i < lhsLength ? rem._blocks[i] : 0;
uint t = i < lhsLength ? remValue._blocks[i] : 0;

ulong valHi = ((ulong)t << 32) | rem._blocks[i - 1];
uint valLo = i > 1 ? rem._blocks[i - 2] : 0;
ulong valHi = ((ulong)t << 32) | remValue._blocks[i - 1];
uint valLo = i > 1 ? remValue._blocks[i - 2] : 0;

// We shifted the divisor, we shift the dividend too
if (shiftLeft > 0)
Expand All @@ -557,7 +566,7 @@ public static void DivRem(scoped ref BigInteger lhs, scoped ref BigInteger rhs,

if (i > 2)
{
valLo |= rem._blocks[i - 3] >> shiftRight;
valLo |= remValue._blocks[i - 3] >> shiftRight;
}
}

Expand All @@ -584,14 +593,14 @@ public static void DivRem(scoped ref BigInteger lhs, scoped ref BigInteger rhs,
// https://github.com/dotnet/roslyn/issues/64393
#pragma warning disable CS9080
// Now it's time to subtract our current quotient
uint carry = SubtractDivisor(ref rem, n, ref rhs, digit);
uint carry = SubtractDivisor(ref remValue, n, ref rhs, digit);

if (carry != t)
{
Debug.Assert(carry == t + 1);

// Our guess was still exactly one too high
carry = AddDivisor(ref rem, n, ref rhs);
carry = AddDivisor(ref remValue, n, ref rhs);
digit--;

Debug.Assert(carry == 1);
Expand Down Expand Up @@ -624,7 +633,7 @@ public static void DivRem(scoped ref BigInteger lhs, scoped ref BigInteger rhs,

for (int i = remLength - 1; i >= 0; i--)
{
if (rem._blocks[i] == 0)
if (remValue._blocks[i] == 0)
{
remLength--;
}
Expand All @@ -635,7 +644,8 @@ public static void DivRem(scoped ref BigInteger lhs, scoped ref BigInteger rhs,
}
}

rem._length = remLength;
remValue._length = remLength;
SetValue(out rem, ref remValue);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ internal static TValue EncodeDecimalIeee754<TDecimal, TValue>(TValue bidBits)
TValue leadingDigit = decoded.Significand / scale;
uint msd = uint.CreateTruncating(leadingDigit);

int exponentContinuationBits = (Unsafe.SizeOf<TValue>() * 8) - 6 - TDecimal.NumberBitsSignificand;
int exponentContinuationBits = (sizeof(TValue) * 8) - 6 - TDecimal.NumberBitsSignificand;
uint exponentHigh = biasedExponent >> exponentContinuationBits;
uint exponentLow = biasedExponent & ((1u << exponentContinuationBits) - 1);

Expand Down Expand Up @@ -598,7 +598,7 @@ internal static TValue DecodeDecimalIeee754<TDecimal, TValue>(TValue dpdBits)
return (dpdBits & (TDecimal.SignMask | TDecimal.SNaNMask)) | payload;
}

int exponentContinuationBits = (Unsafe.SizeOf<TValue>() * 8) - 6 - TDecimal.NumberBitsSignificand;
int exponentContinuationBits = (sizeof(TValue) * 8) - 6 - TDecimal.NumberBitsSignificand;
uint combination = uint.CreateTruncating(dpdBits >> (TDecimal.NumberBitsSignificand + exponentContinuationBits)) & 0x1F;
uint exponentLow = uint.CreateTruncating(dpdBits >> TDecimal.NumberBitsSignificand) & ((1u << exponentContinuationBits) - 1);

Expand Down
Loading
Loading