Skip to content

Commit 3bcc947

Browse files
authored
JIT: Clean up liveness (#103809)
We have a DFS tree available in both early liveness and SSA's liveness, and we can use it to make the data flow cheaper by running in an RPO over the DFS tree. This allows us to propagate the maximal amount of knowledge in each iteration and also to stop the data flow early when there is no cycle in the DFS tree. We do not have the DFS tree available in lowering where we also call liveness. However, lowering was already iterating all blocks to remove dead blocks; switch this to `fgDfsBlocksAndRemove` to remove dead blocks and compute the DFS tree in one go, and remove the old code doing this. Additionally there was a bunch of logic in liveness to consider debug scopes for debug codegen, left-over from a time where we tracked GC pointers even in MinOpts. This code can be deleted since we do not do liveness in MinOpts anymore.
1 parent 98eb17a commit 3bcc947

File tree

10 files changed

+101
-798
lines changed

10 files changed

+101
-798
lines changed

src/coreclr/jit/block.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -909,7 +909,6 @@ void BasicBlock::CloneBlockState(Compiler* compiler, BasicBlock* to, const Basic
909909
to->bbStkDepth = from->bbStkDepth;
910910
to->bbCodeOffs = from->bbCodeOffs;
911911
to->bbCodeOffsEnd = from->bbCodeOffsEnd;
912-
VarSetOps::AssignAllowUninitRhs(compiler, to->bbScope, from->bbScope);
913912
#ifdef DEBUG
914913
to->bbTgtStkDepth = from->bbTgtStkDepth;
915914
#endif // DEBUG
@@ -1470,7 +1469,6 @@ void BasicBlock::InitVarSets(Compiler* comp)
14701469
VarSetOps::AssignNoCopy(comp, bbVarDef, VarSetOps::MakeEmpty(comp));
14711470
VarSetOps::AssignNoCopy(comp, bbLiveIn, VarSetOps::MakeEmpty(comp));
14721471
VarSetOps::AssignNoCopy(comp, bbLiveOut, VarSetOps::MakeEmpty(comp));
1473-
VarSetOps::AssignNoCopy(comp, bbScope, VarSetOps::MakeEmpty(comp));
14741472

14751473
bbMemoryUse = emptyMemoryKindSet;
14761474
bbMemoryDef = emptyMemoryKindSet;
@@ -1672,15 +1670,13 @@ BasicBlock* BasicBlock::New(Compiler* compiler)
16721670
VarSetOps::AssignNoCopy(compiler, block->bbVarDef, VarSetOps::MakeEmpty(compiler));
16731671
VarSetOps::AssignNoCopy(compiler, block->bbLiveIn, VarSetOps::MakeEmpty(compiler));
16741672
VarSetOps::AssignNoCopy(compiler, block->bbLiveOut, VarSetOps::MakeEmpty(compiler));
1675-
VarSetOps::AssignNoCopy(compiler, block->bbScope, VarSetOps::MakeEmpty(compiler));
16761673
}
16771674
else
16781675
{
16791676
VarSetOps::AssignNoCopy(compiler, block->bbVarUse, VarSetOps::UninitVal());
16801677
VarSetOps::AssignNoCopy(compiler, block->bbVarDef, VarSetOps::UninitVal());
16811678
VarSetOps::AssignNoCopy(compiler, block->bbLiveIn, VarSetOps::UninitVal());
16821679
VarSetOps::AssignNoCopy(compiler, block->bbLiveOut, VarSetOps::UninitVal());
1683-
VarSetOps::AssignNoCopy(compiler, block->bbScope, VarSetOps::UninitVal());
16841680
}
16851681

16861682
block->bbMemoryUse = emptyMemoryKindSet;

src/coreclr/jit/block.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1611,8 +1611,6 @@ struct BasicBlock : private LIR::Range
16111611
unsigned bbMemorySsaNumIn[MemoryKindCount]; // The SSA # of memory on entry to the block.
16121612
unsigned bbMemorySsaNumOut[MemoryKindCount]; // The SSA # of memory on exit from the block.
16131613

1614-
VARSET_TP bbScope; // variables in scope over the block
1615-
16161614
void InitVarSets(class Compiler* comp);
16171615

16181616
/* The following are the standard bit sets for dataflow analysis.

src/coreclr/jit/compiler.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10923,9 +10923,6 @@ void Compiler::EnregisterStats::RecordLocal(const LclVarDsc* varDsc)
1092310923
case DoNotEnregisterReason::NoRegVars:
1092410924
m_noRegVars++;
1092510925
break;
10926-
case DoNotEnregisterReason::MinOptsGC:
10927-
m_minOptsGC++;
10928-
break;
1092910926
#if !defined(TARGET_64BIT)
1093010927
case DoNotEnregisterReason::LongParamField:
1093110928
m_longParamField++;
@@ -11080,7 +11077,6 @@ void Compiler::EnregisterStats::Dump(FILE* fout) const
1108011077
PRINT_STATS(m_structArg, notEnreg);
1108111078
PRINT_STATS(m_depField, notEnreg);
1108211079
PRINT_STATS(m_noRegVars, notEnreg);
11083-
PRINT_STATS(m_minOptsGC, notEnreg);
1108411080
#if !defined(TARGET_64BIT)
1108511081
PRINT_STATS(m_longParamField, notEnreg);
1108611082
#endif // !TARGET_64BIT

src/coreclr/jit/compiler.h

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,6 @@ enum class DoNotEnregisterReason
452452
IsStructArg, // Is a struct passed as an argument in a way that requires a stack location.
453453
DepField, // It is a field of a dependently promoted struct
454454
NoRegVars, // opts.compFlags & CLFLG_REGVAR is not set
455-
MinOptsGC, // It is a GC Ref and we are compiling MinOpts
456455
#if !defined(TARGET_64BIT)
457456
LongParamField, // It is a decomposed field of a long parameter.
458457
#endif
@@ -5525,7 +5524,7 @@ class Compiler
55255524

55265525
void fgAddHandlerLiveVars(BasicBlock* block, VARSET_TP& ehHandlerLiveVars, MemoryKindSet& memoryLiveness);
55275526

5528-
void fgLiveVarAnalysis(bool updateInternalOnly = false);
5527+
void fgLiveVarAnalysis();
55295528

55305529
void fgComputeLifeCall(VARSET_TP& life, GenTreeCall* call);
55315530

@@ -5545,10 +5544,10 @@ class Compiler
55455544
void fgComputeLife(VARSET_TP& life,
55465545
GenTree* startNode,
55475546
GenTree* endNode,
5548-
VARSET_VALARG_TP volatileVars,
5547+
VARSET_VALARG_TP keepAliveVars,
55495548
bool* pStmtInfoDirty DEBUGARG(bool* treeModf));
55505549

5551-
void fgComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VARSET_VALARG_TP volatileVars);
5550+
void fgComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VARSET_VALARG_TP keepAliveVars);
55525551

55535552
bool fgTryRemoveNonLocal(GenTree* node, LIR::Range* blockRange);
55545553

@@ -5916,8 +5915,6 @@ class Compiler
59165915

59175916
PhaseStatus fgComputeDominators(); // Compute dominators
59185917

5919-
bool fgRemoveDeadBlocks(); // Identify and remove dead blocks.
5920-
59215918
public:
59225919
enum GCPollType
59235920
{
@@ -6678,19 +6675,6 @@ class Compiler
66786675

66796676
void fgMarkUseDef(GenTreeLclVarCommon* tree);
66806677

6681-
void fgBeginScopeLife(VARSET_TP* inScope, VarScopeDsc* var);
6682-
void fgEndScopeLife(VARSET_TP* inScope, VarScopeDsc* var);
6683-
6684-
void fgMarkInScope(BasicBlock* block, VARSET_VALARG_TP inScope);
6685-
void fgUnmarkInScope(BasicBlock* block, VARSET_VALARG_TP unmarkScope);
6686-
6687-
void fgExtendDbgScopes();
6688-
void fgExtendDbgLifetimes();
6689-
6690-
#ifdef DEBUG
6691-
void fgDispDebugScopes();
6692-
#endif // DEBUG
6693-
66946678
//-------------------------------------------------------------------------
66956679
//
66966680
// The following keeps track of any code we've added for things like array
@@ -10891,7 +10875,6 @@ class Compiler
1089110875
unsigned m_liveInOutHndlr;
1089210876
unsigned m_depField;
1089310877
unsigned m_noRegVars;
10894-
unsigned m_minOptsGC;
1089510878
#ifdef JIT32_GCENCODER
1089610879
unsigned m_PinningRef;
1089710880
#endif // JIT32_GCENCODER

src/coreclr/jit/fgopt.cpp

Lines changed: 0 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -162,111 +162,6 @@ bool Compiler::fgRemoveUnreachableBlocks(CanRemoveBlockBody canRemoveBlock)
162162
return changed;
163163
}
164164

165-
//------------------------------------------------------------------------
166-
// fgRemoveDeadBlocks: Identify all the unreachable blocks and remove them.
167-
//
168-
bool Compiler::fgRemoveDeadBlocks()
169-
{
170-
JITDUMP("\n*************** In fgRemoveDeadBlocks()");
171-
172-
unsigned prevFgCurBBEpoch = fgCurBBEpoch;
173-
EnsureBasicBlockEpoch();
174-
175-
BlockSet visitedBlocks(BlockSetOps::MakeEmpty(this));
176-
177-
jitstd::list<BasicBlock*> worklist(jitstd::allocator<void>(getAllocator(CMK_Reachability)));
178-
worklist.push_back(fgFirstBB);
179-
180-
// Visit all the reachable blocks, everything else can be removed
181-
while (!worklist.empty())
182-
{
183-
BasicBlock* block = *(worklist.begin());
184-
worklist.pop_front();
185-
186-
if (BlockSetOps::IsMember(this, visitedBlocks, block->bbNum))
187-
{
188-
continue;
189-
}
190-
191-
BlockSetOps::AddElemD(this, visitedBlocks, block->bbNum);
192-
193-
for (BasicBlock* succ : block->Succs(this))
194-
{
195-
worklist.push_back(succ);
196-
}
197-
198-
// Add all the "EH" successors. For every `try`, add its handler (including filter) to the worklist.
199-
if (bbIsTryBeg(block))
200-
{
201-
// Due to EH normalization, a block can only be the start of a single `try` region, with the exception
202-
// of mutually-protect regions.
203-
assert(block->hasTryIndex());
204-
unsigned tryIndex = block->getTryIndex();
205-
EHblkDsc* ehDsc = ehGetDsc(tryIndex);
206-
for (;;)
207-
{
208-
worklist.push_back(ehDsc->ebdHndBeg);
209-
if (ehDsc->HasFilter())
210-
{
211-
worklist.push_back(ehDsc->ebdFilter);
212-
}
213-
tryIndex = ehDsc->ebdEnclosingTryIndex;
214-
if (tryIndex == EHblkDsc::NO_ENCLOSING_INDEX)
215-
{
216-
break;
217-
}
218-
ehDsc = ehGetDsc(tryIndex);
219-
if (ehDsc->ebdTryBeg != block)
220-
{
221-
break;
222-
}
223-
}
224-
}
225-
}
226-
227-
// Track if there is any unreachable block. Even if it is marked with
228-
// BBF_DONT_REMOVE, fgRemoveUnreachableBlocks() still removes the code
229-
// inside the block. So this variable tracks if we ever found such blocks
230-
// or not.
231-
bool hasUnreachableBlock = false;
232-
233-
auto isBlockRemovable = [&](BasicBlock* block) -> bool {
234-
const bool isVisited = BlockSetOps::IsMember(this, visitedBlocks, block->bbNum);
235-
const bool isRemovable = !isVisited || (block->bbRefs == 0);
236-
237-
hasUnreachableBlock |= isRemovable;
238-
return isRemovable;
239-
};
240-
241-
bool changed = false;
242-
unsigned iterationCount = 1;
243-
do
244-
{
245-
JITDUMP("\nRemoving unreachable blocks for fgRemoveDeadBlocks iteration #%u\n", iterationCount);
246-
247-
// Just to be paranoid, avoid infinite loops; fall back to minopts.
248-
if (iterationCount++ > 10)
249-
{
250-
noway_assert(!"Too many unreachable block removal loops");
251-
}
252-
changed = fgRemoveUnreachableBlocks(isBlockRemovable);
253-
} while (changed);
254-
255-
#ifdef DEBUG
256-
if (verbose && hasUnreachableBlock)
257-
{
258-
printf("\nAfter dead block removal:\n");
259-
fgDispBasicBlocks(verboseTrees);
260-
printf("\n");
261-
}
262-
263-
fgVerifyHandlerTab();
264-
fgDebugCheckBBlist(false);
265-
#endif // DEBUG
266-
267-
return hasUnreachableBlock;
268-
}
269-
270165
//-------------------------------------------------------------
271166
// fgComputeDominators: Compute dominators
272167
//

src/coreclr/jit/gcencode.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4065,9 +4065,13 @@ void GCInfo::gcMakeRegPtrTable(
40654065
{
40664066
GCENCODER_WITH_LOGGING(gcInfoEncoderWithLog, gcInfoEncoder);
40674067

4068-
const bool noTrackedGCSlots =
4069-
(compiler->opts.MinOpts() && !compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT) &&
4070-
!JitConfig.JitMinOptsTrackGCrefs());
4068+
// TODO: Decide on whether we should enable this optimization for all
4069+
// targets: https://github.com/dotnet/runtime/issues/103917
4070+
#ifdef TARGET_XARCH
4071+
const bool noTrackedGCSlots = compiler->opts.MinOpts() && !compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT);
4072+
#else
4073+
const bool noTrackedGCSlots = false;
4074+
#endif
40714075

40724076
if (mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS)
40734077
{

src/coreclr/jit/jitconfigvalues.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -521,14 +521,6 @@ RELEASE_CONFIG_INTEGER(JitEnableNoWayAssert, W("JitEnableNoWayAssert"), 0)
521521
RELEASE_CONFIG_INTEGER(JitEnableNoWayAssert, W("JitEnableNoWayAssert"), 1)
522522
#endif // !defined(DEBUG) && !defined(_DEBUG)
523523

524-
// Track GC roots
525-
#if defined(TARGET_AMD64) || defined(TARGET_X86)
526-
#define JitMinOptsTrackGCrefs_Default 0 // Not tracking GC refs in MinOpts is new behavior
527-
#else
528-
#define JitMinOptsTrackGCrefs_Default 1
529-
#endif
530-
RELEASE_CONFIG_INTEGER(JitMinOptsTrackGCrefs, W("JitMinOptsTrackGCrefs"), JitMinOptsTrackGCrefs_Default)
531-
532524
// The following should be wrapped inside "#if MEASURE_MEM_ALLOC / #endif", but
533525
// some files include this one without bringing in the definitions from "jit.h"
534526
// so we don't always know what the "true" value of that flag should be. For now

src/coreclr/jit/lclvars.cpp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3212,10 +3212,6 @@ void Compiler::lvaSetVarDoNotEnregister(unsigned varNum DEBUGARG(DoNotEnregister
32123212
JITDUMP("opts.compFlags & CLFLG_REGVAR is not set\n");
32133213
assert(!compEnregLocals());
32143214
break;
3215-
case DoNotEnregisterReason::MinOptsGC:
3216-
JITDUMP("it is a GC Ref and we are compiling MinOpts\n");
3217-
assert(!JitConfig.JitMinOptsTrackGCrefs() && varTypeIsGC(varDsc->TypeGet()));
3218-
break;
32193215
#if !defined(TARGET_64BIT)
32203216
case DoNotEnregisterReason::LongParamField:
32213217
JITDUMP("it is a decomposed field of a long parameter\n");
@@ -4147,11 +4143,6 @@ void Compiler::lvaSortByRefCount()
41474143
lvaSetVarDoNotEnregister(lclNum DEBUGARG(DoNotEnregisterReason::PinningRef));
41484144
#endif
41494145
}
4150-
if (opts.MinOpts() && !JitConfig.JitMinOptsTrackGCrefs() && varTypeIsGC(varDsc->TypeGet()))
4151-
{
4152-
varDsc->lvTracked = 0;
4153-
lvaSetVarDoNotEnregister(lclNum DEBUGARG(DoNotEnregisterReason::MinOptsGC));
4154-
}
41554146
if (!compEnregLocals())
41564147
{
41574148
lvaSetVarDoNotEnregister(lclNum DEBUGARG(DoNotEnregisterReason::NoRegVars));

0 commit comments

Comments
 (0)