Skip to content

Commit 3be5238

Browse files
authored
Support Write-Thru of EH variables in LSRA (dotnet#543)
* Support Write-Thru of EH variables in LSRA Mark EH variables (those that are live in or out of exception regions) only as lvLiveInOutOfHndlr, not necessarily lvDoNotEnregister During register allocation, mark these as write-thru, and mark all defs as write-thru, ensuring that the stack value is always valid. Mark those defs with GTF_SPILLED (this the "reload" flag and is not currently used for pure defs) to indicate that it should be kept in the register. Mark blocks that enter EH regions as having no predecessor, and set the location of all live-in vars to be on the stack. Change genFnPrologCalleeRegArgs to store EH vars also to the stack if they have a register assignment. Tuned throughput to compensate for extra processing by rearranging some fields and short-circuiting the physical register RefPositions during allocation. It is disabled by default
1 parent 40a5cff commit 3be5238

File tree

12 files changed

+1010
-462
lines changed

12 files changed

+1010
-462
lines changed

src/coreclr/src/jit/codegencommon.cpp

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,10 @@ void CodeGenInterface::genUpdateRegLife(const LclVarDsc* varDsc, bool isBorn, bo
504504
}
505505
else
506506
{
507-
assert((regSet.GetMaskVars() & regMask) == 0);
507+
// If this is going live, the register must not have a variable in it, except
508+
// in the case of an exception variable, which may be already treated as live
509+
// in the register.
510+
assert(varDsc->lvLiveInOutOfHndlr || ((regSet.GetMaskVars() & regMask) == 0));
508511
regSet.AddMaskVars(regMask);
509512
}
510513
}
@@ -681,12 +684,14 @@ void Compiler::compChangeLife(VARSET_VALARG_TP newLife)
681684
unsigned deadVarIndex = 0;
682685
while (deadIter.NextElem(&deadVarIndex))
683686
{
684-
unsigned varNum = lvaTrackedIndexToLclNum(deadVarIndex);
685-
LclVarDsc* varDsc = lvaGetDesc(varNum);
686-
bool isGCRef = (varDsc->TypeGet() == TYP_REF);
687-
bool isByRef = (varDsc->TypeGet() == TYP_BYREF);
687+
unsigned varNum = lvaTrackedIndexToLclNum(deadVarIndex);
688+
LclVarDsc* varDsc = lvaGetDesc(varNum);
689+
bool isGCRef = (varDsc->TypeGet() == TYP_REF);
690+
bool isByRef = (varDsc->TypeGet() == TYP_BYREF);
691+
bool isInReg = varDsc->lvIsInReg();
692+
bool isInMemory = !isInReg || varDsc->lvLiveInOutOfHndlr;
688693

689-
if (varDsc->lvIsInReg())
694+
if (isInReg)
690695
{
691696
// TODO-Cleanup: Move the code from compUpdateLifeVar to genUpdateRegLife that updates the
692697
// gc sets
@@ -701,8 +706,8 @@ void Compiler::compChangeLife(VARSET_VALARG_TP newLife)
701706
}
702707
codeGen->genUpdateRegLife(varDsc, false /*isBorn*/, true /*isDying*/ DEBUGARG(nullptr));
703708
}
704-
// This isn't in a register, so update the gcVarPtrSetCur.
705-
else if (isGCRef || isByRef)
709+
// Update the gcVarPtrSetCur if it is in memory.
710+
if (isInMemory && (isGCRef || isByRef))
706711
{
707712
VarSetOps::RemoveElemD(this, codeGen->gcInfo.gcVarPtrSetCur, deadVarIndex);
708713
JITDUMP("\t\t\t\t\t\t\tV%02u becoming dead\n", varNum);
@@ -724,13 +729,18 @@ void Compiler::compChangeLife(VARSET_VALARG_TP newLife)
724729

725730
if (varDsc->lvIsInReg())
726731
{
727-
#ifdef DEBUG
728-
if (VarSetOps::IsMember(this, codeGen->gcInfo.gcVarPtrSetCur, bornVarIndex))
732+
// If this variable is going live in a register, it is no longer live on the stack,
733+
// unless it is an EH var, which always remains live on the stack.
734+
if (!varDsc->lvLiveInOutOfHndlr)
729735
{
730-
JITDUMP("\t\t\t\t\t\t\tRemoving V%02u from gcVarPtrSetCur\n", varNum);
731-
}
736+
#ifdef DEBUG
737+
if (VarSetOps::IsMember(this, codeGen->gcInfo.gcVarPtrSetCur, bornVarIndex))
738+
{
739+
JITDUMP("\t\t\t\t\t\t\tRemoving V%02u from gcVarPtrSetCur\n", varNum);
740+
}
732741
#endif // DEBUG
733-
VarSetOps::RemoveElemD(this, codeGen->gcInfo.gcVarPtrSetCur, bornVarIndex);
742+
VarSetOps::RemoveElemD(this, codeGen->gcInfo.gcVarPtrSetCur, bornVarIndex);
743+
}
734744
codeGen->genUpdateRegLife(varDsc, true /*isBorn*/, false /*isDying*/ DEBUGARG(nullptr));
735745
regMaskTP regMask = varDsc->lvRegMask();
736746
if (isGCRef)
@@ -742,9 +752,9 @@ void Compiler::compChangeLife(VARSET_VALARG_TP newLife)
742752
codeGen->gcInfo.gcRegByrefSetCur |= regMask;
743753
}
744754
}
745-
// This isn't in a register, so update the gcVarPtrSetCur
746755
else if (lvaIsGCTracked(varDsc))
747756
{
757+
// This isn't in a register, so update the gcVarPtrSetCur to show that it's live on the stack.
748758
VarSetOps::AddElemD(this, codeGen->gcInfo.gcVarPtrSetCur, bornVarIndex);
749759
JITDUMP("\t\t\t\t\t\t\tV%02u becoming live\n", varNum);
750760
}
@@ -3269,6 +3279,7 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
32693279
// 1 means the first part of a register argument
32703280
// 2, 3 or 4 means the second,third or fourth part of a multireg argument
32713281
bool stackArg; // true if the argument gets homed to the stack
3282+
bool writeThru; // true if the argument gets homed to both stack and register
32723283
bool processed; // true after we've processed the argument (and it is in its final location)
32733284
bool circular; // true if this register participates in a circular dependency loop.
32743285

@@ -3605,6 +3616,7 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
36053616
}
36063617

36073618
regArgTab[regArgNum + i].processed = false;
3619+
regArgTab[regArgNum + i].writeThru = (varDsc->lvIsInReg() && varDsc->lvLiveInOutOfHndlr);
36083620

36093621
/* mark stack arguments since we will take care of those first */
36103622
regArgTab[regArgNum + i].stackArg = (varDsc->lvIsInReg()) ? false : true;
@@ -3765,9 +3777,9 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
37653777
noway_assert(((regArgMaskLive & RBM_FLTARG_REGS) == 0) &&
37663778
"Homing of float argument registers with circular dependencies not implemented.");
37673779

3768-
/* Now move the arguments to their locations.
3769-
* First consider ones that go on the stack since they may
3770-
* free some registers. */
3780+
// Now move the arguments to their locations.
3781+
// First consider ones that go on the stack since they may free some registers.
3782+
// Also home writeThru args, since they're also homed to the stack.
37713783

37723784
regArgMaskLive = regState->rsCalleeRegArgMaskLiveIn; // reset the live in to what it was at the start
37733785
for (argNum = 0; argNum < argMax; argNum++)
@@ -3805,7 +3817,7 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
38053817
// If this arg is never on the stack, go to the next one.
38063818
if (varDsc->lvType == TYP_LONG)
38073819
{
3808-
if (regArgTab[argNum].slot == 1 && !regArgTab[argNum].stackArg)
3820+
if (regArgTab[argNum].slot == 1 && !regArgTab[argNum].stackArg && !regArgTab[argNum].writeThru)
38093821
{
38103822
continue;
38113823
}
@@ -3839,7 +3851,7 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
38393851

38403852
noway_assert(varDsc->lvIsParam);
38413853
noway_assert(varDsc->lvIsRegArg);
3842-
noway_assert(varDsc->lvIsInReg() == false ||
3854+
noway_assert(varDsc->lvIsInReg() == false || varDsc->lvLiveInOutOfHndlr ||
38433855
(varDsc->lvType == TYP_LONG && varDsc->GetOtherReg() == REG_STK && regArgTab[argNum].slot == 2));
38443856

38453857
var_types storeType = TYP_UNDEF;
@@ -3906,13 +3918,17 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
39063918
#endif // USING_SCOPE_INFO
39073919
}
39083920

3909-
/* mark the argument as processed */
3910-
3911-
regArgTab[argNum].processed = true;
3912-
regArgMaskLive &= ~genRegMask(srcRegNum);
3921+
// Mark the argument as processed, and set it as no longer live in srcRegNum,
3922+
// unless it is a writeThru var, in which case we home it to the stack, but
3923+
// don't mark it as processed until below.
3924+
if (!regArgTab[argNum].writeThru)
3925+
{
3926+
regArgTab[argNum].processed = true;
3927+
regArgMaskLive &= ~genRegMask(srcRegNum);
3928+
}
39133929

39143930
#if defined(TARGET_ARM)
3915-
if (storeType == TYP_DOUBLE)
3931+
if ((storeType == TYP_DOUBLE) && !regArgTab[argNum].writeThru)
39163932
{
39173933
regArgTab[argNum + 1].processed = true;
39183934
regArgMaskLive &= ~genRegMask(REG_NEXT(srcRegNum));
@@ -4618,7 +4634,7 @@ void CodeGen::genCheckUseBlockInit()
46184634
{
46194635
if (!varDsc->lvRegister)
46204636
{
4621-
if (!varDsc->lvIsInReg())
4637+
if (!varDsc->lvIsInReg() || varDsc->lvLiveInOutOfHndlr)
46224638
{
46234639
// Var is on the stack at entry.
46244640
initStkLclCnt +=
@@ -7233,7 +7249,9 @@ void CodeGen::genFnProlog()
72337249
continue;
72347250
}
72357251

7236-
if (varDsc->lvIsInReg())
7252+
bool isInReg = varDsc->lvIsInReg();
7253+
bool isInMemory = !isInReg || varDsc->lvLiveInOutOfHndlr;
7254+
if (isInReg)
72377255
{
72387256
regMaskTP regMask = genRegMask(varDsc->GetRegNum());
72397257
if (!varDsc->IsFloatRegType())
@@ -7264,7 +7282,7 @@ void CodeGen::genFnProlog()
72647282
initFltRegs |= regMask;
72657283
}
72667284
}
7267-
else
7285+
if (isInMemory)
72687286
{
72697287
INIT_STK:
72707288

src/coreclr/src/jit/codegenlinear.cpp

Lines changed: 65 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -239,15 +239,18 @@ void CodeGen::genCodeForBBlist()
239239
{
240240
newRegByrefSet |= varDsc->lvRegMask();
241241
}
242-
#ifdef DEBUG
243-
if (verbose && VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varIndex))
242+
if (!varDsc->lvLiveInOutOfHndlr)
244243
{
245-
VarSetOps::AddElemD(compiler, removedGCVars, varIndex);
246-
}
244+
#ifdef DEBUG
245+
if (verbose && VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varIndex))
246+
{
247+
VarSetOps::AddElemD(compiler, removedGCVars, varIndex);
248+
}
247249
#endif // DEBUG
248-
VarSetOps::RemoveElemD(compiler, gcInfo.gcVarPtrSetCur, varIndex);
250+
VarSetOps::RemoveElemD(compiler, gcInfo.gcVarPtrSetCur, varIndex);
251+
}
249252
}
250-
else if (compiler->lvaIsGCTracked(varDsc))
253+
if ((!varDsc->lvIsInReg() || varDsc->lvLiveInOutOfHndlr) && compiler->lvaIsGCTracked(varDsc))
251254
{
252255
#ifdef DEBUG
253256
if (verbose && !VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varIndex))
@@ -823,10 +826,20 @@ void CodeGen::genSpillVar(GenTree* tree)
823826
var_types lclTyp = genActualType(varDsc->TypeGet());
824827
emitAttr size = emitTypeSize(lclTyp);
825828

826-
instruction storeIns = ins_Store(lclTyp, compiler->isSIMDTypeLocalAligned(varNum));
827-
assert(varDsc->GetRegNum() == tree->GetRegNum());
828-
inst_TT_RV(storeIns, size, tree, tree->GetRegNum());
829+
// If this is a write-thru variable, we don't actually spill at a use, but we will kill the var in the reg
830+
// (below).
831+
if (!varDsc->lvLiveInOutOfHndlr)
832+
{
833+
instruction storeIns = ins_Store(lclTyp, compiler->isSIMDTypeLocalAligned(varNum));
834+
assert(varDsc->GetRegNum() == tree->GetRegNum());
835+
inst_TT_RV(storeIns, size, tree, tree->GetRegNum());
836+
}
829837

838+
// We should only have both GTF_SPILL (i.e. the flag causing this method to be called) and
839+
// GTF_SPILLED on a write-thru def, for which we should not be calling this method.
840+
assert((tree->gtFlags & GTF_SPILLED) == 0);
841+
842+
// Remove the live var from the register.
830843
genUpdateRegLife(varDsc, /*isBorn*/ false, /*isDying*/ true DEBUGARG(tree));
831844
gcInfo.gcMarkRegSetNpt(varDsc->lvRegMask());
832845

@@ -847,10 +860,19 @@ void CodeGen::genSpillVar(GenTree* tree)
847860
}
848861

849862
tree->gtFlags &= ~GTF_SPILL;
850-
varDsc->SetRegNum(REG_STK);
851-
if (varTypeIsMultiReg(tree))
863+
// If this is NOT a write-thru, reset the var location.
864+
if ((tree->gtFlags & GTF_SPILLED) == 0)
852865
{
853-
varDsc->SetOtherReg(REG_STK);
866+
varDsc->SetRegNum(REG_STK);
867+
if (varTypeIsMultiReg(tree))
868+
{
869+
varDsc->SetOtherReg(REG_STK);
870+
}
871+
}
872+
else
873+
{
874+
// We only have 'GTF_SPILL' and 'GTF_SPILLED' on a def of a write-thru lclVar.
875+
assert(varDsc->lvLiveInOutOfHndlr && ((tree->gtFlags & GTF_VAR_DEF) != 0));
854876
}
855877

856878
#ifdef USING_VARIABLE_LIVE_RANGE
@@ -1030,13 +1052,16 @@ void CodeGen::genUnspillRegIfNeeded(GenTree* tree)
10301052
}
10311053
#endif // USING_VARIABLE_LIVE_RANGE
10321054

1033-
#ifdef DEBUG
1034-
if (VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex))
1055+
if (!varDsc->lvLiveInOutOfHndlr)
10351056
{
1036-
JITDUMP("\t\t\t\t\t\t\tRemoving V%02u from gcVarPtrSetCur\n", lcl->GetLclNum());
1037-
}
1057+
#ifdef DEBUG
1058+
if (VarSetOps::IsMember(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex))
1059+
{
1060+
JITDUMP("\t\t\t\t\t\t\tRemoving V%02u from gcVarPtrSetCur\n", lcl->GetLclNum());
1061+
}
10381062
#endif // DEBUG
1039-
VarSetOps::RemoveElemD(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex);
1063+
VarSetOps::RemoveElemD(compiler, gcInfo.gcVarPtrSetCur, varDsc->lvVarIndex);
1064+
}
10401065

10411066
#ifdef DEBUG
10421067
if (compiler->verbose)
@@ -1316,15 +1341,15 @@ regNumber CodeGen::genConsumeReg(GenTree* tree)
13161341
LclVarDsc* varDsc = &compiler->lvaTable[lcl->GetLclNum()];
13171342
assert(varDsc->lvLRACandidate);
13181343

1319-
if ((tree->gtFlags & GTF_VAR_DEATH) != 0)
1320-
{
1321-
gcInfo.gcMarkRegSetNpt(genRegMask(varDsc->GetRegNum()));
1322-
}
1323-
else if (varDsc->GetRegNum() == REG_STK)
1344+
if (varDsc->GetRegNum() == REG_STK)
13241345
{
13251346
// We have loaded this into a register only temporarily
13261347
gcInfo.gcMarkRegSetNpt(genRegMask(tree->GetRegNum()));
13271348
}
1349+
else if ((tree->gtFlags & GTF_VAR_DEATH) != 0)
1350+
{
1351+
gcInfo.gcMarkRegSetNpt(genRegMask(varDsc->GetRegNum()));
1352+
}
13281353
}
13291354
else
13301355
{
@@ -1852,13 +1877,24 @@ void CodeGen::genProduceReg(GenTree* tree)
18521877

18531878
if (genIsRegCandidateLocal(tree))
18541879
{
1855-
// Store local variable to its home location.
1856-
// Ensure that lclVar stores are typed correctly.
1857-
unsigned varNum = tree->AsLclVarCommon()->GetLclNum();
1858-
assert(!compiler->lvaTable[varNum].lvNormalizeOnStore() ||
1859-
(tree->TypeGet() == genActualType(compiler->lvaTable[varNum].TypeGet())));
1860-
inst_TT_RV(ins_Store(tree->gtType, compiler->isSIMDTypeLocalAligned(varNum)), emitTypeSize(tree->TypeGet()),
1861-
tree, tree->GetRegNum());
1880+
unsigned varNum = tree->AsLclVarCommon()->GetLclNum();
1881+
LclVarDsc* varDsc = compiler->lvaGetDesc(varNum);
1882+
assert(!varDsc->lvNormalizeOnStore() || (tree->TypeGet() == genActualType(varDsc->TypeGet())));
1883+
1884+
// If we reach here, we have a register candidate local that is marked with GTF_SPILL.
1885+
// This flag generally means that we need to spill this local.
1886+
// The exception is the case of a use of an EH var use that is being "spilled"
1887+
// to the stack, indicated by GTF_SPILL (note that all EH lclVar defs are always
1888+
// spilled, i.e. write-thru).
1889+
// An EH var use is always valid on the stack (so we don't need to actually spill it),
1890+
// but the GTF_SPILL flag records the fact that the register value is going dead.
1891+
if (((tree->gtFlags & GTF_VAR_DEF) != 0) || !varDsc->lvLiveInOutOfHndlr)
1892+
{
1893+
// Store local variable to its home location.
1894+
// Ensure that lclVar stores are typed correctly.
1895+
inst_TT_RV(ins_Store(tree->gtType, compiler->isSIMDTypeLocalAligned(varNum)),
1896+
emitTypeSize(tree->TypeGet()), tree, tree->GetRegNum());
1897+
}
18621898
}
18631899
else
18641900
{

src/coreclr/src/jit/compiler.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4522,6 +4522,37 @@ void Compiler::compCompile(void** methodCodePtr, ULONG* methodCodeSize, JitFlags
45224522

45234523
EndPhase(PHASE_CLONE_FINALLY);
45244524

4525+
#if DEBUG
4526+
if (lvaEnregEHVars)
4527+
{
4528+
unsigned methHash = info.compMethodHash();
4529+
char* lostr = getenv("JitEHWTHashLo");
4530+
unsigned methHashLo = 0;
4531+
bool dump = false;
4532+
if (lostr != nullptr)
4533+
{
4534+
sscanf_s(lostr, "%x", &methHashLo);
4535+
dump = true;
4536+
}
4537+
char* histr = getenv("JitEHWTHashHi");
4538+
unsigned methHashHi = UINT32_MAX;
4539+
if (histr != nullptr)
4540+
{
4541+
sscanf_s(histr, "%x", &methHashHi);
4542+
dump = true;
4543+
}
4544+
if (methHash < methHashLo || methHash > methHashHi)
4545+
{
4546+
lvaEnregEHVars = false;
4547+
}
4548+
else if (dump)
4549+
{
4550+
printf("Enregistering EH Vars for method %s, hash = 0x%x.\n", info.compFullName, info.compMethodHash());
4551+
printf(""); // flush
4552+
}
4553+
}
4554+
#endif
4555+
45254556
// Compute bbNum, bbRefs and bbPreds
45264557
//
45274558
JITDUMP("\nRenumbering the basic blocks for fgComputePreds\n");

src/coreclr/src/jit/compiler.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,8 @@ class LclVarDsc
415415
unsigned char lvDoNotEnregister : 1; // Do not enregister this variable.
416416
unsigned char lvFieldAccessed : 1; // The var is a struct local, and a field of the variable is accessed. Affects
417417
// struct promotion.
418+
unsigned char lvLiveInOutOfHndlr : 1; // The variable is live in or out of an exception handler, and therefore must
419+
// be on the stack (at least at those boundaries.)
418420

419421
unsigned char lvInSsa : 1; // The variable is in SSA form (set by SsaBuilder)
420422

@@ -424,9 +426,6 @@ class LclVarDsc
424426
// also, lvType == TYP_STRUCT prevents enregistration. At least one of the reasons should be true.
425427
unsigned char lvVMNeedsStackAddr : 1; // The VM may have access to a stack-relative address of the variable, and
426428
// read/write its value.
427-
unsigned char lvLiveInOutOfHndlr : 1; // The variable was live in or out of an exception handler, and this required
428-
// the variable to be
429-
// in the stack (at least at those boundaries.)
430429
unsigned char lvLclFieldExpr : 1; // The variable is not a struct, but was accessed like one (e.g., reading a
431430
// particular byte from an int).
432431
unsigned char lvLclBlockOpAddr : 1; // The variable was written to via a block operation that took its address.
@@ -3005,6 +3004,9 @@ class Compiler
30053004
void lvaSetVarAddrExposed(unsigned varNum);
30063005
void lvaSetVarLiveInOutOfHandler(unsigned varNum);
30073006
bool lvaVarDoNotEnregister(unsigned varNum);
3007+
3008+
bool lvaEnregEHVars;
3009+
30083010
#ifdef DEBUG
30093011
// Reasons why we can't enregister. Some of these correspond to debug properties of local vars.
30103012
enum DoNotEnregisterReason
@@ -3027,6 +3029,7 @@ class Compiler
30273029
DNER_PinningRef,
30283030
#endif
30293031
};
3032+
30303033
#endif
30313034
void lvaSetVarDoNotEnregister(unsigned varNum DEBUGARG(DoNotEnregisterReason reason));
30323035

0 commit comments

Comments
 (0)