return result;
}
+#ifndef _TARGET_64BIT_
//------------------------------------------------------------------------
-// Lowering::LowerCompare: Lowers a compare node.
+// Lowering::DecomposeLongCompare: Decomposes a TYP_LONG compare node.
//
// Arguments:
// cmp - the compare node
// 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
}
}
}
#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())
{