diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index b5bf7dae4e5c3d..6b49b7bcd3d3a7 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -4131,6 +4131,7 @@ void InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U4_R8); break; case StackTypeI4: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U4_I4); break; case StackTypeI8: EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U4_I8); @@ -4171,9 +4172,10 @@ void InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) EmitConv(m_pStackPointer - 1, StackTypeI8, INTOP_CONV_OVF_U8_R8); break; case StackTypeI4: - EmitConv(m_pStackPointer - 1, StackTypeI8, INTOP_CONV_I8_U4); + EmitConv(m_pStackPointer - 1, StackTypeI8, INTOP_CONV_OVF_U8_I4); break; case StackTypeI8: + EmitConv(m_pStackPointer - 1, StackTypeI8, INTOP_CONV_OVF_U8_I8); break; default: assert(0); @@ -4237,17 +4239,218 @@ void InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) break; case StackTypeI4: #ifdef TARGET_64BIT - // FIXME: Is this the right conv opcode? - EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_I8_I4); + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_U8_I4); #else - EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_MOV_4); + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_U4_I4); #endif break; case StackTypeI8: #ifdef TARGET_64BIT - EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_MOV_8); + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_U8_I8); #else EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_U4_I8); +#endif + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_I1_UN: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + case StackTypeR8: + assert(!"Floating point unsigned conversions"); + break; + case StackTypeI4: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_I1_U4); + break; + case StackTypeI8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_I1_U8); + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_U1_UN: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + case StackTypeR8: + assert(!"Floating point unsigned conversions"); + break; + case StackTypeI4: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U1_U4); + break; + case StackTypeI8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U1_U8); + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_I2_UN: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + case StackTypeR8: + assert(!"Floating point unsigned conversions"); + break; + case StackTypeI4: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_I2_U4); + break; + case StackTypeI8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_I2_U8); + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_U2_UN: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + case StackTypeR8: + assert(!"Floating point unsigned conversions"); + break; + case StackTypeI4: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U2_U4); + break; + case StackTypeI8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U2_U8); + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_I4_UN: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + case StackTypeR8: + assert(!"Floating point unsigned conversions"); + break; + case StackTypeI4: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_I4_U4); + break; + case StackTypeI8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_I4_U8); + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_U4_UN: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + case StackTypeR8: + assert(!"Floating point unsigned conversions"); + break; + case StackTypeI4: + break; + case StackTypeI8: + EmitConv(m_pStackPointer - 1, StackTypeI4, INTOP_CONV_OVF_U4_U8); + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_I8_UN: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + case StackTypeR8: + assert(!"Floating point unsigned conversions"); + break; + case StackTypeI4: + EmitConv(m_pStackPointer - 1, StackTypeI8, INTOP_CONV_I8_U4); + break; + case StackTypeI8: + EmitConv(m_pStackPointer - 1, StackTypeI8, INTOP_CONV_OVF_I8_U8); + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_U8_UN: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + case StackTypeR8: + assert(!"Floating point unsigned conversions"); + break; + case StackTypeI4: + EmitConv(m_pStackPointer - 1, StackTypeI8, INTOP_CONV_U8_U4); + break; + case StackTypeI8: + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_I_UN: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + case StackTypeR8: + assert(!"Floating point unsigned conversions"); + break; + case StackTypeI4: +#ifdef TARGET_64BIT + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_I8_U4); +#else + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_I4_U4); +#endif + break; + case StackTypeI8: +#ifdef TARGET_64BIT + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_I8_U8); +#else + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_I4_U8); +#endif + break; + default: + assert(0); + } + m_ip++; + break; + case CEE_CONV_OVF_U_UN: + CHECK_STACK(1); + switch (m_pStackPointer[-1].type) + { + case StackTypeR4: + case StackTypeR8: + assert(!"Floating point unsigned conversions"); + break; + case StackTypeI4: +#ifdef TARGET_64BIT + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_U8_U4); +#else +#endif + break; + case StackTypeI8: +#ifdef TARGET_64BIT +#else + EmitConv(m_pStackPointer - 1, StackTypeI, INTOP_CONV_OVF_U4_U8); #endif break; default: diff --git a/src/coreclr/interpreter/intops.def b/src/coreclr/interpreter/intops.def index cf74817915eb50..3bb90e14cb5d1e 100644 --- a/src/coreclr/interpreter/intops.def +++ b/src/coreclr/interpreter/intops.def @@ -168,6 +168,7 @@ OPDEF(INTOP_CONV_R8_I4, "conv.r8.i4", 3, 1, 1, InterpOpNoArgs) OPDEF(INTOP_CONV_R8_I8, "conv.r8.i8", 3, 1, 1, InterpOpNoArgs) OPDEF(INTOP_CONV_R8_R4, "conv.r8.r4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_U8_U4, "conv.u8.u4", 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) @@ -191,20 +192,41 @@ 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_U4, "conv.ovf.i4.u4", 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_I4, "conv.ovf.u4.i4", 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_U8, "conv.ovf.i8.u8", 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_I4, "conv.ovf.u8.i4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_U8_I8, "conv.ovf.u8.i8", 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_CONV_OVF_I1_U4, "conv.ovf.i1.u4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_I1_U8, "conv.ovf.i1.u8", 3, 1, 1, InterpOpNoArgs) + +OPDEF(INTOP_CONV_OVF_U1_U4, "conv.ovf.u1.u4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_U1_U8, "conv.ovf.u1.u8", 3, 1, 1, InterpOpNoArgs) + +OPDEF(INTOP_CONV_OVF_I2_U4, "conv.ovf.i2.u4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_I2_U8, "conv.ovf.i2.u8", 3, 1, 1, InterpOpNoArgs) + +OPDEF(INTOP_CONV_OVF_U2_U4, "conv.ovf.u2.u4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_CONV_OVF_U2_U8, "conv.ovf.u2.u8", 3, 1, 1, InterpOpNoArgs) + +OPDEF(INTOP_CONV_OVF_I4_U8, "conv.ovf.i4.u8", 3, 1, 1, InterpOpNoArgs) + +OPDEF(INTOP_CONV_OVF_U4_U8, "conv.ovf.u4.u8", 3, 1, 1, InterpOpNoArgs) + OPDEF(INTOP_UNBOX_ANY, "unbox.any", 5, 1, 1, InterpOpHelperFtn) // [class handle data item] [helper data item] OPDEF(INTOP_UNBOX_ANY_GENERIC, "unbox.any.generic", 6, 1, 2, InterpOpGenericHelperFtn) // [class handle data item] [helper data item] // Unary operations end diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index aea5fac135f511..e95739b504fd29 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -268,18 +268,44 @@ template static void ConvOvfFpHelperU64(int8_t *stack, const 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"); + constexpr bool shrinking = sizeof(TResult) < sizeof(TSource); TSource src = LOCAL_VAR(ip[2], TSource); bool inRange; - if (std::numeric_limits::is_signed) + if (shrinking) { - inRange = (src >= (TSource)std::numeric_limits::lowest()) && (src <= (TSource)std::numeric_limits::max()); + 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()); } else { - inRange = (src <= (TSource)std::numeric_limits::max()); + // Growing conversions with the same signedness shouldn't use an ovf opcode. + static_assert( + shrinking || (std::numeric_limits::is_signed != std::numeric_limits::is_signed), + "ConvOvfHelper only does growing conversions with sign changes" + ); + + if (std::numeric_limits::is_signed) + { + if (sizeof(TSource) == sizeof(TResult)) + { + // unsigned -> signed conv with same size. check to make sure the source value isn't too big. + inRange = src <= (TSource)std::numeric_limits::max(); + } + else + { + // growing unsigned -> signed conversion. this can never fail. + inRange = true; + } + } + else + { + // signed -> unsigned conv. check to make sure the source value isn't negative. + inRange = src >= 0; + } } if (inRange) @@ -289,7 +315,10 @@ template void ConvOvfHelper(int8_t *stack, // 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; + if (sizeof (TResult) < 4) + LOCAL_VAR(ip[1], int32_t) = (int32_t)result; + else + LOCAL_VAR(ip[1], TResult) = result; } else { @@ -645,6 +674,10 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr LOCAL_VAR(ip[1], double) = (double)LOCAL_VAR(ip[2], float); ip += 3; break; + case INTOP_CONV_U8_U4: + LOCAL_VAR(ip[1], uint64_t) = LOCAL_VAR(ip[2], uint32_t); + ip += 3; + break; case INTOP_CONV_U8_R4: ConvFpHelper(stack, ip); ip += 3; @@ -722,6 +755,10 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr ip += 3; break; + case INTOP_CONV_OVF_I4_U4: + ConvOvfHelper(stack, ip); + ip += 3; + break; case INTOP_CONV_OVF_I4_I8: ConvOvfHelper(stack, ip); ip += 3; @@ -735,6 +772,10 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr ip += 3; break; + case INTOP_CONV_OVF_U4_I4: + ConvOvfHelper(stack, ip); + ip += 3; + break; case INTOP_CONV_OVF_U4_I8: ConvOvfHelper(stack, ip); ip += 3; @@ -748,6 +789,10 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr ip += 3; break; + case INTOP_CONV_OVF_I8_U8: + ConvOvfHelper(stack, ip); + ip += 3; + break; case INTOP_CONV_OVF_I8_R4: ConvOvfFpHelperI64(stack, ip); ip += 3; @@ -757,6 +802,14 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr ip += 3; break; + case INTOP_CONV_OVF_U8_I4: + ConvOvfHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_U8_I8: + ConvOvfHelper(stack, ip); + ip += 3; + break; case INTOP_CONV_OVF_U8_R4: ConvOvfFpHelperU64(stack, ip); ip += 3; @@ -766,6 +819,52 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr ip += 3; break; + case INTOP_CONV_OVF_I1_U4: + ConvOvfHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_I1_U8: + ConvOvfHelper(stack, ip); + ip += 3; + break; + + case INTOP_CONV_OVF_U1_U4: + ConvOvfHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_U1_U8: + ConvOvfHelper(stack, ip); + ip += 3; + break; + + case INTOP_CONV_OVF_I2_U4: + ConvOvfHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_I2_U8: + ConvOvfHelper(stack, ip); + ip += 3; + break; + + case INTOP_CONV_OVF_U2_U4: + ConvOvfHelper(stack, ip); + ip += 3; + break; + case INTOP_CONV_OVF_U2_U8: + ConvOvfHelper(stack, ip); + ip += 3; + break; + + case INTOP_CONV_OVF_I4_U8: + ConvOvfHelper(stack, ip); + ip += 3; + break; + + case INTOP_CONV_OVF_U4_U8: + ConvOvfHelper(stack, ip); + ip += 3; + break; + case INTOP_SWITCH: { uint32_t val = LOCAL_VAR(ip[1], uint32_t); diff --git a/src/tests/JIT/interpreter/Interpreter.cs b/src/tests/JIT/interpreter/Interpreter.cs index 6930cbae213e52..6c381e67a37c39 100644 --- a/src/tests/JIT/interpreter/Interpreter.cs +++ b/src/tests/JIT/interpreter/Interpreter.cs @@ -814,9 +814,15 @@ public static void RunInterpreterTests() // Unchecked to ensure that the divide-by-zero here doesn't throw since we're using it to generate a NaN unchecked { + Console.WriteLine("TestConvOvf"); if (!TestConvOvf(1, 2, 3, 4, 1.0 / 0.0, -32, 1234567890)) Environment.FailFast(null); + Console.WriteLine("TestConvOvfUn"); + if (!TestConvOvfUn(1, 2, 3, uint.MaxValue, ulong.MaxValue)) + Environment.FailFast(null); + + Console.WriteLine("TestConvBoundaries"); if (!TestConvBoundaries( 32767.999999999996, 32768.00000000001, 2147483647.9999998, 2147483648.0000005 @@ -1567,6 +1573,43 @@ public static bool TestConvOvf(float r4, double r8, int i4, long i8, double nan, return true; } + public static bool TestConvOvfUn(ushort u2, uint u4, ulong u8, uint hugeUint, ulong hugeUlong) + { + checked + { + byte a = (byte)u2, + b = (byte)u4, + c = (byte)u8; + + if (a != u2) + return false; + if (b != u4) + return false; + if (c != u8) + return false; + + try + { + a = (byte)hugeUint; + return false; + } + catch (OverflowException) + { + } + + try + { + b = (byte)hugeUlong; + 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