From dfa72eade7517248b5c112c947b67e860d283e7b Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Sat, 11 May 2024 23:36:08 -0700 Subject: [PATCH] Move checked math helpers to C# - Eliminate a few more FCThrow - Take advantage of ConvertToNative to make helpers faster on current hardware --- src/coreclr/inc/jithelpers.h | 12 +- .../Runtime/CompilerHelpers/MathHelpers.cs | 186 ------------------ .../ILCompiler.Compiler/Compiler/JitHelper.cs | 12 +- src/coreclr/vm/corelib.h | 8 + src/coreclr/vm/ecall.cpp | 26 +++ src/coreclr/vm/jithelpers.cpp | 181 ----------------- src/coreclr/vm/jitinterface.cpp | 9 +- .../System.Private.CoreLib/src/System/Math.cs | 181 ++++++++++++++++- 8 files changed, 234 insertions(+), 381 deletions(-) diff --git a/src/coreclr/inc/jithelpers.h b/src/coreclr/inc/jithelpers.h index 2ee4999453859..ec89b618b909b 100644 --- a/src/coreclr/inc/jithelpers.h +++ b/src/coreclr/inc/jithelpers.h @@ -47,8 +47,8 @@ JITHELPER(CORINFO_HELP_LRSZ, NULL, CORINFO_HELP_SIG_CANNOT_USE_ALIGN_STUB) #endif // TARGET_64BIT JITHELPER(CORINFO_HELP_LMUL, JIT_LMul, CORINFO_HELP_SIG_16_STACK) - JITHELPER(CORINFO_HELP_LMUL_OVF, JIT_LMulOvf, CORINFO_HELP_SIG_16_STACK) - JITHELPER(CORINFO_HELP_ULMUL_OVF, JIT_ULMulOvf, CORINFO_HELP_SIG_16_STACK) + DYNAMICJITHELPER(CORINFO_HELP_LMUL_OVF, NULL, CORINFO_HELP_SIG_16_STACK) + DYNAMICJITHELPER(CORINFO_HELP_ULMUL_OVF, NULL, CORINFO_HELP_SIG_16_STACK) JITHELPER(CORINFO_HELP_LDIV, JIT_LDiv, CORINFO_HELP_SIG_16_STACK) JITHELPER(CORINFO_HELP_LMOD, JIT_LMod, CORINFO_HELP_SIG_16_STACK) JITHELPER(CORINFO_HELP_ULDIV, JIT_ULDiv, CORINFO_HELP_SIG_16_STACK) @@ -56,13 +56,13 @@ JITHELPER(CORINFO_HELP_LNG2DBL, JIT_Lng2Dbl, CORINFO_HELP_SIG_8_STACK) JITHELPER(CORINFO_HELP_ULNG2DBL, JIT_ULng2Dbl, CORINFO_HELP_SIG_8_STACK) JITHELPER(CORINFO_HELP_DBL2INT, JIT_Dbl2Int, CORINFO_HELP_SIG_8_STACK) - JITHELPER(CORINFO_HELP_DBL2INT_OVF, JIT_Dbl2IntOvf, CORINFO_HELP_SIG_8_STACK) + DYNAMICJITHELPER(CORINFO_HELP_DBL2INT_OVF, NULL, CORINFO_HELP_SIG_8_STACK) JITHELPER(CORINFO_HELP_DBL2LNG, JIT_Dbl2Lng, CORINFO_HELP_SIG_8_STACK) - JITHELPER(CORINFO_HELP_DBL2LNG_OVF, JIT_Dbl2LngOvf, CORINFO_HELP_SIG_8_STACK) + DYNAMICJITHELPER(CORINFO_HELP_DBL2LNG_OVF, NULL, CORINFO_HELP_SIG_8_STACK) JITHELPER(CORINFO_HELP_DBL2UINT, JIT_Dbl2UInt, CORINFO_HELP_SIG_8_STACK) - JITHELPER(CORINFO_HELP_DBL2UINT_OVF, JIT_Dbl2UIntOvf, CORINFO_HELP_SIG_8_STACK) + DYNAMICJITHELPER(CORINFO_HELP_DBL2UINT_OVF, NULL, CORINFO_HELP_SIG_8_STACK) JITHELPER(CORINFO_HELP_DBL2ULNG, JIT_Dbl2ULng, CORINFO_HELP_SIG_8_STACK) - JITHELPER(CORINFO_HELP_DBL2ULNG_OVF, JIT_Dbl2ULngOvf, CORINFO_HELP_SIG_8_STACK) + DYNAMICJITHELPER(CORINFO_HELP_DBL2ULNG_OVF, NULL, CORINFO_HELP_SIG_8_STACK) JITHELPER(CORINFO_HELP_FLTREM, JIT_FltRem, CORINFO_HELP_SIG_8_STACK) JITHELPER(CORINFO_HELP_DBLREM, JIT_DblRem, CORINFO_HELP_SIG_16_STACK) DYNAMICJITHELPER(CORINFO_HELP_FLTROUND, NULL, CORINFO_HELP_SIG_8_STACK) 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 42485ea3ef63c..df8c6d407901f 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 @@ -15,195 +15,9 @@ namespace Internal.Runtime.CompilerHelpers [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 - // - private const string RuntimeLibrary = "*"; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint High32Bits(ulong a) - { - return (uint)(a >> 32); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong BigMul(uint left, uint right) - { - return (ulong)left * right; - } - - [RuntimeExport("LMulOvf")] - public static long LMulOvf(long left, long right) - { -#if DEBUG - long result = left * right; -#endif - - // Remember the sign of the result - int sign = (int)(High32Bits((ulong)left) ^ High32Bits((ulong)right)); - - // Convert to unsigned multiplication - if (left < 0) - left = -left; - if (right < 0) - right = -right; - - // Get the upper 32 bits of the numbers - uint val1High = High32Bits((ulong)left); - uint val2High = High32Bits((ulong)right); - - ulong valMid; - - if (val1High == 0) - { - // Compute the 'middle' bits of the long multiplication - valMid = BigMul(val2High, (uint)left); - } - else - { - if (val2High != 0) - goto Overflow; - // Compute the 'middle' bits of the long multiplication - valMid = BigMul(val1High, (uint)right); - } - - // See if any bits after bit 32 are set - if (High32Bits(valMid) != 0) - goto Overflow; - - long ret = (long)(BigMul((uint)left, (uint)right) + (valMid << 32)); - - // check for overflow - if (High32Bits((ulong)ret) < (uint)valMid) - goto Overflow; - - if (sign >= 0) - { - // have we spilled into the sign bit? - if (ret < 0) - goto Overflow; - } - else - { - ret = -ret; - // have we spilled into the sign bit? - if (ret > 0) - goto Overflow; - } - -#if DEBUG - Debug.Assert(ret == result, $"Multiply overflow got: {ret}, expected: {result}"); -#endif - return ret; - - Overflow: - ThrowHelper.ThrowOverflowException(); - return 0; - } - - [RuntimeExport("ULMulOvf")] - public static ulong ULMulOvf(ulong left, ulong right) - { - // Get the upper 32 bits of the numbers - uint val1High = High32Bits(left); - uint val2High = High32Bits(right); - - ulong valMid; - - if (val1High == 0) - { - if (val2High == 0) - return (ulong)(uint)left * (uint)right; - // Compute the 'middle' bits of the long multiplication - valMid = BigMul(val2High, (uint)left); - } - else - { - if (val2High != 0) - goto Overflow; - // Compute the 'middle' bits of the long multiplication - valMid = BigMul(val1High, (uint)right); - } - - // See if any bits after bit 32 are set - if (High32Bits(valMid) != 0) - goto Overflow; - - ulong ret = BigMul((uint)left, (uint)right) + (valMid << 32); - - // check for overflow - if (High32Bits(ret) < (uint)valMid) - goto Overflow; - - Debug.Assert(ret == left * right, $"Multiply overflow got: {ret}, expected: {left * right}"); - return ret; - - Overflow: - ThrowHelper.ThrowOverflowException(); - return 0; - } - [LibraryImport(RuntimeLibrary)] [SuppressGCTransition] private static partial ulong RhpULMod(ulong dividend, ulong divisor); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs index 67cfd6cbe4f7a..13554bd4419be 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs @@ -188,16 +188,16 @@ public static void GetEntryPoint(TypeSystemContext context, ReadyToRunHelper id, break; case ReadyToRunHelper.Dbl2IntOvf: - methodDesc = context.GetHelperEntryPoint("MathHelpers", "Dbl2IntOvf"); + methodDesc = context.SystemModule.GetKnownType("System", "Math").GetKnownMethod("ConvertToInt32Checked", null); break; case ReadyToRunHelper.Dbl2UIntOvf: - methodDesc = context.GetHelperEntryPoint("MathHelpers", "Dbl2UIntOvf"); + methodDesc = context.SystemModule.GetKnownType("System", "Math").GetKnownMethod("ConvertToUInt32Checked", null); break; case ReadyToRunHelper.Dbl2LngOvf: - methodDesc = context.GetHelperEntryPoint("MathHelpers", "Dbl2LngOvf"); + methodDesc = context.SystemModule.GetKnownType("System", "Math").GetKnownMethod("ConvertToInt64Checked", null); break; case ReadyToRunHelper.Dbl2ULngOvf: - methodDesc = context.GetHelperEntryPoint("MathHelpers", "Dbl2ULngOvf"); + methodDesc = context.SystemModule.GetKnownType("System", "Math").GetKnownMethod("ConvertToUInt64Checked", null); break; case ReadyToRunHelper.DblRem: @@ -211,10 +211,10 @@ public static void GetEntryPoint(TypeSystemContext context, ReadyToRunHelper id, mangledName = "RhpLMul"; break; case ReadyToRunHelper.LMulOfv: - methodDesc = context.GetHelperEntryPoint("MathHelpers", "LMulOvf"); + methodDesc = context.SystemModule.GetKnownType("System", "Math").GetKnownMethod("MultiplyInt64Checked", null); break; case ReadyToRunHelper.ULMulOvf: - methodDesc = context.GetHelperEntryPoint("MathHelpers", "ULMulOvf"); + methodDesc = context.SystemModule.GetKnownType("System", "Math").GetKnownMethod("MultiplyUInt64Checked", null); break; case ReadyToRunHelper.Mod: diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 081325545827e..78a624821c904 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -261,6 +261,14 @@ DEFINE_CLASS(UINT128, System, UInt128) DEFINE_CLASS(MATH, System, Math) DEFINE_METHOD(MATH, ROUND, Round, SM_Dbl_RetDbl) +#ifndef TARGET_64BIT +DEFINE_METHOD(MATH, MULTIPLY_INT64_CHECKED, MultiplyInt64Checked, NoSig) +DEFINE_METHOD(MATH, MULTIPLY_UINT64_CHECKED, MultiplyUInt64Checked, NoSig) +#endif +DEFINE_METHOD(MATH, CONVERT_TO_INT32_CHECKED, ConvertToInt32Checked, NoSig) +DEFINE_METHOD(MATH, CONVERT_TO_UINT32_CHECKED, ConvertToUInt32Checked, NoSig) +DEFINE_METHOD(MATH, CONVERT_TO_INT64_CHECKED, ConvertToInt64Checked, NoSig) +DEFINE_METHOD(MATH, CONVERT_TO_UINT64_CHECKED, ConvertToUInt64Checked, NoSig) DEFINE_CLASS(MATHF, System, MathF) DEFINE_METHOD(MATHF, ROUND, Round, SM_Flt_RetFlt) diff --git a/src/coreclr/vm/ecall.cpp b/src/coreclr/vm/ecall.cpp index 56a28a7a57ddb..a270b877733d3 100644 --- a/src/coreclr/vm/ecall.cpp +++ b/src/coreclr/vm/ecall.cpp @@ -161,6 +161,32 @@ void ECall::PopulateManagedHelpers() pDest = pMD->GetMultiCallableAddrOfCode(); SetJitHelperFunction(CORINFO_HELP_BULK_WRITEBARRIER, pDest); +#ifndef TARGET_64BIT + pMD = CoreLibBinder::GetMethod((BinderMethodID)(METHOD__MATH__INT64_MULTIPLY_CHECKED)); + pDest = pMD->GetMultiCallableAddrOfCode(); + SetJitHelperFunction(CORINFO_HELP_LMUL_OVF, pDest); + + pMD = CoreLibBinder::GetMethod((BinderMethodID)(METHOD__MATH__UINT64_MULTIPLY_CHECKED)); + pDest = pMD->GetMultiCallableAddrOfCode(); + SetJitHelperFunction(CORINFO_HELP_ULMUL_OVF, pDest); +#endif + + pMD = CoreLibBinder::GetMethod((BinderMethodID)(METHOD__MATH__CONVERT_TO_INT32_CHECKED)); + pDest = pMD->GetMultiCallableAddrOfCode(); + SetJitHelperFunction(CORINFO_HELP_DBL2INT_OVF, pDest); + + pMD = CoreLibBinder::GetMethod((BinderMethodID)(METHOD__MATH__CONVERT_TO_UINT32_CHECKED)); + pDest = pMD->GetMultiCallableAddrOfCode(); + SetJitHelperFunction(CORINFO_HELP_DBL2UINT_OVF, pDest); + + pMD = CoreLibBinder::GetMethod((BinderMethodID)(METHOD__MATH__CONVERT_TO_INT64_CHECKED)); + pDest = pMD->GetMultiCallableAddrOfCode(); + SetJitHelperFunction(CORINFO_HELP_DBL2LNG_OVF, pDest); + + pMD = CoreLibBinder::GetMethod((BinderMethodID)(METHOD__MATH__CONVERT_TO_UINT64_CHECKED)); + pDest = pMD->GetMultiCallableAddrOfCode(); + SetJitHelperFunction(CORINFO_HELP_DBL2ULNG_OVF, pDest); + pMD = CoreLibBinder::GetMethod((BinderMethodID)(METHOD__MATH__ROUND)); pDest = pMD->GetMultiCallableAddrOfCode(); SetJitHelperFunction(CORINFO_HELP_DBLROUND, pDest); diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index 33caff53a66e9..a65145fbf7814 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -109,20 +109,6 @@ using std::isnan; // #define Is32BitSigned(a) (Hi32Bits(a) == Hi32Bits((INT64)(INT32)(a))) -// -// helper function to shift the result by 32-bits -// -inline UINT64 ShiftToHi32Bits(UINT32 x) -{ - // The shift compiles into slow multiplication by 2^32! VSWhidbey 360736 - // return ((UINT64)x) << 32; - - ULARGE_INTEGER ret; - ret.u.HighPart = x; - ret.u.LowPart = 0; - return ret.QuadPart; -} - #if !defined(TARGET_X86) || defined(TARGET_UNIX) /*********************************************************************/ HCIMPL2_VV(INT64, JIT_LMul, INT64 val1, INT64 val2) @@ -140,117 +126,6 @@ HCIMPL2_VV(INT64, JIT_LMul, INT64 val1, INT64 val2) HCIMPLEND #endif // !TARGET_X86 || TARGET_UNIX -/*********************************************************************/ -HCIMPL2_VV(INT64, JIT_LMulOvf, INT64 val1, INT64 val2) -{ - FCALL_CONTRACT; - - // This short-cut does not actually help since the multiplication - // of two 32-bit signed ints compiles into the call to a slow helper - // if (Is32BitSigned(val1) && Is32BitSigned(val2)) - // return (INT64)(INT32)val1 * (INT64)(INT32)val2; - - INDEBUG(INT64 expected = val1 * val2;) - INT64 ret; - - // Remember the sign of the result - INT32 sign = Hi32Bits(val1) ^ Hi32Bits(val2); - - // Convert to unsigned multiplication - if (val1 < 0) val1 = -val1; - if (val2 < 0) val2 = -val2; - - // Get the upper 32 bits of the numbers - UINT32 val1High = Hi32Bits(val1); - UINT32 val2High = Hi32Bits(val2); - - UINT64 valMid; - - if (val1High == 0) { - // Compute the 'middle' bits of the long multiplication - valMid = Mul32x32To64(val2High, val1); - } - else { - if (val2High != 0) - goto ThrowExcep; - // Compute the 'middle' bits of the long multiplication - valMid = Mul32x32To64(val1High, val2); - } - - // See if any bits after bit 32 are set - if (Hi32Bits(valMid) != 0) - goto ThrowExcep; - - ret = Mul32x32To64(val1, val2) + ShiftToHi32Bits((UINT32)(valMid)); - - // check for overflow - if (Hi32Bits(ret) < (UINT32)valMid) - goto ThrowExcep; - - if (sign >= 0) { - // have we spilled into the sign bit? - if (ret < 0) - goto ThrowExcep; - } - else { - ret = -ret; - // have we spilled into the sign bit? - if (ret > 0) - goto ThrowExcep; - } - _ASSERTE(ret == expected); - return ret; - -ThrowExcep: - FCThrow(kOverflowException); -} -HCIMPLEND - -/*********************************************************************/ -HCIMPL2_VV(UINT64, JIT_ULMulOvf, UINT64 val1, UINT64 val2) -{ - FCALL_CONTRACT; - - INDEBUG(UINT64 expected = val1 * val2;) - UINT64 ret; - - // Get the upper 32 bits of the numbers - UINT32 val1High = Hi32Bits(val1); - UINT32 val2High = Hi32Bits(val2); - - UINT64 valMid; - - if (val1High == 0) { - if (val2High == 0) - return Mul32x32To64(val1, val2); - // Compute the 'middle' bits of the long multiplication - valMid = Mul32x32To64(val2High, val1); - } - else { - if (val2High != 0) - goto ThrowExcep; - // Compute the 'middle' bits of the long multiplication - valMid = Mul32x32To64(val1High, val2); - } - - // See if any bits after bit 32 are set - if (Hi32Bits(valMid) != 0) - goto ThrowExcep; - - ret = Mul32x32To64(val1, val2) + ShiftToHi32Bits((UINT32)(valMid)); - - // check for overflow - if (Hi32Bits(ret) < (UINT32)valMid) - goto ThrowExcep; - - _ASSERTE(ret == expected); - return ret; - -ThrowExcep: - FCThrow(kOverflowException); - } -HCIMPLEND - /*********************************************************************/ HCIMPL2(INT32, JIT_Div, INT32 dividend, INT32 divisor) { @@ -522,62 +397,6 @@ HCIMPL1_V(int64_t, JIT_Dbl2Lng, double val) HCIMPLEND /*********************************************************************/ -HCIMPL1_V(uint32_t, JIT_Dbl2UIntOvf, double val) -{ - FCALL_CONTRACT; - - // Note that this expression also works properly for val = NaN case - if (val > -1.0 && val < 4294967296.0) - return (uint32_t)val; - - FCThrow(kOverflowException); -} -HCIMPLEND - -/*********************************************************************/ -HCIMPL1_V(int, JIT_Dbl2IntOvf, double val) -{ - FCALL_CONTRACT; - - const double two31 = 2147483648.0; - // Note that this expression also works properly for val = NaN case - if (val > -two31 - 1 && val < two31) - return (int32_t)val; - - FCThrow(kOverflowException); -} -HCIMPLEND - -/*********************************************************************/ -HCIMPL1_V(int64_t, JIT_Dbl2LngOvf, double val) -{ - FCALL_CONTRACT; - - 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 (int64_t)val; - - FCThrow(kOverflowException); -} -HCIMPLEND - -/*********************************************************************/ -HCIMPL1_V(uint64_t, JIT_Dbl2ULngOvf, double val) -{ - FCALL_CONTRACT; - - const double two64 = 4294967296.0 * 4294967296.0; - // Note that this expression also works properly for val = NaN case - if (val > -1.0 && val < two64) - return (uint64_t)val; - - FCThrow(kOverflowException); -} -HCIMPLEND - HCIMPL1_V(uint32_t, JIT_Dbl2UInt, double val) { FCALL_CONTRACT; diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 0324a02c2c750..c4863f39a0f81 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -10724,7 +10724,14 @@ void* CEEJitInfo::getHelperFtn(CorInfoHelpFunc ftnNum, /* IN */ dynamicFtnNum == DYNAMIC_CORINFO_HELP_LDELEMA_REF || dynamicFtnNum == DYNAMIC_CORINFO_HELP_MEMSET || dynamicFtnNum == DYNAMIC_CORINFO_HELP_MEMZERO || - dynamicFtnNum == DYNAMIC_CORINFO_HELP_MEMCPY) + dynamicFtnNum == DYNAMIC_CORINFO_HELP_MEMCPY || + dynamicFtnNum == DYNAMIC_CORINFO_HELP_BULK_WRITEBARRIER || + IN_TARGET_32BIT(dynamicFtnNum == DYNAMIC_CORINFO_HELP_LMUL_OVF ||) + IN_TARGET_32BIT(dynamicFtnNum == DYNAMIC_CORINFO_HELP_ULMUL_OVF ||) + dynamicFtnNum == DYNAMIC_CORINFO_HELP_DBL2INT_OVF || + dynamicFtnNum == DYNAMIC_CORINFO_HELP_DBL2LNG_OVF || + dynamicFtnNum == DYNAMIC_CORINFO_HELP_DBL2UINT_OVF || + dynamicFtnNum == DYNAMIC_CORINFO_HELP_DBL2ULNG_OVF) { Precode* pPrecode = Precode::GetPrecodeFromEntryPoint((PCODE)hlpDynamicFuncTable[dynamicFtnNum].pfnHelper); _ASSERTE(pPrecode->GetType() == PRECODE_FIXUP); diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index 521abb22465e7..578472525f401 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -154,7 +154,9 @@ internal static void ThrowNegateTwosCompOverflow() [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static unsafe ulong BigMul(uint a, uint b) { -#if TARGET_32BIT +#if false // TARGET_32BIT + // This generates slower code currently than the simple multiplication + // https://github.com/dotnet/runtime/issues/11782 if (Bmi2.IsSupported) { uint low; @@ -1476,5 +1478,182 @@ public static double ScaleB(double x, int n) double u = BitConverter.Int64BitsToDouble(((long)(0x3ff + n) << 52)); return y * u; } + + // + // Helpers, those methods are referenced from the JIT + // + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint High32Bits(ulong a) => (uint)(a >> 32); + + [StackTraceHidden] + internal static long MultiplyInt64Checked(long left, long right) + { +#if DEBUG + long result = left * right; +#endif + + // Remember the sign of the result + int sign = (int)(High32Bits((ulong)left) ^ High32Bits((ulong)right)); + + // Convert to unsigned multiplication + if (left < 0) + left = -left; + if (right < 0) + right = -right; + + // Get the upper 32 bits of the numbers + uint val1High = High32Bits((ulong)left); + uint val2High = High32Bits((ulong)right); + + ulong valMid; + + if (val1High == 0) + { + // Compute the 'middle' bits of the long multiplication + valMid = BigMul(val2High, (uint)left); + } + else + { + if (val2High != 0) + goto Overflow; + // Compute the 'middle' bits of the long multiplication + valMid = BigMul(val1High, (uint)right); + } + + // See if any bits after bit 32 are set + if (High32Bits(valMid) != 0) + goto Overflow; + + long ret = (long)(BigMul((uint)left, (uint)right) + (valMid << 32)); + + // check for overflow + if (High32Bits((ulong)ret) < (uint)valMid) + goto Overflow; + + if (sign >= 0) + { + // have we spilled into the sign bit? + if (ret < 0) + goto Overflow; + } + else + { + ret = -ret; + // have we spilled into the sign bit? + if (ret > 0) + goto Overflow; + } + +#if DEBUG + Debug.Assert(ret == result, $"Multiply overflow got: {ret}, expected: {result}"); +#endif + return ret; + + Overflow: + ThrowHelper.ThrowOverflowException(); + return 0; + } + + [StackTraceHidden] + internal static ulong MultiplyUInt64Checked(ulong left, ulong right) + { + // Get the upper 32 bits of the numbers + uint val1High = High32Bits(left); + uint val2High = High32Bits(right); + + ulong valMid; + + if (val1High == 0) + { + if (val2High == 0) + return (ulong)(uint)left * (uint)right; + // Compute the 'middle' bits of the long multiplication + valMid = BigMul(val2High, (uint)left); + } + else + { + if (val2High != 0) + goto Overflow; + // Compute the 'middle' bits of the long multiplication + valMid = BigMul(val1High, (uint)right); + } + + // See if any bits after bit 32 are set + if (High32Bits(valMid) != 0) + goto Overflow; + + ulong ret = BigMul((uint)left, (uint)right) + (valMid << 32); + + // check for overflow + if (High32Bits(ret) < (uint)valMid) + goto Overflow; + + Debug.Assert(ret == left * right, $"Multiply overflow got: {ret}, expected: {left * right}"); + return ret; + + Overflow: + ThrowHelper.ThrowOverflowException(); + return 0; + } + + private const double Int32MaxValueOffset = (double)int.MaxValue + 1; + private const double UInt32MaxValueOffset = (double)uint.MaxValue + 1; + + [StackTraceHidden] + internal static int ConvertToInt32Checked(double value) + { + // Note that this expression also works properly for val = NaN case + if (value is > -Int32MaxValueOffset - 1 and < Int32MaxValueOffset) + { + return double.ConvertToIntegerNative(value); + } + + ThrowHelper.ThrowOverflowException(); + return 0; + } + + [StackTraceHidden] + internal static uint ConvertToUInt32Checked(double value) + { + // Note that this expression also works properly for val = NaN case + if (value is > -1.0 and < UInt32MaxValueOffset) + { + return double.ConvertToIntegerNative(value); + } + + ThrowHelper.ThrowOverflowException(); + return 0; + } + + [StackTraceHidden] + internal static long ConvertToInt64Checked(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 double.ConvertToIntegerNative(value); + } + + ThrowHelper.ThrowOverflowException(); + return 0; + } + + [StackTraceHidden] + internal static ulong ConvertToUInt64Checked(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 double.ConvertToIntegerNative(value); + } + + ThrowHelper.ThrowOverflowException(); + return 0; + } } }