From 3c28227195dd472307bfcbaa6a5c874ce309e355 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Tue, 9 Jun 2026 12:24:37 -0700 Subject: [PATCH 1/2] JIT: Allow optNarrowTree CAST case to match ULONG and LONG equivalently Fixes #111888 --- src/coreclr/jit/optimizer.cpp | 10 +++++++--- src/tests/JIT/opt/InstructionCombining/Casts.cs | 2 +- src/tests/JIT/opt/InstructionCombining/Cmn.cs | 3 +-- src/tests/JIT/opt/InstructionCombining/Mvn.cs | 12 ++++++++++++ src/tests/JIT/opt/InstructionCombining/Neg.cs | 3 +-- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 6e8b26bcf21584..60e88f5f595dd2 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -3389,14 +3389,18 @@ bool Compiler::optNarrowTree(GenTree* tree, var_types srct, var_types dstt, Valu } #endif - if ((tree->CastToType() != srct) || tree->gtOverflow()) + // CastToType may differ in signedness from srct (e.g. ULONG vs LONG). Both have the + // same actual type and storage width, so the narrowing transformation handled below + // (converting an int<->long cast into an int<->int identity cast) is bit-identical + // for either signedness. + if ((genActualType(tree->CastToType()) != genActualType(srct)) || tree->gtOverflow()) { return false; } - if (varTypeIsInt(op1) && varTypeIsInt(dstt) && tree->TypeIs(TYP_LONG)) + if (varTypeIsInt(op1) && varTypeIsInt(dstt) && (genActualType(tree) == TYP_LONG)) { - // We have a CAST that converts into to long while dstt is int. + // We have a CAST that converts int to long while dstt is int. // so we can just convert the cast to int -> int and someone will clean it up. if (doit) { diff --git a/src/tests/JIT/opt/InstructionCombining/Casts.cs b/src/tests/JIT/opt/InstructionCombining/Casts.cs index 438b4869b3ccf5..b9586fecd6c249 100644 --- a/src/tests/JIT/opt/InstructionCombining/Casts.cs +++ b/src/tests/JIT/opt/InstructionCombining/Casts.cs @@ -484,7 +484,7 @@ static ulong CastUIntUShortULong(uint x) [MethodImpl(MethodImplOptions.NoInlining)] static uint CastUIntULongUInt(uint x) { - //ARM64-FULL-LINE: mov {{w[0-9]+}}, {{w[0-9]+}} + //ARM64-NOT: mov {{w[0-9]+}}, {{w[0-9]+}} return (uint)(ulong)x; } diff --git a/src/tests/JIT/opt/InstructionCombining/Cmn.cs b/src/tests/JIT/opt/InstructionCombining/Cmn.cs index f8e05ed9125a0d..a45e90c30a01c4 100644 --- a/src/tests/JIT/opt/InstructionCombining/Cmn.cs +++ b/src/tests/JIT/opt/InstructionCombining/Cmn.cs @@ -93,8 +93,7 @@ static bool CmnLSLSwap(int a, int b) [MethodImpl(MethodImplOptions.NoInlining)] static bool CmnLSR(uint a, uint b) { - //ARM64-FULL-LINE: lsr {{w[0-9]+}}, {{w[0-9]+}}, #2 - //ARM64-FULL-LINE: cmn {{w[0-9]+}}, {{w[0-9]+}} + //ARM64-FULL-LINE: cmn {{w[0-9]+}}, {{w[0-9]+}}, LSR #2 if (a == (uint)-(b>>2)) { return true; diff --git a/src/tests/JIT/opt/InstructionCombining/Mvn.cs b/src/tests/JIT/opt/InstructionCombining/Mvn.cs index 74957a091e63af..028fb556f7ce40 100644 --- a/src/tests/JIT/opt/InstructionCombining/Mvn.cs +++ b/src/tests/JIT/opt/InstructionCombining/Mvn.cs @@ -45,6 +45,11 @@ public static int CheckMvn() fail = true; } + if (MvnCastChainLSR(0x10) != 0xFFFFFFFD) + { + fail = true; + } + if (fail) { return 101; @@ -93,5 +98,12 @@ static ulong MvnLargeShift64Bit(ulong a) //ARM64-FULL-LINE: mvn {{x[0-9]+}}, {{x[0-9]+}}, LSR #32 return ~(a>>160); } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint MvnCastChainLSR(uint a) + { + //ARM64-FULL-LINE: mvn {{w[0-9]+}}, {{w[0-9]+}}, LSR #3 + return (uint)~(ulong)(a>>3); + } } } diff --git a/src/tests/JIT/opt/InstructionCombining/Neg.cs b/src/tests/JIT/opt/InstructionCombining/Neg.cs index e270c013c2ccf8..58bcbb355e6b3c 100644 --- a/src/tests/JIT/opt/InstructionCombining/Neg.cs +++ b/src/tests/JIT/opt/InstructionCombining/Neg.cs @@ -573,8 +573,7 @@ static int NegLSL(int a) [MethodImpl(MethodImplOptions.NoInlining)] static uint NegLSR(uint a) { - //ARM64-FULL-LINE: lsr {{w[0-9]+}}, {{w[0-9]+}}, #20 - //ARM64-FULL-LINE: neg {{w[0-9]+}}, {{w[0-9]+}} + //ARM64-FULL-LINE: neg {{w[0-9]+}}, {{w[0-9]+}}, LSR #20 return (uint)-(a >> 20); } From 9b602a52f5dab596557b9d955c9e3e50c057f2ad Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 10 Jun 2026 15:20:32 -0700 Subject: [PATCH 2/2] Address review feedback: revert unneeded TypeIs->genActualType and clarify comment --- src/coreclr/jit/optimizer.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 60e88f5f595dd2..e2b079a04c4616 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -3389,16 +3389,25 @@ bool Compiler::optNarrowTree(GenTree* tree, var_types srct, var_types dstt, Valu } #endif - // CastToType may differ in signedness from srct (e.g. ULONG vs LONG). Both have the - // same actual type and storage width, so the narrowing transformation handled below - // (converting an int<->long cast into an int<->int identity cast) is bit-identical - // for either signedness. + // We are called recursively to push a narrowing cast down through `tree`. + // In this GT_CAST case, `tree` is an inner cast whose result feeds the outer + // narrowing operation; `srct` is that result type (the inner cast's TypeGet) + // and `dstt` is the type we are narrowing to. + // + // We recognize the pattern (simplified): + // CAST( CAST(op1) ) e.g. (int)(long)x + // and below rewrite the inner cast to int<->int so the chain folds away. + // + // CastToType carries signedness independently of tree->TypeGet() (e.g. + // CAST_LONG <- ULONG <- uint has CastToType=ULONG while tree->TypeGet()==LONG). + // Use genActualType on both sides of the compare so an unsigned-widening inner + // cast still matches a LONG `srct`. if ((genActualType(tree->CastToType()) != genActualType(srct)) || tree->gtOverflow()) { return false; } - if (varTypeIsInt(op1) && varTypeIsInt(dstt) && (genActualType(tree) == TYP_LONG)) + if (varTypeIsInt(op1) && varTypeIsInt(dstt) && tree->TypeIs(TYP_LONG)) { // We have a CAST that converts int to long while dstt is int. // so we can just convert the cast to int -> int and someone will clean it up.