diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 99852d7f446123..aeecb0553b989b 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -552,9 +552,9 @@ void CodeGenInterface::genUpdateRegLife(const LclVarDsc* varDsc, bool isBorn, bo 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); } } @@ -736,7 +736,7 @@ void Compiler::compChangeLife(VARSET_VALARG_TP newLife) 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) { @@ -777,8 +777,8 @@ void Compiler::compChangeLife(VARSET_VALARG_TP newLife) 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)) @@ -11422,7 +11422,7 @@ void CodeGen::genMultiRegStoreToLocal(GenTreeLclVar* lclNode) { varReg = REG_STK; } - if ((varReg == REG_STK) || fieldVarDsc->lvLiveInOutOfHndlr) + if ((varReg == REG_STK) || fieldVarDsc->IsAlwaysAliveInMemory()) { if (!lclNode->AsLclVar()->IsLastUse(i)) { diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index eb942332554f34..b822f233e66db3 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -229,7 +229,7 @@ void CodeGen::genCodeForBBlist() { newRegByrefSet |= varDsc->lvRegMask(); } - if (!varDsc->lvLiveInOutOfHndlr) + if (!varDsc->IsAlwaysAliveInMemory()) { #ifdef DEBUG if (verbose && VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varIndex)) @@ -240,7 +240,7 @@ void CodeGen::genCodeForBBlist() 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)) @@ -873,9 +873,9 @@ void CodeGen::genSpillVar(GenTree* tree) 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()); @@ -883,7 +883,7 @@ void CodeGen::genSpillVar(GenTree* tree) } // 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. @@ -918,8 +918,9 @@ void CodeGen::genSpillVar(GenTree* tree) } 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 @@ -1055,7 +1056,7 @@ void CodeGen::genUnspillLocal( } #endif // USING_VARIABLE_LIVE_RANGE - if (!varDsc->lvLiveInOutOfHndlr) + if (!varDsc->IsAlwaysAliveInMemory()) { #ifdef DEBUG if (VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex)) @@ -2044,12 +2045,12 @@ void CodeGen::genSpillLocal(unsigned varNum, var_types type, GenTreeLclVar* lclN // 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. diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index f816e6917a6042..1383b57a7dd3a2 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -446,10 +446,19 @@ class LclVarDsc // 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 @@ -547,7 +556,7 @@ class LclVarDsc unsigned char lvFldOrdinal; #ifdef DEBUG - unsigned char lvDisqualifyEHVarReason = 'H'; + unsigned char lvSingleDefDisqualifyReason = 'H'; #endif #if FEATURE_MULTIREG_ARGS @@ -1030,6 +1039,16 @@ class LclVarDsc 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 diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index b2ca56b406ba4b..33c5abaa30df78 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -2559,7 +2559,7 @@ void Compiler::lvaSetVarLiveInOutOfHandler(unsigned varNum) 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)); } @@ -2567,7 +2567,7 @@ void Compiler::lvaSetVarLiveInOutOfHandler(unsigned varNum) } // 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)); } @@ -4110,33 +4110,35 @@ void Compiler::lvaMarkLclRefs(GenTree* tree, BasicBlock* block, Statement* stmt, } } - 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 { @@ -4146,7 +4148,7 @@ void Compiler::lvaMarkLclRefs(GenTree* tree, BasicBlock* block, Statement* stmt, if (!varTypeNeedsPartialCalleeSave(varDsc->lvType)) #endif { - varDsc->lvEhWriteThruCandidate = true; + varDsc->lvSingleDefRegCandidate = true; JITDUMP("Marking EH Var V%02u as a register candidate.\n", lclNum); } } @@ -4521,8 +4523,8 @@ void Compiler::lvaComputeRefCounts(bool isRecompute, bool setSlotNumbers) // that was set by past phases. if (!isRecompute) { - varDsc->lvSingleDef = varDsc->lvIsParam; - varDsc->lvEhWriteThruCandidate = varDsc->lvIsParam; + varDsc->lvSingleDef = varDsc->lvIsParam; + varDsc->lvSingleDefRegCandidate = varDsc->lvIsParam; } } @@ -7405,11 +7407,6 @@ void Compiler::lvaDumpEntry(unsigned lclNum, FrameLayoutState curState, size_t r printf(" HFA(%s) ", varTypeName(varDsc->GetHfaType())); } - if (varDsc->lvLiveInOutOfHndlr) - { - printf(" EH"); - } - if (varDsc->lvDoNotEnregister) { printf(" do-not-enreg["); @@ -7427,7 +7424,7 @@ void Compiler::lvaDumpEntry(unsigned lclNum, FrameLayoutState curState, size_t r } if (lvaEnregEHVars && varDsc->lvLiveInOutOfHndlr) { - printf("%c", varDsc->lvDisqualifyEHVarReason); + printf("%c", varDsc->lvSingleDefDisqualifyReason); } if (varDsc->lvLclFieldExpr) { @@ -7500,6 +7497,15 @@ void Compiler::lvaDumpEntry(unsigned lclNum, FrameLayoutState curState, size_t r { 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"); diff --git a/src/coreclr/jit/lsra.cpp b/src/coreclr/jit/lsra.cpp index f810ca3d3aa35e..4bbb877fda8c33 100644 --- a/src/coreclr/jit/lsra.cpp +++ b/src/coreclr/jit/lsra.cpp @@ -196,9 +196,9 @@ BasicBlock::weight_t LinearScan::getWeight(RefPosition* refPos) 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; } @@ -1794,7 +1794,7 @@ void LinearScan::identifyCandidates() if (varDsc->lvLiveInOutOfHndlr) { - newInt->isWriteThru = varDsc->lvEhWriteThruCandidate; + newInt->isWriteThru = varDsc->lvSingleDefRegCandidate; setIntervalAsSpilled(newInt); } @@ -3273,6 +3273,24 @@ void LinearScan::spillInterval(Interval* interval, RefPosition* fromRefPosition 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 @@ -3955,16 +3973,16 @@ void LinearScan::unassignIntervalBlockStart(RegRecord* regRecord, VarToRegMap in // // 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. @@ -5915,7 +5933,7 @@ void LinearScan::resolveLocalRef(BasicBlock* block, GenTreeLclVar* treeNode, Ref 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 @@ -5926,10 +5944,7 @@ void LinearScan::resolveLocalRef(BasicBlock* block, GenTreeLclVar* treeNode, Ref 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 { @@ -6018,6 +6033,27 @@ void LinearScan::resolveLocalRef(BasicBlock* block, GenTreeLclVar* treeNode, Ref } } } + + 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 @@ -8936,6 +8972,10 @@ void RefPosition::dump(LinearScan* linearScan) { printf(" spillAfter"); } + if (this->singleDefSpill) + { + printf(" singleDefSpill"); + } if (this->writeThru) { printf(" writeThru"); @@ -9678,12 +9718,12 @@ void LinearScan::dumpLsraAllocationEvent( 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 diff --git a/src/coreclr/jit/lsra.h b/src/coreclr/jit/lsra.h index 10ff1f471b4a71..b0c17358716952 100644 --- a/src/coreclr/jit/lsra.h +++ b/src/coreclr/jit/lsra.h @@ -1928,6 +1928,7 @@ class Interval : public Referenceable , isPartiallySpilled(false) #endif , isWriteThru(false) + , isSingleDef(false) #ifdef DEBUG , intervalIndex(0) #endif @@ -2023,6 +2024,9 @@ class Interval : public Referenceable // 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 @@ -2222,6 +2226,10 @@ class RefPosition // 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, @@ -2240,6 +2248,7 @@ class RefPosition 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. @@ -2287,6 +2296,7 @@ class RefPosition , lastUse(false) , reload(false) , spillAfter(false) + , singleDefSpill(false) , writeThru(false) , copyReg(false) , moveReg(false) diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index cb04ddddf51c09..f5f4d781d759ec 100644 --- a/src/coreclr/jit/lsrabuild.cpp +++ b/src/coreclr/jit/lsrabuild.cpp @@ -620,6 +620,12 @@ RefPosition* LinearScan::newRefPosition(Interval* theInterval, associateRefPosWithInterval(newRP); + if (RefTypeIsDef(newRP->refType)) + { + assert(theInterval != nullptr); + theInterval->isSingleDef = theInterval->firstRefPosition == newRP; + } + DBEXEC(VERBOSE, newRP->dump(this)); return newRP; } @@ -2602,20 +2608,20 @@ void LinearScan::buildIntervals() { 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() { @@ -2630,19 +2636,29 @@ 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) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index daca2c487e2f05..526a996e1b40ca 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -17441,14 +17441,18 @@ void Compiler::fgRetypeImplicitByRefArgs() #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 diff --git a/src/coreclr/jit/treelifeupdater.cpp b/src/coreclr/jit/treelifeupdater.cpp index 20a9745362b570..15c32596cc4223 100644 --- a/src/coreclr/jit/treelifeupdater.cpp +++ b/src/coreclr/jit/treelifeupdater.cpp @@ -60,7 +60,7 @@ bool TreeLifeUpdater::UpdateLifeFieldVar(GenTreeLclVar* lclNode, uns { regNumber reg = lclNode->GetRegNumByIdx(multiRegIndex); bool isInReg = fldVarDsc->lvIsInReg() && reg != REG_NA; - isInMemory = !isInReg || fldVarDsc->lvLiveInOutOfHndlr; + isInMemory = !isInReg || fldVarDsc->IsAlwaysAliveInMemory(); if (isInReg) { if (isBorn) @@ -259,7 +259,7 @@ void TreeLifeUpdater::UpdateLifeVar(GenTree* tree) 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)); @@ -283,7 +283,7 @@ void TreeLifeUpdater::UpdateLifeVar(GenTree* 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)) {