diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/MathHelpers.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/MathHelpers.cs index 85fef043acb0e..42485ea3ef63c 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/MathHelpers.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/MathHelpers.cs @@ -2,20 +2,78 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; using System.Runtime; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using Internal.Runtime; - namespace Internal.Runtime.CompilerHelpers { /// - /// Math helpers for generated code. The helpers marked with [RuntimeExport] and the type - /// itself need to be public because they constitute a public contract with the .NET Native toolchain. + /// Math helpers for generated code. The helpers here are referenced by the runtime. /// + [StackTraceHidden] internal static partial class MathHelpers { + private const double Int32MaxValueOffset = (double)int.MaxValue + 1; + private const double UInt32MaxValueOffset = (double)uint.MaxValue + 1; + + [RuntimeExport("Dbl2IntOvf")] + public static int Dbl2IntOvf(double value) + { + // Note that this expression also works properly for val = NaN case + if (value is > -Int32MaxValueOffset - 1 and < Int32MaxValueOffset) + { + return (int)value; + } + + ThrowHelper.ThrowOverflowException(); + return 0; + } + + [RuntimeExport("Dbl2UIntOvf")] + public static uint Dbl2UIntOvf(double value) + { + // Note that this expression also works properly for val = NaN case + if (value is > -1.0 and < UInt32MaxValueOffset) + { + return (uint)value; + } + + ThrowHelper.ThrowOverflowException(); + return 0; + } + + [RuntimeExport("Dbl2LngOvf")] + public static long Dbl2LngOvf(double value) + { + const double two63 = Int32MaxValueOffset * UInt32MaxValueOffset; + + // Note that this expression also works properly for val = NaN case + // We need to compare with the very next double to two63. 0x402 is epsilon to get us there. + if (value is > -two63 - 0x402 and < two63) + { + return (long)value; + } + + ThrowHelper.ThrowOverflowException(); + return 0; + } + + [RuntimeExport("Dbl2ULngOvf")] + public static ulong Dbl2ULngOvf(double value) + { + const double two64 = UInt32MaxValueOffset * UInt32MaxValueOffset; + // Note that this expression also works properly for val = NaN case + if (value is > -1.0 and < two64) + { + return (ulong)value; + } + + ThrowHelper.ThrowOverflowException(); + return 0; + } + #if !TARGET_64BIT // // 64-bit checked multiplication for 32-bit platforms @@ -23,360 +81,234 @@ internal static partial class MathHelpers private const string RuntimeLibrary = "*"; - // Helper to multiply two 32-bit uints - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong Mul32x32To64(uint a, uint b) - { - return a * (ulong)b; - } - - // Helper to get high 32-bit of 64-bit int [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Hi32Bits(long a) + private static uint High32Bits(ulong a) { return (uint)(a >> 32); } - // Helper to get high 32-bit of 64-bit int [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Hi32Bits(ulong a) + private static ulong BigMul(uint left, uint right) { - return (uint)(a >> 32); + return (ulong)left * right; } [RuntimeExport("LMulOvf")] - public static long LMulOvf(long i, long j) + public static long LMulOvf(long left, long right) { - long ret; +#if DEBUG + long result = left * right; +#endif // Remember the sign of the result - int sign = (int)(Hi32Bits(i) ^ Hi32Bits(j)); + int sign = (int)(High32Bits((ulong)left) ^ High32Bits((ulong)right)); // Convert to unsigned multiplication - if (i < 0) i = -i; - if (j < 0) j = -j; + if (left < 0) + left = -left; + if (right < 0) + right = -right; // Get the upper 32 bits of the numbers - uint val1High = Hi32Bits(i); - uint val2High = Hi32Bits(j); + uint val1High = High32Bits((ulong)left); + uint val2High = High32Bits((ulong)right); ulong valMid; if (val1High == 0) { // Compute the 'middle' bits of the long multiplication - valMid = Mul32x32To64(val2High, (uint)i); + valMid = BigMul(val2High, (uint)left); } else { if (val2High != 0) - goto ThrowExcep; + goto Overflow; // Compute the 'middle' bits of the long multiplication - valMid = Mul32x32To64(val1High, (uint)j); + valMid = BigMul(val1High, (uint)right); } // See if any bits after bit 32 are set - if (Hi32Bits(valMid) != 0) - goto ThrowExcep; + if (High32Bits(valMid) != 0) + goto Overflow; - ret = (long)(Mul32x32To64((uint)i, (uint)j) + (valMid << 32)); + long ret = (long)(BigMul((uint)left, (uint)right) + (valMid << 32)); // check for overflow - if (Hi32Bits(ret) < (uint)valMid) - goto ThrowExcep; + if (High32Bits((ulong)ret) < (uint)valMid) + goto Overflow; if (sign >= 0) { // have we spilled into the sign bit? if (ret < 0) - goto ThrowExcep; + goto Overflow; } else { ret = -ret; // have we spilled into the sign bit? if (ret > 0) - goto ThrowExcep; + goto Overflow; } + +#if DEBUG + Debug.Assert(ret == result, $"Multiply overflow got: {ret}, expected: {result}"); +#endif return ret; - ThrowExcep: - return ThrowLngOvf(); + Overflow: + ThrowHelper.ThrowOverflowException(); + return 0; } [RuntimeExport("ULMulOvf")] - public static ulong ULMulOvf(ulong i, ulong j) + public static ulong ULMulOvf(ulong left, ulong right) { - ulong ret; - // Get the upper 32 bits of the numbers - uint val1High = Hi32Bits(i); - uint val2High = Hi32Bits(j); + uint val1High = High32Bits(left); + uint val2High = High32Bits(right); ulong valMid; if (val1High == 0) { if (val2High == 0) - return Mul32x32To64((uint)i, (uint)j); + return (ulong)(uint)left * (uint)right; // Compute the 'middle' bits of the long multiplication - valMid = Mul32x32To64(val2High, (uint)i); + valMid = BigMul(val2High, (uint)left); } else { if (val2High != 0) - goto ThrowExcep; + goto Overflow; // Compute the 'middle' bits of the long multiplication - valMid = Mul32x32To64(val1High, (uint)j); + valMid = BigMul(val1High, (uint)right); } // See if any bits after bit 32 are set - if (Hi32Bits(valMid) != 0) - goto ThrowExcep; + if (High32Bits(valMid) != 0) + goto Overflow; - ret = Mul32x32To64((uint)i, (uint)j) + (valMid << 32); + ulong ret = BigMul((uint)left, (uint)right) + (valMid << 32); // check for overflow - if (Hi32Bits(ret) < (uint)valMid) - goto ThrowExcep; + if (High32Bits(ret) < (uint)valMid) + goto Overflow; + + Debug.Assert(ret == left * right, $"Multiply overflow got: {ret}, expected: {left * right}"); return ret; - ThrowExcep: - return ThrowULngOvf(); + Overflow: + ThrowHelper.ThrowOverflowException(); + return 0; } [LibraryImport(RuntimeLibrary)] [SuppressGCTransition] - private static partial ulong RhpULMod(ulong i, ulong j); + private static partial ulong RhpULMod(ulong dividend, ulong divisor); - public static ulong ULMod(ulong i, ulong j) + public static ulong ULMod(ulong dividend, ulong divisor) { - if (j == 0) - return ThrowULngDivByZero(); - else - return RhpULMod(i, j); - } - - [LibraryImport(RuntimeLibrary)] - [SuppressGCTransition] - private static partial long RhpLMod(long i, long j); + if (divisor == 0) + ThrowHelper.ThrowDivideByZeroException(); - public static long LMod(long i, long j) - { - if (j == 0) - return ThrowLngDivByZero(); - else if (j == -1 && i == long.MinValue) - return ThrowLngOvf(); - else - return RhpLMod(i, j); + return RhpULMod(dividend, divisor); } [LibraryImport(RuntimeLibrary)] [SuppressGCTransition] - private static partial ulong RhpULDiv(ulong i, ulong j); + private static partial long RhpLMod(long dividend, long divisor); - public static ulong ULDiv(ulong i, ulong j) + public static long LMod(long dividend, long divisor) { - if (j == 0) - return ThrowULngDivByZero(); - else - return RhpULDiv(i, j); + if (divisor == 0) + ThrowHelper.ThrowDivideByZeroException(); + if (divisor == -1 && dividend == long.MinValue) + ThrowHelper.ThrowOverflowException(); + + return RhpLMod(dividend, divisor); } [LibraryImport(RuntimeLibrary)] [SuppressGCTransition] - private static partial long RhpLDiv(long i, long j); - - public static long LDiv(long i, long j) - { - if (j == 0) - return ThrowLngDivByZero(); - else if (j == -1 && i == long.MinValue) - return ThrowLngOvf(); - else - return RhpLDiv(i, j); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static long ThrowLngDivByZero() - { - throw new DivideByZeroException(); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static ulong ThrowULngDivByZero() - { - throw new DivideByZeroException(); - } -#endif // TARGET_64BIT + private static partial ulong RhpULDiv(ulong dividend, ulong divisor); - [RuntimeExport("Dbl2IntOvf")] - public static int Dbl2IntOvf(double val) - { - const double two31 = 2147483648.0; - - // Note that this expression also works properly for val = NaN case - if (val > -two31 - 1 && val < two31) - return unchecked((int)val); - - return ThrowIntOvf(); - } - - [RuntimeExport("Dbl2UIntOvf")] - public static uint Dbl2UIntOvf(double val) - { - // Note that this expression also works properly for val = NaN case - if (val > -1.0 && val < 4294967296.0) - return unchecked((uint)val); - - return ThrowUIntOvf(); - } - - [RuntimeExport("Dbl2LngOvf")] - public static long Dbl2LngOvf(double val) - { - const double two63 = 2147483648.0 * 4294967296.0; - - // Note that this expression also works properly for val = NaN case - // We need to compare with the very next double to two63. 0x402 is epsilon to get us there. - if (val > -two63 - 0x402 && val < two63) - return unchecked((long)val); - - return ThrowLngOvf(); - } - - [RuntimeExport("Dbl2ULngOvf")] - public static ulong Dbl2ULngOvf(double val) + public static ulong ULDiv(ulong dividend, ulong divisor) { - const double two64 = 2.0 * 2147483648.0 * 4294967296.0; - - // Note that this expression also works properly for val = NaN case - if (val > -1.0 && val < two64) - return unchecked((ulong)val); + if (divisor == 0) + ThrowHelper.ThrowDivideByZeroException(); - return ThrowULngOvf(); + return RhpULDiv(dividend, divisor); } - [RuntimeExport("Flt2IntOvf")] - public static int Flt2IntOvf(float val) - { - const double two31 = 2147483648.0; - - // Note that this expression also works properly for val = NaN case - if (val > -two31 - 1 && val < two31) - return ((int)val); - - return ThrowIntOvf(); - } + [LibraryImport(RuntimeLibrary)] + [SuppressGCTransition] + private static partial long RhpLDiv(long dividend, long divisor); - [RuntimeExport("Flt2LngOvf")] - public static long Flt2LngOvf(float val) + public static long LDiv(long dividend, long divisor) { - const double two63 = 2147483648.0 * 4294967296.0; - - // Note that this expression also works properly for val = NaN case - // We need to compare with the very next double to two63. 0x402 is epsilon to get us there. - if (val > -two63 - 0x402 && val < two63) - return ((long)val); + if (divisor == 0) + ThrowHelper.ThrowDivideByZeroException(); + if (divisor == -1 && dividend == long.MinValue) + ThrowHelper.ThrowOverflowException(); - return ThrowIntOvf(); + return RhpLDiv(dividend, divisor); } #if TARGET_ARM [RuntimeImport(RuntimeLibrary, "RhpIDiv")] - [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern int RhpIDiv(int i, int j); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern int RhpIDiv(int dividend, int divisor); - public static int IDiv(int i, int j) + public static int IDiv(int dividend, int divisor) { - if (j == 0) - return ThrowIntDivByZero(); - else if (j == -1 && i == int.MinValue) - return ThrowIntOvf(); - else - return RhpIDiv(i, j); - } + if (divisor == 0) + ThrowHelper.ThrowDivideByZeroException(); + if (divisor == -1 && dividend == int.MinValue) + ThrowHelper.ThrowOverflowException(); - [RuntimeImport(RuntimeLibrary, "RhpUDiv")] - [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern uint RhpUDiv(uint i, uint j); - - public static long UDiv(uint i, uint j) - { - if (j == 0) - return ThrowUIntDivByZero(); - else - return RhpUDiv(i, j); + return RhpIDiv(dividend, divisor); } - [RuntimeImport(RuntimeLibrary, "RhpIMod")] - [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern int RhpIMod(int i, int j); + [RuntimeImport(RuntimeLibrary, "RhpUDiv")] + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern uint RhpUDiv(uint dividend, uint divisor); - public static int IMod(int i, int j) + public static long UDiv(uint dividend, uint divisor) { - if (j == 0) - return ThrowIntDivByZero(); - else if (j == -1 && i == int.MinValue) - return ThrowIntOvf(); - else - return RhpIMod(i, j); - } + if (divisor == 0) + ThrowHelper.ThrowDivideByZeroException(); - [RuntimeImport(RuntimeLibrary, "RhpUMod")] - [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern uint RhpUMod(uint i, uint j); - - public static long UMod(uint i, uint j) - { - if (j == 0) - return ThrowUIntDivByZero(); - else - return RhpUMod(i, j); + return RhpUDiv(dividend, divisor); } -#endif // TARGET_ARM - - // - // Matching return types of throw helpers enables tailcalling them. It improves performance - // of the hot path because of it does not need to raise full stackframe. - // - [MethodImpl(MethodImplOptions.NoInlining)] - private static int ThrowIntOvf() - { - throw new OverflowException(); - } + [RuntimeImport(RuntimeLibrary, "RhpIMod")] + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern int RhpIMod(int dividend, int divisor); - [MethodImpl(MethodImplOptions.NoInlining)] - private static uint ThrowUIntOvf() + public static int IMod(int dividend, int divisor) { - throw new OverflowException(); - } + if (divisor == 0) + ThrowHelper.ThrowDivideByZeroException(); + if (divisor == -1 && dividend == int.MinValue) + ThrowHelper.ThrowOverflowException(); - [MethodImpl(MethodImplOptions.NoInlining)] - private static long ThrowLngOvf() - { - throw new OverflowException(); + return RhpIMod(dividend, divisor); } - [MethodImpl(MethodImplOptions.NoInlining)] - private static ulong ThrowULngOvf() - { - throw new OverflowException(); - } + [RuntimeImport(RuntimeLibrary, "RhpUMod")] + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern uint RhpUMod(uint dividend, uint divisor); -#if TARGET_ARM - [MethodImpl(MethodImplOptions.NoInlining)] - private static int ThrowIntDivByZero() + public static long UMod(uint dividend, uint divisor) { - throw new DivideByZeroException(); - } + if (divisor == 0) + ThrowHelper.ThrowDivideByZeroException(); - [MethodImpl(MethodImplOptions.NoInlining)] - private static uint ThrowUIntDivByZero() - { - throw new DivideByZeroException(); + return RhpUMod(dividend, divisor); } #endif // TARGET_ARM +#endif // TARGET_64BIT } }