else
{
// If this is going live, the register must not have a variable in it, except
- // in the case of an exception variable, which may be already treated as live
- // in the register.
- assert(varDsc->lvLiveInOutOfHndlr || ((regSet.GetMaskVars() & regMask) == 0));
+ // in the case of an exception or "spill at single-def" variable, which may be already treated
+ // as live in the register.
+ assert(varDsc->IsAlwaysAliveInMemory() || ((regSet.GetMaskVars() & regMask) == 0));
regSet.AddMaskVars(regMask);
}
}
bool isGCRef = (varDsc->TypeGet() == TYP_REF);
bool isByRef = (varDsc->TypeGet() == TYP_BYREF);
bool isInReg = varDsc->lvIsInReg();
- bool isInMemory = !isInReg || varDsc->lvLiveInOutOfHndlr;
+ bool isInMemory = !isInReg || varDsc->IsAlwaysAliveInMemory();
if (isInReg)
{
if (varDsc->lvIsInReg())
{
// If this variable is going live in a register, it is no longer live on the stack,
- // unless it is an EH var, which always remains live on the stack.
- if (!varDsc->lvLiveInOutOfHndlr)
+ // unless it is an EH/"spill at single-def" var, which always remains live on the stack.
+ if (!varDsc->IsAlwaysAliveInMemory())
{
#ifdef DEBUG
if (VarSetOps::IsMember(this, codeGen->gcInfo.gcVarPtrSetCur, bornVarIndex))
{
varReg = REG_STK;
}
- if ((varReg == REG_STK) || fieldVarDsc->lvLiveInOutOfHndlr)
+ if ((varReg == REG_STK) || fieldVarDsc->IsAlwaysAliveInMemory())
{
if (!lclNode->AsLclVar()->IsLastUse(i))
{
{
newRegByrefSet |= varDsc->lvRegMask();
}
- if (!varDsc->lvLiveInOutOfHndlr)
+ if (!varDsc->IsAlwaysAliveInMemory())
{
#ifdef DEBUG
if (verbose && VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varIndex))
VarSetOps::RemoveElemD(compiler, gcInfo.gcVarPtrSetCur, varIndex);
}
}
- if ((!varDsc->lvIsInReg() || varDsc->lvLiveInOutOfHndlr) && compiler->lvaIsGCTracked(varDsc))
+ if ((!varDsc->lvIsInReg() || varDsc->IsAlwaysAliveInMemory()) && compiler->lvaIsGCTracked(varDsc))
{
#ifdef DEBUG
if (verbose && !VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varIndex))
var_types lclType = varDsc->GetActualRegisterType();
emitAttr size = emitTypeSize(lclType);
- // If this is a write-thru variable, we don't actually spill at a use, but we will kill the var in the reg
- // (below).
- if (!varDsc->lvLiveInOutOfHndlr)
+ // If this is a write-thru or a single-def variable, we don't actually spill at a use,
+ // but we will kill the var in the reg (below).
+ if (!varDsc->IsAlwaysAliveInMemory())
{
instruction storeIns = ins_Store(lclType, compiler->isSIMDTypeLocalAligned(varNum));
assert(varDsc->GetRegNum() == tree->GetRegNum());
}
// We should only have both GTF_SPILL (i.e. the flag causing this method to be called) and
- // GTF_SPILLED on a write-thru def, for which we should not be calling this method.
+ // GTF_SPILLED on a write-thru/single-def def, for which we should not be calling this method.
assert((tree->gtFlags & GTF_SPILLED) == 0);
// Remove the live var from the register.
}
else
{
- // We only have 'GTF_SPILL' and 'GTF_SPILLED' on a def of a write-thru lclVar.
- assert(varDsc->lvLiveInOutOfHndlr && ((tree->gtFlags & GTF_VAR_DEF) != 0));
+ // We only have 'GTF_SPILL' and 'GTF_SPILLED' on a def of a write-thru lclVar
+ // or a single-def var that is to be spilled at its definition.
+ assert((varDsc->IsAlwaysAliveInMemory()) && ((tree->gtFlags & GTF_VAR_DEF) != 0));
}
#ifdef USING_VARIABLE_LIVE_RANGE
}
#endif // USING_VARIABLE_LIVE_RANGE
- if (!varDsc->lvLiveInOutOfHndlr)
+ if (!varDsc->IsAlwaysAliveInMemory())
{
#ifdef DEBUG
if (VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex))
// We have a register candidate local that is marked with GTF_SPILL.
// This flag generally means that we need to spill this local.
- // The exception is the case of a use of an EH var use that is being "spilled"
+ // The exception is the case of a use of an EH/spill-at-single-def var use that is being "spilled"
// to the stack, indicated by GTF_SPILL (note that all EH lclVar defs are always
- // spilled, i.e. write-thru).
- // An EH var use is always valid on the stack (so we don't need to actually spill it),
+ // spilled, i.e. write-thru. Likewise, single-def vars that are spilled at its definitions).
+ // An EH or single-def var use is always valid on the stack (so we don't need to actually spill it),
// but the GTF_SPILL flag records the fact that the register value is going dead.
- if (((lclNode->gtFlags & GTF_VAR_DEF) != 0) || !varDsc->lvLiveInOutOfHndlr)
+ if (((lclNode->gtFlags & GTF_VAR_DEF) != 0) || (!varDsc->IsAlwaysAliveInMemory()))
{
// Store local variable to its home location.
// Ensure that lclVar stores are typed correctly.
// before lvaMarkLocalVars: identifies ref type locals that can get type updates
// after lvaMarkLocalVars: identifies locals that are suitable for optAddCopies
- unsigned char lvEhWriteThruCandidate : 1; // variable has a single def and hence is a register candidate if
- // if it is an EH variable
+ unsigned char lvSingleDefRegCandidate : 1; // variable has a single def and hence is a register candidate
+ // Currently, this is only used to decide if an EH variable can be
+ // a register candiate or not.
- unsigned char lvDisqualifyForEhWriteThru : 1; // tracks variable that are disqualified from register candidancy
+ unsigned char lvDisqualifySingleDefRegCandidate : 1; // tracks variable that are disqualified from register
+ // candidancy
+
+ unsigned char lvSpillAtSingleDef : 1; // variable has a single def (as determined by LSRA interval scan)
+ // and is spilled making it candidate to spill right after the
+ // first (and only) definition.
+ // Note: We cannot reuse lvSingleDefRegCandidate because it is set
+ // in earlier phase and the information might not be appropriate
+ // in LSRA.
#if ASSERTION_PROP
unsigned char lvDisqualify : 1; // variable is no longer OK for add copy optimization
unsigned char lvFldOrdinal;
#ifdef DEBUG
- unsigned char lvDisqualifyEHVarReason = 'H';
+ unsigned char lvSingleDefDisqualifyReason = 'H';
#endif
#if FEATURE_MULTIREG_ARGS
return IsEnregisterableType();
}
+ //-----------------------------------------------------------------------------
+ // IsAlwaysAliveInMemory: Determines if this variable's value is always
+ // up-to-date on stack. This is possible if this is an EH-var or
+ // we decided to spill after single-def.
+ //
+ bool IsAlwaysAliveInMemory() const
+ {
+ return lvLiveInOutOfHndlr || lvSpillAtSingleDef;
+ }
+
bool CanBeReplacedWithItsField(Compiler* comp) const;
#ifdef DEBUG
noway_assert(lvaTable[i].lvIsStructField);
lvaTable[i].lvLiveInOutOfHndlr = 1;
// For now, only enregister an EH Var if it is a single def and whose refCnt > 1.
- if (!lvaEnregEHVars || !lvaTable[i].lvEhWriteThruCandidate || lvaTable[i].lvRefCnt() <= 1)
+ if (!lvaEnregEHVars || !lvaTable[i].lvSingleDefRegCandidate || lvaTable[i].lvRefCnt() <= 1)
{
lvaSetVarDoNotEnregister(i DEBUGARG(DNER_LiveInOutOfHandler));
}
}
// For now, only enregister an EH Var if it is a single def and whose refCnt > 1.
- if (!lvaEnregEHVars || !varDsc->lvEhWriteThruCandidate || varDsc->lvRefCnt() <= 1)
+ if (!lvaEnregEHVars || !varDsc->lvSingleDefRegCandidate || varDsc->lvRefCnt() <= 1)
{
lvaSetVarDoNotEnregister(varNum DEBUGARG(DNER_LiveInOutOfHandler));
}
}
}
- if (!varDsc->lvDisqualifyForEhWriteThru) // If this EH var already disqualified, we can skip this
+ if (!varDsc->lvDisqualifySingleDefRegCandidate) // If this var is already disqualified, we can skip this
{
if (tree->gtFlags & GTF_VAR_DEF) // Is this is a def of our variable
{
- bool bbInALoop = (block->bbFlags & BBF_BACKWARD_JUMP) != 0;
- bool bbIsReturn = block->bbJumpKind == BBJ_RETURN;
+ bool bbInALoop = (block->bbFlags & BBF_BACKWARD_JUMP) != 0;
+ bool bbIsReturn = block->bbJumpKind == BBJ_RETURN;
+ // TODO: Zero-inits in LSRA are created with below condition. Try to use similar condition here as well.
+ // if (compiler->info.compInitMem || varTypeIsGC(varDsc->TypeGet()))
bool needsExplicitZeroInit = fgVarNeedsExplicitZeroInit(lclNum, bbInALoop, bbIsReturn);
- if (varDsc->lvEhWriteThruCandidate || needsExplicitZeroInit)
+ if (varDsc->lvSingleDefRegCandidate || needsExplicitZeroInit)
{
#ifdef DEBUG
if (needsExplicitZeroInit)
{
- varDsc->lvDisqualifyEHVarReason = 'Z';
- JITDUMP("EH Var V%02u needs explicit zero init. Disqualified as a register candidate.\n",
+ varDsc->lvSingleDefDisqualifyReason = 'Z';
+ JITDUMP("V%02u needs explicit zero init. Disqualified as a single-def register candidate.\n",
lclNum);
}
else
{
- varDsc->lvDisqualifyEHVarReason = 'M';
- JITDUMP("EH Var V%02u has multiple definitions. Disqualified as a register candidate.\n",
+ varDsc->lvSingleDefDisqualifyReason = 'M';
+ JITDUMP("V%02u has multiple definitions. Disqualified as a single-def register candidate.\n",
lclNum);
}
#endif // DEBUG
- varDsc->lvEhWriteThruCandidate = false;
- varDsc->lvDisqualifyForEhWriteThru = true;
+ varDsc->lvSingleDefRegCandidate = false;
+ varDsc->lvDisqualifySingleDefRegCandidate = true;
}
else
{
if (!varTypeNeedsPartialCalleeSave(varDsc->lvType))
#endif
{
- varDsc->lvEhWriteThruCandidate = true;
+ varDsc->lvSingleDefRegCandidate = true;
JITDUMP("Marking EH Var V%02u as a register candidate.\n", lclNum);
}
}
// that was set by past phases.
if (!isRecompute)
{
- varDsc->lvSingleDef = varDsc->lvIsParam;
- varDsc->lvEhWriteThruCandidate = varDsc->lvIsParam;
+ varDsc->lvSingleDef = varDsc->lvIsParam;
+ varDsc->lvSingleDefRegCandidate = varDsc->lvIsParam;
}
}
printf(" HFA(%s) ", varTypeName(varDsc->GetHfaType()));
}
- if (varDsc->lvLiveInOutOfHndlr)
- {
- printf(" EH");
- }
-
if (varDsc->lvDoNotEnregister)
{
printf(" do-not-enreg[");
}
if (lvaEnregEHVars && varDsc->lvLiveInOutOfHndlr)
{
- printf("%c", varDsc->lvDisqualifyEHVarReason);
+ printf("%c", varDsc->lvSingleDefDisqualifyReason);
}
if (varDsc->lvLclFieldExpr)
{
{
printf(" EH-live");
}
+ if (varDsc->lvSpillAtSingleDef)
+ {
+ printf(" spill-single-def");
+ }
+ else if (varDsc->lvSingleDefRegCandidate)
+ {
+ printf(" single-def");
+ }
+
#ifndef TARGET_64BIT
if (varDsc->lvStructDoubleAlign)
printf(" double-align");
if (refPos->getInterval()->isSpilled)
{
// Decrease the weight if the interval has already been spilled.
- if (varDsc->lvLiveInOutOfHndlr)
+ if (varDsc->lvLiveInOutOfHndlr || refPos->getInterval()->firstRefPosition->singleDefSpill)
{
- // An EH var is always spilled at defs, and we'll decrease the weight by half,
+ // An EH-var/single-def is always spilled at defs, and we'll decrease the weight by half,
// since only the reload is needed.
weight = weight / 2;
}
if (varDsc->lvLiveInOutOfHndlr)
{
- newInt->isWriteThru = varDsc->lvEhWriteThruCandidate;
+ newInt->isWriteThru = varDsc->lvSingleDefRegCandidate;
setIntervalAsSpilled(newInt);
}
fromRefPosition->spillAfter = true;
}
}
+
+ // Only handle the singledef intervals whose firstRefPosition is RefTypeDef and is not yet marked as spillAfter.
+ // The singledef intervals whose firstRefPositions are already marked as spillAfter, no need to mark them as
+ // singleDefSpill because they will always get spilled at firstRefPosition.
+ // This helps in spilling the singleDef at definition
+ //
+ // Note: Only mark "singleDefSpill" for those intervals who ever get spilled. The intervals that are never spilled
+ // will not be marked as "singleDefSpill" and hence won't get spilled at the first definition.
+ if (interval->isSingleDef && RefTypeIsDef(interval->firstRefPosition->refType) &&
+ !interval->firstRefPosition->spillAfter)
+ {
+ // TODO-CQ: Check if it is beneficial to spill at def, meaning, if it is a hot block don't worry about
+ // doing the spill. Another option is to track number of refpositions and a interval has more than X
+ // refpositions
+ // then perform this optimization.
+ interval->firstRefPosition->singleDefSpill = true;
+ }
+
assert(toRefPosition != nullptr);
#ifdef DEBUG
//
// Arguments:
// currentBlock - the BasicBlock we are about to allocate registers for
-// allocationPass - true if we are currently allocating registers (versus writing them back)
//
// Return Value:
// None
//
// Notes:
-// During the allocation pass, we use the outVarToRegMap of the selected predecessor to
-// determine the lclVar locations for the inVarToRegMap.
-// During the resolution (write-back) pass, we only modify the inVarToRegMap in cases where
-// a lclVar was spilled after the block had been completed.
+// During the allocation pass (allocationPassComplete = false), we use the outVarToRegMap
+// of the selected predecessor to determine the lclVar locations for the inVarToRegMap.
+// During the resolution (write-back when allocationPassComplete = true) pass, we only
+// modify the inVarToRegMap in cases where a lclVar was spilled after the block had been
+// completed.
void LinearScan::processBlockStartLocations(BasicBlock* currentBlock)
{
// If we have no register candidates we should only call this method during allocation.
assert(currentRefPosition->refType == RefTypeExpUse);
}
}
- else if (spillAfter && !RefTypeIsUse(currentRefPosition->refType) &&
+ else if (spillAfter && !RefTypeIsUse(currentRefPosition->refType) && (treeNode != nullptr) &&
(!treeNode->IsMultiReg() || treeNode->gtGetOp1()->IsMultiRegNode()))
{
// In the case of a pure def, don't bother spilling - just assign it to the
assert(interval->isSpilled);
varDsc->SetRegNum(REG_STK);
interval->physReg = REG_NA;
- if (treeNode != nullptr)
- {
- writeLocalReg(treeNode->AsLclVar(), interval->varNum, REG_NA);
- }
+ writeLocalReg(treeNode->AsLclVar(), interval->varNum, REG_NA);
}
else // Not reload and Not pure-def that's spillAfter
{
}
}
}
+
+ if (currentRefPosition->singleDefSpill && (treeNode != nullptr))
+ {
+ // This is the first (and only) def of a single-def var (only defs are marked 'singleDefSpill').
+ // Mark it as GTF_SPILL, so it is spilled immediately to the stack at definition and
+ // GTF_SPILLED, so the variable stays live in the register.
+ //
+ // TODO: This approach would still create the resolution moves but during codegen, will check for
+ // `lvSpillAtSingleDef` to decide whether to generate spill or not. In future, see if there is some
+ // better way to avoid resolution moves, perhaps by updating the varDsc->SetRegNum(REG_STK) in this
+ // method?
+ treeNode->gtFlags |= GTF_SPILL;
+ treeNode->gtFlags |= GTF_SPILLED;
+
+ if (treeNode->IsMultiReg())
+ {
+ treeNode->SetRegSpillFlagByIdx(GTF_SPILLED, currentRefPosition->getMultiRegIdx());
+ }
+
+ varDsc->lvSpillAtSingleDef = true;
+ }
}
// Update the physRegRecord for the register, so that we know what vars are in
{
printf(" spillAfter");
}
+ if (this->singleDefSpill)
+ {
+ printf(" singleDefSpill");
+ }
if (this->writeThru)
{
printf(" writeThru");
case LSRA_EVENT_DONE_KILL_GC_REFS:
dumpRefPositionShort(activeRefPosition, currentBlock);
- printf("Done ");
+ printf("Done ");
break;
case LSRA_EVENT_NO_GC_KILLS:
dumpRefPositionShort(activeRefPosition, currentBlock);
- printf("None ");
+ printf("None ");
break;
// Block boundaries
, isPartiallySpilled(false)
#endif
, isWriteThru(false)
+ , isSingleDef(false)
#ifdef DEBUG
, intervalIndex(0)
#endif
// True if this interval is associated with a lclVar that is written to memory at each definition.
bool isWriteThru : 1;
+ // True if this interval has a single definition.
+ bool isSingleDef : 1;
+
#ifdef DEBUG
unsigned int intervalIndex;
#endif // DEBUG
// Spill and Copy info
// reload indicates that the value was spilled, and must be reloaded here.
// spillAfter indicates that the value is spilled here, so a spill must be added.
+ // singleDefSpill indicates that it is associated with a single-def var and if it
+ // is decided to get spilled, it will be spilled at firstRefPosition def. That
+ // way, the the value of stack will always be up-to-date and no more spills or
+ // resolutions (from reg to stack) will be needed for such single-def var.
// copyReg indicates that the value needs to be copied to a specific register,
// but that it will also retain its current assigned register.
// moveReg indicates that the value needs to be moved to a different register,
unsigned char reload : 1;
unsigned char spillAfter : 1;
+ unsigned char singleDefSpill : 1;
unsigned char writeThru : 1; // true if this var is defined in a register and also spilled. spillAfter must NOT be
// set.
, lastUse(false)
, reload(false)
, spillAfter(false)
+ , singleDefSpill(false)
, writeThru(false)
, copyReg(false)
, moveReg(false)
associateRefPosWithInterval(newRP);
+ if (RefTypeIsDef(newRP->refType))
+ {
+ assert(theInterval != nullptr);
+ theInterval->isSingleDef = theInterval->firstRefPosition == newRP;
+ }
+
DBEXEC(VERBOSE, newRP->dump(this));
return newRP;
}
{
lsraDumpIntervals("BEFORE VALIDATING INTERVALS");
dumpRefPositions("BEFORE VALIDATING INTERVALS");
- validateIntervals();
}
+ validateIntervals();
+
#endif // DEBUG
}
#ifdef DEBUG
//------------------------------------------------------------------------
-// validateIntervals: A DEBUG-only method that checks that the lclVar RefPositions
-// do not reflect uses of undefined values
-//
-// Notes: If an undefined use is encountered, it merely prints a message.
+// validateIntervals: A DEBUG-only method that checks that:
+// - the lclVar RefPositions do not reflect uses of undefined values
+// - A singleDef interval should have just first RefPosition as RefTypeDef.
//
-// TODO-Cleanup: This should probably assert, or at least print the message only
-// when doing a JITDUMP.
+// TODO-Cleanup: If an undefined use is encountered, it merely prints a message
+// but probably assert.
//
void LinearScan::validateIntervals()
{
Interval* interval = getIntervalForLocalVar(i);
bool defined = false;
- printf("-----------------\n");
+ JITDUMP("-----------------\n");
for (RefPosition* ref = interval->firstRefPosition; ref != nullptr; ref = ref->nextRefPosition)
{
- ref->dump(this);
+ if (VERBOSE)
+ {
+ ref->dump(this);
+ }
RefType refType = ref->refType;
if (!defined && RefTypeIsUse(refType))
{
if (compiler->info.compMethodName != nullptr)
{
- printf("%s: ", compiler->info.compMethodName);
+ JITDUMP("%s: ", compiler->info.compMethodName);
}
- printf("LocalVar V%02u: undefined use at %u\n", interval->varNum, ref->nodeLocation);
+ JITDUMP("LocalVar V%02u: undefined use at %u\n", interval->varNum, ref->nodeLocation);
+ }
+
+ // For single-def intervals, the only the first refposition should be a RefTypeDef
+ if (interval->isSingleDef && RefTypeIsDef(refType))
+ {
+ assert(ref == interval->firstRefPosition);
}
+
// Note that there can be multiple last uses if they are on disjoint paths,
// so we can't really check the lastUse flag
if (ref->lastUse)
#endif // DEBUG
// Propagate address-taken-ness and do-not-enregister-ness.
- newVarDsc->lvAddrExposed = varDsc->lvAddrExposed;
- newVarDsc->lvDoNotEnregister = varDsc->lvDoNotEnregister;
+ newVarDsc->lvAddrExposed = varDsc->lvAddrExposed;
+ newVarDsc->lvDoNotEnregister = varDsc->lvDoNotEnregister;
+ newVarDsc->lvLiveInOutOfHndlr = varDsc->lvLiveInOutOfHndlr;
+ newVarDsc->lvSingleDef = varDsc->lvSingleDef;
+ newVarDsc->lvSingleDefRegCandidate = varDsc->lvSingleDefRegCandidate;
+ newVarDsc->lvSpillAtSingleDef = varDsc->lvSpillAtSingleDef;
#ifdef DEBUG
- newVarDsc->lvLclBlockOpAddr = varDsc->lvLclBlockOpAddr;
- newVarDsc->lvLclFieldExpr = varDsc->lvLclFieldExpr;
- newVarDsc->lvVMNeedsStackAddr = varDsc->lvVMNeedsStackAddr;
- newVarDsc->lvLiveInOutOfHndlr = varDsc->lvLiveInOutOfHndlr;
- newVarDsc->lvLiveAcrossUCall = varDsc->lvLiveAcrossUCall;
+ newVarDsc->lvLclBlockOpAddr = varDsc->lvLclBlockOpAddr;
+ newVarDsc->lvLclFieldExpr = varDsc->lvLclFieldExpr;
+ newVarDsc->lvVMNeedsStackAddr = varDsc->lvVMNeedsStackAddr;
+ newVarDsc->lvSingleDefDisqualifyReason = varDsc->lvSingleDefDisqualifyReason;
+ newVarDsc->lvLiveAcrossUCall = varDsc->lvLiveAcrossUCall;
#endif // DEBUG
// If the promotion is dependent, the promoted temp would just be committed
{
regNumber reg = lclNode->GetRegNumByIdx(multiRegIndex);
bool isInReg = fldVarDsc->lvIsInReg() && reg != REG_NA;
- isInMemory = !isInReg || fldVarDsc->lvLiveInOutOfHndlr;
+ isInMemory = !isInReg || fldVarDsc->IsAlwaysAliveInMemory();
if (isInReg)
{
if (isBorn)
compiler->codeGen->genUpdateVarReg(varDsc, tree);
}
bool isInReg = varDsc->lvIsInReg() && tree->GetRegNum() != REG_NA;
- bool isInMemory = !isInReg || varDsc->lvLiveInOutOfHndlr;
+ bool isInMemory = !isInReg || varDsc->IsAlwaysAliveInMemory();
if (isInReg)
{
compiler->codeGen->genUpdateRegLife(varDsc, isBorn, isDying DEBUGARG(tree));
unsigned fldVarIndex = fldVarDsc->lvVarIndex;
regNumber reg = lclVarTree->AsLclVar()->GetRegNumByIdx(i);
bool isInReg = fldVarDsc->lvIsInReg() && reg != REG_NA;
- bool isInMemory = !isInReg || fldVarDsc->lvLiveInOutOfHndlr;
+ bool isInMemory = !isInReg || fldVarDsc->IsAlwaysAliveInMemory();
bool isFieldDying = lclVarTree->AsLclVar()->IsLastUse(i);
if ((isBorn && !isFieldDying) || (!isBorn && isFieldDying))
{