From f3a2d46a05e09b8efd89f5086ab70160d4f63995 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Mon, 29 Jun 2020 11:14:11 -0700 Subject: [PATCH] Remove decimal alignment quirk from the type loader System.Decimal fields did not match Win32 DECIMAL type for historic reasons. It required type loader to have a quirk to artifically inflate System.Decimal alignment to match the alignment of Win32 DECIMAL type to make interop work well. This change is fixing the System.Decimal fields to match Win32 DECIMAL type and removing the quirk from the type loader since it does not belong there. The downsides are: - Slightly lower code quality on 32-bit platforms. 32-bit platforms are not our high performance targets anymore. - Explicit implementation of ISerializable is required on System.Decimal for binary serialization compatibility. Fixes #38390 --- src/coreclr/src/vm/methodtablebuilder.cpp | 17 -- .../src/System/Decimal.DecCalc.cs | 58 +++---- .../src/System/Decimal.cs | 159 +++++++++--------- .../System.Runtime/ref/System.Runtime.cs | 5 +- 4 files changed, 111 insertions(+), 128 deletions(-) diff --git a/src/coreclr/src/vm/methodtablebuilder.cpp b/src/coreclr/src/vm/methodtablebuilder.cpp index c1284a68db35e..0ca6e14517602 100644 --- a/src/coreclr/src/vm/methodtablebuilder.cpp +++ b/src/coreclr/src/vm/methodtablebuilder.cpp @@ -9724,23 +9724,6 @@ void MethodTableBuilder::CheckForSystemTypes() pMT->SetInternalCorElementType (ELEMENT_TYPE_I); } #endif -#if defined(ALIGN_ACCESS) || defined(FEATURE_64BIT_ALIGNMENT) - else if (strcmp(name, g_DecimalName) == 0) - { - // This is required because native layout of System.Decimal causes it to be aligned - // differently to the layout of the native DECIMAL structure, which will cause - // data misalignent exceptions if Decimal is embedded in another type. - - EEClassLayoutInfo* pLayout = pClass->GetLayoutInfo(); - pLayout->m_ManagedLargestAlignmentRequirementOfAllMembers = sizeof(ULONGLONG); - -#ifdef FEATURE_64BIT_ALIGNMENT - // Also need to mark the type so it will be allocated on a 64-bit boundary for - // platforms that won't do this naturally. - SetAlign8Candidate(); -#endif - } -#endif // ALIGN_ACCESS || FEATURE_64BIT_ALIGNMENT } else { diff --git a/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs b/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs index 2cf32dfc960f7..8f145614a4b34 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs @@ -14,19 +14,15 @@ namespace System public partial struct Decimal { // Low level accessors used by a DecCalc and formatting - internal uint High => (uint)hi; - internal uint Low => (uint)lo; - internal uint Mid => (uint)mid; + internal uint High => _hi32; + internal uint Low => (uint)_lo64; + internal uint Mid => (uint)(_lo64 >> 32); - internal bool IsNegative => flags < 0; + internal bool IsNegative => _flags < 0; - internal int Scale => (byte)(flags >> ScaleShift); + internal int Scale => (byte)(_flags >> ScaleShift); -#if BIGENDIAN - private ulong Low64 => ((ulong)Mid << 32) | Low; -#else - private ulong Low64 => Unsafe.As(ref Unsafe.AsRef(in lo)); -#endif + private ulong Low64 => _lo64; private static ref DecCalc AsMutable(ref decimal d) => ref Unsafe.As(ref d); @@ -50,16 +46,23 @@ private struct DecCalc private uint uflags; [FieldOffset(4)] private uint uhi; +#if BIGENDIAN + [FieldOffset(8)] + private uint umid; + [FieldOffset(12)] + private uint ulo; +#else [FieldOffset(8)] private uint ulo; [FieldOffset(12)] private uint umid; +#endif /// - /// The low and mid fields combined in little-endian order + /// The low and mid fields combined /// [FieldOffset(8)] - private ulong ulomidLE; + private ulong ulomid; private uint High { @@ -85,13 +88,8 @@ private uint Mid private ulong Low64 { -#if BIGENDIAN - get { return ((ulong)umid << 32) | ulo; } - set { umid = (uint)(value >> 32); ulo = (uint)value; } -#else - get => ulomidLE; - set => ulomidLE = value; -#endif + get => ulomid; + set => ulomid = value; } private const uint SignMask = 0x80000000; @@ -163,7 +161,7 @@ private static unsafe void DebugPoison(ref T s) where T : unmanaged MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref s, 1)).Fill(0xCD); } - #region Decimal Math Helpers +#region Decimal Math Helpers private static unsafe uint GetExponent(float f) { @@ -1249,16 +1247,16 @@ internal static long VarCyFromDec(ref DecCalc pdecIn) /// internal static int VarDecCmp(in decimal d1, in decimal d2) { - if ((d2.Low | d2.Mid | d2.High) == 0) + if ((d2.Low64 | d2.High) == 0) { - if ((d1.Low | d1.Mid | d1.High) == 0) + if ((d1.Low64 | d1.High) == 0) return 0; - return (d1.flags >> 31) | 1; + return (d1._flags >> 31) | 1; } - if ((d1.Low | d1.Mid | d1.High) == 0) - return -((d2.flags >> 31) | 1); + if ((d1.Low64 | d1.High) == 0) + return -((d2._flags >> 31) | 1); - int sign = (d1.flags >> 31) - (d2.flags >> 31); + int sign = (d1._flags >> 31) - (d2._flags >> 31); if (sign != 0) return sign; return VarDecCmpSub(in d1, in d2); @@ -1266,9 +1264,9 @@ internal static int VarDecCmp(in decimal d1, in decimal d2) private static int VarDecCmpSub(in decimal d1, in decimal d2) { - int flags = d2.flags; + int flags = d2._flags; int sign = (flags >> 31) | 1; - int scale = flags - d1.flags; + int scale = flags - d1._flags; ulong low64 = d1.Low64; uint high = d1.High; @@ -1899,10 +1897,10 @@ internal static double VarR8FromDec(in decimal value) internal static int GetHashCode(in decimal d) { - if ((d.Low | d.Mid | d.High) == 0) + if ((d.Low64 | d.High) == 0) return 0; - uint flags = (uint)d.flags; + uint flags = (uint)d._flags; if ((flags & ScaleMask) == 0 || (d.Low & 1) != 0) return (int)(flags ^ d.High ^ d.Mid ^ d.Low); diff --git a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs index cc32ce56213e1..ec9cc194097d7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs @@ -58,7 +58,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 readonly partial struct Decimal : IFormattable, IComparable, IConvertible, IComparable, IEquatable, IDeserializationCallback, ISpanFormattable + public readonly partial struct Decimal : IFormattable, IComparable, IConvertible, IComparable, IEquatable, ISpanFormattable, ISerializable, 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 @@ -102,13 +102,11 @@ namespace System // and finally bit 31 indicates the sign of the Decimal value, 0 meaning // positive and 1 meaning negative. // - // NOTE: Do not change the order in which these fields are declared. The - // native methods in this class rely on this particular order. - // Do not rename (binary serialization). - private readonly int flags; - private readonly int hi; - private readonly int lo; - private readonly int mid; + // NOTE: Do not change the order and types of these fields. The layout has to + // match Win32 DECIMAL type. + private readonly int _flags; + private readonly uint _hi32; + private readonly ulong _lo64; // Constructs a Decimal from an integer value. // @@ -116,16 +114,15 @@ public Decimal(int value) { if (value >= 0) { - flags = 0; + _flags = 0; } else { - flags = SignMask; + _flags = SignMask; value = -value; } - lo = value; - mid = 0; - hi = 0; + _lo64 = (uint)value; + _hi32 = 0; } // Constructs a Decimal from an unsigned integer value. @@ -133,10 +130,9 @@ public Decimal(int value) [CLSCompliant(false)] public Decimal(uint value) { - flags = 0; - lo = (int)value; - mid = 0; - hi = 0; + _flags = 0; + _lo64 = value; + _hi32 = 0; } // Constructs a Decimal from a long value. @@ -145,16 +141,15 @@ public Decimal(long value) { if (value >= 0) { - flags = 0; + _flags = 0; } else { - flags = SignMask; + _flags = SignMask; value = -value; } - lo = (int)value; - mid = (int)(value >> 32); - hi = 0; + _lo64 = (ulong)value; + _hi32 = 0; } // Constructs a Decimal from an unsigned long value. @@ -162,10 +157,9 @@ public Decimal(long value) [CLSCompliant(false)] public Decimal(ulong value) { - flags = 0; - lo = (int)value; - mid = (int)(value >> 32); - hi = 0; + _flags = 0; + _lo64 = value; + _hi32 = 0; } // Constructs a Decimal from a float value. @@ -182,6 +176,28 @@ public Decimal(double value) DecCalc.VarDecFromR8(value, out AsMutable(ref this)); } + private Decimal(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new ArgumentNullException(nameof(info)); + + _flags = info.GetInt32("flags"); + _hi32 = (uint)info.GetInt32("hi"); + _lo64 = (uint)info.GetInt32("lo") + ((ulong)info.GetInt32("mid") << 32); + } + + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new ArgumentNullException(nameof(info)); + + // Serialize both the old and the new format + info.AddValue("flags", _flags); + info.AddValue("hi", (int)High); + info.AddValue("lo", (int)Low); + info.AddValue("mid", (int)Mid); + } + // // Decimal <==> Currency conversion. // @@ -261,10 +277,9 @@ public Decimal(ReadOnlySpan bits) int f = bits[3]; if (IsValid(f)) { - lo = bits[0]; - mid = bits[1]; - hi = bits[2]; - flags = f; + _lo64 = (uint)bits[0] + ((ulong)(uint)bits[1] << 32); + _hi32 = (uint)bits[2]; + _flags = f; return; } } @@ -277,19 +292,18 @@ public Decimal(int lo, int mid, int hi, bool isNegative, byte scale) { if (scale > 28) throw new ArgumentOutOfRangeException(nameof(scale), SR.ArgumentOutOfRange_DecimalScale); - this.lo = lo; - this.mid = mid; - this.hi = hi; - flags = ((int)scale) << 16; + _lo64 = (uint)lo + ((ulong)(uint)mid << 32); + _hi32 = (uint)hi; + _flags = ((int)scale) << 16; if (isNegative) - flags |= SignMask; + _flags |= SignMask; } void IDeserializationCallback.OnDeserialization(object? sender) { // OnDeserialization is called after each instance of this class is deserialized. // This callback method performs decimal validation after being deserialized. - if (!IsValid(flags)) + if (!IsValid(_flags)) throw new SerializationException(SR.Overflow_Decimal); } @@ -298,10 +312,9 @@ private Decimal(int lo, int mid, int hi, int flags) { if (IsValid(flags)) { - this.lo = lo; - this.mid = mid; - this.hi = hi; - this.flags = flags; + _lo64 = (uint)lo + ((ulong)(uint)mid << 32); + _hi32 = (uint)hi; + _flags = flags; return; } throw new ArgumentException(SR.Arg_DecBitCtor); @@ -310,7 +323,7 @@ private Decimal(int lo, int mid, int hi, int flags) private Decimal(in decimal d, int flags) { this = d; - this.flags = flags; + _flags = flags; } // Returns the absolute value of the given Decimal. If d is @@ -319,7 +332,7 @@ private Decimal(in decimal d, int flags) // internal static decimal Abs(in decimal d) { - return new decimal(in d, d.flags & ~SignMask); + return new decimal(in d, d._flags & ~SignMask); } // Adds two Decimal values. @@ -334,7 +347,7 @@ public static decimal Add(decimal d1, decimal d2) // towards positive infinity. public static decimal Ceiling(decimal d) { - int flags = d.flags; + int flags = d._flags; if ((flags & ScaleMask) != 0) DecCalc.InternalRound(ref AsMutable(ref d), (byte)(flags >> ScaleShift), MidpointRounding.ToPositiveInfinity); return d; @@ -406,7 +419,7 @@ public static bool Equals(decimal d1, decimal d2) // public static decimal Floor(decimal d) { - int flags = d.flags; + int flags = d._flags; if ((flags & ScaleMask) != 0) DecCalc.InternalRound(ref AsMutable(ref d), (byte)(flags >> ScaleShift), MidpointRounding.ToNegativeInfinity); return d; @@ -528,7 +541,7 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro // public static int[] GetBits(decimal d) { - return new int[] { d.lo, d.mid, d.hi, d.flags }; + return new int[] { (int)d.Low, (int)d.Mid, (int)d.High, d._flags }; } /// @@ -545,10 +558,10 @@ public static int GetBits(decimal d, Span destination) ThrowHelper.ThrowArgumentException_DestinationTooShort(); } - destination[0] = d.lo; - destination[1] = d.mid; - destination[2] = d.hi; - destination[3] = d.flags; + destination[0] = (int)d.Low; + destination[1] = (int)d.Mid; + destination[2] = (int)d.High; + destination[3] = d._flags; return 4; } @@ -567,10 +580,10 @@ public static bool TryGetBits(decimal d, Span destination, out int valuesWr return false; } - destination[0] = d.lo; - destination[1] = d.mid; - destination[2] = d.hi; - destination[3] = d.flags; + destination[0] = (int)d.Low; + destination[1] = (int)d.Mid; + destination[2] = (int)d.High; + destination[3] = d._flags; valuesWritten = 4; return true; } @@ -578,25 +591,13 @@ public static bool TryGetBits(decimal d, Span destination, out int valuesWr internal static void GetBytes(in decimal d, byte[] buffer) { Debug.Assert(buffer != null && buffer.Length >= 16, "[GetBytes]buffer != null && buffer.Length >= 16"); - buffer[0] = (byte)d.lo; - buffer[1] = (byte)(d.lo >> 8); - buffer[2] = (byte)(d.lo >> 16); - buffer[3] = (byte)(d.lo >> 24); - - buffer[4] = (byte)d.mid; - buffer[5] = (byte)(d.mid >> 8); - buffer[6] = (byte)(d.mid >> 16); - buffer[7] = (byte)(d.mid >> 24); - buffer[8] = (byte)d.hi; - buffer[9] = (byte)(d.hi >> 8); - buffer[10] = (byte)(d.hi >> 16); - buffer[11] = (byte)(d.hi >> 24); + Span span = buffer; - buffer[12] = (byte)d.flags; - buffer[13] = (byte)(d.flags >> 8); - buffer[14] = (byte)(d.flags >> 16); - buffer[15] = (byte)(d.flags >> 24); + BinaryPrimitives.WriteInt32LittleEndian(span, (int)d.Low); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(4), (int)d.Mid); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(8), (int)d.High); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(12), d._flags); } internal static decimal ToDecimal(ReadOnlySpan span) @@ -642,7 +643,7 @@ public static decimal Multiply(decimal d1, decimal d2) // public static decimal Negate(decimal d) { - return new decimal(in d, d.flags ^ SignMask); + return new decimal(in d, d._flags ^ SignMask); } // Rounds a Decimal value to a given number of decimal places. The value @@ -671,7 +672,7 @@ private static decimal Round(ref decimal d, int decimals, MidpointRounding mode) return d; } - internal static int Sign(in decimal d) => (d.lo | d.mid | d.hi) == 0 ? 0 : (d.flags >> 31) | 1; + internal static int Sign(in decimal d) => (d.Low64 | d.High) == 0 ? 0 : (d._flags >> 31) | 1; // Subtracts two Decimal values. // @@ -757,9 +758,9 @@ public static double ToDouble(decimal d) public static int ToInt32(decimal d) { Truncate(ref d); - if ((d.hi | d.mid) == 0) + if ((d.High | d.Mid) == 0) { - int i = d.lo; + int i = (int)d.Low; if (!d.IsNegative) { if (i >= 0) return i; @@ -780,7 +781,7 @@ public static int ToInt32(decimal d) public static long ToInt64(decimal d) { Truncate(ref d); - if (d.hi == 0) + if (d.High == 0) { long l = (long)d.Low64; if (!d.IsNegative) @@ -825,7 +826,7 @@ public static ushort ToUInt16(decimal value) public static uint ToUInt32(decimal d) { Truncate(ref d); - if ((d.hi | d.mid) == 0) + if ((d.High| d.Mid) == 0) { uint i = d.Low; if (!d.IsNegative || i == 0) @@ -842,7 +843,7 @@ public static uint ToUInt32(decimal d) public static ulong ToUInt64(decimal d) { Truncate(ref d); - if (d.hi == 0) + if (d.High == 0) { ulong l = d.Low64; if (!d.IsNegative || l == 0) @@ -872,7 +873,7 @@ public static decimal Truncate(decimal d) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void Truncate(ref decimal d) { - int flags = d.flags; + int flags = d._flags; if ((flags & ScaleMask) != 0) DecCalc.InternalRound(ref AsMutable(ref d), (byte)(flags >> ScaleShift), MidpointRounding.ToZero); } @@ -943,7 +944,7 @@ public static explicit operator char(decimal value) public static decimal operator +(decimal d) => d; - public static decimal operator -(decimal d) => new decimal(in d, d.flags ^ SignMask); + public static decimal operator -(decimal d) => new decimal(in d, d._flags ^ SignMask); public static decimal operator ++(decimal d) => Add(d, One); diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index f8d831da14e2d..667e8fcc36cbe 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -1538,7 +1538,7 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S public override string ToString() { throw null; } public string ToString(System.IFormatProvider? provider) { throw null; } } - public readonly partial struct Decimal : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.Runtime.Serialization.IDeserializationCallback + public readonly partial struct Decimal : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.Runtime.Serialization.IDeserializationCallback, System.Runtime.Serialization.ISerializable { private readonly int _dummyPrimitive; [System.Runtime.CompilerServices.DecimalConstantAttribute((byte)0, (byte)0, (uint)4294967295, (uint)4294967295, (uint)4294967295)] @@ -1555,8 +1555,8 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S public Decimal(int value) { throw null; } public Decimal(int lo, int mid, int hi, bool isNegative, byte scale) { throw null; } public Decimal(int[] bits) { throw null; } - public Decimal(System.ReadOnlySpan bits) { throw null; } public Decimal(long value) { throw null; } + public Decimal(System.ReadOnlySpan bits) { throw null; } public Decimal(float value) { throw null; } [System.CLSCompliantAttribute(false)] public Decimal(uint value) { throw null; } @@ -1651,6 +1651,7 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S uint System.IConvertible.ToUInt32(System.IFormatProvider? provider) { throw null; } ulong System.IConvertible.ToUInt64(System.IFormatProvider? provider) { throw null; } void System.Runtime.Serialization.IDeserializationCallback.OnDeserialization(object? sender) { } + void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public static byte ToByte(System.Decimal value) { throw null; } public static double ToDouble(System.Decimal d) { throw null; } public static short ToInt16(System.Decimal value) { throw null; }