Skip to content

Commit d964353

Browse files
Add detailed logging for not-spilling reasons to LSSA (#3136)
1 parent 933c4e2 commit d964353

File tree

1 file changed

+190
-35
lines changed

1 file changed

+190
-35
lines changed

src/coreclr/jit/llvmlssa.cpp

Lines changed: 190 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,9 @@ class ShadowStackAllocator
241241

242242
class LssaDomTreeVisitor : public DomTreeVisitor<LssaDomTreeVisitor>
243243
{
244+
class NotExposedReason;
245+
struct NotExposedReasonLink;
246+
244247
// Cannot use raw node pointers as their values influence hash table iteration order.
245248
struct DeterministicNodeHashInfo : public HashTableInfo<DeterministicNodeHashInfo>
246249
{
@@ -326,7 +329,7 @@ class ShadowStackAllocator
326329
ArrayStack<BitVec> m_candidateBitSetPool;
327330

328331
#ifdef DEBUG
329-
JitHashTable<LclSsaVarDsc*, JitPtrKeyFuncs<LclSsaVarDsc>, const char*> m_gcExposedStatusReasonMap;
332+
JitHashTable<LclSsaVarDsc*, JitPtrKeyFuncs<LclSsaVarDsc>, NotExposedReasonLink*> m_notExposedReasonMap;
330333
#endif // DEBUG
331334

332335
public:
@@ -345,7 +348,7 @@ class ShadowStackAllocator
345348
, m_candidateBitSetTraits(lssa->m_largestCandidateVarIndexPlusOne, m_compiler)
346349
, m_candidateBitSetPool(m_compiler->getAllocator(CMK_LSRA))
347350
#ifdef DEBUG
348-
, m_gcExposedStatusReasonMap(m_compiler->getAllocator(CMK_DebugOnly))
351+
, m_notExposedReasonMap(m_compiler->getAllocator(CMK_DebugOnly))
349352
#endif // DEBUG
350353
{
351354
}
@@ -550,7 +553,7 @@ class ShadowStackAllocator
550553
if (IsCandidateLocal(varDsc))
551554
{
552555
unsigned ssaNum = m_activeDefs.Top(lclVarIndex);
553-
SpillLocalValue(lclNum, ssaNum DEBUGARG("is live"));
556+
SpillLocalValue(lclNum, ssaNum DEBUGARG(node));
554557
}
555558
}
556559
}
@@ -616,6 +619,7 @@ class ShadowStackAllocator
616619
if (*pSpillLclNum != BAD_VAR_NUM)
617620
{
618621
// We may have already spilled this def live across multiple safe points.
622+
JITDUMP("[%06u] is live, but not exposed: already spilled\n", Compiler::dspTreeID(defNode));
619623
return;
620624
}
621625

@@ -629,15 +633,15 @@ class ShadowStackAllocator
629633
*pSpillLclNum = spillLclNum;
630634
}
631635

632-
void SpillLocalValue(unsigned lclNum, unsigned ssaNum DEBUGARG(const char* spillReason))
636+
void SpillLocalValue(unsigned lclNum, unsigned ssaNum DEBUGARG(GenTree* safePoint))
633637
{
634638
LclVarDsc* varDsc = m_compiler->lvaGetDesc(lclNum);
635639
LclSsaVarDsc* ssaDsc = varDsc->GetPerSsaData(ssaNum);
636640

637-
INDEBUG(const char* reason);
641+
INDEBUG(NotExposedReason reason(this));
638642
if (!IsGcExposedLocalValue(varDsc, ssaNum, DefStatus::Active DEBUGARG(&reason)))
639643
{
640-
JITDUMP("V%02u/%d %s, but did not insert a spill: %s\n", lclNum, ssaNum, spillReason, reason);
644+
JITDUMPEXEC(reason.Print("V%02u/%d is live, but not exposed: ", "\n", lclNum, ssaNum));
641645
return;
642646
}
643647

@@ -667,28 +671,30 @@ class ShadowStackAllocator
667671
defBlockRange.InsertAfter(defNode, value, store);
668672
}
669673

670-
JITDUMP("V%02u/%d %s, inserted a spill in " FMT_BB ":\n", lclNum, ssaNum, spillReason, block->bbNum);
674+
JITDUMP("V%02u/%d is live, inserted a spill in " FMT_BB ":\n", lclNum, ssaNum, block->bbNum);
671675
DISPTREERANGE(defBlockRange, store);
672676

677+
INDEBUG(NotExposedReason spillReason(this));
678+
INDEBUG(spillReason.AddSpill(safePoint));
673679
varDsc->SetRegNum(REG_STK_CANDIDATE_COMMITED);
674-
SetGcExposedStatus(ssaDsc, GC_EXPOSED_SPILL DEBUGARG("already spilled"));
680+
SetGcExposedStatus(ssaDsc, GC_EXPOSED_SPILL DEBUGARG(varDsc) DEBUGARG(&spillReason));
675681
UpdateShadowSlotsStateForSpilledDef(varDsc, ssaDsc);
676682
}
677683

678-
bool IsGcExposedValue(GenTree* node, DefStatus defStatus)
684+
bool IsGcExposedValue(GenTree* node, DefStatus defStatus DEBUGARG(NotExposedReason* pReason))
679685
{
680686
// TODO-LLVM-LSSA-CQ: add handling for PHIs here. Take note to handle cycles properly.
681687
assert(node->IsValue());
682688
LclVarDsc* varDsc;
683689
if (IsCandidateLocalNode(node, &varDsc))
684690
{
685-
return IsGcExposedLocalValue(varDsc, node->AsLclVarCommon()->GetSsaNum(), defStatus);
691+
return IsGcExposedLocalValue(varDsc, node->AsLclVarCommon()->GetSsaNum(), defStatus DEBUGARG(pReason));
686692
}
687693

688-
return IsGcExposedSdsuValue(node, defStatus);
694+
return IsGcExposedSdsuValue(node, defStatus DEBUGARG(pReason));
689695
}
690696

691-
bool IsGcExposedSdsuValue(GenTree* node, DefStatus defStatus)
697+
bool IsGcExposedSdsuValue(GenTree* node, DefStatus defStatus DEBUGARG(NotExposedReason* pReason))
692698
{
693699
assert(node->IsValue() && !IsCandidateLocalNode(node));
694700

@@ -697,10 +703,13 @@ class ShadowStackAllocator
697703
// of the underlying object, or be performed on pinned values only.
698704
if (node->OperIs(GT_ADD, GT_SUB) && node->gtGetOp2()->TypeIs(TYP_I_IMPL))
699705
{
700-
return IsGcExposedValue(node->gtGetOp1(), DefStatus::Unknown);
706+
if (!IsGcExposedValue(node->gtGetOp1(), DefStatus::Unknown DEBUGARG(pReason)))
707+
{
708+
INDEBUG(pReason->AddOp(node));
709+
return false;
710+
}
701711
}
702-
703-
if (node->OperIsLocalRead())
712+
else if (node->OperIsLocalRead())
704713
{
705714
// For non-candidate locals, all definitions are spilled to the shadow stack. Thus, the only
706715
// way for a value to get exposed is if something modifies the backing location. This can
@@ -712,35 +721,36 @@ class ShadowStackAllocator
712721
// even a 'normal' untracked local may be redefined via a local store; we handle this via
713722
// the check on 'defStatus'.
714723
//
715-
if ((defStatus == DefStatus::Unknown) ||
716-
m_compiler->lvaGetDesc(node->AsLclVarCommon())->IsAddressExposed())
724+
if ((defStatus == DefStatus::Active) &&
725+
!m_compiler->lvaGetDesc(node->AsLclVarCommon())->IsAddressExposed())
717726
{
718-
return true;
727+
INDEBUG(pReason->AddNonCandidate(node->AsLclVarCommon()));
728+
return false;
719729
}
720-
721-
return false;
722730
}
723-
724731
// Local address nodes always point to the stack (native or shadow). Constant handles will
725732
// only point to immortal and immovable (frozen) objects. TODO-LLVM-LSSA-CQ: add LCLHEAP here.
726-
if (node->OperIs(GT_LCL_ADDR) || node->IsIconHandle() || node->IsIntegralConst(0))
733+
else if (node->OperIs(GT_LCL_ADDR) || node->IsIconHandle() || node->IsIntegralConst(0))
727734
{
735+
INDEBUG(pReason->AddOp(node));
728736
return false;
729737
}
730738

731739
return true;
732740
}
733741

734742
bool IsGcExposedLocalValue(
735-
LclVarDsc* varDsc, unsigned ssaNum, DefStatus defStatus DEBUGARG(const char** pReason = nullptr))
743+
LclVarDsc* varDsc, unsigned ssaNum, DefStatus defStatus DEBUGARG(NotExposedReason* pReason))
736744
{
745+
assert(IsCandidateLocal(varDsc));
746+
737747
LclSsaVarDsc* ssaDsc = varDsc->GetPerSsaData(ssaNum);
738-
unsigned status = GetGcExposedStatus(ssaDsc DEBUGARG(pReason));
748+
unsigned status = GetGcExposedStatus(ssaDsc);
739749
if (status == GC_EXPOSED_UNKNOWN)
740750
{
741751
bool isExposed = true;
742-
INDEBUG(const char* reason = nullptr);
743752
GenTreeLclVarCommon* defNode = ssaDsc->GetDefNode();
753+
INDEBUG(NotExposedReason defReason(this));
744754
if (defNode == nullptr)
745755
{
746756
assert(ssaNum == SsaConfig::FIRST_SSA_NUM);
@@ -750,20 +760,19 @@ class ShadowStackAllocator
750760
assert(initKind != ValueInitKind::None);
751761
if (initKind != ValueInitKind::Param)
752762
{
753-
INDEBUG(reason = "value is zero at implicit def");
763+
INDEBUG(defReason.Add("implicit zero def"));
754764
isExposed = false;
755765
}
756766
}
757-
else if (defNode->OperIs(GT_STORE_LCL_VAR) && !IsGcExposedValue(defNode->Data(), DefStatus::Unknown))
767+
else if (defNode->OperIs(GT_STORE_LCL_VAR) &&
768+
!IsGcExposedValue(defNode->Data(), DefStatus::Unknown DEBUGARG(&defReason)))
758769
{
759-
INDEBUG(reason = "value not exposed");
760770
isExposed = false;
761771
}
762772

763773
// This caching is designed to prevent quadratic behavior.
764774
status = isExposed ? GC_EXPOSED_YES : GC_EXPOSED_NO;
765-
SetGcExposedStatus(ssaDsc, status DEBUGARG(reason));
766-
DBEXEC(pReason != nullptr, *pReason = reason);
775+
SetGcExposedStatus(ssaDsc, status DEBUGARG(varDsc) DEBUGARG(&defReason));
767776
}
768777
// Take care not to depend on potentially stale information. Consider:
769778
//
@@ -787,6 +796,7 @@ class ShadowStackAllocator
787796
}
788797

789798
assert(status != GC_EXPOSED_UNKNOWN);
799+
DBEXEC(status != GC_EXPOSED_YES, pReason->AddDef(varDsc, ssaNum));
790800
return status == GC_EXPOSED_YES;
791801
}
792802

@@ -799,6 +809,7 @@ class ShadowStackAllocator
799809
}
800810

801811
LclVarDsc* varDsc;
812+
INDEBUG(NotExposedReason reason(this));
802813
if (IsCandidateLocalNode(node, &varDsc))
803814
{
804815
JITDUMP(" -- Processing a candidate:\n");
@@ -826,7 +837,7 @@ class ShadowStackAllocator
826837
}
827838
}
828839
else if (node->IsValue() && !node->IsUnusedValue() && IsGcExposedType(node) &&
829-
IsGcExposedSdsuValue(node, DefStatus::Active))
840+
IsGcExposedSdsuValue(node, DefStatus::Active DEBUGARG(&reason)))
830841
{
831842
node->gtLIRFlags |= LIR::Flags::Mark;
832843
m_liveSdsuGcDefs.AddOrUpdate(node, BAD_VAR_NUM);
@@ -1100,20 +1111,23 @@ class ShadowStackAllocator
11001111
m_candidateBitSetPool.Push(bitSet);
11011112
}
11021113

1103-
void SetGcExposedStatus(LclSsaVarDsc* ssaDsc, unsigned status DEBUGARG(const char* reason))
1114+
void SetGcExposedStatus(
1115+
LclSsaVarDsc* ssaDsc, unsigned status DEBUGARG(LclVarDsc* varDsc) DEBUGARG(NotExposedReason* pReason))
11041116
{
11051117
unsigned oldStatus = GetGcExposedStatus(ssaDsc);
11061118
assert((oldStatus == GC_EXPOSED_UNKNOWN) || ((oldStatus == GC_EXPOSED_YES) && (status == GC_EXPOSED_SPILL)));
11071119
assert(status != GC_EXPOSED_UNKNOWN);
11081120

11091121
unsigned* pStatus = GetRawDefStatusRef(ssaDsc);
1110-
DBEXEC(reason != nullptr, m_gcExposedStatusReasonMap.Set(ssaDsc, reason));
11111122
*pStatus = (*pStatus & ~GC_EXPOSED_MASK) | status;
1123+
1124+
JITDUMP("V%02u/%u: %s -> %s\n", m_compiler->lvaGetLclNum(varDsc),
1125+
varDsc->GetSsaNumForSsaDef(ssaDsc), GcStatusToString(oldStatus), GcStatusToString(status));
1126+
INDEBUG(pReason->SaveForDef(ssaDsc));
11121127
}
11131128

1114-
unsigned GetGcExposedStatus(LclSsaVarDsc* ssaDsc DEBUGARG(const char** pReason = nullptr))
1129+
unsigned GetGcExposedStatus(LclSsaVarDsc* ssaDsc)
11151130
{
1116-
INDEBUG(m_gcExposedStatusReasonMap.Lookup(ssaDsc, pReason));
11171131
return *GetRawDefStatusRef(ssaDsc) & GC_EXPOSED_MASK;
11181132
}
11191133

@@ -1321,6 +1335,147 @@ class ShadowStackAllocator
13211335
}
13221336
// printf("}\n");
13231337
}
1338+
1339+
const char* GcStatusToString(unsigned status)
1340+
{
1341+
switch (status)
1342+
{
1343+
case GC_EXPOSED_NO:
1344+
return "GC_EXPOSED_NO";
1345+
case GC_EXPOSED_SPILL:
1346+
return "GC_EXPOSED_SPILL";
1347+
case GC_EXPOSED_YES:
1348+
return "GC_EXPOSED_YES";
1349+
case GC_EXPOSED_UNKNOWN:
1350+
return "GC_EXPOSED_UNKNOWN";
1351+
default:
1352+
unreached();
1353+
}
1354+
}
1355+
1356+
struct NotExposedReasonLink
1357+
{
1358+
const char* Reason;
1359+
NotExposedReasonLink* Next;
1360+
};
1361+
1362+
class NotExposedReason
1363+
{
1364+
LssaDomTreeVisitor* m_visitor;
1365+
ArrayStack<const char*> m_chain;
1366+
bool m_enabled;
1367+
1368+
public:
1369+
NotExposedReason(LssaDomTreeVisitor* visitor)
1370+
: m_visitor(visitor)
1371+
, m_chain(visitor->m_compiler->getAllocator(CMK_DebugOnly))
1372+
, m_enabled(visitor->m_compiler->verbose)
1373+
{
1374+
}
1375+
1376+
void AddOp(GenTree* node)
1377+
{
1378+
if (m_enabled)
1379+
{
1380+
Add("%s [%06u]", GenTree::OpName(node->OperGet()), Compiler::dspTreeID(node));
1381+
}
1382+
}
1383+
1384+
void AddNonCandidate(GenTreeLclVarCommon* node)
1385+
{
1386+
if (m_enabled)
1387+
{
1388+
Add("V%02u [%06u]", node->GetLclNum(), Compiler::dspTreeID(node));
1389+
}
1390+
}
1391+
1392+
void AddDef(LclVarDsc* varDsc, unsigned ssaNum)
1393+
{
1394+
if (m_enabled)
1395+
{
1396+
LclSsaVarDsc* ssaDsc = varDsc->GetPerSsaData(ssaNum);
1397+
NotExposedReasonLink* link = m_visitor->m_notExposedReasonMap[ssaDsc];
1398+
while (link != nullptr)
1399+
{
1400+
Add(link->Reason);
1401+
link = link->Next;
1402+
}
1403+
1404+
unsigned lclNum = m_visitor->m_compiler->lvaGetLclNum(varDsc);
1405+
Add("V%02u/%u def", lclNum, ssaNum);
1406+
}
1407+
}
1408+
1409+
void AddSpill(GenTree* safePoint)
1410+
{
1411+
if (m_enabled)
1412+
{
1413+
Add("spilled due to [%06u]", Compiler::dspTreeID(safePoint));
1414+
}
1415+
}
1416+
1417+
template <typename... TArgs>
1418+
void Add(const char* linkFmt, TArgs... args)
1419+
{
1420+
const char* link = m_visitor->m_compiler->printfAlloc(linkFmt, args...);
1421+
Add(link);
1422+
}
1423+
1424+
void Add(const char* link)
1425+
{
1426+
assert(m_enabled);
1427+
m_chain.Push(link);
1428+
}
1429+
1430+
void SaveForDef(LclSsaVarDsc* ssaDsc)
1431+
{
1432+
CompAllocator alloc = m_visitor->m_compiler->getAllocator(CMK_DebugOnly);
1433+
NotExposedReasonLink* links = nullptr;
1434+
NotExposedReasonLink* last = nullptr;
1435+
for (int i = 0; i < m_chain.Height(); i++)
1436+
{
1437+
NotExposedReasonLink* link = new (alloc) NotExposedReasonLink();
1438+
link->Reason = m_chain.Bottom(i);
1439+
if (last == nullptr)
1440+
{
1441+
links = link;
1442+
}
1443+
else
1444+
{
1445+
last->Next = link;
1446+
}
1447+
last = link;
1448+
}
1449+
if (links != nullptr)
1450+
{
1451+
m_visitor->m_notExposedReasonMap.Set(ssaDsc, links);
1452+
}
1453+
}
1454+
1455+
template <typename... TArgs>
1456+
void Print(const char* prefix, const char* postfix, TArgs... args)
1457+
{
1458+
printf(prefix, args...);
1459+
PrintLinks();
1460+
printf("%s", postfix);
1461+
}
1462+
1463+
private:
1464+
void PrintLinks()
1465+
{
1466+
for (int i = m_chain.Height() - 1; i >= 0; i--)
1467+
{
1468+
const char* link = m_chain.Bottom(i);
1469+
assert(link != nullptr);
1470+
printf("%s", link);
1471+
1472+
if (i != 0)
1473+
{
1474+
printf(" <= ");
1475+
}
1476+
}
1477+
}
1478+
};
13241479
#endif // DEBUG
13251480
};
13261481

0 commit comments

Comments
 (0)