diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 796b113528143..ffde30cf5bd04 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6192,6 +6192,7 @@ class Compiler GenTree* fgMorphForRegisterFP(GenTree* tree); GenTree* fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac = nullptr); GenTree* fgOptimizeEqualityComparisonWithConst(GenTreeOp* cmp); + GenTree* fgOptimizeRelationalComparisonWithConst(GenTreeOp* cmp); GenTree* fgMorphRetInd(GenTreeUnOp* tree); GenTree* fgMorphModToSubMulDiv(GenTreeOp* tree); GenTree* fgMorphSmpOpOptional(GenTreeOp* tree); diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index d1af90935a468..e3d174cdad6e6 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -3053,6 +3053,7 @@ struct GenTreeIntConCommon : public GenTree inline ssize_t IconValue() const; inline void SetIconValue(ssize_t val); inline INT64 IntegralValue() const; + inline void SetIntegralValue(int64_t value); template inline void SetValueTruncating(T value); @@ -3250,6 +3251,23 @@ inline INT64 GenTreeIntConCommon::IntegralValue() const #endif // TARGET_64BIT } +inline void GenTreeIntConCommon::SetIntegralValue(int64_t value) +{ +#ifdef TARGET_64BIT + SetIconValue(value); +#else + if (OperIs(GT_CNS_LNG)) + { + SetLngValue(value); + } + else + { + assert(FitsIn(value)); + SetIconValue(static_cast(value)); + } +#endif // TARGET_64BIT +} + //------------------------------------------------------------------------ // SetValueTruncating: Set the value, truncating to TYP_INT if necessary. // diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 89baa60c1595d..79f0e20bba643 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -12191,75 +12191,14 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) case GT_GE: case GT_GT: - if (op2->gtOper == GT_CNS_INT) + // op2's value may be changed, so it cannot be a CSE candidate. + if (op2->IsIntegralConst() && !gtIsActiveCSE_Candidate(op2)) { - cns2 = op2; - /* Check for "expr relop 1" */ - if (cns2->IsIntegralConst(1)) - { - /* Check for "expr >= 1" */ - if (oper == GT_GE) - { - /* Change to "expr != 0" for unsigned and "expr > 0" for signed */ - oper = (tree->IsUnsigned()) ? GT_NE : GT_GT; - goto SET_OPER; - } - /* Check for "expr < 1" */ - else if (oper == GT_LT) - { - /* Change to "expr == 0" for unsigned and "expr <= 0" for signed */ - oper = (tree->IsUnsigned()) ? GT_EQ : GT_LE; - goto SET_OPER; - } - } - /* Check for "expr relop -1" */ - else if (!tree->IsUnsigned() && cns2->IsIntegralConst(-1)) - { - /* Check for "expr <= -1" */ - if (oper == GT_LE) - { - /* Change to "expr < 0" */ - oper = GT_LT; - goto SET_OPER; - } - /* Check for "expr > -1" */ - else if (oper == GT_GT) - { - /* Change to "expr >= 0" */ - oper = GT_GE; - - SET_OPER: - // IF we get here we should be changing 'oper' - assert(tree->OperGet() != oper); - - // Keep the old ValueNumber for 'tree' as the new expr - // will still compute the same value as before - tree->SetOper(oper, GenTree::PRESERVE_VN); - cns2->AsIntCon()->gtIconVal = 0; + tree = fgOptimizeRelationalComparisonWithConst(tree->AsOp()); + oper = tree->OperGet(); - // vnStore is null before the ValueNumber phase has run - if (vnStore != nullptr) - { - // Update the ValueNumber for 'cns2', as we just changed it to 0 - fgValueNumberTreeConst(cns2); - } - op2 = tree->AsOp()->gtOp2 = gtFoldExpr(op2); - } - } - else if (tree->IsUnsigned() && op2->IsIntegralConst(0)) - { - if ((oper == GT_GT) || (oper == GT_LE)) - { - // IL doesn't have a cne instruction so compilers use cgt.un instead. The JIT - // recognizes certain patterns that involve GT_NE (e.g (x & 4) != 0) and fails - // if GT_GT is used instead. Transform (x GT_GT.unsigned 0) into (x GT_NE 0) - // and (x GT_LE.unsigned 0) into (x GT_EQ 0). The later case is rare, it sometimes - // occurs as a result of branch inversion. - oper = (oper == GT_LE) ? GT_EQ : GT_NE; - tree->SetOper(oper, GenTree::PRESERVE_VN); - tree->gtFlags &= ~GTF_UNSIGNED; - } - } + assert(op1 == tree->AsOp()->gtGetOp1()); + assert(op2 == tree->AsOp()->gtGetOp2()); } COMPARE: @@ -13844,6 +13783,104 @@ GenTree* Compiler::fgOptimizeEqualityComparisonWithConst(GenTreeOp* cmp) return cmp; } +//------------------------------------------------------------------------ +// fgOptimizeRelationalComparisonWithConst: optimizes a comparison operation. +// +// Recognizes comparisons against various constant operands and morphs +// them, if possible, into comparisons against zero. +// +// Arguments: +// cmp - the GT_LE/GT_LT/GT_GE/GT_GT tree to morph. +// +// Return Value: +// The "cmp" tree, possibly with a modified oper. +// The second operand's constant value may be modified as well. +// +// Assumptions: +// The operands have been swapped so that any constants are on the right. +// The second operand is an integral constant. +// +GenTree* Compiler::fgOptimizeRelationalComparisonWithConst(GenTreeOp* cmp) +{ + assert(cmp->OperIs(GT_LE, GT_LT, GT_GE, GT_GT)); + assert(cmp->gtGetOp2()->IsIntegralConst()); + assert(!gtIsActiveCSE_Candidate(cmp->gtGetOp2())); + + GenTree* op1 = cmp->gtGetOp1(); + GenTreeIntConCommon* op2 = cmp->gtGetOp2()->AsIntConCommon(); + + assert(genActualType(op1) == genActualType(op2)); + + genTreeOps oper = cmp->OperGet(); + int64_t op2Value = op2->IntegralValue(); + + if (op2Value == 1) + { + // Check for "expr >= 1". + if (oper == GT_GE) + { + // Change to "expr != 0" for unsigned and "expr > 0" for signed. + oper = cmp->IsUnsigned() ? GT_NE : GT_GT; + } + // Check for "expr < 1". + else if (oper == GT_LT) + { + // Change to "expr == 0" for unsigned and "expr <= 0". + oper = cmp->IsUnsigned() ? GT_EQ : GT_LE; + } + } + // Check for "expr relop -1". + else if (!cmp->IsUnsigned() && (op2Value == -1)) + { + // Check for "expr <= -1". + if (oper == GT_LE) + { + // Change to "expr < 0". + oper = GT_LT; + } + // Check for "expr > -1". + else if (oper == GT_GT) + { + // Change to "expr >= 0". + oper = GT_GE; + } + } + else if (cmp->IsUnsigned()) + { + if ((oper == GT_LE) || (oper == GT_GT)) + { + if (op2Value == 0) + { + // IL doesn't have a cne instruction so compilers use cgt.un instead. The JIT + // recognizes certain patterns that involve GT_NE (e.g (x & 4) != 0) and fails + // if GT_GT is used instead. Transform (x GT_GT.unsigned 0) into (x GT_NE 0) + // and (x GT_LE.unsigned 0) into (x GT_EQ 0). The later case is rare, it sometimes + // occurs as a result of branch inversion. + oper = (oper == GT_LE) ? GT_EQ : GT_NE; + cmp->gtFlags &= ~GTF_UNSIGNED; + } + // LE_UN/GT_UN(expr, int/long.MaxValue) => GE/LT(expr, 0). + else if (((op1->TypeIs(TYP_LONG) && (op2Value == INT64_MAX))) || + ((genActualType(op1) == TYP_INT) && (op2Value == INT32_MAX))) + { + oper = (oper == GT_LE) ? GT_GE : GT_LT; + cmp->gtFlags &= ~GTF_UNSIGNED; + } + } + } + + if (!cmp->OperIs(oper)) + { + // Keep the old ValueNumber for 'tree' as the new expr + // will still compute the same value as before. + cmp->SetOper(oper, GenTree::PRESERVE_VN); + op2->SetIntegralValue(0); + fgUpdateConstTreeValueNumber(op2); + } + + return cmp; +} + //---------------------------------------------------------------------------------------------- // fgMorphRetInd: Try to get rid of extra IND(ADDR()) pairs in a return tree. //