diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index c2abb2f5b9e1cb..688787a6761674 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -359,6 +359,22 @@ GenTree* Compiler::fgMorphExpandCast(GenTreeCast* tree) } } } + + // If we have a double->float cast, and the double node is itself a cast, + // look through it and see if we can cast directly to float. This is valid + // only if the cast to double would have been lossless. + // + // This pattern most often appears as CAST(float <- CAST(double <- float)), + // which is reduced to CAST(float <- float) and handled in codegen as an optional mov. + else if ((srcType == TYP_DOUBLE) && (dstType == TYP_FLOAT) && oper->OperIs(GT_CAST) && + !varTypeIsLong(oper->AsCast()->CastOp())) + { + oper->gtType = TYP_FLOAT; + oper->CastToType() = TYP_FLOAT; + + return fgMorphTree(oper); + } + #ifndef TARGET_64BIT // The code generation phase (for x86 & ARM32) does not handle casts // directly from [u]long to anything other than [u]int. Insert an @@ -372,36 +388,6 @@ GenTree* Compiler::fgMorphExpandCast(GenTreeCast* tree) } #endif //! TARGET_64BIT -#if defined(TARGET_ARMARCH) || defined(TARGET_XARCH) - // Because there is no IL instruction conv.r4.un, uint/ulong -> float - // casts are always imported as CAST(float <- CAST(double <- uint/ulong)). - // We can usually eliminate the redundant intermediate cast as an optimization. - // - // AArch and xarch+EVEX have instructions that can cast directly from - // all integers (except for longs on ARM32) to floats. - // On x64, we also have the option of widening uint -> long and - // using the signed conversion instructions, and ulong -> float/double - // is handled directly in codegen, so we can allow all casts. - // - // This logic will also catch CAST(float <- CAST(double <- float)) - // and reduce it to CAST(float <- float), which is handled in codegen as - // an optional mov. - else if ((dstType == TYP_FLOAT) && (srcType == TYP_DOUBLE) && oper->OperIs(GT_CAST) -#ifndef TARGET_64BIT - && !varTypeIsLong(oper->AsCast()->CastOp()) -#endif // !TARGET_64BIT -#ifdef TARGET_X86 - && canUseEvexEncoding() -#endif // TARGET_X86 - ) - { - oper->gtType = TYP_FLOAT; - oper->CastToType() = TYP_FLOAT; - - return fgMorphTree(oper); - } -#endif // TARGET_ARMARCH || TARGET_XARCH - #ifdef TARGET_ARM // converts long/ulong --> float/double casts into helper calls. else if (varTypeIsFloating(dstType) && varTypeIsLong(srcType)) diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_106338/Runtime_106338.cs b/src/tests/JIT/Regression/JitBlue/Runtime_106338/Runtime_106338.cs index 450c002a5d9479..b3d4667c0750d0 100644 --- a/src/tests/JIT/Regression/JitBlue/Runtime_106338/Runtime_106338.cs +++ b/src/tests/JIT/Regression/JitBlue/Runtime_106338/Runtime_106338.cs @@ -8,21 +8,74 @@ // Debug: Outputs 1600094603 // Release: Outputs 1600094604 using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics.X86; using Xunit; public class Runtime_106338 { + [MethodImpl(MethodImplOptions.NoInlining)] + static float CastToFloatDirect(long val) => (float)val; + + [MethodImpl(MethodImplOptions.NoInlining)] + static float CastToFloatDirect(ulong val) => (float)val; + + [MethodImpl(MethodImplOptions.NoInlining)] + static float CastToFloatThroughDouble(long val) => (float)(double)val; + + [MethodImpl(MethodImplOptions.NoInlining)] + static float CastToFloatThroughDouble(ulong val) => (float)(double)val; + [Fact] [SkipOnMono("https://github.com/dotnet/runtime/issues/100368", TestPlatforms.Any)] public static void TestEntryPoint() { - ulong vr10 = 16105307123914158031UL; - float vr11 = 4294967295U | vr10; - uint result = BitConverter.SingleToUInt32Bits(vr11); + ulong vr10 = 0xDF818B7F_FFFFFFFF; + float vr11 = vr10; + float vr12 = (float)(double)vr10; + + // These results will be const folded + uint resultDirect = BitConverter.SingleToUInt32Bits(vr11); + uint resultIntermediate = BitConverter.SingleToUInt32Bits(vr12); // Expected to cast ulong -> float directly - Assert.Equal(1600094603U, result); + Assert.Equal(1600094603U, resultDirect); + + // Expected to preserve ulong -> double intermediate cast + Assert.Equal(1600094604U, resultIntermediate); + + // Check that run-time computed values match + resultDirect = BitConverter.SingleToUInt32Bits(CastToFloatDirect(vr10)); + resultIntermediate = BitConverter.SingleToUInt32Bits(CastToFloatThroughDouble(vr10)); + + Assert.Equal(1600094603U, resultDirect); + Assert.Equal(1600094604U, resultIntermediate); + } + + [Fact] + [SkipOnMono("https://github.com/dotnet/runtime/issues/100368", TestPlatforms.Any)] + public static void TestEntryPointSigned() + { + long vr10 = 0x002FFFFF_DFFFFFFF; + float vr11 = vr10; + float vr12 = (float)(double)vr10; + + // These results will be const folded + uint resultDirect = BitConverter.SingleToUInt32Bits(vr11); + uint resultIntermediate = BitConverter.SingleToUInt32Bits(vr12); + + // Expected to cast long -> float directly + Assert.Equal(1514143743U, resultDirect); + + // Expected to preserve long -> double intermediate cast + Assert.Equal(1514143744U, resultIntermediate); + + // Check that run-time computed values match + resultDirect = BitConverter.SingleToUInt32Bits(CastToFloatDirect(vr10)); + resultIntermediate = BitConverter.SingleToUInt32Bits(CastToFloatThroughDouble(vr10)); + + Assert.Equal(1514143743U, resultDirect); + Assert.Equal(1514143744U, resultIntermediate); } } \ No newline at end of file