Disable LowerCompare optimizations in minopts
authorMike Danes <onemihaid@hotmail.com>
Sat, 9 Dec 2017 10:08:20 +0000 (12:08 +0200)
committerMike Danes <onemihaid@hotmail.com>
Sat, 9 Dec 2017 10:08:20 +0000 (12:08 +0200)
src/jit/lower.cpp
src/jit/lower.h

index 576c5e5..e359004 100644 (file)
@@ -2420,8 +2420,9 @@ GenTree* Lowering::LowerTailCallViaHelper(GenTreeCall* call, GenTree* callTarget
     return result;
 }
 
+#ifndef _TARGET_64BIT_
 //------------------------------------------------------------------------
-// Lowering::LowerCompare: Lowers a compare node.
+// Lowering::DecomposeLongCompare: Decomposes a TYP_LONG compare node.
 //
 // Arguments:
 //    cmp - the compare node
@@ -2430,381 +2431,394 @@ GenTree* Lowering::LowerTailCallViaHelper(GenTreeCall* call, GenTree* callTarget
 //    The next node to lower.
 //
 // Notes:
-//    - Decomposes long comparisons that feed a GT_JTRUE (32 bit specific).
-//    - Decomposes long comparisons that produce a value (X86 specific).
-//    - Ensures that we don't have a mix of int/long operands (XARCH specific).
-//    - Narrow operands to enable memory operand containment (XARCH specific).
-//    - Transform cmp(and(x, y), 0) into test(x, y) (XARCH/Arm64 specific but could
-//      be used for ARM as well if support for GT_TEST_EQ/GT_TEST_NE is added).
-//    - Transform TEST(x, LSH(1, y)) into BT(x, y) (XARCH specific)
-//    - Transform RELOP(OP, 0) into SETCC(OP) or JCC(OP) if OP can set the
-//      condition flags appropriately (XARCH/ARM64 specific but could be extended
-//      to ARM32 as well if ARM32 codegen supports GTF_SET_FLAGS).
-
-GenTree* Lowering::LowerCompare(GenTree* cmp)
+//    This is done during lowering because DecomposeLongs handles only nodes
+//    that produce TYP_LONG values. Compare nodes may consume TYP_LONG values
+//    but produce TYP_INT values.
+//
+GenTree* Lowering::DecomposeLongCompare(GenTree* cmp)
 {
-#ifndef _TARGET_64BIT_
-    if (cmp->gtGetOp1()->TypeGet() == TYP_LONG)
+    assert(cmp->gtGetOp1()->TypeGet() == TYP_LONG);
+
+    GenTree* src1 = cmp->gtGetOp1();
+    GenTree* src2 = cmp->gtGetOp2();
+    assert(src1->OperIs(GT_LONG));
+    assert(src2->OperIs(GT_LONG));
+    GenTree* loSrc1 = src1->gtGetOp1();
+    GenTree* hiSrc1 = src1->gtGetOp2();
+    GenTree* loSrc2 = src2->gtGetOp1();
+    GenTree* hiSrc2 = src2->gtGetOp2();
+    BlockRange().Remove(src1);
+    BlockRange().Remove(src2);
+
+    genTreeOps condition = cmp->OperGet();
+    GenTree*   loCmp;
+    GenTree*   hiCmp;
+
+    if (cmp->OperIs(GT_EQ, GT_NE))
     {
-        GenTree* src1 = cmp->gtGetOp1();
-        GenTree* src2 = cmp->gtGetOp2();
-        assert(src1->OperIs(GT_LONG));
-        assert(src2->OperIs(GT_LONG));
-        GenTree* loSrc1 = src1->gtGetOp1();
-        GenTree* hiSrc1 = src1->gtGetOp2();
-        GenTree* loSrc2 = src2->gtGetOp1();
-        GenTree* hiSrc2 = src2->gtGetOp2();
-        BlockRange().Remove(src1);
-        BlockRange().Remove(src2);
+        //
+        // Transform (x EQ|NE y) into (((x.lo XOR y.lo) OR (x.hi XOR y.hi)) EQ|NE 0). If y is 0 then this can
+        // be reduced to just ((x.lo OR x.hi) EQ|NE 0). The OR is expected to set the condition flags so we
+        // don't need to generate a redundant compare against 0, we only generate a SETCC|JCC instruction.
+        //
+        // XOR is used rather than SUB because it is commutative and thus allows swapping the operands when
+        // the first happens to be a constant. Usually only the second compare operand is a constant but it's
+        // still possible to have a constant on the left side. For example, when src1 is a uint->ulong cast
+        // then hiSrc1 would be 0.
+        //
 
-        genTreeOps condition = cmp->OperGet();
-        GenTree*   loCmp;
-        GenTree*   hiCmp;
+        if (loSrc1->OperIs(GT_CNS_INT))
+        {
+            std::swap(loSrc1, loSrc2);
+        }
 
-        if (cmp->OperIs(GT_EQ, GT_NE))
+        if (loSrc2->IsIntegralConst(0))
         {
-            //
-            // Transform (x EQ|NE y) into (((x.lo XOR y.lo) OR (x.hi XOR y.hi)) EQ|NE 0). If y is 0 then this can
-            // be reduced to just ((x.lo OR x.hi) EQ|NE 0). The OR is expected to set the condition flags so we
-            // don't need to generate a redundant compare against 0, we only generate a SETCC|JCC instruction.
-            //
-            // XOR is used rather than SUB because it is commutative and thus allows swapping the operands when
-            // the first happens to be a constant. Usually only the second compare operand is a constant but it's
-            // still possible to have a constant on the left side. For example, when src1 is a uint->ulong cast
-            // then hiSrc1 would be 0.
-            //
+            BlockRange().Remove(loSrc2);
+            loCmp = loSrc1;
+        }
+        else
+        {
+            loCmp = comp->gtNewOperNode(GT_XOR, TYP_INT, loSrc1, loSrc2);
+            BlockRange().InsertBefore(cmp, loCmp);
+            ContainCheckBinary(loCmp->AsOp());
+        }
 
-            if (loSrc1->OperIs(GT_CNS_INT))
-            {
-                std::swap(loSrc1, loSrc2);
-            }
+        if (hiSrc1->OperIs(GT_CNS_INT))
+        {
+            std::swap(hiSrc1, hiSrc2);
+        }
 
-            if (loSrc2->IsIntegralConst(0))
-            {
-                BlockRange().Remove(loSrc2);
-                loCmp = loSrc1;
-            }
-            else
+        if (hiSrc2->IsIntegralConst(0))
+        {
+            BlockRange().Remove(hiSrc2);
+            hiCmp = hiSrc1;
+        }
+        else
+        {
+            hiCmp = comp->gtNewOperNode(GT_XOR, TYP_INT, hiSrc1, hiSrc2);
+            BlockRange().InsertBefore(cmp, hiCmp);
+            ContainCheckBinary(hiCmp->AsOp());
+        }
+
+        hiCmp = comp->gtNewOperNode(GT_OR, TYP_INT, loCmp, hiCmp);
+        BlockRange().InsertBefore(cmp, hiCmp);
+        ContainCheckBinary(hiCmp->AsOp());
+    }
+    else
+    {
+        assert(cmp->OperIs(GT_LT, GT_LE, GT_GE, GT_GT));
+
+        //
+        // If the compare is signed then (x LT|GE y) can be transformed into ((x SUB y) LT|GE 0).
+        // If the compare is unsigned we can still use SUB but we need to check the Carry flag,
+        // not the actual result. In both cases we can simply check the appropiate condition flags
+        // and ignore the actual result:
+        //     SUB_LO loSrc1, loSrc2
+        //     SUB_HI hiSrc1, hiSrc2
+        //     SETCC|JCC (signed|unsigned LT|GE)
+        // If loSrc2 happens to be 0 then the first SUB can be eliminated and the second one can
+        // be turned into a CMP because the first SUB would have set carry to 0. This effectively
+        // transforms a long compare against 0 into an int compare of the high part against 0.
+        //
+        // (x LE|GT y) can to be transformed into ((x SUB y) LE|GT 0) but checking that a long value
+        // is greater than 0 is not so easy. We need to turn this into a positive/negative check
+        // like the one we get for LT|GE compares, this can be achieved by swapping the compare:
+        //     (x LE|GT y) becomes (y GE|LT x)
+        //
+        // Having to swap operands is problematic when the second operand is a constant. The constant
+        // moves to the first operand where it cannot be contained and thus needs a register. This can
+        // be avoided by changing the constant such that LE|GT becomes LT|GE:
+        //     (x LE|GT 41) becomes (x LT|GE 42)
+        //
+
+        if (cmp->OperIs(GT_LE, GT_GT))
+        {
+            bool mustSwap = true;
+
+            if (loSrc2->OperIs(GT_CNS_INT) && hiSrc2->OperIs(GT_CNS_INT))
             {
-                loCmp = comp->gtNewOperNode(GT_XOR, TYP_INT, loSrc1, loSrc2);
-                BlockRange().InsertBefore(cmp, loCmp);
-                ContainCheckBinary(loCmp->AsOp());
+                uint32_t loValue  = static_cast<uint32_t>(loSrc2->AsIntCon()->IconValue());
+                uint32_t hiValue  = static_cast<uint32_t>(hiSrc2->AsIntCon()->IconValue());
+                uint64_t value    = static_cast<uint64_t>(loValue) | (static_cast<uint64_t>(hiValue) << 32);
+                uint64_t maxValue = cmp->IsUnsigned() ? UINT64_MAX : INT64_MAX;
+
+                if (value != maxValue)
+                {
+                    value++;
+                    loValue = value & UINT32_MAX;
+                    hiValue = (value >> 32) & UINT32_MAX;
+                    loSrc2->AsIntCon()->SetIconValue(loValue);
+                    hiSrc2->AsIntCon()->SetIconValue(hiValue);
+
+                    condition = cmp->OperIs(GT_LE) ? GT_LT : GT_GE;
+                    mustSwap  = false;
+                }
             }
 
-            if (hiSrc1->OperIs(GT_CNS_INT))
+            if (mustSwap)
             {
+                std::swap(loSrc1, loSrc2);
                 std::swap(hiSrc1, hiSrc2);
+                condition = GenTree::SwapRelop(condition);
             }
+        }
+
+        assert((condition == GT_LT) || (condition == GT_GE));
 
-            if (hiSrc2->IsIntegralConst(0))
+        if (loSrc2->IsIntegralConst(0))
+        {
+            BlockRange().Remove(loSrc2);
+
+            // Very conservative dead code removal... but it helps.
+
+            if (loSrc1->OperIs(GT_CNS_INT, GT_LCL_VAR, GT_LCL_FLD))
             {
-                BlockRange().Remove(hiSrc2);
-                hiCmp = hiSrc1;
+                BlockRange().Remove(loSrc1);
+
+                if (loSrc1->OperIs(GT_LCL_VAR, GT_LCL_FLD))
+                {
+                    comp->lvaDecRefCnts(m_block, loSrc1);
+                }
             }
             else
             {
-                hiCmp = comp->gtNewOperNode(GT_XOR, TYP_INT, hiSrc1, hiSrc2);
-                BlockRange().InsertBefore(cmp, hiCmp);
-                ContainCheckBinary(hiCmp->AsOp());
+                loSrc1->SetUnusedValue();
             }
 
-            hiCmp = comp->gtNewOperNode(GT_OR, TYP_INT, loCmp, hiCmp);
+            hiCmp = comp->gtNewOperNode(GT_CMP, TYP_VOID, hiSrc1, hiSrc2);
             BlockRange().InsertBefore(cmp, hiCmp);
-            ContainCheckBinary(hiCmp->AsOp());
+            ContainCheckCompare(hiCmp->AsOp());
         }
         else
         {
-            assert(cmp->OperIs(GT_LT, GT_LE, GT_GE, GT_GT));
+            loCmp = comp->gtNewOperNode(GT_CMP, TYP_VOID, loSrc1, loSrc2);
+            hiCmp = comp->gtNewOperNode(GT_SUB_HI, TYP_INT, hiSrc1, hiSrc2);
+            BlockRange().InsertBefore(cmp, loCmp, hiCmp);
+            ContainCheckCompare(loCmp->AsOp());
+            ContainCheckBinary(hiCmp->AsOp());
 
             //
-            // If the compare is signed then (x LT|GE y) can be transformed into ((x SUB y) LT|GE 0).
-            // If the compare is unsigned we can still use SUB but we need to check the Carry flag,
-            // not the actual result. In both cases we can simply check the appropiate condition flags
-            // and ignore the actual result:
-            //     SUB_LO loSrc1, loSrc2
-            //     SUB_HI hiSrc1, hiSrc2
-            //     SETCC|JCC (signed|unsigned LT|GE)
-            // If loSrc2 happens to be 0 then the first SUB can be eliminated and the second one can
-            // be turned into a CMP because the first SUB would have set carry to 0. This effectively
-            // transforms a long compare against 0 into an int compare of the high part against 0.
-            //
-            // (x LE|GT y) can to be transformed into ((x SUB y) LE|GT 0) but checking that a long value
-            // is greater than 0 is not so easy. We need to turn this into a positive/negative check
-            // like the one we get for LT|GE compares, this can be achieved by swapping the compare:
-            //     (x LE|GT y) becomes (y GE|LT x)
-            //
-            // Having to swap operands is problematic when the second operand is a constant. The constant
-            // moves to the first operand where it cannot be contained and thus needs a register. This can
-            // be avoided by changing the constant such that LE|GT becomes LT|GE:
-            //     (x LE|GT 41) becomes (x LT|GE 42)
+            // Try to move the first SUB_HI operands right in front of it, this allows using
+            // a single temporary register instead of 2 (one for CMP and one for SUB_HI). Do
+            // this only for locals as they won't change condition flags. Note that we could
+            // move constants (except 0 which generates XOR reg, reg) but it's extremly rare
+            // to have a constant as the first operand.
             //
 
-            if (cmp->OperIs(GT_LE, GT_GT))
+            if (hiSrc1->OperIs(GT_LCL_VAR, GT_LCL_FLD))
             {
-                bool mustSwap = true;
-
-                if (loSrc2->OperIs(GT_CNS_INT) && hiSrc2->OperIs(GT_CNS_INT))
-                {
-                    uint32_t loValue  = static_cast<uint32_t>(loSrc2->AsIntCon()->IconValue());
-                    uint32_t hiValue  = static_cast<uint32_t>(hiSrc2->AsIntCon()->IconValue());
-                    uint64_t value    = static_cast<uint64_t>(loValue) | (static_cast<uint64_t>(hiValue) << 32);
-                    uint64_t maxValue = cmp->IsUnsigned() ? UINT64_MAX : INT64_MAX;
-
-                    if (value != maxValue)
-                    {
-                        value++;
-                        loValue = value & UINT32_MAX;
-                        hiValue = (value >> 32) & UINT32_MAX;
-                        loSrc2->AsIntCon()->SetIconValue(loValue);
-                        hiSrc2->AsIntCon()->SetIconValue(hiValue);
-
-                        condition = cmp->OperIs(GT_LE) ? GT_LT : GT_GE;
-                        mustSwap  = false;
-                    }
-                }
-
-                if (mustSwap)
-                {
-                    std::swap(loSrc1, loSrc2);
-                    std::swap(hiSrc1, hiSrc2);
-                    condition = GenTree::SwapRelop(condition);
-                }
+                BlockRange().Remove(hiSrc1);
+                BlockRange().InsertBefore(hiCmp, hiSrc1);
             }
+        }
+    }
 
-            assert((condition == GT_LT) || (condition == GT_GE));
-
-            if (loSrc2->IsIntegralConst(0))
-            {
-                BlockRange().Remove(loSrc2);
-
-                // Very conservative dead code removal... but it helps.
-
-                if (loSrc1->OperIs(GT_CNS_INT, GT_LCL_VAR, GT_LCL_FLD))
-                {
-                    BlockRange().Remove(loSrc1);
-
-                    if (loSrc1->OperIs(GT_LCL_VAR, GT_LCL_FLD))
-                    {
-                        comp->lvaDecRefCnts(m_block, loSrc1);
-                    }
-                }
-                else
-                {
-                    loSrc1->SetUnusedValue();
-                }
+    hiCmp->gtFlags |= GTF_SET_FLAGS;
+    if (hiCmp->IsValue())
+    {
+        hiCmp->SetUnusedValue();
+    }
 
-                hiCmp = comp->gtNewOperNode(GT_CMP, TYP_VOID, hiSrc1, hiSrc2);
-                BlockRange().InsertBefore(cmp, hiCmp);
-                ContainCheckCompare(hiCmp->AsOp());
-            }
-            else
-            {
-                loCmp = comp->gtNewOperNode(GT_CMP, TYP_VOID, loSrc1, loSrc2);
-                hiCmp = comp->gtNewOperNode(GT_SUB_HI, TYP_INT, hiSrc1, hiSrc2);
-                BlockRange().InsertBefore(cmp, loCmp, hiCmp);
-                ContainCheckCompare(loCmp->AsOp());
-                ContainCheckBinary(hiCmp->AsOp());
+    LIR::Use cmpUse;
+    if (BlockRange().TryGetUse(cmp, &cmpUse) && cmpUse.User()->OperIs(GT_JTRUE))
+    {
+        BlockRange().Remove(cmp);
 
-                //
-                // Try to move the first SUB_HI operands right in front of it, this allows using
-                // a single temporary register instead of 2 (one for CMP and one for SUB_HI). Do
-                // this only for locals as they won't change condition flags. Note that we could
-                // move constants (except 0 which generates XOR reg, reg) but it's extremly rare
-                // to have a constant as the first operand.
-                //
+        GenTree* jcc    = cmpUse.User();
+        jcc->gtOp.gtOp1 = nullptr;
+        jcc->ChangeOper(GT_JCC);
+        jcc->gtFlags |= (cmp->gtFlags & GTF_UNSIGNED) | GTF_USE_FLAGS;
+        jcc->AsCC()->gtCondition = condition;
+    }
+    else
+    {
+        cmp->gtOp.gtOp1 = nullptr;
+        cmp->gtOp.gtOp2 = nullptr;
+        cmp->ChangeOper(GT_SETCC);
+        cmp->gtFlags |= GTF_USE_FLAGS;
+        cmp->AsCC()->gtCondition = condition;
+    }
 
-                if (hiSrc1->OperIs(GT_LCL_VAR, GT_LCL_FLD))
-                {
-                    BlockRange().Remove(hiSrc1);
-                    BlockRange().InsertBefore(hiCmp, hiSrc1);
-                }
-            }
-        }
+    return cmp->gtNext;
+}
+#endif // !_TARGET_64BIT_
 
-        hiCmp->gtFlags |= GTF_SET_FLAGS;
-        if (hiCmp->IsValue())
-        {
-            hiCmp->SetUnusedValue();
-        }
+//------------------------------------------------------------------------
+// Lowering::OptimizeConstCompare: Performs various "compare with const" optimizations.
+//
+// Arguments:
+//    cmp - the compare node
+//
+// Return Value:
+//    The original compare node if lowering should proceed as usual or the next node
+//    to lower if the compare node was changed in such a way that lowering is no
+//    longer needed.
+//
+// Notes:
+//    - Narrow operands to enable memory operand containment (XARCH specific).
+//    - Transform cmp(and(x, y), 0) into test(x, y) (XARCH/Arm64 specific but could
+//      be used for ARM as well if support for GT_TEST_EQ/GT_TEST_NE is added).
+//    - Transform TEST(x, LSH(1, y)) into BT(x, y) (XARCH specific)
+//    - Transform RELOP(OP, 0) into SETCC(OP) or JCC(OP) if OP can set the
+//      condition flags appropriately (XARCH/ARM64 specific but could be extended
+//      to ARM32 as well if ARM32 codegen supports GTF_SET_FLAGS).
+//
+GenTree* Lowering::OptimizeConstCompare(GenTree* cmp)
+{
+    assert(cmp->gtGetOp2()->IsIntegralConst());
 
-        LIR::Use cmpUse;
-        if (BlockRange().TryGetUse(cmp, &cmpUse) && cmpUse.User()->OperIs(GT_JTRUE))
-        {
-            BlockRange().Remove(cmp);
+#if defined(_TARGET_XARCH_) || defined(_TARGET_ARM64_)
+    GenTree*       op1      = cmp->gtGetOp1();
+    var_types      op1Type  = op1->TypeGet();
+    GenTreeIntCon* op2      = cmp->gtGetOp2()->AsIntCon();
+    ssize_t        op2Value = op2->IconValue();
 
-            GenTree* jcc    = cmpUse.User();
-            jcc->gtOp.gtOp1 = nullptr;
-            jcc->ChangeOper(GT_JCC);
-            jcc->gtFlags |= (cmp->gtFlags & GTF_UNSIGNED) | GTF_USE_FLAGS;
-            jcc->AsCC()->gtCondition = condition;
-        }
-        else
-        {
-            cmp->gtOp.gtOp1 = nullptr;
-            cmp->gtOp.gtOp2 = nullptr;
-            cmp->ChangeOper(GT_SETCC);
-            cmp->gtFlags |= GTF_USE_FLAGS;
-            cmp->AsCC()->gtCondition = condition;
-        }
+#ifdef _TARGET_XARCH_
+    if (IsContainableMemoryOp(op1) && varTypeIsSmall(op1Type) && genTypeCanRepresentValue(op1Type, op2Value))
+    {
+        //
+        // If op1's type is small then try to narrow op2 so it has the same type as op1.
+        // Small types are usually used by memory loads and if both compare operands have
+        // the same type then the memory load can be contained. In certain situations
+        // (e.g "cmp ubyte, 200") we also get a smaller instruction encoding.
+        //
 
-        return cmp->gtNext;
+        op2->gtType = op1Type;
     }
+    else
 #endif
-
-#if defined(_TARGET_XARCH_) || defined(_TARGET_ARM64_)
-    if (cmp->gtGetOp2()->IsIntegralConst())
+        if (op1->OperIs(GT_CAST) && !op1->gtOverflow())
     {
-        GenTree*       op1      = cmp->gtGetOp1();
-        var_types      op1Type  = op1->TypeGet();
-        GenTreeIntCon* op2      = cmp->gtGetOp2()->AsIntCon();
-        ssize_t        op2Value = op2->IconValue();
+        GenTreeCast* cast       = op1->AsCast();
+        var_types    castToType = cast->CastToType();
+        GenTree*     castOp     = cast->gtGetOp1();
 
-#ifdef _TARGET_XARCH_
-        if (IsContainableMemoryOp(op1) && varTypeIsSmall(op1Type) && genTypeCanRepresentValue(op1Type, op2Value))
+        if (((castToType == TYP_BOOL) || (castToType == TYP_UBYTE)) && FitsIn<UINT8>(op2Value))
         {
             //
-            // If op1's type is small then try to narrow op2 so it has the same type as op1.
-            // Small types are usually used by memory loads and if both compare operands have
-            // the same type then the memory load can be contained. In certain situations
-            // (e.g "cmp ubyte, 200") we also get a smaller instruction encoding.
+            // Since we're going to remove the cast we need to be able to narrow the cast operand
+            // to the cast type. This can be done safely only for certain opers (e.g AND, OR, XOR).
+            // Some opers just can't be narrowed (e.g DIV, MUL) while other could be narrowed but
+            // doing so would produce incorrect results (e.g. RSZ, RSH).
             //
-
-            op2->gtType = op1Type;
-        }
-        else
-#endif
-            if (op1->OperIs(GT_CAST) && !op1->gtOverflow())
-        {
-            GenTreeCast* cast       = op1->AsCast();
-            var_types    castToType = cast->CastToType();
-            GenTree*     castOp     = cast->gtGetOp1();
-
-            if (((castToType == TYP_BOOL) || (castToType == TYP_UBYTE)) && FitsIn<UINT8>(op2Value))
-            {
-                //
-                // Since we're going to remove the cast we need to be able to narrow the cast operand
-                // to the cast type. This can be done safely only for certain opers (e.g AND, OR, XOR).
-                // Some opers just can't be narrowed (e.g DIV, MUL) while other could be narrowed but
-                // doing so would produce incorrect results (e.g. RSZ, RSH).
-                //
-                // The below list of handled opers is conservative but enough to handle the most common
-                // situations. In particular this include CALL, sometimes the JIT unnecessarilly widens
-                // the result of bool returning calls.
-                //
-                bool removeCast =
+            // The below list of handled opers is conservative but enough to handle the most common
+            // situations. In particular this include CALL, sometimes the JIT unnecessarilly widens
+            // the result of bool returning calls.
+            //
+            bool removeCast =
 #ifdef _TARGET_ARM64_
-                    (op2Value == 0) && cmp->OperIs(GT_EQ, GT_NE, GT_GT) &&
+                (op2Value == 0) && cmp->OperIs(GT_EQ, GT_NE, GT_GT) &&
 #endif
-                    (castOp->OperIs(GT_CALL, GT_LCL_VAR) || castOp->OperIsLogical()
+                (castOp->OperIs(GT_CALL, GT_LCL_VAR) || castOp->OperIsLogical()
 #ifdef _TARGET_XARCH_
-                     || IsContainableMemoryOp(castOp)
+                 || IsContainableMemoryOp(castOp)
 #endif
-                         );
+                     );
 
-                if (removeCast)
-                {
-                    assert(!castOp->gtOverflowEx()); // Must not be an overflow checking operation
+            if (removeCast)
+            {
+                assert(!castOp->gtOverflowEx()); // Must not be an overflow checking operation
 
 #ifdef _TARGET_ARM64_
-                    bool cmpEq = cmp->OperIs(GT_EQ);
+                bool cmpEq = cmp->OperIs(GT_EQ);
 
-                    cmp->SetOperRaw(cmpEq ? GT_TEST_EQ : GT_TEST_NE);
-                    op2->SetIconValue(0xff);
-                    op2->gtType = castOp->gtType;
+                cmp->SetOperRaw(cmpEq ? GT_TEST_EQ : GT_TEST_NE);
+                op2->SetIconValue(0xff);
+                op2->gtType = castOp->gtType;
 #else
-                    castOp->gtType = castToType;
-                    op2->gtType    = castToType;
+                castOp->gtType = castToType;
+                op2->gtType    = castToType;
 #endif
-                    // If we have any contained memory ops on castOp, they must now not be contained.
-                    if (castOp->OperIsLogical())
+                // If we have any contained memory ops on castOp, they must now not be contained.
+                if (castOp->OperIsLogical())
+                {
+                    GenTree* op1 = castOp->gtGetOp1();
+                    if ((op1 != nullptr) && !op1->IsCnsIntOrI())
                     {
-                        GenTree* op1 = castOp->gtGetOp1();
-                        if ((op1 != nullptr) && !op1->IsCnsIntOrI())
-                        {
-                            op1->ClearContained();
-                        }
-                        GenTree* op2 = castOp->gtGetOp2();
-                        if ((op2 != nullptr) && !op2->IsCnsIntOrI())
-                        {
-                            op2->ClearContained();
-                        }
+                        op1->ClearContained();
+                    }
+                    GenTree* op2 = castOp->gtGetOp2();
+                    if ((op2 != nullptr) && !op2->IsCnsIntOrI())
+                    {
+                        op2->ClearContained();
                     }
-                    cmp->gtOp.gtOp1 = castOp;
-
-                    BlockRange().Remove(cast);
                 }
+                cmp->gtOp.gtOp1 = castOp;
+
+                BlockRange().Remove(cast);
             }
         }
-        else if (op1->OperIs(GT_AND) && cmp->OperIs(GT_EQ, GT_NE))
+    }
+    else if (op1->OperIs(GT_AND) && cmp->OperIs(GT_EQ, GT_NE))
+    {
+        //
+        // Transform ((x AND y) EQ|NE 0) into (x TEST_EQ|TEST_NE y) when possible.
+        //
+
+        GenTree* andOp1 = op1->gtGetOp1();
+        GenTree* andOp2 = op1->gtGetOp2();
+
+        if (op2Value != 0)
         {
             //
-            // Transform ((x AND y) EQ|NE 0) into (x TEST_EQ|TEST_NE y) when possible.
+            // If we don't have a 0 compare we can get one by transforming ((x AND mask) EQ|NE mask)
+            // into ((x AND mask) NE|EQ 0) when mask is a single bit.
             //
 
-            GenTree* andOp1 = op1->gtGetOp1();
-            GenTree* andOp2 = op1->gtGetOp2();
-
-            if (op2Value != 0)
+            if (isPow2(static_cast<size_t>(op2Value)) && andOp2->IsIntegralConst(op2Value))
             {
-                //
-                // If we don't have a 0 compare we can get one by transforming ((x AND mask) EQ|NE mask)
-                // into ((x AND mask) NE|EQ 0) when mask is a single bit.
-                //
-
-                if (isPow2(static_cast<size_t>(op2Value)) && andOp2->IsIntegralConst(op2Value))
-                {
-                    op2Value = 0;
-                    op2->SetIconValue(0);
-                    cmp->SetOperRaw(GenTree::ReverseRelop(cmp->OperGet()));
-                }
+                op2Value = 0;
+                op2->SetIconValue(0);
+                cmp->SetOperRaw(GenTree::ReverseRelop(cmp->OperGet()));
             }
+        }
 
-            if (op2Value == 0)
-            {
-                BlockRange().Remove(op1);
-                BlockRange().Remove(op2);
+        if (op2Value == 0)
+        {
+            BlockRange().Remove(op1);
+            BlockRange().Remove(op2);
 
-                cmp->SetOperRaw(cmp->OperIs(GT_EQ) ? GT_TEST_EQ : GT_TEST_NE);
-                cmp->gtOp.gtOp1 = andOp1;
-                cmp->gtOp.gtOp2 = andOp2;
-                // We will re-evaluate containment below
-                andOp1->ClearContained();
-                andOp2->ClearContained();
+            cmp->SetOperRaw(cmp->OperIs(GT_EQ) ? GT_TEST_EQ : GT_TEST_NE);
+            cmp->gtOp.gtOp1 = andOp1;
+            cmp->gtOp.gtOp2 = andOp2;
+            // We will re-evaluate containment below
+            andOp1->ClearContained();
+            andOp2->ClearContained();
 
 #ifdef _TARGET_XARCH_
-                if (IsContainableMemoryOp(andOp1) && andOp2->IsIntegralConst())
-                {
-                    //
-                    // For "test" we only care about the bits that are set in the second operand (mask).
-                    // If the mask fits in a small type then we can narrow both operands to generate a "test"
-                    // instruction with a smaller encoding ("test" does not have a r/m32, imm8 form) and avoid
-                    // a widening load in some cases.
-                    //
-                    // For 16 bit operands we narrow only if the memory operand is already 16 bit. This matches
-                    // the behavior of a previous implementation and avoids adding more cases where we generate
-                    // 16 bit instructions that require a length changing prefix (0x66). These suffer from
-                    // significant decoder stalls on Intel CPUs.
-                    //
-                    // We could also do this for 64 bit masks that fit into 32 bit but it doesn't help.
-                    // In such cases morph narrows down the existing GT_AND by inserting a cast between it and
-                    // the memory operand so we'd need to add more code to recognize and eliminate that cast.
-                    //
+            if (IsContainableMemoryOp(andOp1) && andOp2->IsIntegralConst())
+            {
+                //
+                // For "test" we only care about the bits that are set in the second operand (mask).
+                // If the mask fits in a small type then we can narrow both operands to generate a "test"
+                // instruction with a smaller encoding ("test" does not have a r/m32, imm8 form) and avoid
+                // a widening load in some cases.
+                //
+                // For 16 bit operands we narrow only if the memory operand is already 16 bit. This matches
+                // the behavior of a previous implementation and avoids adding more cases where we generate
+                // 16 bit instructions that require a length changing prefix (0x66). These suffer from
+                // significant decoder stalls on Intel CPUs.
+                //
+                // We could also do this for 64 bit masks that fit into 32 bit but it doesn't help.
+                // In such cases morph narrows down the existing GT_AND by inserting a cast between it and
+                // the memory operand so we'd need to add more code to recognize and eliminate that cast.
+                //
 
-                    size_t mask = static_cast<size_t>(andOp2->AsIntCon()->IconValue());
+                size_t mask = static_cast<size_t>(andOp2->AsIntCon()->IconValue());
 
-                    if (FitsIn<UINT8>(mask))
-                    {
-                        andOp1->gtType = TYP_UBYTE;
-                        andOp2->gtType = TYP_UBYTE;
-                    }
-                    else if (FitsIn<UINT16>(mask) && genTypeSize(andOp1) == 2)
-                    {
-                        andOp1->gtType = TYP_CHAR;
-                        andOp2->gtType = TYP_CHAR;
-                    }
+                if (FitsIn<UINT8>(mask))
+                {
+                    andOp1->gtType = TYP_UBYTE;
+                    andOp2->gtType = TYP_UBYTE;
+                }
+                else if (FitsIn<UINT16>(mask) && genTypeSize(andOp1) == 2)
+                {
+                    andOp1->gtType = TYP_CHAR;
+                    andOp2->gtType = TYP_CHAR;
                 }
-#endif
             }
+#endif
         }
     }
 
@@ -2918,6 +2932,38 @@ GenTree* Lowering::LowerCompare(GenTree* cmp)
     }
 #endif // defined(_TARGET_XARCH_) || defined(_TARGET_ARM64_)
 
+    return cmp;
+}
+
+//------------------------------------------------------------------------
+// Lowering::LowerCompare: Lowers a compare node.
+//
+// Arguments:
+//    cmp - the compare node
+//
+// Return Value:
+//    The next node to lower.
+//
+GenTree* Lowering::LowerCompare(GenTree* cmp)
+{
+#ifndef _TARGET_64BIT_
+    if (cmp->gtGetOp1()->TypeGet() == TYP_LONG)
+    {
+        return DecomposeLongCompare(cmp);
+    }
+#endif
+
+    if (cmp->gtGetOp2()->IsIntegralConst() && !comp->opts.MinOpts())
+    {
+        GenTree* next = OptimizeConstCompare(cmp);
+
+        // If OptimizeConstCompare return the compare node as "next" then we need to continue lowering.
+        if (next != cmp)
+        {
+            return next;
+        }
+    }
+
 #ifdef _TARGET_XARCH_
     if (cmp->gtGetOp1()->TypeGet() == cmp->gtGetOp2()->TypeGet())
     {
index 97b0ccd..0c95913 100644 (file)
@@ -137,7 +137,11 @@ private:
     // Call Lowering
     // ------------------------------
     void LowerCall(GenTree* call);
-    GenTree* LowerCompare(GenTree* tree);
+#ifndef _TARGET_64BIT_
+    GenTree* DecomposeLongCompare(GenTree* cmp);
+#endif
+    GenTree* OptimizeConstCompare(GenTree* cmp);
+    GenTree* LowerCompare(GenTree* cmp);
     GenTree* LowerJTrue(GenTreeOp* jtrue);
     void LowerJmpMethod(GenTree* jmp);
     void LowerRet(GenTree* ret);