From 63f1a0f6fbb0f24dc881be1defa899eae9dbf86c Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 22 Nov 2017 00:13:37 -0500 Subject: [PATCH] Add Decimal.TryFormat span-based method (#15145) * Move decimal formatting to managed code - Move decimal formatting to shared (decimal itself is still not shared) - Delete VM's decimal formatting code * Add Decimal.TryFormat span-based method * Workaround ProjectN compiler bug Apply the same attribution that's applied to the code in corert. * Address PR feedback * Temporarily undo explicit layout change Seeing whether it's the cause of these failures on Unix (Ubuntu, CentOS, and OSX): ``` JIT.Directed.coverage.oldtests.lclfldadd_cs_r.lclfldadd_cs_r JIT.Directed.perffix.primitivevt.mixed1_cs_ro.mixed1_cs_ro JIT.Methodical.fp.exgen.1000w1d_cs_r.1000w1d_cs_r JIT.Methodical.fp.exgen.1000w1d_cs_ro.1000w1d_cs_ro JIT.Methodical.fp.exgen.1000w1d_cs_do.1000w1d_cs_do JIT.Methodical.fp.exgen.1000w1d_cs_d.1000w1d_cs_d JIT.Methodical.fp.exgen.10w5d_cs_d.10w5d_cs_d JIT.Methodical.fp.exgen.10w5d_cs_do.10w5d_cs_do JIT.Methodical.fp.exgen.10w5d_cs_ro.10w5d_cs_ro JIT.Methodical.fp.exgen.200w1d-02_cs_do.200w1d-02_cs_do JIT.Methodical.fp.exgen.10w5d_cs_r.10w5d_cs_r JIT.Methodical.fp.exgen.200w1d-02_cs_ro.200w1d-02_cs_ro JIT.Directed.perffix.primitivevt.mixed1_cs_do.mixed1_cs_do ``` Example failure: ``` FAILED - JIT/Methodical/fp/exgen/200w1d-02_cs_ro/200w1d-02_cs_ro.sh BEGIN EXECUTION /mnt/j/workspace/dotnet_coreclr/master/checked_ubuntu_tst_prtest/bin/tests/Windows_NT.x64.Checked/Tests/coreoverlay/corerun 200w1d-02_cs_ro.exe Unhandled Exception: System.OverflowException: Value was either too large or too small for an Int32. at System.Convert.ThrowInt32OverflowException() at System.Convert.ToInt32(Int64 value) at testout1.Func_0() at testout1.Main() ./200w1d-02_cs_ro.sh: line 243: 101339 Aborted (core dumped) $_DebuggerFullPath "$CORE_ROOT/corerun" $ExePath $CLRTestExecutionArguments Expected: 100 Actual: 134 END EXECUTION - FAILED ``` --- src/classlibnative/bcltype/decimal.cpp | 27 --------- src/classlibnative/bcltype/decimal.h | 1 - src/classlibnative/bcltype/number.cpp | 39 ------------ src/classlibnative/bcltype/number.h | 1 - src/mscorlib/System.Private.CoreLib.csproj | 1 + src/mscorlib/shared/System/Number.Formatting.cs | 81 +++++++++++++++++++++++++ src/mscorlib/src/System/Decimal.DecCalc.cs | 26 ++++++++ src/mscorlib/src/System/Decimal.cs | 13 +++- src/mscorlib/src/System/Number.CoreCLR.cs | 3 - src/vm/ecalllist.h | 1 - 10 files changed, 120 insertions(+), 73 deletions(-) create mode 100644 src/mscorlib/src/System/Decimal.DecCalc.cs diff --git a/src/classlibnative/bcltype/decimal.cpp b/src/classlibnative/bcltype/decimal.cpp index ed63391..c338e5d 100644 --- a/src/classlibnative/bcltype/decimal.cpp +++ b/src/classlibnative/bcltype/decimal.cpp @@ -288,33 +288,6 @@ FCIMPL1(void, COMDecimal::DoTruncate, DECIMAL * d) } FCIMPLEND - -void COMDecimal::DecimalToNumber(DECIMAL* value, NUMBER* number) -{ - WRAPPER_NO_CONTRACT - _ASSERTE(number != NULL); - _ASSERTE(value != NULL); - - wchar_t buffer[DECIMAL_PRECISION+1]; - DECIMAL d = *value; - number->precision = DECIMAL_PRECISION; - number->sign = DECIMAL_SIGN(d)? 1: 0; - wchar_t* p = buffer + DECIMAL_PRECISION; - while (DECIMAL_MID32(d) | DECIMAL_HI32(d)) { - p = COMNumber::Int32ToDecChars(p, DecDivMod1E9(&d), 9); - _ASSERTE(p != NULL); - } - p = COMNumber::Int32ToDecChars(p, DECIMAL_LO32(d), 0); - _ASSERTE(p != NULL); - int i = (int) (buffer + DECIMAL_PRECISION - p); - number->scale = i - DECIMAL_SCALE(d); - wchar_t* dst = number->digits; - _ASSERTE(dst != NULL); - while (--i >= 0) *dst++ = *p++; - *dst = 0; - -} - int COMDecimal::NumberToDecimal(NUMBER* number, DECIMAL* value) { WRAPPER_NO_CONTRACT diff --git a/src/classlibnative/bcltype/decimal.h b/src/classlibnative/bcltype/decimal.h index 6ce1fbe..b932420 100644 --- a/src/classlibnative/bcltype/decimal.h +++ b/src/classlibnative/bcltype/decimal.h @@ -43,7 +43,6 @@ public: static FCDECL1(INT32, ToInt32, FC_DECIMAL d); static FCDECL1(Object*, ToString, FC_DECIMAL d); - static void DecimalToNumber(DECIMAL* value, NUMBER* number); static int NumberToDecimal(NUMBER* number, DECIMAL* value); diff --git a/src/classlibnative/bcltype/number.cpp b/src/classlibnative/bcltype/number.cpp index e399c82..eea2b2e 100644 --- a/src/classlibnative/bcltype/number.cpp +++ b/src/classlibnative/bcltype/number.cpp @@ -2009,45 +2009,6 @@ ParseSection: #pragma warning(pop) #endif -FCIMPL3_VII(Object*, COMNumber::FormatDecimal, FC_DECIMAL value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE) -{ - FCALL_CONTRACT; - - NUMBER number; - - wchar fmt; - int digits; - - STRINGREF refRetVal = NULL; - HELPER_METHOD_FRAME_BEGIN_RET_1(refRetVal); - - struct _gc - { - STRINGREF format; - NUMFMTREF numfmt; - } gc; - - gc.format = (STRINGREF) formatUNSAFE; - gc.numfmt = (NUMFMTREF) numfmtUNSAFE; - - if (gc.numfmt == 0) - COMPlusThrowArgumentNull(W("NumberFormatInfo")); - - COMDecimal::DecimalToNumber(&value, &number); - - fmt = ParseFormatSpecifier(gc.format, &digits); - if (fmt != 0) { - refRetVal = NumberToString(&number, fmt, digits, gc.numfmt, TRUE); - } else { - refRetVal = NumberToStringFormat(&number, gc.format, gc.numfmt); - } - - HELPER_METHOD_FRAME_END(); - - return OBJECTREFToObject(refRetVal); -} -FCIMPLEND - FCIMPL3_VII(Object*, COMNumber::FormatDouble, double value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE) { FCALL_CONTRACT; diff --git a/src/classlibnative/bcltype/number.h b/src/classlibnative/bcltype/number.h index 77f902f..e9651b6 100644 --- a/src/classlibnative/bcltype/number.h +++ b/src/classlibnative/bcltype/number.h @@ -31,7 +31,6 @@ struct NUMBER { class COMNumber { public: - static FCDECL3_VII(Object*, FormatDecimal, FC_DECIMAL value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE); static FCDECL3_VII(Object*, FormatDouble, double value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE); static FCDECL3_VII(Object*, FormatSingle, float value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE); static FCDECL2(FC_BOOL_RET, NumberBufferToDecimal, BYTE* number, DECIMAL* value); diff --git a/src/mscorlib/System.Private.CoreLib.csproj b/src/mscorlib/System.Private.CoreLib.csproj index 9dd49af..6fa4900 100644 --- a/src/mscorlib/System.Private.CoreLib.csproj +++ b/src/mscorlib/System.Private.CoreLib.csproj @@ -329,6 +329,7 @@ + diff --git a/src/mscorlib/shared/System/Number.Formatting.cs b/src/mscorlib/shared/System/Number.Formatting.cs index 9cf13e6..919c06b 100644 --- a/src/mscorlib/shared/System/Number.Formatting.cs +++ b/src/mscorlib/shared/System/Number.Formatting.cs @@ -50,6 +50,87 @@ namespace System "(#)", "-#", "- #", "#-", "# -", }; + public static string FormatDecimal(decimal value, string format, NumberFormatInfo info) + { + char fmt = ParseFormatSpecifier(format, out int digits); + + NumberBuffer number = default; + DecimalToNumber(value, ref number); + + ValueStringBuilder sb; + unsafe + { + char* stackPtr = stackalloc char[CharStackBufferSize]; + sb = new ValueStringBuilder(new Span(stackPtr, CharStackBufferSize)); + } + + if (fmt != 0) + { + NumberToString(ref sb, ref number, fmt, digits, info, isDecimal:true); + } + else + { + NumberToStringFormat(ref sb, ref number, format, info); + } + + return sb.GetString(); + } + + public static bool TryFormatDecimal(decimal value, string format, NumberFormatInfo info, Span destination, out int charsWritten) + { + char fmt = ParseFormatSpecifier(format, out int digits); + + NumberBuffer number = default; + DecimalToNumber(value, ref number); + + ValueStringBuilder sb; + unsafe + { + char* stackPtr = stackalloc char[CharStackBufferSize]; + sb = new ValueStringBuilder(new Span(stackPtr, CharStackBufferSize)); + } + + if (fmt != 0) + { + NumberToString(ref sb, ref number, fmt, digits, info, isDecimal: true); + } + else + { + NumberToStringFormat(ref sb, ref number, format, info); + } + + return sb.TryCopyTo(destination, out charsWritten); + } + +#if PROJECTN + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] +#endif + private static unsafe void DecimalToNumber(decimal value, ref NumberBuffer number) + { + decimal d = value; + + char* buffer = number.digits; + number.precision = DecimalPrecision; + number.sign = d.IsNegative; + + char* p = buffer + DecimalPrecision; + while ((d.Mid | d.High) != 0) + { + p = UInt32ToDecChars(p, decimal.DecDivMod1E9(ref d), 9); + } + p = UInt32ToDecChars(p, d.Low, 0); + + int i = (int)(buffer + DecimalPrecision - p); + number.scale = i - d.Scale; + + char* dst = number.digits; + while (--i >= 0) + { + *dst++ = *p++; + } + *dst = '\0'; + } + public static string FormatInt32(int value, string format, NumberFormatInfo info) { int digits; diff --git a/src/mscorlib/src/System/Decimal.DecCalc.cs b/src/mscorlib/src/System/Decimal.DecCalc.cs new file mode 100644 index 0000000..ded7558 --- /dev/null +++ b/src/mscorlib/src/System/Decimal.DecCalc.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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.Runtime.CompilerServices; + +namespace System +{ + public partial struct Decimal + { + internal static uint DecDivMod1E9(ref decimal value) + { + return D32DivMod1E9(D32DivMod1E9(D32DivMod1E9(0, + ref Unsafe.As(ref value.hi)), + ref Unsafe.As(ref value.mid)), + ref Unsafe.As(ref value.lo)); + + uint D32DivMod1E9(uint hi32, ref uint lo32) + { + ulong n = (ulong)hi32 << 32 | lo32; + lo32 = (uint)(n / 1000000000); + return (uint)(n % 1000000000); + } + } + } +} diff --git a/src/mscorlib/src/System/Decimal.cs b/src/mscorlib/src/System/Decimal.cs index c2b67d6..2ea0ca5 100644 --- a/src/mscorlib/src/System/Decimal.cs +++ b/src/mscorlib/src/System/Decimal.cs @@ -60,7 +60,7 @@ namespace System [Serializable] [System.Runtime.Versioning.NonVersionable] // This only applies to field layout [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] - public struct Decimal : IFormattable, IComparable, IConvertible, IComparable, IEquatable, IDeserializationCallback + public partial struct Decimal : IFormattable, IComparable, IConvertible, IComparable, IEquatable, IDeserializationCallback { // Sign mask for the flags field. A value of zero in this bit indicates a // positive Decimal value, and a value of one in this bit indicates a @@ -140,6 +140,9 @@ namespace System private int lo; private int mid; + internal uint High => (uint)hi; + internal uint Low => (uint)lo; + internal uint Mid => (uint)mid; // Constructs a zero Decimal. //public Decimal() { @@ -363,6 +366,10 @@ namespace System return d1; } + internal bool IsNegative => (flags & SignMask) != 0; + + internal int Scale => (byte)((uint)flags >> ScaleShift); + // FCallAddSub adds or subtracts two decimal values. On return, d1 contains the result // of the operation. Passing in DECIMAL_ADD or DECIMAL_NEG for bSign indicates // addition or subtraction, respectively. @@ -494,6 +501,10 @@ namespace System return Number.FormatDecimal(this, format, NumberFormatInfo.GetInstance(provider)); } + public bool TryFormat(Span destination, out int charsWritten, string format = null, IFormatProvider provider = null) + { + return Number.TryFormatDecimal(this, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten); + } // Converts a string to a Decimal. The string must consist of an optional // minus sign ("-") followed by a sequence of digits ("0" - "9"). The diff --git a/src/mscorlib/src/System/Number.CoreCLR.cs b/src/mscorlib/src/System/Number.CoreCLR.cs index 24dcc3d..3de9940 100644 --- a/src/mscorlib/src/System/Number.CoreCLR.cs +++ b/src/mscorlib/src/System/Number.CoreCLR.cs @@ -278,9 +278,6 @@ namespace System internal static partial class Number { [MethodImpl(MethodImplOptions.InternalCall)] - public static extern string FormatDecimal(decimal value, string format, NumberFormatInfo info); - - [MethodImpl(MethodImplOptions.InternalCall)] public static extern string FormatDouble(double value, string format, NumberFormatInfo info); [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/src/vm/ecalllist.h b/src/vm/ecalllist.h index 14ce9dd..8d82fdd 100644 --- a/src/vm/ecalllist.h +++ b/src/vm/ecalllist.h @@ -744,7 +744,6 @@ FCFuncStart(gWaitHandleFuncs) FCFuncEnd() FCFuncStart(gNumberFuncs) - FCFuncElement("FormatDecimal", COMNumber::FormatDecimal) FCFuncElement("FormatDouble", COMNumber::FormatDouble) FCFuncElement("FormatSingle", COMNumber::FormatSingle) FCFuncElement("NumberBufferToDecimal", COMNumber::NumberBufferToDecimal) -- 2.7.4