From 3adb73f70047a2123581260a6059c1f83f9a5c5f Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Thu, 5 Jun 2025 17:06:49 -0700 Subject: [PATCH 1/8] Implement CONV_OVF_U1 Refactorings / update for EmitConv change Implement more overflowing conversion opcodes Refactor tagged possibly indirect helper ftn stuff to use a helper template to strip the tag Use jit helpers when doing conv_ovf on floating point inputs for correctness Cleanup Add missing data items for u and i Static assert that we're not conv ovfing in the wrong direction due to a typo Explicit unchecked when generating a nan in the test Add static assertion and comment Address PR feedback Fix CI Checkpoint Rewrite ConvOvfFpHelper and fix a bug in ConvOvfHelper Implement float -> int conversions directly based on advice from @jkotas Add boundary tests and tweak so they pass Better test of floating point to int conversion at rounding boundaries More exact implementation of fp->int overflow check Fix fp conversion helpers and update tests --- src/coreclr/interpreter/compiler.cpp | 251 ++++++++++++++++++ src/coreclr/interpreter/intops.def | 38 ++- src/coreclr/interpreter/intops.h | 1 + src/coreclr/vm/interpexec.cpp | 319 ++++++++++++++++++++--- src/tests/JIT/interpreter/Interpreter.cs | 92 +++++++ 5 files changed, 657 insertions(+), 44 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index f4e007d06dfc28..5f6b789b696519 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -1287,6 +1287,8 @@ void InterpCompiler::EmitConv(StackInfo *sp, StackType type, InterpOpcode convOp int32_t var = CreateVarExplicit(g_interpTypeFromStackType[type], NULL, INTERP_STACK_SLOT_SIZE); sp->var = var; newInst->SetDVar(var); + + // NOTE: We rely on m_pLastNewIns == newInst upon return from this function. Make sure you preserve that if you change anything. } static InterpType GetInterpType(CorInfoType corInfoType) @@ -2120,6 +2122,7 @@ int32_t InterpCompiler::GetMethodDataItemIndex(CORINFO_METHOD_HANDLE mHandle) int32_t InterpCompiler::GetDataItemIndexForHelperFtn(CorInfoHelpFunc ftn) { + // Interpreter-TODO: Find an existing data item index for this helper if possible and reuse it void *indirect; void *direct = m_compHnd->getHelperFtn(ftn, &indirect); size_t data = !direct @@ -3526,6 +3529,245 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) } m_ip++; break; + case CEE_CONV_OVF_I1: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_I1_R4); + break; + case StackTypeR8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_I1_R8); + break; + case StackTypeI4: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_I1_I4); + break; + case StackTypeI8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_I1_I8); + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_U1: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U1_R4); + break; + case StackTypeR8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U1_R8); + break; + case StackTypeI4: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U1_I4); + break; + case StackTypeI8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U1_I8); + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_I2: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_I2_R4); + break; + case StackTypeR8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_I2_R8); + break; + case StackTypeI4: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_I2_I4); + break; + case StackTypeI8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_I2_I8); + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_U2: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U2_R4); + break; + case StackTypeR8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U2_R8); + break; + case StackTypeI4: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U2_I4); + break; + case StackTypeI8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U2_I8); + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_I4: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_I4_R4); + break; + case StackTypeR8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_I4_R8); + break; + case StackTypeI4: + break; + case StackTypeI8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_I4_I8); + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_U4: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U4_R4); + break; + case StackTypeR8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U4_R8); + break; + case StackTypeI4: + break; + case StackTypeI8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U4_I8); + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_I8: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + EmitConv(m_pStackPointer - 1, StackTypeI8, INTOP_CONV_OVF_I8_R4); + break; + case StackTypeR8: + EmitConv(m_pStackPointer - 1, StackTypeI8, INTOP_CONV_OVF_I8_R8); + break; + case StackTypeI4: + EmitConv(m_pStackPointer - 1, StackTypeI8, INTOP_CONV_I8_I4); + break; + case StackTypeI8: + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_U8: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + EmitConv(m_pStackPointer - 1, StackTypeI8, INTOP_CONV_OVF_U8_R4); + break; + case StackTypeR8: + EmitConv(m_pStackPointer - 1, StackTypeI8, INTOP_CONV_OVF_U8_R8); + break; + case StackTypeI4: + EmitConv(m_pStackPointer - 1, StackTypeI8, INTOP_CONV_I8_U4); + break; + case StackTypeI8: + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_I: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: +#ifdef TARGET_64BIT + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_I8_R4); +#else + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_I4_R4); +#endif + break; + case StackTypeR8: +#ifdef TARGET_64BIT + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_I8_R8); +#else + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_I4_R8); +#endif + break; + case StackTypeI4: +#ifdef TARGET_64BIT + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_I8_I4); +#else + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_MOV_4); +#endif + break; + case StackTypeI8: +#ifdef TARGET_64BIT + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_MOV_8); +#else + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_I4_I8); +#endif + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_U: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: +#ifdef TARGET_64BIT + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_U8_R4); +#else + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_U4_R4); +#endif + break; + case StackTypeR8: +#ifdef TARGET_64BIT + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_U8_R8); +#else + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_U4_R8); +#endif + break; + case StackTypeI4: +#ifdef TARGET_64BIT + // FIXME: Is this the right conv opcode? + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_I8_I4); +#else + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_MOV_4); +#endif + break; + case StackTypeI8: +#ifdef TARGET_64BIT + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_MOV_8); +#else + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_U4_I8); +#endif + break; + default: + assert(0); + } + m_ip++; + break; case CEE_SWITCH: { m_ip++; @@ -5009,6 +5251,15 @@ void InterpCompiler::PrintInsData(InterpInst *ins, int32_t insOffset, const int3 PrintClassName(ch); break; } + case InterpOpHelperFtn: + { + size_t helperDirectOrIndirect = (size_t)m_dataItems.Get(*pData); + if (helperDirectOrIndirect & INTERP_INDIRECT_HELPER_TAG) + printf(" (indirect) %p", (void*)(helperDirectOrIndirect & ~INTERP_INDIRECT_HELPER_TAG)); + else + printf(" (direct) %p", (void*)helperDirectOrIndirect); + break; + } default: assert(0); break; diff --git a/src/coreclr/interpreter/intops.def b/src/coreclr/interpreter/intops.def index 6a5bffd5b2639e..22147c95d26aa8 100644 --- a/src/coreclr/interpreter/intops.def +++ b/src/coreclr/interpreter/intops.def @@ -171,6 +171,40 @@ OPDEF(INTOP_CONV_R8_R4, "conv.r8.r4", 3, 1, 1, InterpOpNoArgs) OPDEF(INTOP_CONV_U8_R4, "conv.u8.r4", 3, 1, 1, InterpOpNoArgs) OPDEF(INTOP_CONV_U8_R8, "conv.u8.r8", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_I1_I4, "conv.ovf.i1.i4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_I1_I8, "conv.ovf.i1.i8", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_I1_R4, "conv.ovf.i1.r4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_I1_R8, "conv.ovf.i1.r8", 3, 1, 1, InterpOpNoArgs) + +OPDEF(INTOP_CONV_OVF_U1_I4, "conv.ovf.u1.i4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_U1_I8, "conv.ovf.u1.i8", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_U1_R4, "conv.ovf.u1.r4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_U1_R8, "conv.ovf.u1.r8", 3, 1, 1, InterpOpNoArgs) + +OPDEF(INTOP_CONV_OVF_I2_I4, "conv.ovf.i2.i4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_I2_I8, "conv.ovf.i2.i8", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_I2_R4, "conv.ovf.i2.r4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_I2_R8, "conv.ovf.i2.r8", 3, 1, 1, InterpOpNoArgs) + +OPDEF(INTOP_CONV_OVF_U2_I4, "conv.ovf.u2.i4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_U2_I8, "conv.ovf.u2.i8", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_U2_R4, "conv.ovf.u2.r4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_U2_R8, "conv.ovf.u2.r8", 3, 1, 1, InterpOpNoArgs) + +OPDEF(INTOP_CONV_OVF_I4_I8, "conv.ovf.i4.i8", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_I4_R4, "conv.ovf.i4.r4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_I4_R8, "conv.ovf.i4.r8", 3, 1, 1, InterpOpNoArgs) + +OPDEF(INTOP_CONV_OVF_U4_I8, "conv.ovf.u4.i8", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_U4_R4, "conv.ovf.u4.r4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_U4_R8, "conv.ovf.u4.r8", 3, 1, 1, InterpOpNoArgs) + +OPDEF(INTOP_CONV_OVF_I8_R4, "conv.ovf.i8.r4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_I8_R8, "conv.ovf.i8.r8", 3, 1, 1, InterpOpNoArgs) + +OPDEF(INTOP_CONV_OVF_U8_R4, "conv.ovf.u8.r4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_U8_R8, "conv.ovf.u8.r8", 3, 1, 1, InterpOpNoArgs) + OPDEF(INTOP_BOX, "box", 5, 1, 1, InterpOpClassHandle) // [class handle data item] [helper data item] OPDEF(INTOP_UNBOX, "unbox", 5, 1, 1, InterpOpClassHandle) // [class handle data item] [helper data item] OPDEF(INTOP_UNBOX_ANY, "unbox.any", 5, 1, 1, InterpOpClassHandle) // [class handle data item] [helper data item] @@ -293,8 +327,8 @@ OPDEF(INTOP_NEWOBJ, "newobj", 5, 1, 1, InterpOpMethodHandle) OPDEF(INTOP_NEWOBJ_VAR, "newobj.var", 5, 1, 2, InterpOpMethodHandle) OPDEF(INTOP_NEWOBJ_VT, "newobj.vt", 5, 1, 1, InterpOpMethodHandle) -OPDEF(INTOP_CALL_HELPER_PP, "call.helper.pp", 4, 1, 0, InterpOpTwoInts) -OPDEF(INTOP_CALL_HELPER_PP_2, "call.helper.pp.2", 5, 1, 1, InterpOpTwoInts) +OPDEF(INTOP_CALL_HELPER_PP, "call.helper.pp", 4, 1, 0, InterpOpHelperFtn) +OPDEF(INTOP_CALL_HELPER_PP_2, "call.helper.pp.2", 5, 1, 1, InterpOpHelperFtn) OPDEF(INTOP_GENERICLOOKUP_METHOD, "generic.method", 4, 1, 1, InterpOpGenericLookup) OPDEF(INTOP_GENERICLOOKUP_CLASS, "generic.class", 4, 1, 1, InterpOpGenericLookup) diff --git a/src/coreclr/interpreter/intops.h b/src/coreclr/interpreter/intops.h index 5ff775b2b0d896..82ab2f66bbfb55 100644 --- a/src/coreclr/interpreter/intops.h +++ b/src/coreclr/interpreter/intops.h @@ -24,6 +24,7 @@ typedef enum InterpOpClassHandle, InterpOpGenericLookup, InterpOpLdPtr, + InterpOpHelperFtn, } InterpOpArgType; extern const uint8_t g_interpOpLen[]; diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index c29294c2da23c5..46954bd15952ee 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -8,6 +8,11 @@ #include "interpexec.h" #include "callstubgenerator.h" +// for numeric_limits +#include +// for nexttoward +#include + FCDECL1(uint64_t, JIT_Dbl2ULng, double); FCDECL1(int64_t, JIT_Dbl2Lng, double); FCDECL1(uint32_t, JIT_Dbl2UInt, double); @@ -66,7 +71,7 @@ CallStubHeader *CreateNativeToInterpreterCallStub(InterpMethod* pInterpMethod) if (pHeader == NULL) { // Ensure that there is an interpreter thread context instance and thus an interpreter stack - // allocated for this thread. This allows us to not to have to check and allocate it from + // allocated for this thread. This allows us to not to have to check and allocate it from // the interpreter stub right after this call. GetThread()->GetInterpThreadContext(); @@ -94,6 +99,8 @@ typedef void* (*HELPER_FTN_PP)(void*); typedef void* (*HELPER_FTN_BOX_UNBOX)(MethodTable*, void*); typedef Object* (*HELPER_FTN_NEWARR)(CORINFO_CLASS_HANDLE, intptr_t); typedef void* (*HELPER_FTN_PP_2)(void*, void*); +typedef int32_t (*HELPER_FTN_R82I4)(double); +typedef int64_t (*HELPER_FTN_R82I8)(double); InterpThreadContext::InterpThreadContext() { @@ -143,6 +150,143 @@ static OBJECTREF CreateMultiDimArray(MethodTable* arrayClass, int8_t* stack, int #define LOCAL_VAR(offset,type) (*LOCAL_VAR_ADDR(offset, type)) #define NULL_CHECK(o) do { if ((o) == NULL) { COMPlusThrow(kNullReferenceException); } } while (0) +template static THelper GetPossiblyIndirectHelper(void* dataItem) +{ + size_t helperDirectOrIndirect = (size_t)dataItem; + if (helperDirectOrIndirect & INTERP_INDIRECT_HELPER_TAG) + return *(THelper *)(helperDirectOrIndirect & ~INTERP_INDIRECT_HELPER_TAG); + else + return (THelper)helperDirectOrIndirect; +} + +template static void ConvFpHelper(int8_t *stack, const int32_t *ip) +{ + static_assert(!std::numeric_limits::is_integer, "ConvFpHelper is only for use on floats and doubles"); + static_assert(std::numeric_limits::is_integer, "ConvFpHelper is only for use on floats and doubles to be converted to integers"); + + // First, promote the source value to double + double src = LOCAL_VAR(ip[2], TSource), + minValue = (double)std::numeric_limits::lowest(), + maxValue = (double)std::numeric_limits::max(); + + // (src != src) checks for NaN, then we check whether the min and max values (as represented by their closest double) + // properly bound the source value so that when it is truncated it will be in range + // We assume that we are in round-towards-zero mode. For NaN we want to return 0, and for out of range values, saturate. + // We don't need to be fancy with nexttoward and 0.5 since we're saturating. + TResult result; + if (src != src) + result = 0; + else if (src >= maxValue) + result = std::numeric_limits::max(); + else if (std::numeric_limits::is_signed && (src <= -1)) + result = 0; + else if (!std::numeric_limits::is_signed && (src < minValue)) + result = std::numeric_limits::lowest(); + else + result = (TResult)src; + + // According to spec, for result types smaller than int32, we store them on the stack as int32 + if (sizeof(TResult) >= 4) + LOCAL_VAR(ip[1], TResult) = result; + else + LOCAL_VAR(ip[1], int32_t) = (int32_t)result; +} + +template static void ConvOvfFpHelper(int8_t *stack, const int32_t *ip) +{ + static_assert(!std::numeric_limits::is_integer, "ConvOvfFpHelper is only for use on floats and doubles"); + static_assert(std::numeric_limits::is_integer, "ConvOvfFpHelper is only for use on floats and doubles to be converted to integers"); + static_assert(sizeof(TResult) <= 4, "ConvOvfFpHelper's generic version is only for use on results <= 4 bytes in size"); + + // First, promote the source value to double + double src = LOCAL_VAR(ip[2], TSource), + minValue = (double)std::numeric_limits::lowest() - 1, + maxValue = (double)std::numeric_limits::max() + 1; + + // (src != src) checks for NaN, then we check whether the min and max values properly bound the source value + // We assume that we are in round-towards-zero mode + bool outOfRange = (src != src) || (src < minValue) || (src > maxValue); + + if (outOfRange) + COMPlusThrow(kOverflowException); + + TResult truncated = (TResult)src; + // According to spec, for result types smaller than int32, we store them on the stack as int32 + LOCAL_VAR(ip[1], int32_t) = (int32_t)truncated; +} + +// I64 and U64 versions based on mono_math.h + +template static void ConvOvfFpHelperI64(int8_t *stack, const int32_t *ip) +{ + static_assert(!std::numeric_limits::is_integer, "ConvOvfFpHelper is only for use on floats and doubles"); + + const double two63 = 2147483648.0 * 4294967296.0; + // First, promote the source value to double + double src = LOCAL_VAR(ip[2], TSource), + // Define the boundary values we need to be between (see mono_try_trunc_i64) + minValue = (-two63 - 0x402), + maxValue = two63; + + bool inRange = (src > minValue) && (src < maxValue); + if (!inRange) + COMPlusThrow(kOverflowException); + + int64_t truncated = (int64_t)src; + // According to spec, for result types smaller than int32, we store them on the stack as int32 + LOCAL_VAR(ip[1], int64_t) = truncated; +} + +template static void ConvOvfFpHelperU64(int8_t *stack, const int32_t *ip) +{ + static_assert(!std::numeric_limits::is_integer, "ConvOvfFpHelper is only for use on floats and doubles"); + + // First, promote the source value to double + double src = LOCAL_VAR(ip[2], TSource), + // Define the boundary values we need to be between (see mono_try_trunc_u64) + minValue = -1.0, + maxValue = 4294967296.0 * 4294967296.0; + + bool inRange = (src > minValue) && (src < maxValue); + if (!inRange) + COMPlusThrow(kOverflowException); + + uint64_t truncated = (uint64_t)src; + // According to spec, for result types smaller than int32, we store them on the stack as int32 + LOCAL_VAR(ip[1], uint64_t) = truncated; +} + +template void ConvOvfHelper(int8_t *stack, const int32_t *ip) +{ + static_assert(sizeof(TResult) < sizeof(TSource), "ConvOvfHelper only can convert to results smaller than the source value"); + static_assert(std::numeric_limits::is_integer, "ConvOvfHelper is only for use on integers"); + + TSource src = LOCAL_VAR(ip[2], TSource); + bool inRange; + if (std::numeric_limits::is_signed) + { + inRange = (src >= (TSource)std::numeric_limits::lowest()) && (src <= (TSource)std::numeric_limits::max()); + } + else + { + inRange = (src <= (TSource)std::numeric_limits::max()); + } + + if (inRange) + { + // conv_ovf with floating point inputs is handled in ConvOvfFpHelper, so all possible conv_ovfs in this function are + // from int64 into int32-or-smaller, or int32-or-smaller into int16-or-smaller. + // The spec says that conversion results are stored on the evaluation stack as signed, so we always want to convert + // the final truncated result into int32_t before storing it on the stack, even if the truncated result is unsigned. + TResult result = (TResult)src; + LOCAL_VAR(ip[1], int32_t) = (int32_t)result; + } + else + { + COMPlusThrow(kOverflowException); + } +} + void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFrame *pFrame, InterpThreadContext *pThreadContext, ExceptionClauseArgs *pExceptionClauseArgs) { CONTRACTL @@ -307,11 +451,11 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr ip += 3; break; case INTOP_CONV_I1_R4: - LOCAL_VAR(ip[1], int32_t) = (int8_t)HCCALL1(JIT_Dbl2Int, (double)LOCAL_VAR(ip[2], float)); + ConvFpHelper(stack, ip); ip += 3; break; case INTOP_CONV_I1_R8: - LOCAL_VAR(ip[1], int32_t) = (int8_t)HCCALL1(JIT_Dbl2Int, LOCAL_VAR(ip[2], double)); + ConvFpHelper(stack, ip); ip += 3; break; case INTOP_CONV_U1_I4: @@ -323,11 +467,11 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr ip += 3; break; case INTOP_CONV_U1_R4: - LOCAL_VAR(ip[1], int32_t) = (uint8_t)HCCALL1(JIT_Dbl2UInt, (double)LOCAL_VAR(ip[2], float)); + ConvFpHelper(stack, ip); ip += 3; break; case INTOP_CONV_U1_R8: - LOCAL_VAR(ip[1], int32_t) = (uint8_t)HCCALL1(JIT_Dbl2UInt, LOCAL_VAR(ip[2], double)); + ConvFpHelper(stack, ip); ip += 3; break; case INTOP_CONV_I2_I4: @@ -339,11 +483,11 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr ip += 3; break; case INTOP_CONV_I2_R4: - LOCAL_VAR(ip[1], int32_t) = (int16_t)HCCALL1(JIT_Dbl2Int, (double)LOCAL_VAR(ip[2], float)); + ConvFpHelper(stack, ip); ip += 3; break; case INTOP_CONV_I2_R8: - LOCAL_VAR(ip[1], int32_t) = (int16_t)HCCALL1(JIT_Dbl2Int, LOCAL_VAR(ip[2], double)); + ConvFpHelper(stack, ip); ip += 3; break; case INTOP_CONV_U2_I4: @@ -355,27 +499,27 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr ip += 3; break; case INTOP_CONV_U2_R4: - LOCAL_VAR(ip[1], int32_t) = (uint16_t)HCCALL1(JIT_Dbl2UInt, (double)LOCAL_VAR(ip[2], float)); + ConvFpHelper(stack, ip); ip += 3; break; case INTOP_CONV_U2_R8: - LOCAL_VAR(ip[1], int32_t) = (uint16_t)HCCALL1(JIT_Dbl2UInt, LOCAL_VAR(ip[2], double)); + ConvFpHelper(stack, ip); ip += 3; break; case INTOP_CONV_I4_R4: - LOCAL_VAR(ip[1], int32_t) = HCCALL1(JIT_Dbl2Int, (double)LOCAL_VAR(ip[2], float)); + ConvFpHelper(stack, ip); ip += 3; break;; case INTOP_CONV_I4_R8: - LOCAL_VAR(ip[1], int32_t) = HCCALL1(JIT_Dbl2Int, LOCAL_VAR(ip[2], double)); + ConvFpHelper(stack, ip); ip += 3; break;; case INTOP_CONV_U4_R4: - LOCAL_VAR(ip[1], uint32_t) = HCCALL1(JIT_Dbl2UInt, (double)LOCAL_VAR(ip[2], float)); + ConvFpHelper(stack, ip); ip += 3; break; case INTOP_CONV_U4_R8: - LOCAL_VAR(ip[1], uint32_t) = HCCALL1(JIT_Dbl2UInt, LOCAL_VAR(ip[2], double)); + ConvFpHelper(stack, ip); ip += 3; break; case INTOP_CONV_I8_I4: @@ -387,11 +531,11 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr ip += 3; break;; case INTOP_CONV_I8_R4: - LOCAL_VAR(ip[1], int64_t) = HCCALL1(JIT_Dbl2Lng, (double)LOCAL_VAR(ip[2], float)); + ConvFpHelper(stack, ip); ip += 3; break; case INTOP_CONV_I8_R8: - LOCAL_VAR(ip[1], int64_t) = HCCALL1(JIT_Dbl2Lng, LOCAL_VAR(ip[2], double)); + ConvFpHelper(stack, ip); ip += 3; break; case INTOP_CONV_R4_I4: @@ -419,11 +563,123 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr ip += 3; break; case INTOP_CONV_U8_R4: - LOCAL_VAR(ip[1], uint64_t) = HCCALL1(JIT_Dbl2ULng, (double)LOCAL_VAR(ip[2], float)); + ConvFpHelper(stack, ip); ip += 3; break; case INTOP_CONV_U8_R8: - LOCAL_VAR(ip[1], uint64_t) = HCCALL1(JIT_Dbl2ULng, LOCAL_VAR(ip[2], double)); + ConvFpHelper(stack, ip); + ip += 3; + break; + + case INTOP_CONV_OVF_I1_I4: + ConvOvfHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_I1_I8: + ConvOvfHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_I1_R4: + ConvOvfFpHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_I1_R8: + ConvOvfFpHelper(stack, ip); + ip += 3; + break; + + case INTOP_CONV_OVF_U1_I4: + ConvOvfHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_U1_I8: + ConvOvfHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_U1_R4: + ConvOvfFpHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_U1_R8: + ConvOvfFpHelper(stack, ip); + ip += 3; + break; + + case INTOP_CONV_OVF_I2_I4: + ConvOvfHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_I2_I8: + ConvOvfHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_I2_R4: + ConvOvfFpHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_I2_R8: + ConvOvfFpHelper(stack, ip); + ip += 3; + break; + + case INTOP_CONV_OVF_U2_I4: + ConvOvfHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_U2_I8: + ConvOvfHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_U2_R4: + ConvOvfFpHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_U2_R8: + ConvOvfFpHelper(stack, ip); + ip += 3; + break; + + case INTOP_CONV_OVF_I4_I8: + ConvOvfHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_I4_R4: + ConvOvfFpHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_I4_R8: + ConvOvfFpHelper(stack, ip); + ip += 3; + break; + + case INTOP_CONV_OVF_U4_I8: + ConvOvfHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_U4_R4: + ConvOvfFpHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_U4_R8: + ConvOvfFpHelper(stack, ip); + ip += 3; + break; + + case INTOP_CONV_OVF_I8_R4: + ConvOvfFpHelperI64(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_I8_R8: + ConvOvfFpHelperI64(stack, ip); + ip += 3; + break; + + case INTOP_CONV_OVF_U8_R4: + ConvOvfFpHelperU64(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_U8_R8: + ConvOvfFpHelperU64(stack, ip); ip += 3; break; @@ -1164,13 +1420,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr { int base = (*ip == INTOP_CALL_HELPER_PP) ? 2 : 3; - size_t helperDirectOrIndirect = (size_t)pMethod->pDataItems[ip[base]]; - HELPER_FTN_PP helperFtn = nullptr; - if (helperDirectOrIndirect & INTERP_INDIRECT_HELPER_TAG) - helperFtn = *(HELPER_FTN_PP *)(helperDirectOrIndirect & ~INTERP_INDIRECT_HELPER_TAG); - else - helperFtn = (HELPER_FTN_PP)helperDirectOrIndirect; - + HELPER_FTN_PP helperFtn = GetPossiblyIndirectHelper(pMethod->pDataItems[ip[base]]); void* helperArg = pMethod->pDataItems[ip[base + 1]]; // This can call either native or compiled managed code. For an interpreter @@ -1400,12 +1650,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr int dreg = ip[1]; int sreg = ip[2]; MethodTable *pMT = (MethodTable*)pMethod->pDataItems[ip[3]]; - size_t helperDirectOrIndirect = (size_t)pMethod->pDataItems[ip[4]]; - HELPER_FTN_BOX_UNBOX helper = nullptr; - if (helperDirectOrIndirect & INTERP_INDIRECT_HELPER_TAG) - helper = *(HELPER_FTN_BOX_UNBOX *)(helperDirectOrIndirect & ~INTERP_INDIRECT_HELPER_TAG); - else - helper = (HELPER_FTN_BOX_UNBOX)helperDirectOrIndirect; + HELPER_FTN_BOX_UNBOX helper = GetPossiblyIndirectHelper(pMethod->pDataItems[ip[4]]); if (opcode == INTOP_BOX) { // internal static object Box(MethodTable* typeMT, ref byte unboxedData) @@ -1431,12 +1676,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr COMPlusThrow(kArgumentOutOfRangeException); CORINFO_CLASS_HANDLE arrayClsHnd = (CORINFO_CLASS_HANDLE)pMethod->pDataItems[ip[3]]; - size_t helperDirectOrIndirect = (size_t)pMethod->pDataItems[ip[4]]; - HELPER_FTN_NEWARR helper = nullptr; - if (helperDirectOrIndirect & INTERP_INDIRECT_HELPER_TAG) - helper = *(HELPER_FTN_NEWARR *)(helperDirectOrIndirect & ~INTERP_INDIRECT_HELPER_TAG); - else - helper = (HELPER_FTN_NEWARR)helperDirectOrIndirect; + HELPER_FTN_NEWARR helper = GetPossiblyIndirectHelper(pMethod->pDataItems[ip[4]]); Object* arr = helper(arrayClsHnd, (intptr_t)length); LOCAL_VAR(ip[1], OBJECTREF) = ObjectToOBJECTREF(arr); @@ -1792,12 +2032,7 @@ do { \ { int dreg = ip[1]; void *nativeHandle = pMethod->pDataItems[ip[3]]; - size_t helperDirectOrIndirect = (size_t)pMethod->pDataItems[ip[2]]; - HELPER_FTN_PP helper = nullptr; - if (helperDirectOrIndirect & INTERP_INDIRECT_HELPER_TAG) - helper = *(HELPER_FTN_PP *)(helperDirectOrIndirect & ~INTERP_INDIRECT_HELPER_TAG); - else - helper = (HELPER_FTN_PP)helperDirectOrIndirect; + HELPER_FTN_PP helper = GetPossiblyIndirectHelper(pMethod->pDataItems[ip[2]]); void *managedHandle = helper(nativeHandle); LOCAL_VAR(dreg, void*) = managedHandle; ip += 4; diff --git a/src/tests/JIT/interpreter/Interpreter.cs b/src/tests/JIT/interpreter/Interpreter.cs index 01812ec3708ba2..61b40a7d97988d 100644 --- a/src/tests/JIT/interpreter/Interpreter.cs +++ b/src/tests/JIT/interpreter/Interpreter.cs @@ -799,6 +799,25 @@ public static void RunInterpreterTests() if (!TestFloat()) Environment.FailFast(null); + // Unchecked to ensure that the divide-by-zero here doesn't throw since we're using it to generate a NaN + unchecked + { + if (!TestConvOvf(1, 2, 3, 4, 1.0 / 0.0, -32, 1234567890)) + Environment.FailFast(null); + + if (!TestConvBoundaries( + 32767.999999999996, 32768.00000000001, + 2147483647.9999998, 2147483648.0000005 + )) + Environment.FailFast(null); + + if (!TestConvBoundaries( + -32768.99999999999, -32769.00000000001, + -2147483648.9999995, -2147483649.0000005 + )) + Environment.FailFast(null); + } + Console.WriteLine("TestLocalloc"); if (!TestLocalloc()) Environment.FailFast(null); @@ -1464,6 +1483,79 @@ public static bool PowLoop(int n, long nr, int expected) return (int)ret == expected; } + public static bool TestConvOvf(float r4, double r8, int i4, long i8, double nan, int negativeInt, long hugeInt) + { + checked + { + byte a = (byte)r4, + b = (byte)r8, + c = (byte)i4, + d = (byte)i8; + + if (a != r4) + return false; + if (b != r8) + return false; + if (c != i4) + return false; + if (d != i8) + return false; + + try { + a = (byte)nan; + return false; + } catch (OverflowException) { + } + + try { + b = (byte)hugeInt; + return false; + } catch (OverflowException) { + } + + try { + c = (byte)negativeInt; + return false; + } catch (OverflowException) { + } + } + + return true; + } + + public static bool TestConvBoundaries (double inRangeShort, double outOfRangeShort, double inRangeInt, double outOfRangeInt) { + // In unchecked mode, the interpreter saturates on float->int conversions if the value is out of range + unchecked { + short a = (short)inRangeShort, + b = (short)outOfRangeShort; + int c = (int)inRangeInt, + d = (int)outOfRangeInt; + + if (a != b) + return false; + if (c != d) + return false; + } + + checked { + short tempA = (short)inRangeShort; + try { + tempA = (short)outOfRangeShort; + return false; + } catch (OverflowException) { + } + + int tempB = (int)inRangeInt; + try { + tempB = (int)outOfRangeInt; + return false; + } catch (OverflowException) { + } + } + + return true; + } + public static int jitField1; [ThreadStatic] public static int jitField2; From 36baf76c15da952013169749ca0f86521c46fb3c Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Mon, 9 Jun 2025 16:42:50 -0700 Subject: [PATCH 2/8] Cleanup --- src/coreclr/vm/interpexec.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 46954bd15952ee..54188d56a92a1c 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -10,13 +10,7 @@ // for numeric_limits #include -// for nexttoward -#include -FCDECL1(uint64_t, JIT_Dbl2ULng, double); -FCDECL1(int64_t, JIT_Dbl2Lng, double); -FCDECL1(uint32_t, JIT_Dbl2UInt, double); -FCDECL1(int32_t, JIT_Dbl2Int, double); FCDECL1(float, JIT_ULng2Flt, uint64_t val); FCDECL1(double, JIT_ULng2Dbl, uint64_t val); FCDECL1(float, JIT_Lng2Flt, int64_t val); @@ -172,7 +166,6 @@ template static void ConvFpHelper(int8_t *s // (src != src) checks for NaN, then we check whether the min and max values (as represented by their closest double) // properly bound the source value so that when it is truncated it will be in range // We assume that we are in round-towards-zero mode. For NaN we want to return 0, and for out of range values, saturate. - // We don't need to be fancy with nexttoward and 0.5 since we're saturating. TResult result; if (src != src) result = 0; From 4df352611d04c24d0d69a5ac5eab0ae68bde94c3 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Mon, 9 Jun 2025 16:43:40 -0700 Subject: [PATCH 3/8] Fix backwards conditional --- src/coreclr/vm/interpexec.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 54188d56a92a1c..4caca970beb2e4 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -171,9 +171,9 @@ template static void ConvFpHelper(int8_t *s result = 0; else if (src >= maxValue) result = std::numeric_limits::max(); - else if (std::numeric_limits::is_signed && (src <= -1)) + else if (!std::numeric_limits::is_signed && (src <= -1)) result = 0; - else if (!std::numeric_limits::is_signed && (src < minValue)) + else if (std::numeric_limits::is_signed && (src < minValue)) result = std::numeric_limits::lowest(); else result = (TResult)src; From ae7387f1519483f8cb079bf780d07ef24b46117f Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Mon, 9 Jun 2025 16:46:21 -0700 Subject: [PATCH 4/8] Fix boundary values Cleanup unused pointer type --- src/coreclr/vm/interpexec.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 4caca970beb2e4..c0fdcb12f83a24 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -93,8 +93,6 @@ typedef void* (*HELPER_FTN_PP)(void*); typedef void* (*HELPER_FTN_BOX_UNBOX)(MethodTable*, void*); typedef Object* (*HELPER_FTN_NEWARR)(CORINFO_CLASS_HANDLE, intptr_t); typedef void* (*HELPER_FTN_PP_2)(void*, void*); -typedef int32_t (*HELPER_FTN_R82I4)(double); -typedef int64_t (*HELPER_FTN_R82I8)(double); InterpThreadContext::InterpThreadContext() { @@ -198,7 +196,7 @@ template static void ConvOvfFpHelper(int8_t // (src != src) checks for NaN, then we check whether the min and max values properly bound the source value // We assume that we are in round-towards-zero mode - bool outOfRange = (src != src) || (src < minValue) || (src > maxValue); + bool outOfRange = (src != src) || (src <= minValue) || (src >= maxValue); if (outOfRange) COMPlusThrow(kOverflowException); From 5b08d73f66e399b4208b8ffffdce921e32871456 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Mon, 9 Jun 2025 17:06:29 -0700 Subject: [PATCH 5/8] Apply suggestions from code review Co-authored-by: Jan Kotas --- src/coreclr/vm/interpexec.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index c0fdcb12f83a24..c77a92bd8658ac 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -212,10 +212,10 @@ template static void ConvOvfFpHelperI64(int8_t *stack, const { static_assert(!std::numeric_limits::is_integer, "ConvOvfFpHelper is only for use on floats and doubles"); - const double two63 = 2147483648.0 * 4294967296.0; + const double two63 = 2147483648.0 * 4294967296.0; // First, promote the source value to double double src = LOCAL_VAR(ip[2], TSource), - // Define the boundary values we need to be between (see mono_try_trunc_i64) + // Define the boundary values we need to be between (see System.Math.ConvertToInt64Checked) minValue = (-two63 - 0x402), maxValue = two63; @@ -234,7 +234,7 @@ template static void ConvOvfFpHelperU64(int8_t *stack, const // First, promote the source value to double double src = LOCAL_VAR(ip[2], TSource), - // Define the boundary values we need to be between (see mono_try_trunc_u64) + // Define the boundary values we need to be between (see System.Math.ConvertToUInt64Checked) minValue = -1.0, maxValue = 4294967296.0 * 4294967296.0; From b5637cc51b1afdec0eb0865ad3fce03c1108c3f7 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Mon, 9 Jun 2025 17:07:24 -0700 Subject: [PATCH 6/8] Address PR feedback --- src/coreclr/vm/interpexec.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index c77a92bd8658ac..deb2f07262d7c3 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -196,9 +196,9 @@ template static void ConvOvfFpHelper(int8_t // (src != src) checks for NaN, then we check whether the min and max values properly bound the source value // We assume that we are in round-towards-zero mode - bool outOfRange = (src != src) || (src <= minValue) || (src >= maxValue); + bool inRange = (src > minValue) && (src < maxValue); - if (outOfRange) + if (!inRange) COMPlusThrow(kOverflowException); TResult truncated = (TResult)src; @@ -212,7 +212,7 @@ template static void ConvOvfFpHelperI64(int8_t *stack, const { static_assert(!std::numeric_limits::is_integer, "ConvOvfFpHelper is only for use on floats and doubles"); - const double two63 = 2147483648.0 * 4294967296.0; + const double two63 = 2147483648.0 * 4294967296.0; // First, promote the source value to double double src = LOCAL_VAR(ip[2], TSource), // Define the boundary values we need to be between (see System.Math.ConvertToInt64Checked) From fd7f11311b4694bfe5103a7dc9e3be324b9629a9 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Mon, 9 Jun 2025 17:18:31 -0700 Subject: [PATCH 7/8] Remove outdated comments --- src/coreclr/vm/interpexec.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index deb2f07262d7c3..b45ea7e26dc5c6 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -194,7 +194,6 @@ template static void ConvOvfFpHelper(int8_t minValue = (double)std::numeric_limits::lowest() - 1, maxValue = (double)std::numeric_limits::max() + 1; - // (src != src) checks for NaN, then we check whether the min and max values properly bound the source value // We assume that we are in round-towards-zero mode bool inRange = (src > minValue) && (src < maxValue); @@ -224,7 +223,6 @@ template static void ConvOvfFpHelperI64(int8_t *stack, const COMPlusThrow(kOverflowException); int64_t truncated = (int64_t)src; - // According to spec, for result types smaller than int32, we store them on the stack as int32 LOCAL_VAR(ip[1], int64_t) = truncated; } @@ -243,7 +241,6 @@ template static void ConvOvfFpHelperU64(int8_t *stack, const COMPlusThrow(kOverflowException); uint64_t truncated = (uint64_t)src; - // According to spec, for result types smaller than int32, we store them on the stack as int32 LOCAL_VAR(ip[1], uint64_t) = truncated; } From 97d10e3b170f81313b60e27710b3bb4bb9a8431c Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Mon, 9 Jun 2025 18:05:09 -0700 Subject: [PATCH 8/8] Fix musl build --- src/coreclr/vm/interpexec.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index b45ea7e26dc5c6..4e9d559425c164 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -8,6 +8,11 @@ #include "interpexec.h" #include "callstubgenerator.h" +// HACK: debugreturn.h breaks constexpr which is used by +#if defined(debug_instrumented_return) || defined(_DEBUGRETURN_H_) +#undef return +#endif // debug_instrumented_return + // for numeric_limits #include