Skip to content

Commit

Permalink
JIT: Clean up liveness (#103809)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jakobbotsch authored Jun 25, 2024
1 parent 98eb17a commit 3bcc947
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 798 deletions.
4 changes: 0 additions & 4 deletions src/coreclr/jit/block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,6 @@ void BasicBlock::CloneBlockState(Compiler* compiler, BasicBlock* to, const Basic
to->bbStkDepth = from->bbStkDepth;
to->bbCodeOffs = from->bbCodeOffs;
to->bbCodeOffsEnd = from->bbCodeOffsEnd;
VarSetOps::AssignAllowUninitRhs(compiler, to->bbScope, from->bbScope);
#ifdef DEBUG
to->bbTgtStkDepth = from->bbTgtStkDepth;
#endif // DEBUG
Expand Down Expand Up @@ -1470,7 +1469,6 @@ void BasicBlock::InitVarSets(Compiler* comp)
VarSetOps::AssignNoCopy(comp, bbVarDef, VarSetOps::MakeEmpty(comp));
VarSetOps::AssignNoCopy(comp, bbLiveIn, VarSetOps::MakeEmpty(comp));
VarSetOps::AssignNoCopy(comp, bbLiveOut, VarSetOps::MakeEmpty(comp));
VarSetOps::AssignNoCopy(comp, bbScope, VarSetOps::MakeEmpty(comp));

bbMemoryUse = emptyMemoryKindSet;
bbMemoryDef = emptyMemoryKindSet;
Expand Down Expand Up @@ -1672,15 +1670,13 @@ BasicBlock* BasicBlock::New(Compiler* compiler)
VarSetOps::AssignNoCopy(compiler, block->bbVarDef, VarSetOps::MakeEmpty(compiler));
VarSetOps::AssignNoCopy(compiler, block->bbLiveIn, VarSetOps::MakeEmpty(compiler));
VarSetOps::AssignNoCopy(compiler, block->bbLiveOut, VarSetOps::MakeEmpty(compiler));
VarSetOps::AssignNoCopy(compiler, block->bbScope, VarSetOps::MakeEmpty(compiler));
}
else
{
VarSetOps::AssignNoCopy(compiler, block->bbVarUse, VarSetOps::UninitVal());
VarSetOps::AssignNoCopy(compiler, block->bbVarDef, VarSetOps::UninitVal());
VarSetOps::AssignNoCopy(compiler, block->bbLiveIn, VarSetOps::UninitVal());
VarSetOps::AssignNoCopy(compiler, block->bbLiveOut, VarSetOps::UninitVal());
VarSetOps::AssignNoCopy(compiler, block->bbScope, VarSetOps::UninitVal());
}

block->bbMemoryUse = emptyMemoryKindSet;
Expand Down
2 changes: 0 additions & 2 deletions src/coreclr/jit/block.h
Original file line number Diff line number Diff line change
Expand Up @@ -1611,8 +1611,6 @@ struct BasicBlock : private LIR::Range
unsigned bbMemorySsaNumIn[MemoryKindCount]; // The SSA # of memory on entry to the block.
unsigned bbMemorySsaNumOut[MemoryKindCount]; // The SSA # of memory on exit from the block.

VARSET_TP bbScope; // variables in scope over the block

void InitVarSets(class Compiler* comp);

/* The following are the standard bit sets for dataflow analysis.
Expand Down
4 changes: 0 additions & 4 deletions src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10923,9 +10923,6 @@ void Compiler::EnregisterStats::RecordLocal(const LclVarDsc* varDsc)
case DoNotEnregisterReason::NoRegVars:
m_noRegVars++;
break;
case DoNotEnregisterReason::MinOptsGC:
m_minOptsGC++;
break;
#if !defined(TARGET_64BIT)
case DoNotEnregisterReason::LongParamField:
m_longParamField++;
Expand Down Expand Up @@ -11080,7 +11077,6 @@ void Compiler::EnregisterStats::Dump(FILE* fout) const
PRINT_STATS(m_structArg, notEnreg);
PRINT_STATS(m_depField, notEnreg);
PRINT_STATS(m_noRegVars, notEnreg);
PRINT_STATS(m_minOptsGC, notEnreg);
#if !defined(TARGET_64BIT)
PRINT_STATS(m_longParamField, notEnreg);
#endif // !TARGET_64BIT
Expand Down
23 changes: 3 additions & 20 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,6 @@ enum class DoNotEnregisterReason
IsStructArg, // Is a struct passed as an argument in a way that requires a stack location.
DepField, // It is a field of a dependently promoted struct
NoRegVars, // opts.compFlags & CLFLG_REGVAR is not set
MinOptsGC, // It is a GC Ref and we are compiling MinOpts
#if !defined(TARGET_64BIT)
LongParamField, // It is a decomposed field of a long parameter.
#endif
Expand Down Expand Up @@ -5525,7 +5524,7 @@ class Compiler

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

void fgLiveVarAnalysis(bool updateInternalOnly = false);
void fgLiveVarAnalysis();

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

Expand All @@ -5545,10 +5544,10 @@ class Compiler
void fgComputeLife(VARSET_TP& life,
GenTree* startNode,
GenTree* endNode,
VARSET_VALARG_TP volatileVars,
VARSET_VALARG_TP keepAliveVars,
bool* pStmtInfoDirty DEBUGARG(bool* treeModf));

void fgComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VARSET_VALARG_TP volatileVars);
void fgComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VARSET_VALARG_TP keepAliveVars);

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

Expand Down Expand Up @@ -5916,8 +5915,6 @@ class Compiler

PhaseStatus fgComputeDominators(); // Compute dominators

bool fgRemoveDeadBlocks(); // Identify and remove dead blocks.

public:
enum GCPollType
{
Expand Down Expand Up @@ -6678,19 +6675,6 @@ class Compiler

void fgMarkUseDef(GenTreeLclVarCommon* tree);

void fgBeginScopeLife(VARSET_TP* inScope, VarScopeDsc* var);
void fgEndScopeLife(VARSET_TP* inScope, VarScopeDsc* var);

void fgMarkInScope(BasicBlock* block, VARSET_VALARG_TP inScope);
void fgUnmarkInScope(BasicBlock* block, VARSET_VALARG_TP unmarkScope);

void fgExtendDbgScopes();
void fgExtendDbgLifetimes();

#ifdef DEBUG
void fgDispDebugScopes();
#endif // DEBUG

//-------------------------------------------------------------------------
//
// The following keeps track of any code we've added for things like array
Expand Down Expand Up @@ -10891,7 +10875,6 @@ class Compiler
unsigned m_liveInOutHndlr;
unsigned m_depField;
unsigned m_noRegVars;
unsigned m_minOptsGC;
#ifdef JIT32_GCENCODER
unsigned m_PinningRef;
#endif // JIT32_GCENCODER
Expand Down
105 changes: 0 additions & 105 deletions src/coreclr/jit/fgopt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,111 +162,6 @@ bool Compiler::fgRemoveUnreachableBlocks(CanRemoveBlockBody canRemoveBlock)
return changed;
}

//------------------------------------------------------------------------
// fgRemoveDeadBlocks: Identify all the unreachable blocks and remove them.
//
bool Compiler::fgRemoveDeadBlocks()
{
JITDUMP("\n*************** In fgRemoveDeadBlocks()");

unsigned prevFgCurBBEpoch = fgCurBBEpoch;
EnsureBasicBlockEpoch();

BlockSet visitedBlocks(BlockSetOps::MakeEmpty(this));

jitstd::list<BasicBlock*> worklist(jitstd::allocator<void>(getAllocator(CMK_Reachability)));
worklist.push_back(fgFirstBB);

// Visit all the reachable blocks, everything else can be removed
while (!worklist.empty())
{
BasicBlock* block = *(worklist.begin());
worklist.pop_front();

if (BlockSetOps::IsMember(this, visitedBlocks, block->bbNum))
{
continue;
}

BlockSetOps::AddElemD(this, visitedBlocks, block->bbNum);

for (BasicBlock* succ : block->Succs(this))
{
worklist.push_back(succ);
}

// Add all the "EH" successors. For every `try`, add its handler (including filter) to the worklist.
if (bbIsTryBeg(block))
{
// Due to EH normalization, a block can only be the start of a single `try` region, with the exception
// of mutually-protect regions.
assert(block->hasTryIndex());
unsigned tryIndex = block->getTryIndex();
EHblkDsc* ehDsc = ehGetDsc(tryIndex);
for (;;)
{
worklist.push_back(ehDsc->ebdHndBeg);
if (ehDsc->HasFilter())
{
worklist.push_back(ehDsc->ebdFilter);
}
tryIndex = ehDsc->ebdEnclosingTryIndex;
if (tryIndex == EHblkDsc::NO_ENCLOSING_INDEX)
{
break;
}
ehDsc = ehGetDsc(tryIndex);
if (ehDsc->ebdTryBeg != block)
{
break;
}
}
}
}

// Track if there is any unreachable block. Even if it is marked with
// BBF_DONT_REMOVE, fgRemoveUnreachableBlocks() still removes the code
// inside the block. So this variable tracks if we ever found such blocks
// or not.
bool hasUnreachableBlock = false;

auto isBlockRemovable = [&](BasicBlock* block) -> bool {
const bool isVisited = BlockSetOps::IsMember(this, visitedBlocks, block->bbNum);
const bool isRemovable = !isVisited || (block->bbRefs == 0);

hasUnreachableBlock |= isRemovable;
return isRemovable;
};

bool changed = false;
unsigned iterationCount = 1;
do
{
JITDUMP("\nRemoving unreachable blocks for fgRemoveDeadBlocks iteration #%u\n", iterationCount);

// Just to be paranoid, avoid infinite loops; fall back to minopts.
if (iterationCount++ > 10)
{
noway_assert(!"Too many unreachable block removal loops");
}
changed = fgRemoveUnreachableBlocks(isBlockRemovable);
} while (changed);

#ifdef DEBUG
if (verbose && hasUnreachableBlock)
{
printf("\nAfter dead block removal:\n");
fgDispBasicBlocks(verboseTrees);
printf("\n");
}

fgVerifyHandlerTab();
fgDebugCheckBBlist(false);
#endif // DEBUG

return hasUnreachableBlock;
}

//-------------------------------------------------------------
// fgComputeDominators: Compute dominators
//
Expand Down
10 changes: 7 additions & 3 deletions src/coreclr/jit/gcencode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4065,9 +4065,13 @@ void GCInfo::gcMakeRegPtrTable(
{
GCENCODER_WITH_LOGGING(gcInfoEncoderWithLog, gcInfoEncoder);

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

if (mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS)
{
Expand Down
8 changes: 0 additions & 8 deletions src/coreclr/jit/jitconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -521,14 +521,6 @@ RELEASE_CONFIG_INTEGER(JitEnableNoWayAssert, W("JitEnableNoWayAssert"), 0)
RELEASE_CONFIG_INTEGER(JitEnableNoWayAssert, W("JitEnableNoWayAssert"), 1)
#endif // !defined(DEBUG) && !defined(_DEBUG)

// Track GC roots
#if defined(TARGET_AMD64) || defined(TARGET_X86)
#define JitMinOptsTrackGCrefs_Default 0 // Not tracking GC refs in MinOpts is new behavior
#else
#define JitMinOptsTrackGCrefs_Default 1
#endif
RELEASE_CONFIG_INTEGER(JitMinOptsTrackGCrefs, W("JitMinOptsTrackGCrefs"), JitMinOptsTrackGCrefs_Default)

// The following should be wrapped inside "#if MEASURE_MEM_ALLOC / #endif", but
// some files include this one without bringing in the definitions from "jit.h"
// so we don't always know what the "true" value of that flag should be. For now
Expand Down
9 changes: 0 additions & 9 deletions src/coreclr/jit/lclvars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3212,10 +3212,6 @@ void Compiler::lvaSetVarDoNotEnregister(unsigned varNum DEBUGARG(DoNotEnregister
JITDUMP("opts.compFlags & CLFLG_REGVAR is not set\n");
assert(!compEnregLocals());
break;
case DoNotEnregisterReason::MinOptsGC:
JITDUMP("it is a GC Ref and we are compiling MinOpts\n");
assert(!JitConfig.JitMinOptsTrackGCrefs() && varTypeIsGC(varDsc->TypeGet()));
break;
#if !defined(TARGET_64BIT)
case DoNotEnregisterReason::LongParamField:
JITDUMP("it is a decomposed field of a long parameter\n");
Expand Down Expand Up @@ -4147,11 +4143,6 @@ void Compiler::lvaSortByRefCount()
lvaSetVarDoNotEnregister(lclNum DEBUGARG(DoNotEnregisterReason::PinningRef));
#endif
}
if (opts.MinOpts() && !JitConfig.JitMinOptsTrackGCrefs() && varTypeIsGC(varDsc->TypeGet()))
{
varDsc->lvTracked = 0;
lvaSetVarDoNotEnregister(lclNum DEBUGARG(DoNotEnregisterReason::MinOptsGC));
}
if (!compEnregLocals())
{
lvaSetVarDoNotEnregister(lclNum DEBUGARG(DoNotEnregisterReason::NoRegVars));
Expand Down
Loading

0 comments on commit 3bcc947

Please sign in to comment.