From e3763103cc6b16db1e02f85454aee41192c1fd0b Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Sat, 11 May 2024 23:36:08 -0700 Subject: [PATCH 1/3] 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 +- .../IL/ILImporter.Scanner.cs | 14 +- 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 ++++++++++++++++- 9 files changed, 247 insertions(+), 382 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/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs index 41869a9e2f18c..35e7d1f6f1f62 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -1278,6 +1278,19 @@ private void ImportBinaryOperation(ILOpcode opcode) } } + private void ImportConvert(WellKnownType wellKnownType, bool checkOverflow, bool unsigned) + { + if (checkOverflow) + { + _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Dbl2IntOvf), "_dbl2intovf"); + _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Dbl2UIntOvf), "_dbl2uintovf"); + _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Dbl2LngOvf), "_dbl2lngovf"); + _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Dbl2ULngOvf), "_dbl2ulngovf"); + + _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Overflow), "_ovf"); + } + } + private void ImportFallthrough(BasicBlock next) { MarkBasicBlock(next); @@ -1403,7 +1416,6 @@ private static void ImportStoreIndirect(int token) { } private static void ImportStoreIndirect(TypeDesc type) { } private static void ImportShiftOperation(ILOpcode opcode) { } private static void ImportCompareOperation(ILOpcode opcode) { } - private static void ImportConvert(WellKnownType wellKnownType, bool checkOverflow, bool unsigned) { } private static void ImportUnaryOperation(ILOpcode opCode) { } private static void ImportCpOpj(int token) { } private static void ImportCkFinite() { } 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..5d1dde847761e 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__MULTIPLY_INT64_CHECKED)); + pDest = pMD->GetMultiCallableAddrOfCode(); + SetJitHelperFunction(CORINFO_HELP_LMUL_OVF, pDest); + + pMD = CoreLibBinder::GetMethod((BinderMethodID)(METHOD__MATH__MULTIPLY_UINT64_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; + } } } From 368dfd7b77427ec8ace2fad66bb89fc87e0e29d8 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Sun, 12 May 2024 10:34:08 -0700 Subject: [PATCH 2/3] FB --- .../aot/ILCompiler.Compiler/Compiler/JitHelper.cs | 12 ++++++++++-- src/coreclr/vm/corelib.h | 4 ++-- src/coreclr/vm/ecall.cpp | 4 ++-- src/coreclr/vm/metasig.h | 2 ++ .../System.Private.CoreLib/src/System/Math.cs | 4 ++-- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs index 13554bd4419be..c04658df379f7 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs @@ -211,10 +211,18 @@ public static void GetEntryPoint(TypeSystemContext context, ReadyToRunHelper id, mangledName = "RhpLMul"; break; case ReadyToRunHelper.LMulOfv: - methodDesc = context.SystemModule.GetKnownType("System", "Math").GetKnownMethod("MultiplyInt64Checked", null); + { + TypeDesc t = context.GetWellKnownType(WellKnownType.Int64); + methodDesc = context.SystemModule.GetKnownType("System", "Math").GetKnownMethod("MultiplyChecked", + new MethodSignature(MethodSignatureFlags.Static, 0, t, [t, t])); + } break; case ReadyToRunHelper.ULMulOvf: - methodDesc = context.SystemModule.GetKnownType("System", "Math").GetKnownMethod("MultiplyUInt64Checked", null); + { + TypeDesc t = context.GetWellKnownType(WellKnownType.UInt64); + methodDesc = context.SystemModule.GetKnownType("System", "Math").GetKnownMethod("MultiplyChecked", + new MethodSignature(MethodSignatureFlags.Static, 0, t, [t, t])); + } break; case ReadyToRunHelper.Mod: diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 78a624821c904..17f74503bdc28 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -262,8 +262,8 @@ 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) +DEFINE_METHOD(MATH, MULTIPLY_CHECKED_INT64, MultiplyChecked, SM_Long_Long_RetLong) +DEFINE_METHOD(MATH, MULTIPLY_CHECKED_UINT64, MultiplyChecked, SM_ULong_ULong_RetULong) #endif DEFINE_METHOD(MATH, CONVERT_TO_INT32_CHECKED, ConvertToInt32Checked, NoSig) DEFINE_METHOD(MATH, CONVERT_TO_UINT32_CHECKED, ConvertToUInt32Checked, NoSig) diff --git a/src/coreclr/vm/ecall.cpp b/src/coreclr/vm/ecall.cpp index 5d1dde847761e..d3e8415d6adee 100644 --- a/src/coreclr/vm/ecall.cpp +++ b/src/coreclr/vm/ecall.cpp @@ -162,11 +162,11 @@ void ECall::PopulateManagedHelpers() SetJitHelperFunction(CORINFO_HELP_BULK_WRITEBARRIER, pDest); #ifndef TARGET_64BIT - pMD = CoreLibBinder::GetMethod((BinderMethodID)(METHOD__MATH__MULTIPLY_INT64_CHECKED)); + pMD = CoreLibBinder::GetMethod((BinderMethodID)(METHOD__MATH__MULTIPLY_CHECKED_INT64)); pDest = pMD->GetMultiCallableAddrOfCode(); SetJitHelperFunction(CORINFO_HELP_LMUL_OVF, pDest); - pMD = CoreLibBinder::GetMethod((BinderMethodID)(METHOD__MATH__MULTIPLY_UINT64_CHECKED)); + pMD = CoreLibBinder::GetMethod((BinderMethodID)(METHOD__MATH__MULTIPLY_CHECKED_UINT64)); pDest = pMD->GetMultiCallableAddrOfCode(); SetJitHelperFunction(CORINFO_HELP_ULMUL_OVF, pDest); #endif diff --git a/src/coreclr/vm/metasig.h b/src/coreclr/vm/metasig.h index 7ab9d6015e03e..35e6472b4b8da 100644 --- a/src/coreclr/vm/metasig.h +++ b/src/coreclr/vm/metasig.h @@ -259,6 +259,8 @@ DEFINE_METASIG(SM(Flt_RetFlt, f, f)) DEFINE_METASIG(SM(Dbl_RetDbl, d, d)) DEFINE_METASIG(SM(RefDbl_Dbl_RetDbl, r(d) d, d)) DEFINE_METASIG(SM(RefDbl_Dbl_Dbl_RetDbl, r(d) d d, d)) +DEFINE_METASIG(SM(Long_Long_RetLong, l l, l)) +DEFINE_METASIG(SM(ULong_ULong_RetULong, L L, L)) DEFINE_METASIG(SM(RefLong_Long_RetLong, r(l) l, l)) DEFINE_METASIG(SM(RefLong_Long_Long_RetLong, r(l) l l, l)) DEFINE_METASIG(SM(RefFlt_Flt_RetFlt, r(f) f, f)) diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index 578472525f401..4bba6d5f832b8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -1487,7 +1487,7 @@ public static double ScaleB(double x, int n) private static uint High32Bits(ulong a) => (uint)(a >> 32); [StackTraceHidden] - internal static long MultiplyInt64Checked(long left, long right) + internal static long MultiplyChecked(long left, long right) { #if DEBUG long result = left * right; @@ -1556,7 +1556,7 @@ internal static long MultiplyInt64Checked(long left, long right) } [StackTraceHidden] - internal static ulong MultiplyUInt64Checked(ulong left, ulong right) + internal static ulong MultiplyChecked(ulong left, ulong right) { // Get the upper 32 bits of the numbers uint val1High = High32Bits(left); From f17631cdb88c2c778eba9b40def7e70eabb951a7 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Sun, 12 May 2024 10:54:40 -0700 Subject: [PATCH 3/3] Use collection expressions in more places --- .../tools/Common/TypeSystem/IL/Stubs/DelegateThunks.cs | 2 +- .../IL/Stubs/ValueTypeGetFieldHelperMethodOverride.cs | 5 +---- .../Common/TypeSystem/Interop/IL/Marshaller.Aot.cs | 10 +++++----- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/DelegateThunks.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/DelegateThunks.cs index 3845514eb4db0..b12db35c7b59e 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/DelegateThunks.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/DelegateThunks.cs @@ -686,7 +686,7 @@ public override MethodSignature Signature TypeDesc intPtrType = context.GetWellKnownType(WellKnownType.IntPtr); TypeDesc int32Type = context.GetWellKnownType(WellKnownType.Int32); - _signature = new MethodSignature(0, 0, intPtrType, new[] { int32Type }); + _signature = new MethodSignature(0, 0, intPtrType, [ int32Type ]); } return _signature; diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/ValueTypeGetFieldHelperMethodOverride.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/ValueTypeGetFieldHelperMethodOverride.cs index a66ce62ea00ef..a4c967fbf967a 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/ValueTypeGetFieldHelperMethodOverride.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/ValueTypeGetFieldHelperMethodOverride.cs @@ -48,10 +48,7 @@ public override MethodSignature Signature TypeDesc int32Type = context.GetWellKnownType(WellKnownType.Int32); TypeDesc eeTypePtrType = context.SystemModule.GetKnownType("Internal.Runtime", "MethodTable").MakePointerType(); - _signature = new MethodSignature(0, 0, int32Type, new[] { - int32Type, - eeTypePtrType.MakeByRefType() - }); + _signature = new MethodSignature(0, 0, int32Type, [ int32Type, eeTypePtrType.MakeByRefType() ]); } return _signature; diff --git a/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.Aot.cs b/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.Aot.cs index f35e028ff49fb..0dff54cd75a19 100644 --- a/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.Aot.cs +++ b/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.Aot.cs @@ -1170,7 +1170,7 @@ private ILLocalVariable InitializeMarshallerVariable() var customMarshallerType = Context.SystemModule.GetKnownType("System.Runtime.InteropServices", "ICustomMarshaler"); var getInstanceMethod = marshallerType.GetMethod( "GetInstance", - new MethodSignature(MethodSignatureFlags.Static, 0, customMarshallerType, new[] { Context.GetWellKnownType(WellKnownType.String) })); + new MethodSignature(MethodSignatureFlags.Static, 0, customMarshallerType, [ Context.GetWellKnownType(WellKnownType.String) ])); if (ManagedType.IsValueType || ManagedType.IsPointer || ManagedType.IsFunctionPointer) { ThrowHelper.ThrowMarshalDirectiveException(); @@ -1210,7 +1210,7 @@ protected override void TransformManagedToNative(ILCodeStream codeStream) ILEmitter emitter = _ilCodeStreams.Emitter; var manageToNativeMethod = customMarshallerType.GetKnownMethod( "MarshalManagedToNative", - new MethodSignature(MethodSignatureFlags.None, 0, Context.GetWellKnownType(WellKnownType.IntPtr), new[] { Context.GetWellKnownType(WellKnownType.Object) })); + new MethodSignature(MethodSignatureFlags.None, 0, Context.GetWellKnownType(WellKnownType.IntPtr), [ Context.GetWellKnownType(WellKnownType.Object) ])); codeStream.EmitLdLoc(lMarshaller); LoadManagedValue(codeStream); @@ -1239,7 +1239,7 @@ protected override void TransformNativeToManaged(ILCodeStream codeStream) ILEmitter emitter = _ilCodeStreams.Emitter; var marshalNativeToManagedMethod = customMarshallerType.GetKnownMethod( "MarshalNativeToManaged", - new MethodSignature(MethodSignatureFlags.None, 0, Context.GetWellKnownType(WellKnownType.Object), new[] { Context.GetWellKnownType(WellKnownType.IntPtr) })); + new MethodSignature(MethodSignatureFlags.None, 0, Context.GetWellKnownType(WellKnownType.Object), [ Context.GetWellKnownType(WellKnownType.IntPtr) ])); codeStream.EmitLdLoc(lMarshaller); LoadNativeValue(codeStream); @@ -1257,7 +1257,7 @@ protected void EmitCleanUpManagedData(ILCodeStream codeStream) // Call CleanUpManagedData on cleanup code stream. var cleanupManagedDataMethod = customMarshallerType.GetKnownMethod( "CleanUpManagedData", - new MethodSignature(MethodSignatureFlags.None, 0, Context.GetWellKnownType(WellKnownType.Void), new[] { Context.GetWellKnownType(WellKnownType.Object) })); + new MethodSignature(MethodSignatureFlags.None, 0, Context.GetWellKnownType(WellKnownType.Void), [ Context.GetWellKnownType(WellKnownType.Object) ])); codeStream.EmitLdLoc(lMarshaller); LoadManagedValue(codeStream); @@ -1274,7 +1274,7 @@ protected void EmitCleanUpNativeData(ILCodeStream codeStream) // Call CleanUpNativeData on cleanup code stream. var cleanupNativeDataMethod = customMarshallerType.GetKnownMethod( "CleanUpNativeData", - new MethodSignature(MethodSignatureFlags.None, 0, Context.GetWellKnownType(WellKnownType.Void), new[] { Context.GetWellKnownType(WellKnownType.IntPtr) })); + new MethodSignature(MethodSignatureFlags.None, 0, Context.GetWellKnownType(WellKnownType.Void), [ Context.GetWellKnownType(WellKnownType.IntPtr) ])); codeStream.EmitLdLoc(lMarshaller); LoadNativeValue(codeStream);