Skip to content

Commit

Permalink
JIT: Propagate LCL_ADDRs into natural loops (dotnet#109190)
Browse files Browse the repository at this point in the history
We can allow keeping assertions around for natural loop headers provided
that we precompute the set of locals whose assertions may be killed
inside the loop.
  • Loading branch information
jakobbotsch authored Oct 25, 2024
1 parent 661fd5e commit 9228ccf
Showing 1 changed file with 240 additions and 15 deletions.
255 changes: 240 additions & 15 deletions src/coreclr/jit/lclmorph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,203 @@ void Compiler::fgSequenceLocals(Statement* stmt)
seq.Sequence(stmt);
}

// Data structure that keeps track of local definitions inside loops.
class LoopDefinitions
{
typedef JitHashTable<unsigned, JitSmallPrimitiveKeyFuncs<unsigned>, bool> LocalDefinitionsMap;

FlowGraphNaturalLoops* m_loops;
// For every loop, we track all definitions exclusive to that loop.
// Definitions in descendant loops are not kept in their ancestor's maps.
LocalDefinitionsMap** m_maps;
// Blocks whose IR we have visited to find local definitions in.
BitVec m_visitedBlocks;

LocalDefinitionsMap* GetOrCreateMap(FlowGraphNaturalLoop* loop);

template <typename TFunc>
bool VisitLoopNestMaps(FlowGraphNaturalLoop* loop, TFunc& func);
public:
LoopDefinitions(FlowGraphNaturalLoops* loops);

template <typename TFunc>
void VisitDefinedLocalNums(FlowGraphNaturalLoop* loop, TFunc func);
};

LoopDefinitions::LoopDefinitions(FlowGraphNaturalLoops* loops)
: m_loops(loops)
{
Compiler* comp = loops->GetDfsTree()->GetCompiler();
m_maps = loops->NumLoops() == 0 ? nullptr : new (comp, CMK_LoopOpt) LocalDefinitionsMap* [loops->NumLoops()] {};
BitVecTraits poTraits = loops->GetDfsTree()->PostOrderTraits();
m_visitedBlocks = BitVecOps::MakeEmpty(&poTraits);
}

//------------------------------------------------------------------------------
// LoopDefinitions:GetOrCreateMap:
// Get or create the map of occurrences exclusive to a single loop.
//
// Parameters:
// loop - The loop
//
// Returns:
// Map of occurrences.
//
// Remarks:
// As a precondition occurrences of all descendant loops must already have
// been found.
//
LoopDefinitions::LocalDefinitionsMap* LoopDefinitions::GetOrCreateMap(FlowGraphNaturalLoop* loop)
{
LocalDefinitionsMap* map = m_maps[loop->GetIndex()];
if (map != nullptr)
{
return map;
}

BitVecTraits poTraits = m_loops->GetDfsTree()->PostOrderTraits();

#ifdef DEBUG
// As an invariant the map contains only the locals exclusive to each loop
// (i.e. occurrences inside descendant loops are not contained in ancestor
// loop maps). Double check that we've already computed the child maps to
// make sure we do not visit descendant blocks below.
for (FlowGraphNaturalLoop* child = loop->GetChild(); child != nullptr; child = child->GetSibling())
{
assert(BitVecOps::IsMember(&poTraits, m_visitedBlocks, child->GetHeader()->bbPostorderNum));
}
#endif

Compiler* comp = m_loops->GetDfsTree()->GetCompiler();
map = new (comp, CMK_LoopOpt) LocalDefinitionsMap(comp->getAllocator(CMK_LoopOpt));
m_maps[loop->GetIndex()] = map;

struct LocalsVisitor : GenTreeVisitor<LocalsVisitor>
{
enum
{
DoPreOrder = true,
DoLclVarsOnly = true,
};

LocalsVisitor(Compiler* comp, LoopDefinitions::LocalDefinitionsMap* map)
: GenTreeVisitor(comp)
, m_map(map)
{
}

fgWalkResult PreOrderVisit(GenTree** use, GenTree* user)
{
GenTreeLclVarCommon* lcl = (*use)->AsLclVarCommon();
if (!lcl->OperIsLocalStore())
{
return Compiler::WALK_CONTINUE;
}

m_map->Set(lcl->GetLclNum(), true, LocalDefinitionsMap::Overwrite);

LclVarDsc* lclDsc = m_compiler->lvaGetDesc(lcl);
if (m_compiler->lvaIsImplicitByRefLocal(lcl->GetLclNum()) && lclDsc->lvPromoted)
{
// fgRetypeImplicitByRefArgs created a new promoted
// struct local to represent this arg. The stores will
// be rewritten by morph.
assert(lclDsc->lvFieldLclStart != 0);
m_map->Set(lclDsc->lvFieldLclStart, true, LocalDefinitionsMap::Overwrite);
lclDsc = m_compiler->lvaGetDesc(lclDsc->lvFieldLclStart);
}

if (lclDsc->lvPromoted)
{
for (unsigned i = 0; i < lclDsc->lvFieldCnt; i++)
{
unsigned fieldLclNum = lclDsc->lvFieldLclStart + i;
m_map->Set(fieldLclNum, true, LocalDefinitionsMap::Overwrite);
}
}
else if (lclDsc->lvIsStructField)
{
m_map->Set(lclDsc->lvParentLcl, true, LocalDefinitionsMap::Overwrite);
}

return Compiler::WALK_CONTINUE;
}

private:
LoopDefinitions::LocalDefinitionsMap* m_map;
};

LocalsVisitor visitor(comp, map);

loop->VisitLoopBlocksReversePostOrder([=, &poTraits, &visitor](BasicBlock* block) {
if (!BitVecOps::TryAddElemD(&poTraits, m_visitedBlocks, block->bbPostorderNum))
{
return BasicBlockVisit::Continue;
}

for (Statement* stmt : block->NonPhiStatements())
{
visitor.WalkTree(stmt->GetRootNodePointer(), nullptr);
}

return BasicBlockVisit::Continue;
});

return map;
}

//------------------------------------------------------------------------------
// LoopDefinitions:VisitLoopNestMaps:
// Visit all occurrence maps of the specified loop nest.
//
// Type parameters:
// TFunc - bool(LocalToOccurrenceMap*) functor that returns true to continue
// the visit and false to abort.
//
// Parameters:
// loop - Root loop of the nest.
// func - Functor instance
//
// Returns:
// True if the visit completed; false if "func" returned false for any map.
//
template <typename TFunc>
bool LoopDefinitions::VisitLoopNestMaps(FlowGraphNaturalLoop* loop, TFunc& func)
{
for (FlowGraphNaturalLoop* child = loop->GetChild(); child != nullptr; child = child->GetSibling())
{
if (!VisitLoopNestMaps(child, func))
{
return false;
}
}

return func(GetOrCreateMap(loop));
}

//------------------------------------------------------------------------------
// LoopDefinitions:VisitDefinedLocalNums:
// Call a callback for all locals that are defined in the specified loop.
//
// Parameters:
// loop - The loop
// func - The callback
//
template <typename TFunc>
void LoopDefinitions::VisitDefinedLocalNums(FlowGraphNaturalLoop* loop, TFunc func)
{
auto visit = [=, &func](LocalDefinitionsMap* map) {
for (unsigned lclNum : LocalDefinitionsMap::KeyIteration(map))
{
func(lclNum);
}

return true;
};

VisitLoopNestMaps(loop, visit);
}

struct LocalEqualsLocalAddrAssertion
{
// Local num on the LHS
Expand Down Expand Up @@ -221,6 +418,7 @@ typedef JitHashTable<LocalEqualsLocalAddrAssertion, AssertionKeyFuncs, unsigned>
class LocalEqualsLocalAddrAssertions
{
Compiler* m_comp;
LoopDefinitions* m_loopDefs;
ArrayStack<LocalEqualsLocalAddrAssertion> m_assertions;
AssertionToIndexMap m_map;
uint64_t* m_lclAssertions;
Expand All @@ -234,8 +432,9 @@ class LocalEqualsLocalAddrAssertions
uint64_t CurrentAssertions = 0;
uint64_t AlwaysAssertions = 0;

LocalEqualsLocalAddrAssertions(Compiler* comp)
LocalEqualsLocalAddrAssertions(Compiler* comp, LoopDefinitions* loopDefs)
: m_comp(comp)
, m_loopDefs(loopDefs)
, m_assertions(comp->getAllocator(CMK_LocalAddressVisitor))
, m_map(comp->getAllocator(CMK_LocalAddressVisitor))
{
Expand Down Expand Up @@ -289,20 +488,19 @@ class LocalEqualsLocalAddrAssertions
//
void StartBlock(BasicBlock* block)
{
FlowEdge* preds;
if ((m_assertions.Height() == 0) || ((preds = m_comp->BlockPredsWithEH(block)) == nullptr))
CurrentAssertions = 0;
if (m_assertions.Height() == 0)
{
CurrentAssertions = 0;
AlwaysAssertions = 0;
AlwaysAssertions = 0;
return;
}

CurrentAssertions = UINT64_MAX;
FlowEdge* preds = m_comp->BlockPredsWithEH(block);
bool first = true;
FlowGraphNaturalLoop* loop = nullptr;

uint64_t* assertionMap = m_comp->bbIsHandlerBeg(block) ? m_alwaysTrueAssertions : m_outgoingAssertions;

INDEBUG(bool anyReachablePred = false);

for (FlowEdge* predEdge = preds; predEdge != nullptr; predEdge = predEdge->getNextPredEdge())
{
BasicBlock* pred = predEdge->getSourceBlock();
Expand All @@ -313,22 +511,46 @@ class LocalEqualsLocalAddrAssertions
continue;
}

INDEBUG(anyReachablePred = true);

if (pred->bbPostorderNum <= block->bbPostorderNum)
{
loop = m_comp->m_loops->GetLoopByHeader(block);
if ((loop != nullptr) && loop->ContainsBlock(pred))
{
JITDUMP("Ignoring loop backedge " FMT_BB "->" FMT_BB "\n", pred->bbNum, block->bbNum);
continue;
}

JITDUMP("Found non-loop backedge " FMT_BB "->" FMT_BB ", clearing assertions\n", pred->bbNum,
block->bbNum);
CurrentAssertions = 0;
break;
}

CurrentAssertions &= assertionMap[pred->bbPostorderNum];
uint64_t prevAssertions = assertionMap[pred->bbPostorderNum];
if (first)
{
CurrentAssertions = prevAssertions;
first = false;
}
else
{
CurrentAssertions &= prevAssertions;
}
}

// There should always be at least one reachable pred for all blocks.
assert(anyReachablePred);

AlwaysAssertions = CurrentAssertions;

if ((loop != nullptr) && (CurrentAssertions != 0))
{
JITDUMP("Block " FMT_BB " is a loop header; clearing assertions about defined locals\n", block->bbNum);
m_loopDefs->VisitDefinedLocalNums(loop, [=](unsigned lclNum) {
JITDUMP(" V%02u", lclNum);
Clear(lclNum);
});

JITDUMP("\n");
}

#ifdef DEBUG
if (CurrentAssertions != 0)
{
Expand Down Expand Up @@ -2162,7 +2384,10 @@ PhaseStatus Compiler::fgMarkAddressExposedLocals()
// We'll be using BlockPredsWithEH, so clear its cache.
m_blockToEHPreds = nullptr;

LocalEqualsLocalAddrAssertions assertions(this);
m_loops = FlowGraphNaturalLoops::Find(m_dfsTree);
LoopDefinitions loopDefs(m_loops);

LocalEqualsLocalAddrAssertions assertions(this, &loopDefs);
LocalEqualsLocalAddrAssertions* pAssertions = &assertions;

#ifdef DEBUG
Expand Down

0 comments on commit 9228ccf

Please sign in to comment.