summaryrefslogtreecommitdiff
path: root/src/jit/lsra.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/jit/lsra.cpp')
-rw-r--r--src/jit/lsra.cpp218
1 files changed, 129 insertions, 89 deletions
diff --git a/src/jit/lsra.cpp b/src/jit/lsra.cpp
index ac76e29364..e7c1c839d1 100644
--- a/src/jit/lsra.cpp
+++ b/src/jit/lsra.cpp
@@ -39,9 +39,6 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Overview (doLinearScan):
- Walk all blocks, building intervals and RefPositions (buildIntervals)
- - Traverse the RefPositions, marking last uses (setLastUses)
- - Note that this is necessary because the execution order doesn't accurately reflect use order.
- There is a "TODO-Throughput" to eliminate this.
- Allocate registers (allocateRegisters)
- Annotate nodes with register assignments (resolveRegisters)
- Add move nodes as needed to resolve conflicting register
@@ -723,12 +720,25 @@ void LinearScan::associateRefPosWithInterval(RefPosition* rp)
applyCalleeSaveHeuristics(rp);
- // Ensure that we have consistent def/use on SDSU temps.
- // However, in the case of a non-commutative rmw def, we must avoid over-constraining
- // the def, so don't propagate a single-register restriction from the consumer to the producer
+ if (theInterval->isLocalVar)
+ {
+ if (RefTypeIsUse(rp->refType))
+ {
+ RefPosition* const prevRP = theInterval->recentRefPosition;
+ if ((prevRP != nullptr) && (prevRP->bbNum == rp->bbNum))
+ {
+ prevRP->lastUse = false;
+ }
+ }
- if (RefTypeIsUse(rp->refType) && !theInterval->isLocalVar)
+ rp->lastUse = (rp->refType != RefTypeExpUse) && (rp->refType != RefTypeParamDef) &&
+ (rp->refType != RefTypeZeroInit) && !extendLifetimes();
+ }
+ else if (rp->refType == RefTypeUse)
{
+ // Ensure that we have consistent def/use on SDSU temps.
+ // However, in the case of a non-commutative rmw def, we must avoid over-constraining
+ // the def, so don't propagate a single-register restriction from the consumer to the producer
RefPosition* prevRefPosition = theInterval->recentRefPosition;
assert(prevRefPosition != nullptr && theInterval->firstRefPosition == prevRefPosition);
regMaskTP prevAssignment = prevRefPosition->registerAssignment;
@@ -744,6 +754,8 @@ void LinearScan::associateRefPosWithInterval(RefPosition* rp)
{
theInterval->hasConflictingDefUse = true;
}
+
+ rp->lastUse = true;
}
}
@@ -2486,16 +2498,15 @@ RefType refTypeForLocalRefNode(GenTree* node)
// being set by dataflow analysis. It is necessary to do it this way only because the execution
// order wasn't strictly correct.
-void LinearScan::setLastUses(BasicBlock* block)
-{
#ifdef DEBUG
+void LinearScan::checkLastUses(BasicBlock* block)
+{
if (VERBOSE)
{
- JITDUMP("\n\nCALCULATING LAST USES for block %u, liveout=", block->bbNum);
+ JITDUMP("\n\nCHECKING LAST USES for block %u, liveout=", block->bbNum);
dumpConvertedVarSet(compiler, block->bbLiveOut);
JITDUMP("\n==============================\n");
}
-#endif // DEBUG
unsigned keepAliveVarNum = BAD_VAR_NUM;
if (compiler->lvaKeepAliveAndReportThis())
@@ -2513,8 +2524,8 @@ void LinearScan::setLastUses(BasicBlock* block)
VARSET_TP VARSET_INIT(compiler, temp, block->bbLiveOut);
+ bool foundDiff = false;
auto currentRefPosition = refPositions.rbegin();
-
while (currentRefPosition->refType != RefTypeBB)
{
// We should never see ParamDefs or ZeroInits within a basic block.
@@ -2523,42 +2534,46 @@ void LinearScan::setLastUses(BasicBlock* block)
{
unsigned varNum = currentRefPosition->getInterval()->varNum;
unsigned varIndex = currentRefPosition->getInterval()->getVarIndex(compiler);
+
+ LsraLocation loc = currentRefPosition->nodeLocation;
+
// We should always have a tree node for a localVar, except for the "special" RefPositions.
GenTreePtr tree = currentRefPosition->treeNode;
assert(tree != nullptr || currentRefPosition->refType == RefTypeExpUse ||
currentRefPosition->refType == RefTypeDummyDef);
+
if (!VarSetOps::IsMember(compiler, temp, varIndex) && varNum != keepAliveVarNum)
{
- // There was no exposed use, so this is a
- // "last use" (and we mark it thus even if it's a def)
+ // There was no exposed use, so this is a "last use" (and we mark it thus even if it's a def)
- if (tree != nullptr)
+ if (extendLifetimes())
{
- tree->gtFlags |= GTF_VAR_DEATH;
- }
- LsraLocation loc = currentRefPosition->nodeLocation;
-#ifdef DEBUG
- if (getLsraExtendLifeTimes())
- {
- JITDUMP("last use of V%02u @%u (not marked as last use for LSRA due to extendLifetimes stress "
- "option)\n",
- compiler->lvaTrackedToVarNum[varIndex], loc);
+ // NOTE: this is a bit of a hack. When extending lifetimes, the "last use" bit will be clear.
+ // This bit, however, would normally be used during resolveLocalRef to set the value of
+ // GTF_VAR_DEATH on the node for a ref position. If this bit is not set correctly even when
+ // extending lifetimes, the code generator will assert as it expects to have accurate last
+ // use information. To avoid these asserts, set the GTF_VAR_DEATH bit here.
+ if (tree != nullptr)
+ {
+ tree->gtFlags |= GTF_VAR_DEATH;
+ }
}
- else
-#endif // DEBUG
+ else if (!currentRefPosition->lastUse)
{
- JITDUMP("last use of V%02u @%u\n", compiler->lvaTrackedToVarNum[varIndex], loc);
- currentRefPosition->lastUse = true;
+ JITDUMP("missing expected last use of V%02u @%u\n", compiler->lvaTrackedToVarNum[varIndex], loc);
+ foundDiff = true;
}
VarSetOps::AddElemD(compiler, temp, varIndex);
}
- else
+ else if (currentRefPosition->lastUse)
{
- currentRefPosition->lastUse = false;
- if (tree != nullptr)
- {
- tree->gtFlags &= ~GTF_VAR_DEATH;
- }
+ JITDUMP("unexpected last use of V%02u @%u\n", compiler->lvaTrackedToVarNum[varIndex], loc);
+ foundDiff = true;
+ }
+ else if (extendLifetimes() && tree != nullptr)
+ {
+ // NOTE: see the comment above re: the extendLifetimes hack.
+ tree->gtFlags &= ~GTF_VAR_DEATH;
}
if (currentRefPosition->refType == RefTypeDef || currentRefPosition->refType == RefTypeDummyDef)
@@ -2566,15 +2581,14 @@ void LinearScan::setLastUses(BasicBlock* block)
VarSetOps::RemoveElemD(compiler, temp, varIndex);
}
}
+
assert(currentRefPosition != refPositions.rend());
++currentRefPosition;
}
-#ifdef DEBUG
VARSET_TP VARSET_INIT(compiler, temp2, block->bbLiveIn);
VarSetOps::DiffD(compiler, temp2, temp);
VarSetOps::DiffD(compiler, temp, block->bbLiveIn);
- bool foundDiff = false;
{
VARSET_ITER_INIT(compiler, iter, temp, varIndex);
@@ -2603,8 +2617,8 @@ void LinearScan::setLastUses(BasicBlock* block)
}
assert(!foundDiff);
-#endif // DEBUG
}
+#endif // DEBUG
void LinearScan::addRefsForPhysRegMask(regMaskTP mask, LsraLocation currentLoc, RefType refType, bool isLastUse)
{
@@ -2758,6 +2772,8 @@ regMaskTP LinearScan::getKillSetForNode(GenTree* tree)
needFloatTmpForFPCall = true;
}
}
+#endif // _TARGET_X86_
+#if defined(_TARGET_X86_) || defined(_TARGET_ARM_)
if (tree->IsHelperCall())
{
GenTreeCall* call = tree->AsCall();
@@ -2765,7 +2781,7 @@ regMaskTP LinearScan::getKillSetForNode(GenTree* tree)
killMask = compiler->compHelperCallKillSet(helpFunc);
}
else
-#endif // _TARGET_X86_
+#endif // defined(_TARGET_X86_) || defined(_TARGET_ARM_)
{
// if there is no FP used, we can ignore the FP kills
if (compiler->compFloatingPointUsed)
@@ -2782,9 +2798,6 @@ regMaskTP LinearScan::getKillSetForNode(GenTree* tree)
if (compiler->codeGen->gcInfo.gcIsWriteBarrierAsgNode(tree))
{
killMask = RBM_CALLEE_TRASH_NOGC;
-#if !NOGC_WRITE_BARRIERS && (defined(_TARGET_ARM_) || defined(_TARGET_AMD64_))
- killMask |= (RBM_ARG_0 | RBM_ARG_1);
-#endif // !NOGC_WRITE_BARRIERS && (defined(_TARGET_ARM_) || defined(_TARGET_AMD64_))
}
break;
@@ -3030,7 +3043,6 @@ void LinearScan::buildInternalRegisterUsesForNode(GenTree* tree,
{
RefPosition* newest = newRefPosition(defs[i]->getInterval(), currentLoc, RefTypeUse, tree, mask,
0 DEBUG_ARG(minRegCandidateCount));
- newest->lastUse = true;
if (tree->gtLsraInfo.isInternalRegDelayFree)
{
@@ -3549,8 +3561,6 @@ void LinearScan::buildRefPositionsForNode(GenTree* tree,
}
RefPosition* pos = newRefPosition(interval, currentLoc, RefTypeUse, tree, candidates);
pos->isLocalDefUse = true;
- bool isLastUse = ((tree->gtFlags & GTF_VAR_DEATH) != 0);
- pos->lastUse = isLastUse;
pos->setAllocateIfProfitable(tree->IsRegOptional());
DBEXEC(VERBOSE, pos->dump());
return;
@@ -3566,6 +3576,39 @@ void LinearScan::buildRefPositionsForNode(GenTree* tree,
}
#endif // DEBUG
+ const bool isContainedNode = !info.isLocalDefUse && consume == 0 && produce == 0 && tree->canBeContained();
+ if (isContainedNode)
+ {
+ assert(info.internalIntCount == 0);
+ assert(info.internalFloatCount == 0);
+
+ // Contained nodes map to the concatenated lists of their operands.
+ LocationInfoList locationInfoList;
+ for (GenTree* op : tree->Operands())
+ {
+ if (!op->gtLsraInfo.definesAnyRegisters)
+ {
+ assert(ComputeOperandDstCount(op) == 0);
+ continue;
+ }
+
+ LocationInfoList operandList;
+ bool removed = operandToLocationInfoMap.TryRemove(op, &operandList);
+ assert(removed);
+
+ locationInfoList.Append(operandList);
+ }
+
+ if (!locationInfoList.IsEmpty())
+ {
+ bool added = operandToLocationInfoMap.AddOrUpdate(tree, locationInfoList);
+ assert(added);
+ tree->gtLsraInfo.definesAnyRegisters = true;
+ }
+
+ return;
+ }
+
// Handle the case of local variable assignment
Interval* varDefInterval = nullptr;
RefType defRefType = RefTypeDef;
@@ -3851,31 +3894,28 @@ void LinearScan::buildRefPositionsForNode(GenTree* tree,
}
#endif // FEATURE_SIMD
- bool delayRegFree = (hasDelayFreeSrc && useNode->gtLsraInfo.isDelayFree);
if (useNode->gtLsraInfo.isTgtPref)
{
prefSrcInterval = i;
}
- bool regOptionalAtUse = useNode->IsRegOptional();
- bool isLastUse = true;
- if (isCandidateLocalRef(useNode))
+ regMaskTP fixedAssignment = fixedCandidateMask(type, candidates);
+ if (fixedAssignment != RBM_NONE)
{
- isLastUse = ((useNode->gtFlags & GTF_VAR_DEATH) != 0);
+ candidates = fixedAssignment;
}
- else
+
+ const bool regOptionalAtUse = useNode->IsRegOptional();
+ const bool delayRegFree = (hasDelayFreeSrc && useNode->gtLsraInfo.isDelayFree);
+
+ assert(isCandidateLocalRef(useNode) == i->isLocalVar);
+ if (!i->isLocalVar)
{
// For non-localVar uses we record nothing,
// as nothing needs to be written back to the tree.
useNode = nullptr;
}
- regMaskTP fixedAssignment = fixedCandidateMask(type, candidates);
- if (fixedAssignment != RBM_NONE)
- {
- candidates = fixedAssignment;
- }
-
#ifdef DEBUG
// If delayRegFree, then Use will interfere with the destination of
// the consuming node. Therefore, we also need add the kill set of
@@ -3936,11 +3976,6 @@ void LinearScan::buildRefPositionsForNode(GenTree* tree,
pos->delayRegFree = true;
}
- if (isLastUse)
- {
- pos->lastUse = true;
- }
-
if (regOptionalAtUse)
{
pos->setAllocateIfProfitable(1);
@@ -3973,8 +4008,6 @@ void LinearScan::buildRefPositionsForNode(GenTree* tree,
#if defined(_TARGET_AMD64_)
// Multi-reg call node is the only node that could produce multi-reg value
assert(produce <= 1 || (tree->IsMultiRegCall() && produce == MAX_RET_REG_COUNT));
-#elif defined(_TARGET_ARM_)
- assert(!varTypeIsMultiReg(tree->TypeGet()));
#endif // _TARGET_xxx_
// Add kill positions before adding def positions
@@ -4074,27 +4107,6 @@ void LinearScan::buildRefPositionsForNode(GenTree* tree,
buildUpperVectorRestoreRefPositions(tree, defLocation, liveLargeVectors);
#endif // FEATURE_PARTIAL_SIMD_CALLEE_SAVE
- bool isContainedNode = !noAdd && consume == 0 && produce == 0 &&
- (tree->OperIsFieldListHead() || ((tree->TypeGet() != TYP_VOID) && !tree->OperIsStore()));
- if (isContainedNode)
- {
- // Contained nodes map to the concatenated lists of their operands.
- for (GenTree* op : tree->Operands())
- {
- if (!op->gtLsraInfo.definesAnyRegisters)
- {
- assert(ComputeOperandDstCount(op) == 0);
- continue;
- }
-
- LocationInfoList operandList;
- bool removed = operandToLocationInfoMap.TryRemove(op, &operandList);
- assert(removed);
-
- locationInfoList.Append(operandList);
- }
- }
-
if (!locationInfoList.IsEmpty())
{
bool added = operandToLocationInfoMap.AddOrUpdate(tree, locationInfoList);
@@ -4716,15 +4728,27 @@ void LinearScan::buildIntervals()
JITDUMP("\n");
}
- // Identify the last uses of each variable, except in the case of MinOpts, where all vars
- // are kept live everywhere.
-
- if (!compiler->opts.MinOpts())
+ // Clear the "last use" flag on any vars that are live-out from this block.
{
- setLastUses(block);
+ VARSET_ITER_INIT(compiler, iter, block->bbLiveOut, varIndex);
+ while (iter.NextElem(compiler, &varIndex))
+ {
+ unsigned varNum = compiler->lvaTrackedToVarNum[varIndex];
+ LclVarDsc* const varDsc = &compiler->lvaTable[varNum];
+ if (isCandidateVar(varDsc))
+ {
+ RefPosition* const lastRP = getIntervalForLocalVar(varNum)->lastRefPosition;
+ if ((lastRP != nullptr) && (lastRP->bbNum == block->bbNum))
+ {
+ lastRP->lastUse = false;
+ }
+ }
+ }
}
#ifdef DEBUG
+ checkLastUses(block);
+
if (VERBOSE)
{
printf("use: ");
@@ -7669,6 +7693,22 @@ void LinearScan::resolveLocalRef(BasicBlock* block, GenTreePtr treeNode, RefPosi
interval->recentRefPosition = currentRefPosition;
LclVarDsc* varDsc = interval->getLocalVar(compiler);
+ // NOTE: we set the GTF_VAR_DEATH flag here unless we are extending lifetimes, in which case we write
+ // this bit in checkLastUses. This is a bit of a hack, but is necessary because codegen requires
+ // accurate last use info that is not reflected in the lastUse bit on ref positions when we are extending
+ // lifetimes. See also the comments in checkLastUses.
+ if ((treeNode != nullptr) && !extendLifetimes())
+ {
+ if (currentRefPosition->lastUse)
+ {
+ treeNode->gtFlags |= GTF_VAR_DEATH;
+ }
+ else
+ {
+ treeNode->gtFlags &= ~GTF_VAR_DEATH;
+ }
+ }
+
if (currentRefPosition->registerAssignment == RBM_NONE)
{
assert(!currentRefPosition->RequiresRegister());