diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index ed2f3f55ce905..285b5591f2880 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -1242,6 +1242,16 @@ class IntegralRange assert(lowerBound <= upperBound); } + SymbolicIntegerValue GetLowerBound() + { + return m_lowerBound; + } + + SymbolicIntegerValue GetUpperBound() + { + return m_upperBound; + } + bool Contains(int64_t value) const; bool Contains(IntegralRange other) const @@ -5690,6 +5700,7 @@ class Compiler GenTree* fgOptimizeCast(GenTreeCast* cast); GenTree* fgOptimizeEqualityComparisonWithConst(GenTreeOp* cmp); GenTree* fgOptimizeRelationalComparisonWithConst(GenTreeOp* cmp); + GenTree* fgOptimizeRelationalComparisonWithFullRangeConst(GenTreeOp* cmp); #ifdef FEATURE_HW_INTRINSICS GenTree* fgOptimizeHWIntrinsic(GenTreeHWIntrinsic* node); #endif diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 48b88f100d473..addca6c8b6c8c 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -11406,6 +11406,21 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) assert(op2 == tree->AsOp()->gtGetOp2()); } + if (opts.OptimizationEnabled() && fgGlobalMorph) + { + if (op2->IsIntegralConst() || op1->IsIntegralConst()) + { + if (tree->OperIs(GT_GT, GT_LT, GT_LE, GT_GE)) + { + tree = fgOptimizeRelationalComparisonWithFullRangeConst(tree->AsOp()); + if (tree->OperIs(GT_CNS_INT)) + { + return tree; + } + } + } + } + COMPARE: noway_assert(tree->OperIsCompare()); @@ -12631,6 +12646,123 @@ GenTree* Compiler::fgOptimizeEqualityComparisonWithConst(GenTreeOp* cmp) return cmp; } +//------------------------------------------------------------------------ +// fgOptimizeRelationalComparisonWithFullRangeConst: optimizes a comparison operation. +// +// Recognizes "Always false"/"Always true" comparisons against various full range constant operands and morphs +// them into zero/one. +// +// Arguments: +// cmp - the GT_LT/GT_GT tree to morph. +// +// Return Value: +// 1. The unmodified "cmp" tree. +// 2. A CNS_INT node containing zero. +// 3. A CNS_INT node containing one. +// Assumptions: +// The second operand is an integral constant or the first operand is an integral constant. +// +GenTree* Compiler::fgOptimizeRelationalComparisonWithFullRangeConst(GenTreeOp* cmp) +{ + if (gtTreeHasSideEffects(cmp, GTF_SIDE_EFFECT)) + { + return cmp; + } + + int64_t lhsMin; + int64_t lhsMax; + if (cmp->gtGetOp1()->IsIntegralConst()) + { + lhsMin = cmp->gtGetOp1()->AsIntConCommon()->IntegralValue(); + lhsMax = lhsMin; + } + else + { + IntegralRange lhsRange = IntegralRange::ForNode(cmp->gtGetOp1(), this); + lhsMin = IntegralRange::SymbolicToRealValue(lhsRange.GetLowerBound()); + lhsMax = IntegralRange::SymbolicToRealValue(lhsRange.GetUpperBound()); + } + + int64_t rhsMin; + int64_t rhsMax; + if (cmp->gtGetOp2()->IsIntegralConst()) + { + rhsMin = cmp->gtGetOp2()->AsIntConCommon()->IntegralValue(); + rhsMax = rhsMin; + } + else + { + IntegralRange rhsRange = IntegralRange::ForNode(cmp->gtGetOp2(), this); + rhsMin = IntegralRange::SymbolicToRealValue(rhsRange.GetLowerBound()); + rhsMax = IntegralRange::SymbolicToRealValue(rhsRange.GetUpperBound()); + } + + genTreeOps op = cmp->gtOper; + if ((op != GT_LT) && (op != GT_LE)) + { + op = GenTree::SwapRelop(op); + std::swap(lhsMin, rhsMin); + std::swap(lhsMax, rhsMax); + } + + GenTree* ret = nullptr; + + if (cmp->IsUnsigned()) + { + if ((lhsMin < 0) && (lhsMax >= 0)) + { + // [0, (uint64_t)lhsMax] U [(uint64_t)lhsMin, MaxValue] + lhsMin = 0; + lhsMax = -1; + } + + if ((rhsMin < 0) && (rhsMax >= 0)) + { + // [0, (uint64_t)rhsMax] U [(uint64_t)rhsMin, MaxValue] + rhsMin = 0; + rhsMax = -1; + } + + if (((op == GT_LT) && ((uint64_t)lhsMax < (uint64_t)rhsMin)) || + ((op == GT_LE) && ((uint64_t)lhsMax <= (uint64_t)rhsMin))) + { + ret = gtNewOneConNode(TYP_INT); + } + else if (((op == GT_LT) && ((uint64_t)lhsMin >= (uint64_t)rhsMax)) || + ((op == GT_LE) && ((uint64_t)lhsMin > (uint64_t)rhsMax))) + { + ret = gtNewZeroConNode(TYP_INT); + } + } + else + { + // [x0, x1] < [y0, y1] is false if x0 >= y1 + // [x0, x1] <= [y0, y1] is false if x0 > y1 + if (((op == GT_LT) && (lhsMin >= rhsMax)) || (((op == GT_LE) && (lhsMin > rhsMax)))) + { + ret = gtNewZeroConNode(TYP_INT); + } + // [x0, x1] < [y0, y1] is true if x1 < y0 + else if ((op == GT_LT) && (lhsMax < rhsMin)) + { + ret = gtNewOneConNode(TYP_INT); + } + } + + if (ret != nullptr) + { + fgUpdateConstTreeValueNumber(ret); + + DEBUG_DESTROY_NODE(cmp); + + INDEBUG(ret->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED); + + return ret; + } + + return cmp; +} + //------------------------------------------------------------------------ // fgOptimizeRelationalComparisonWithConst: optimizes a comparison operation. // diff --git a/src/tests/JIT/opt/Compares/compares.cs b/src/tests/JIT/opt/Compares/compares.cs new file mode 100644 index 0000000000000..7d914254ee7ae --- /dev/null +++ b/src/tests/JIT/opt/Compares/compares.cs @@ -0,0 +1,204 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// unit test for the full range comparison optimization + +using System; +using System.Runtime.CompilerServices; + +public class FullRangeComparisonTest +{ + // Class for testing side effects promotion + public class SideEffects + { + public byte B; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrGreaterThan_MinValue_RHSConst_Byte(byte b) => b >= 0; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrGreaterThan_MinValue_RHSConst_Short(short s) => s >= short.MinValue; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrGreaterThan_MinValue_RHSConst_Int(int i) => i >= int.MinValue; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrGreaterThan_MinValue_RHSConst_Long(long l) => l >= long.MinValue; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrLessThan_MaxValue_RHSConst_Byte(byte b) => b <= byte.MaxValue; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrLessThan_MaxValue_RHSConst_Short(short s) => s <= short.MaxValue; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrLessThan_MaxValue_RHSConst_Int(int i) => i <= int.MaxValue; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrLessThan_MaxValue_RHSConst_Long(long l) => l <= long.MaxValue; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrLessThan_MaxValue_RHSConst_UShort(ushort us) => us <= ushort.MaxValue; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrLessThan_MaxValue_RHSConst_UInt(uint ui) => ui <= uint.MaxValue; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrLessThan_MaxValue_RHSConst_ULong(ulong ul) => ul <= uint.MaxValue; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrLessThan_MinValue_LHSConst_Byte(byte b) => 0 <= b; + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrGreaterThan_MinValue_LHSConst_Short(short i) => short.MinValue <= i; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrLessThan_MinValue_LHSConst_Int(int i) => int.MinValue <= i; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrLessThan_MinValue_LHSConst_Long(long l) => long.MinValue <= l; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrGreaterThan_MaxValue_LHSConst_Byte(byte b) => byte.MaxValue >= b; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrGreaterThan_MaxValue_LHSConst_Short(short s) => short.MaxValue >= s; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrGreaterThan_MaxValue_LHSConst_Int(int i) => int.MaxValue >= i; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrGreaterThan_MaxValue_LHSConst_Long(long l) => long.MaxValue >= l; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool EqualsOrGreaterThan_MaxValue_LHSConst_SideEffects(SideEffects c) => c.B <= 255; + public static int Main() + { + // Optimize comparison with full range values + // RHS Const Optimization + if (!EqualsOrGreaterThan_MinValue_RHSConst_Byte(10)) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MinValue_RHSConst_Byte(10) failed"); + return 101; + } + + if (!EqualsOrGreaterThan_MinValue_RHSConst_Short(-10)) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MinValue_RHSConst_Short(10) failed"); + return 101; + } + + if (!EqualsOrGreaterThan_MinValue_RHSConst_Int(-10)) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MinValue_RHSConst_Int(10) failed"); + return 101; + } + + if (!EqualsOrGreaterThan_MinValue_RHSConst_Long(-10)) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MinValue_RHSConst_Long(10) failed"); + return 101; + } + + if (!EqualsOrLessThan_MaxValue_RHSConst_Byte(10)) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MaxValue_RHSConst_Byte(10) failed"); + return 101; + } + + if (!EqualsOrLessThan_MaxValue_RHSConst_Short(10)) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MaxValue_RHSConst_Short(10) failed"); + return 101; + } + + if (!EqualsOrLessThan_MaxValue_RHSConst_Int(10)) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MaxValue_RHSConst_Int(10) failed"); + return 101; + } + + if (!EqualsOrLessThan_MaxValue_RHSConst_Long(10)) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MaxValue_RHSConst_Long(10) failed"); + return 101; + } + + // LHS Const Optimization + if (!EqualsOrLessThan_MinValue_LHSConst_Byte(10)) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MinValue_LHSConst_Byte(10) failed"); + return 101; + } + + if (!EqualsOrLessThan_MinValue_LHSConst_Int(-10)) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MinValue_LHSConst_Int(10) failed"); + return 101; + } + + if (!EqualsOrLessThan_MinValue_LHSConst_Long(-10)) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MinValue_LHSConst_Long(10) failed"); + return 101; + } + + if (!EqualsOrGreaterThan_MaxValue_LHSConst_Byte(10)) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MaxValue_LHSConst_Byte(10) failed"); + return 101; + } + + if (!EqualsOrGreaterThan_MaxValue_LHSConst_Int(10)) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MaxValue_LHSConst_Int(10) failed"); + return 101; + } + + if (!EqualsOrGreaterThan_MaxValue_LHSConst_Long(10)) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MaxValue_LHSConst_Long(10) failed"); + return 101; + } + + + // Unsigned values + if (!EqualsOrLessThan_MaxValue_RHSConst_UShort(10)) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MaxValue_RHSConst_UShort(10) failed"); + return 101; + } + + if (!EqualsOrLessThan_MaxValue_RHSConst_UInt(10)) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MaxValue_RHSConst_UInt(10) failed"); + return 101; + } + + if (!EqualsOrLessThan_MaxValue_RHSConst_ULong(10)) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MaxValue_RHSConst_ULong(10) failed"); + return 101; + } + + // Side effects persist + try + { + EqualsOrGreaterThan_MaxValue_LHSConst_SideEffects(null); + Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MaxValue_LHSConst_SideEffects(null) failed"); + return 101; + } + catch (NullReferenceException ex) + { + + } + catch (Exception ex) + { + Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MaxValue_LHSConst_SideEffects(null) failed"); + return 101; + } + + Console.WriteLine("PASSED"); + return 100; + } +} diff --git a/src/tests/JIT/opt/Compares/compares.csproj b/src/tests/JIT/opt/Compares/compares.csproj new file mode 100644 index 0000000000000..5e5fbae5cb863 --- /dev/null +++ b/src/tests/JIT/opt/Compares/compares.csproj @@ -0,0 +1,12 @@ + + + Exe + + + PdbOnly + True + + + + +