// IsContainableMemoryOp: Checks whether this is a memory op that can be contained.
//
// Arguments:
-// node - the node of interest.
+// node - the node of interest.
+// useTracked - true if this is being called after liveness so lvTracked is correct
+//
+// Return value:
+// True if this will definitely be a memory reference that could be contained.
//
// Notes:
// This differs from the isMemoryOp() method on GenTree because it checks for
-// the case of an untracked local. Note that this won't include locals that
-// for some reason do not become register candidates, nor those that get
+// the case of doNotEnregister local. This won't include locals that
+// for some other reason do not become register candidates, nor those that get
// spilled.
+// Also, if we call this before we redo liveness analysis, any new lclVars
+// introduced after the last dataflow analysis will not yet be marked lvTracked,
+// so we don't use that.
//
-// Return value:
-// True if this will definitely be a memory reference that could be contained.
-//
-bool Lowering::IsContainableMemoryOp(GenTree* node)
+bool Lowering::IsContainableMemoryOp(GenTree* node, bool useTracked)
{
- return node->isMemoryOp() || (node->IsLocal() && !comp->lvaTable[node->AsLclVar()->gtLclNum].lvTracked);
+#ifdef _TARGET_XARCH_
+ if (node->isMemoryOp())
+ {
+ return true;
+ }
+ if (node->IsLocal())
+ {
+ if (!m_lsra->enregisterLocalVars)
+ {
+ return true;
+ }
+ LclVarDsc* varDsc = &comp->lvaTable[node->AsLclVar()->gtLclNum];
+ return (varDsc->lvDoNotEnregister || (useTracked && !varDsc->lvTracked));
+ }
+#endif // _TARGET_XARCH_
+ return false;
}
//------------------------------------------------------------------------
GenTreeIntCon* op2 = cmp->gtGetOp2()->AsIntCon();
ssize_t op2Value = op2->IconValue();
- if (IsContainableMemoryOp(op1) && varTypeIsSmall(op1Type) && genTypeCanRepresentValue(op1Type, op2Value))
+ if (IsContainableMemoryOp(op1, false) && varTypeIsSmall(op1Type) && genTypeCanRepresentValue(op1Type, op2Value))
{
//
// If op1's type is small then try to narrow op2 so it has the same type as op1.
// the result of bool returning calls.
//
- if (castOp->OperIs(GT_CALL, GT_LCL_VAR) || castOp->OperIsLogical() || IsContainableMemoryOp(castOp))
+ if (castOp->OperIs(GT_CALL, GT_LCL_VAR) || castOp->OperIsLogical() ||
+ IsContainableMemoryOp(castOp, false))
{
assert(!castOp->gtOverflowEx()); // Must not be an overflow checking operation
cmp->gtOp.gtOp1 = andOp1;
cmp->gtOp.gtOp2 = andOp2;
- if (IsContainableMemoryOp(andOp1) && andOp2->IsIntegralConst())
+ if (IsContainableMemoryOp(andOp1, false) && andOp2->IsIntegralConst())
{
//
// For "test" we only care about the bits that are set in the second operand (mask).
// This routine uses the following heuristics:
//
- // a) If both are tracked locals, marking the one with lower weighted
+ // a) If both are register candidates, marking the one with lower weighted
// ref count as reg-optional would likely be beneficial as it has
// higher probability of not getting a register.
//
LclVarDsc* v1 = comp->lvaTable + op1->AsLclVarCommon()->GetLclNum();
LclVarDsc* v2 = comp->lvaTable + op2->AsLclVarCommon()->GetLclNum();
- if (v1->lvTracked && v2->lvTracked)
+ bool v1IsRegCandidate = !v1->lvDoNotEnregister && v1->lvTracked;
+ bool v2IsRegCandidate = !v2->lvDoNotEnregister && v2->lvTracked;
+ if (v1IsRegCandidate && v2IsRegCandidate)
{
- // Both are tracked locals. The one with lower weight is less likely
+ // Both are tracked enregisterable locals. The one with lower weight is less likely
// to get a register and hence beneficial to mark the one with lower
// weight as reg optional.
if (v1->lvRefCntWtd < v2->lvRefCntWtd)
preferredOp = op2;
}
}
- else if (v2->lvTracked)
+ else if (v2IsRegCandidate)
{
- // v1 is an untracked lcl and it is use position is less likely to
- // get a register.
+ // v1 is not a reg candidate and its use position is less likely to get a register.
preferredOp = op1;
}
- else if (v1->lvTracked)
+ else if (v1IsRegCandidate)
{
- // v2 is an untracked lcl and its def position always
+ // v2 is not a reg candidate and its def position always
// needs a reg. Hence it is better to mark v1 as
// reg optional.
preferredOp = op1;
info->srcCount = 2;
info->dstCount = 1;
- if (IsContainableMemoryOp(op2) || op2->IsCnsNonZeroFltOrDbl())
+ if (IsContainableMemoryOp(op2, true) || op2->IsCnsNonZeroFltOrDbl())
{
MakeSrcContained(tree, op2);
}
else if (tree->OperIsCommutative() &&
- (op1->IsCnsNonZeroFltOrDbl() || (IsContainableMemoryOp(op1) && IsSafeToContainMem(tree, op1))))
+ (op1->IsCnsNonZeroFltOrDbl() ||
+ (IsContainableMemoryOp(op1, true) && IsSafeToContainMem(tree, op1))))
{
// Though we have GT_ADD(op1=memOp, op2=non-memOp, we try to reorder the operands
// as long as it is safe so that the following efficient code sequence is generated:
{
other = node->gtIndex;
}
- else if (IsContainableMemoryOp(node->gtIndex))
+ else if (IsContainableMemoryOp(node->gtIndex, true))
{
other = node->gtIndex;
}
if (node->gtIndex->TypeGet() == node->gtArrLen->TypeGet())
{
- if (IsContainableMemoryOp(other))
+ if (IsContainableMemoryOp(other, true))
{
MakeSrcContained(tree, other);
}
delayUseSrc = op1;
}
- else if ((op2 != nullptr) &&
- (!tree->OperIsCommutative() || (IsContainableMemoryOp(op2) && (op2->gtLsraInfo.srcCount == 0))))
+ else if ((op2 != nullptr) && (!tree->OperIsCommutative() ||
+ (IsContainableMemoryOp(op2, true) && (op2->gtLsraInfo.srcCount == 0))))
{
delayUseSrc = op2;
}
binOpInRMW = IsBinOpInRMWStoreInd(tree);
if (!binOpInRMW)
{
- if (IsContainableMemoryOp(op2) && tree->TypeGet() == op2->TypeGet())
+ const unsigned operatorSize = genTypeSize(tree->TypeGet());
+ if (IsContainableMemoryOp(op2, true) && (genTypeSize(op2->TypeGet()) == operatorSize))
{
directlyEncodable = true;
operand = op2;
else if (tree->OperIsCommutative())
{
if (IsContainableImmed(tree, op1) ||
- (IsContainableMemoryOp(op1) && tree->TypeGet() == op1->TypeGet() && IsSafeToContainMem(tree, op1)))
+ (IsContainableMemoryOp(op1, true) && (genTypeSize(op1->TypeGet()) == operatorSize) &&
+ IsSafeToContainMem(tree, op1)))
{
// If it is safe, we can reverse the order of operands of commutative operations for efficient
// codegen
// everything is made explicit by adding casts.
assert(op1->TypeGet() == op2->TypeGet());
- if (IsContainableMemoryOp(op2) || op2->IsCnsNonZeroFltOrDbl())
+ if (IsContainableMemoryOp(op2, true) || op2->IsCnsNonZeroFltOrDbl())
{
MakeSrcContained(tree, op2);
}
}
// divisor can be an r/m, but the memory indirection must be of the same size as the divide
- if (IsContainableMemoryOp(op2) && (op2->TypeGet() == tree->TypeGet()))
+ if (IsContainableMemoryOp(op2, true) && (op2->TypeGet() == tree->TypeGet()))
{
MakeSrcContained(tree, op2);
}
switch (tree->gtIntrinsic.gtIntrinsicId)
{
case CORINFO_INTRINSIC_Sqrt:
- if (IsContainableMemoryOp(op1) || op1->IsCnsNonZeroFltOrDbl())
+ if (IsContainableMemoryOp(op1, true) || op1->IsCnsNonZeroFltOrDbl())
{
MakeSrcContained(tree, op1);
}
info->srcCount = 1;
}
- if (IsContainableMemoryOp(op1))
+ if (IsContainableMemoryOp(op1, true))
{
MakeSrcContained(tree, op1);
// U8 -> R8 conversion requires that the operand be in a register.
if (castOpType != TYP_ULONG)
{
- if (IsContainableMemoryOp(castOp) || castOp->IsCnsNonZeroFltOrDbl())
+ if (IsContainableMemoryOp(castOp, true) || castOp->IsCnsNonZeroFltOrDbl())
{
MakeSrcContained(tree, castOp);
}
{
MakeSrcContained(tree, otherOp);
}
- else if (IsContainableMemoryOp(otherOp) && ((otherOp == op2) || IsSafeToContainMem(tree, otherOp)))
+ else if (IsContainableMemoryOp(otherOp, true) && ((otherOp == op2) || IsSafeToContainMem(tree, otherOp)))
{
MakeSrcContained(tree, otherOp);
}
// we can treat the MemoryOp as contained.
if (op1Type == op2Type)
{
- if (IsContainableMemoryOp(op1))
+ if (IsContainableMemoryOp(op1, true))
{
MakeSrcContained(tree, op1);
}
// Note that TEST does not have a r,rm encoding like CMP has but we can still
// contain the second operand because the emitter maps both r,rm and rm,r to
// the same instruction code. This avoids the need to special case TEST here.
- if (IsContainableMemoryOp(op2))
+ if (IsContainableMemoryOp(op2, true))
{
MakeSrcContained(tree, op2);
}
- else if (IsContainableMemoryOp(op1) && IsSafeToContainMem(tree, op1))
+ else if (IsContainableMemoryOp(op1, true) && IsSafeToContainMem(tree, op1))
{
MakeSrcContained(tree, op1);
}
// On Xarch RMW operations require that the non-rmw operand be an immediate or in a register.
// Therefore, if we have previously marked the indirOpSource as a contained memory op while lowering
// the binary node, we need to reset that now.
- if (IsContainableMemoryOp(indirOpSource))
+ if (IsContainableMemoryOp(indirOpSource, true))
{
indirOpSource->ClearContained();
}
{
assert(tree->OperGet() == GT_MUL);
- if (IsContainableMemoryOp(op2) || op2->IsCnsNonZeroFltOrDbl())
+ if (IsContainableMemoryOp(op2, true) || op2->IsCnsNonZeroFltOrDbl())
{
MakeSrcContained(tree, op2);
}
- else if (op1->IsCnsNonZeroFltOrDbl() || (IsContainableMemoryOp(op1) && IsSafeToContainMem(tree, op1)))
+ else if (op1->IsCnsNonZeroFltOrDbl() || (IsContainableMemoryOp(op1, true) && IsSafeToContainMem(tree, op1)))
{
// Since GT_MUL is commutative, we will try to re-order operands if it is safe to
// generate more efficient code sequence for the case of GT_MUL(op1=memOp, op2=non-memOp)
}
MakeSrcContained(tree, imm); // The imm is always contained
- if (IsContainableMemoryOp(other))
+ if (IsContainableMemoryOp(other, true))
{
memOp = other; // memOp may be contained below
}
// This is because during codegen we use 'tree' type to derive EmitTypeSize.
// E.g op1 type = byte, op2 type = byte but GT_MUL tree type is int.
//
- if (memOp == nullptr && IsContainableMemoryOp(op2))
+ if (memOp == nullptr)
{
- memOp = op2;
+ if (IsContainableMemoryOp(op2, true) && (op2->TypeGet() == tree->TypeGet()) && IsSafeToContainMem(tree, op2))
+ {
+ memOp = op2;
+ }
+ else if (IsContainableMemoryOp(op1, true) && (op1->TypeGet() == tree->TypeGet()) &&
+ IsSafeToContainMem(tree, op1))
+ {
+ memOp = op1;
+ }
+ }
+ else
+ {
+ if ((memOp->TypeGet() != tree->TypeGet()) || !IsSafeToContainMem(tree, memOp))
+ {
+ memOp = nullptr;
+ }
}
// To generate an LEA we need to force memOp into a register
//
if (!useLeaEncoding)
{
- if ((memOp != nullptr) && (memOp->TypeGet() == tree->TypeGet()) && IsSafeToContainMem(tree, memOp))
+ if (memOp != nullptr)
{
MakeSrcContained(tree, memOp);
}
GenTree* op1 = simdNode->gtGetOp1();
GenTree* op2 = simdNode->gtGetOp2();
var_types baseType = simdNode->gtSIMDBaseType;
- if (!IsContainableMemoryOp(op1) && op2->IsCnsIntOrI() && varTypeIsSmallInt(baseType))
+ if (!IsContainableMemoryOp(op1, true) && op2->IsCnsIntOrI() && varTypeIsSmallInt(baseType))
{
bool ZeroOrSignExtnReqd = true;
unsigned baseSize = genTypeSize(baseType);