Skip to content

Commit 2f3b6e9

Browse files
authored
JIT: more bv-centric refactoring for escape analysis (#115291)
Rework connection graph building in anticipation of having potentially different sorts of escapes happen as we walk up a tree and switch which resource we are tracking. For example we may discover that a local doesn't escape in this tree, but its fields can escape. Make the ancestor walk responsible for recording escapes, instead of deferring to its caller. Also address a few small things deferred from the previous refactor.
1 parent efee748 commit 2f3b6e9

File tree

2 files changed

+70
-51
lines changed

2 files changed

+70
-51
lines changed

src/coreclr/jit/objectalloc.cpp

Lines changed: 52 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ void ObjectAllocator::PrepareAnalysis()
373373
//
374374
// If conditional escape analysis is enabled, we reserve the range [N...N+M-1]
375375
// for locals allocated during the conditional escape analysis expansions,
376-
// where N is the maximum number of pseudos.
376+
// where M is the maximum number of pseudos.
377377
//
378378
// We reserve the range [N+M ... N+2M-1] for pseudos.
379379
//
@@ -614,47 +614,51 @@ void ObjectAllocator::MarkEscapingVarsAndBuildConnGraph()
614614
unsigned const lclNum = tree->AsLclVarCommon()->GetLclNum();
615615
LclVarDsc* const lclDsc = m_compiler->lvaGetDesc(lclNum);
616616

617-
// If this local already escapes, no need to look further.
617+
// Are we tracking this local?
618618
//
619-
if (m_allocator->CanLclVarEscape(lclNum))
619+
if (!m_allocator->IsTrackedLocal(lclNum))
620620
{
621621
return Compiler::fgWalkResult::WALK_CONTINUE;
622622
}
623623

624-
bool lclEscapes = true;
624+
const unsigned lclIndex = m_allocator->LocalToIndex(lclNum);
625+
626+
// If this local already escapes, no need to look further.
627+
//
628+
if (m_allocator->CanIndexEscape(lclIndex))
629+
{
630+
return Compiler::fgWalkResult::WALK_CONTINUE;
631+
}
625632

626633
if (tree->OperIsLocalStore())
627634
{
628-
lclEscapes = false;
629635
m_allocator->CheckForGuardedAllocationOrCopy(m_block, m_stmt, use, lclNum);
630636
}
631-
else if (tree->OperIs(GT_LCL_VAR) && m_allocator->IsTrackedLocal(lclNum))
637+
else if (tree->OperIs(GT_LCL_VAR))
632638
{
633639
assert(tree == m_ancestors.Top());
634-
if (!m_allocator->CanLclVarEscapeViaParentStack(&m_ancestors, lclNum, m_block))
635-
{
636-
lclEscapes = false;
637-
}
640+
m_allocator->AnalyzeParentStack(&m_ancestors, lclIndex, m_block);
638641
}
639-
else if (tree->OperIs(GT_LCL_ADDR) && (lclDsc->TypeGet() == TYP_STRUCT) &&
640-
m_allocator->IsTrackedLocal(lclNum))
642+
else if (tree->OperIs(GT_LCL_ADDR) && (lclDsc->TypeGet() == TYP_STRUCT))
641643
{
642644
assert(tree == m_ancestors.Top());
643-
if (!m_allocator->CanLclVarEscapeViaParentStack(&m_ancestors, lclNum, m_block))
644-
{
645-
lclEscapes = false;
646-
}
645+
m_allocator->AnalyzeParentStack(&m_ancestors, lclIndex, m_block);
647646
}
648-
649-
if (lclEscapes)
647+
else if (tree->OperIs(GT_LCL_FLD))
650648
{
651-
if (!m_allocator->CanLclVarEscape(lclNum))
652-
{
653-
JITDUMP("V%02u first escapes via [%06u]\n", lclNum, m_compiler->dspTreeID(tree));
654-
}
649+
// We generally don't see these in early IR. Bail for now.
650+
//
651+
JITDUMP("V%02u local field at [%06u]\n", lclNum, m_compiler->dspTreeID(tree));
652+
m_allocator->MarkLclVarAsEscaping(lclNum);
653+
}
654+
else
655+
{
656+
assert((tree->OperIs(GT_LCL_ADDR) && (lclDsc->TypeGet() != TYP_STRUCT)));
657+
JITDUMP("V%02u address taken at [%06u]\n", lclNum, m_compiler->dspTreeID(tree));
655658
m_allocator->MarkLclVarAsEscaping(lclNum);
656659
}
657-
else if (!tree->OperIsLocalStore())
660+
661+
if (!m_allocator->CanIndexEscape(lclIndex) && !tree->OperIsLocalStore())
658662
{
659663
// Note uses of variables of interest to conditional escape analysis.
660664
//
@@ -1606,29 +1610,22 @@ unsigned int ObjectAllocator::MorphAllocObjNodeIntoStackAlloc(GenTreeAllocObj* a
16061610
}
16071611

16081612
//------------------------------------------------------------------------
1609-
// CanLclVarEscapeViaParentStack: Check if the local variable escapes via the given parent stack.
1613+
// AnalyzeParentStack: Check if the local variable escapes via the given parent stack.
16101614
// Update the connection graph as necessary.
16111615
//
16121616
// Arguments:
16131617
// parentStack - Parent stack of the current visit
1614-
// lclNum - Local variable number
1618+
// lclIndex - Index for a tracked, unescaped local referenced at the top of the stack
16151619
// block - basic block holding the trees
16161620
//
1617-
// Return Value:
1618-
// true if the local can escape via the parent stack; false otherwise
1619-
//
1620-
// Notes:
1621-
// The method currently treats all locals assigned to a field as escaping.
1622-
// The can potentially be tracked by special field edges in the connection graph.
1623-
//
1624-
bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack<GenTree*>* parentStack,
1625-
unsigned int lclNum,
1626-
BasicBlock* block)
1621+
void ObjectAllocator::AnalyzeParentStack(ArrayStack<GenTree*>* parentStack, unsigned int lclIndex, BasicBlock* block)
16271622
{
16281623
assert(parentStack != nullptr);
1629-
int parentIndex = 1;
1624+
assert(!CanIndexEscape(lclIndex));
16301625

1631-
LclVarDsc* const lclDsc = comp->lvaGetDesc(lclNum);
1626+
int parentIndex = 1;
1627+
const unsigned lclNum = IndexToLocal(lclIndex);
1628+
LclVarDsc* const lclDsc = comp->lvaGetDesc(lclNum);
16321629

16331630
bool keepChecking = true;
16341631
bool canLclVarEscapeViaParentStack = true;
@@ -1675,18 +1672,19 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack<GenTree*>* parent
16751672
break;
16761673
}
16771674

1678-
// Add an edge to the connection graph.
1679-
const unsigned int srcLclNum = lclNum;
1675+
const unsigned dstIndex = LocalToIndex(dstLclNum);
16801676

1681-
AddConnGraphEdge(dstLclNum, srcLclNum);
1677+
// Add an edge to the connection graph.
1678+
//
1679+
AddConnGraphEdgeIndex(dstIndex, lclIndex);
16821680
canLclVarEscapeViaParentStack = false;
16831681

16841682
// If the source of this store is an enumerator local,
16851683
// then the dest also becomes an enumerator local.
16861684
//
16871685
if (isCopy)
16881686
{
1689-
CheckForEnumeratorUse(srcLclNum, dstLclNum);
1687+
CheckForEnumeratorUse(lclNum, dstLclNum);
16901688
}
16911689

16921690
// Note that we modelled this store in the connection graph
@@ -1788,14 +1786,15 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack<GenTree*>* parent
17881786

17891787
if (base->OperIs(GT_LCL_ADDR))
17901788
{
1791-
unsigned const dstLclNum = base->AsLclVarCommon()->GetLclNum();
1792-
LclVarDsc* const dstDsc = comp->lvaGetDesc(dstLclNum);
1789+
unsigned const dstLclNum = base->AsLclVarCommon()->GetLclNum();
17931790

17941791
if (IsTrackedLocal(dstLclNum))
17951792
{
17961793
JITDUMP("... local.field store\n");
1794+
const unsigned dstIndex = LocalToIndex(dstLclNum);
17971795
// Add an edge to the connection graph.
1798-
AddConnGraphEdge(dstLclNum, lclNum);
1796+
//
1797+
AddConnGraphEdgeIndex(dstIndex, lclIndex);
17991798
canLclVarEscapeViaParentStack = false;
18001799
}
18011800
}
@@ -1902,7 +1901,12 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack<GenTree*>* parent
19021901
}
19031902
}
19041903

1905-
return canLclVarEscapeViaParentStack;
1904+
if (canLclVarEscapeViaParentStack)
1905+
{
1906+
JITDUMP("V%02u first escapes via [%06u]...[%06u]\n", lclNum, comp->dspTreeID(parentStack->Top()),
1907+
comp->dspTreeID(parentStack->Top(parentIndex)));
1908+
MarkLclVarAsEscaping(lclNum);
1909+
}
19061910
}
19071911

19081912
//------------------------------------------------------------------------
@@ -2510,11 +2514,11 @@ bool ObjectAllocator::AnalyzeIfCloningCanPreventEscape(BitVecTraits* bitVecTrait
25102514

25112515
// See what locals were "assigned" to the pseudo.
25122516
//
2513-
BitVec PseudoAdjacencies = m_ConnGraphAdjacencyMatrix[pseudoIndex];
2517+
BitVec pseudoAdjacencies = m_ConnGraphAdjacencyMatrix[pseudoIndex];
25142518

25152519
// If we found an allocation but didn't find any conditionally escaping uses, then cloning is of no use
25162520
//
2517-
if (BitVecOps::IsEmpty(bitVecTraits, PseudoAdjacencies))
2521+
if (BitVecOps::IsEmpty(bitVecTraits, pseudoAdjacencies))
25182522
{
25192523
JITDUMP(" No conditionally escaping uses under");
25202524
JITDUMPEXEC(DumpIndex(pseudoIndex));
@@ -2525,7 +2529,7 @@ bool ObjectAllocator::AnalyzeIfCloningCanPreventEscape(BitVecTraits* bitVecTrait
25252529

25262530
// Check if each conditionally escaping local escapes on its own; if so cloning is of no use
25272531
//
2528-
BitVecOps::Iter iterator(bitVecTraits, PseudoAdjacencies);
2532+
BitVecOps::Iter iterator(bitVecTraits, pseudoAdjacencies);
25292533
unsigned lclNumIndex = BAD_VAR_NUM;
25302534
while (canClone && iterator.NextElem(&lclNumIndex))
25312535
{

src/coreclr/jit/objectalloc.h

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ class ObjectAllocator final : public Phase
173173
unsigned LocalToIndex(unsigned lclNum);
174174
unsigned IndexToLocal(unsigned bvIndex);
175175
bool CanLclVarEscape(unsigned int lclNum);
176+
bool CanIndexEscape(unsigned int index);
176177
void MarkLclVarAsPossiblyStackPointing(unsigned int lclNum);
177178
void MarkIndexAsPossiblyStackPointing(unsigned int index);
178179
void MarkLclVarAsDefinitelyStackPointing(unsigned int lclNum);
@@ -204,7 +205,7 @@ class ObjectAllocator final : public Phase
204205
BasicBlock* block,
205206
Statement* stmt);
206207
struct BuildConnGraphVisitorCallbackData;
207-
bool CanLclVarEscapeViaParentStack(ArrayStack<GenTree*>* parentStack, unsigned int lclNum, BasicBlock* block);
208+
void AnalyzeParentStack(ArrayStack<GenTree*>* parentStack, unsigned int lclNum, BasicBlock* block);
208209
void UpdateAncestorTypes(GenTree* tree, ArrayStack<GenTree*>* parentStack, var_types newType, bool retypeFields);
209210
ObjectAllocationType AllocationKind(GenTree* tree);
210211

@@ -263,6 +264,21 @@ inline void ObjectAllocator::EnableObjectStackAllocation()
263264
m_IsObjectStackAllocationEnabled = true;
264265
}
265266

267+
//------------------------------------------------------------------------
268+
// CanIndexEscape: Returns true iff resource described by index can
269+
// potentially escape from the method
270+
//
271+
// Arguments:
272+
// index - bv index
273+
//
274+
// Return Value:
275+
// Returns true if so
276+
277+
inline bool ObjectAllocator::CanIndexEscape(unsigned int index)
278+
{
279+
return BitVecOps::IsMember(&m_bitVecTraits, m_EscapingPointers, index);
280+
}
281+
266282
//------------------------------------------------------------------------
267283
// CanLclVarEscape: Returns true iff local variable can
268284
// potentially escape from the method
@@ -280,8 +296,7 @@ inline bool ObjectAllocator::CanLclVarEscape(unsigned int lclNum)
280296
return true;
281297
}
282298

283-
const unsigned bvIndex = LocalToIndex(lclNum);
284-
return BitVecOps::IsMember(&m_bitVecTraits, m_EscapingPointers, bvIndex);
299+
return CanIndexEscape(LocalToIndex(lclNum));
285300
}
286301

287302
//------------------------------------------------------------------------

0 commit comments

Comments
 (0)