Skip to content

Commit e6f143b

Browse files
authored
JIT: Add Statement::m_treeListEnd (#81031)
Before this change statements have only one field for the "tree list" even though bidirectional iteration is supported after nodes have been linked. This worked fine before the locals tree list existed since the "root node" was always the last node. This does not work for the locals tree list since the "root node" is not part of the list, meaning that we need somewhere else to store the 'last' node. Before this PR the assumption was that the root node was _never_ part of the locals linked list, so we could use the gtNext/gtPrev fields of the root node. However, the added test case shows that in fact it is possible we end up with a top level local. This PR fixes the problem by adding a Statement::m_treeListEnd field that can keep the last node of the locals linked list. While this takes some memory, it seems like the most maintainable way to resolve the problem. I experimented with making the linked list circular or by allocating a new stand-in node when necessary but eventually I ended up here. Fix #81018
1 parent 9f54ca6 commit e6f143b

File tree

9 files changed

+128
-40
lines changed

9 files changed

+128
-40
lines changed

src/coreclr/jit/compiler.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4609,15 +4609,15 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl
46094609
//
46104610
lvaRefCountState = RCS_EARLY;
46114611

4612-
// Figure out what locals are address-taken.
4613-
//
4614-
DoPhase(this, PHASE_STR_ADRLCL, &Compiler::fgMarkAddressExposedLocals);
4615-
46164612
if (opts.OptimizationEnabled())
46174613
{
46184614
fgNodeThreading = NodeThreading::AllLocals;
46194615
}
46204616

4617+
// Figure out what locals are address-taken.
4618+
//
4619+
DoPhase(this, PHASE_STR_ADRLCL, &Compiler::fgMarkAddressExposedLocals);
4620+
46214621
// Do an early pass of liveness for forward sub and morph. This data is
46224622
// valid until after morph.
46234623
//

src/coreclr/jit/compiler.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4531,8 +4531,8 @@ class Compiler
45314531
// doubly linked lists during certain phases of the compilation.
45324532
// - Local morph threads all locals to be used for early liveness and
45334533
// forward sub when optimizing. This is kept valid until after forward sub.
4534-
// The first local is kept in Statement::GetRootNode()->gtNext and the last
4535-
// local in Statement::GetRootNode()->gtPrev. fgSequenceLocals can be used
4534+
// The first local is kept in Statement::GetTreeList() and the last
4535+
// local in Statement::GetTreeListEnd(). fgSequenceLocals can be used
45364536
// to (re-)sequence a statement into this form, and
45374537
// Statement::LocalsTreeList for range-based iteration. The order must
45384538
// match tree order.

src/coreclr/jit/fgdiagnostic.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3412,15 +3412,25 @@ void Compiler::fgDebugCheckLinkedLocals()
34123412
{
34133413
for (Statement* stmt : block->Statements())
34143414
{
3415-
GenTree* first = stmt->GetRootNode()->gtNext;
3415+
GenTree* first = stmt->GetTreeList();
34163416
CheckDoublyLinkedList<GenTree, &GenTree::gtPrev, &GenTree::gtNext>(first);
34173417

34183418
seq.Sequence(stmt);
34193419

34203420
ArrayStack<GenTree*>* expected = seq.GetSequence();
34213421

3422-
bool success = true;
3423-
int nodeIndex = 0;
3422+
bool success = true;
3423+
3424+
if (expected->Height() > 0)
3425+
{
3426+
success &= (stmt->GetTreeList() == expected->Bottom(0)) && (stmt->GetTreeListEnd() == expected->Top(0));
3427+
}
3428+
else
3429+
{
3430+
success &= (stmt->GetTreeList() == nullptr) && (stmt->GetTreeListEnd() == nullptr);
3431+
}
3432+
3433+
int nodeIndex = 0;
34243434
for (GenTree* cur = first; cur != nullptr; cur = cur->gtNext)
34253435
{
34263436
success &= cur->OperIsLocal() || cur->OperIsLocalAddr();

src/coreclr/jit/gentree.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@ void GenTree::DumpNodeSizes(FILE* fp)
562562
//
563563
LocalsGenTreeList::iterator LocalsGenTreeList::begin() const
564564
{
565-
GenTree* first = m_stmt->GetRootNode()->gtNext;
565+
GenTree* first = m_stmt->GetTreeList();
566566
assert((first == nullptr) || first->OperIsLocal() || first->OperIsLocalAddr());
567567
return iterator(static_cast<GenTreeLclVarCommon*>(first));
568568
}
@@ -580,8 +580,8 @@ GenTree** LocalsGenTreeList::GetForwardEdge(GenTreeLclVarCommon* node)
580580
{
581581
if (node->gtPrev == nullptr)
582582
{
583-
assert(m_stmt->GetRootNode()->gtNext == node);
584-
return &m_stmt->GetRootNode()->gtNext;
583+
assert(m_stmt->GetTreeList() == node);
584+
return m_stmt->GetTreeListPointer();
585585
}
586586
else
587587
{
@@ -603,8 +603,8 @@ GenTree** LocalsGenTreeList::GetBackwardEdge(GenTreeLclVarCommon* node)
603603
{
604604
if (node->gtNext == nullptr)
605605
{
606-
assert(m_stmt->GetRootNode()->gtPrev == node);
607-
return &m_stmt->GetRootNode()->gtPrev;
606+
assert(m_stmt->GetTreeListEnd() == node);
607+
return m_stmt->GetTreeListEndPointer();
608608
}
609609
else
610610
{

src/coreclr/jit/gentree.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7449,6 +7449,7 @@ struct Statement
74497449
Statement(GenTree* expr DEBUGARG(unsigned stmtID))
74507450
: m_rootNode(expr)
74517451
, m_treeList(nullptr)
7452+
, m_treeListEnd(nullptr)
74527453
, m_next(nullptr)
74537454
, m_prev(nullptr)
74547455
#ifdef DEBUG
@@ -7478,11 +7479,31 @@ struct Statement
74787479
return m_treeList;
74797480
}
74807481

7482+
GenTree** GetTreeListPointer()
7483+
{
7484+
return &m_treeList;
7485+
}
7486+
74817487
void SetTreeList(GenTree* treeHead)
74827488
{
74837489
m_treeList = treeHead;
74847490
}
74857491

7492+
GenTree* GetTreeListEnd() const
7493+
{
7494+
return m_treeListEnd;
7495+
}
7496+
7497+
GenTree** GetTreeListEndPointer()
7498+
{
7499+
return &m_treeListEnd;
7500+
}
7501+
7502+
void SetTreeListEnd(GenTree* end)
7503+
{
7504+
m_treeListEnd = end;
7505+
}
7506+
74867507
GenTreeList TreeList() const;
74877508
LocalsGenTreeList LocalsTreeList();
74887509

@@ -7559,6 +7580,12 @@ struct Statement
75597580
// The value is `nullptr` until we have set the sequencing of the nodes.
75607581
GenTree* m_treeList;
75617582

7583+
// The tree list tail. Only valid when locals are linked (fgNodeThreading
7584+
// == AllLocals), in which case this is the last local.
7585+
// When all nodes are linked (fgNodeThreading == AllTrees), m_rootNode
7586+
// should be considered the last node.
7587+
GenTree* m_treeListEnd;
7588+
75627589
// The statement nodes are doubly-linked. The first statement node in a block points
75637590
// to the last node in the block via its `m_prev` link. Note that the last statement node
75647591
// does not point to the first: it has `m_next == nullptr`; that is, the list is not fully circular.

src/coreclr/jit/lclmorph.cpp

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
class LocalSequencer final : public GenTreeVisitor<LocalSequencer>
77
{
8-
GenTree* m_rootNode;
98
GenTree* m_prevNode;
109

1110
public:
@@ -15,7 +14,7 @@ class LocalSequencer final : public GenTreeVisitor<LocalSequencer>
1514
UseExecutionOrder = true,
1615
};
1716

18-
LocalSequencer(Compiler* comp) : GenTreeVisitor(comp), m_rootNode(nullptr), m_prevNode(nullptr)
17+
LocalSequencer(Compiler* comp) : GenTreeVisitor(comp), m_prevNode(nullptr)
1918
{
2019
}
2120

@@ -30,12 +29,10 @@ class LocalSequencer final : public GenTreeVisitor<LocalSequencer>
3029
{
3130
// We use the root node as a 'sentinel' node that will keep the head
3231
// and tail of the sequenced list.
33-
m_rootNode = stmt->GetRootNode();
34-
assert(!m_rootNode->OperIsLocal() && !m_rootNode->OperIsLocalAddr());
35-
36-
m_rootNode->gtPrev = nullptr;
37-
m_rootNode->gtNext = nullptr;
38-
m_prevNode = m_rootNode;
32+
GenTree* rootNode = stmt->GetRootNode();
33+
rootNode->gtPrev = nullptr;
34+
rootNode->gtNext = nullptr;
35+
m_prevNode = rootNode;
3936
}
4037

4138
//-------------------------------------------------------------------
@@ -47,26 +44,35 @@ class LocalSequencer final : public GenTreeVisitor<LocalSequencer>
4744
//
4845
void Finish(Statement* stmt)
4946
{
50-
assert(stmt->GetRootNode() == m_rootNode);
47+
GenTree* rootNode = stmt->GetRootNode();
48+
49+
GenTree* firstNode = rootNode->gtNext;
50+
GenTree* lastNode = m_prevNode;
5151

52-
GenTree* firstNode = m_rootNode->gtNext;
5352
if (firstNode == nullptr)
5453
{
55-
assert(m_rootNode->gtPrev == nullptr);
54+
lastNode = nullptr;
5655
}
5756
else
5857
{
59-
GenTree* lastNode = m_prevNode;
60-
61-
// We only sequence leaf nodes that we shouldn't see as standalone
62-
// statements here.
63-
assert(m_rootNode != firstNode);
64-
assert((m_rootNode->gtPrev == nullptr) && (lastNode->gtNext == nullptr));
58+
// In the rare case that the root node becomes part of the linked
59+
// list (i.e. top level local) we get a circular linked list here.
60+
if (firstNode == rootNode)
61+
{
62+
assert(firstNode == lastNode);
63+
lastNode->gtNext = nullptr;
64+
}
65+
else
66+
{
67+
assert(lastNode->gtNext == nullptr);
68+
assert(lastNode->OperIsLocal() || lastNode->OperIsLocalAddr());
69+
}
6570

66-
assert(lastNode->OperIsLocal() || lastNode->OperIsLocalAddr());
67-
firstNode->gtPrev = nullptr;
68-
m_rootNode->gtPrev = lastNode;
71+
firstNode->gtPrev = nullptr;
6972
}
73+
74+
stmt->SetTreeList(firstNode);
75+
stmt->SetTreeListEnd(lastNode);
7076
}
7177

7278
fgWalkResult PostOrderVisit(GenTree** use, GenTree* user)
@@ -202,7 +208,7 @@ class LocalSequencer final : public GenTreeVisitor<LocalSequencer>
202208
//
203209
void Compiler::fgSequenceLocals(Statement* stmt)
204210
{
205-
assert((fgNodeThreading == NodeThreading::AllLocals) || (mostRecentlyActivePhase == PHASE_STR_ADRLCL));
211+
assert(fgNodeThreading == NodeThreading::AllLocals);
206212
LocalSequencer seq(this);
207213
seq.Sequence(stmt);
208214
}

src/coreclr/jit/liveness.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ void Compiler::fgPerBlockLocalVarLiveness()
498498
{
499499
// Assigned local should be the very last local.
500500
assert((dst == nullptr) ||
501-
((stmt->GetRootNode()->gtPrev == dst) && ((dst->gtFlags & GTF_VAR_DEF) != 0)));
501+
((stmt->GetTreeListEnd() == dst) && ((dst->gtFlags & GTF_VAR_DEF) != 0)));
502502

503503
// Conservatively ignore defs that may be conditional
504504
// but would otherwise still interfere with the
@@ -1825,7 +1825,7 @@ GenTree* Compiler::fgTryRemoveDeadStoreEarly(Statement* stmt, GenTreeLclVarCommo
18251825

18261826
JITDUMP("Store [%06u] is dead", dspTreeID(stmt->GetRootNode()));
18271827
// The def ought to be the last thing.
1828-
assert(stmt->GetRootNode()->gtPrev == cur);
1828+
assert(stmt->GetTreeListEnd() == cur);
18291829

18301830
GenTree* sideEffects = nullptr;
18311831
gtExtractSideEffList(stmt->GetRootNode()->gtGetOp2(), &sideEffects);
@@ -1844,7 +1844,7 @@ GenTree* Compiler::fgTryRemoveDeadStoreEarly(Statement* stmt, GenTreeLclVarCommo
18441844
DISPTREE(sideEffects);
18451845
JITDUMP("\n");
18461846
// continue at tail of the side effects
1847-
return stmt->GetRootNode()->gtPrev;
1847+
return stmt->GetTreeListEnd();
18481848
}
18491849
}
18501850

@@ -2820,7 +2820,7 @@ void Compiler::fgInterBlockLocalVarLiveness()
28202820

28212821
if (qmark != nullptr)
28222822
{
2823-
for (GenTree* cur = stmt->GetRootNode()->gtPrev; cur != nullptr;)
2823+
for (GenTree* cur = stmt->GetTreeListEnd(); cur != nullptr;)
28242824
{
28252825
assert(cur->OperIsLocal() || cur->OperIsLocalAddr());
28262826
bool isDef = ((cur->gtFlags & GTF_VAR_DEF) != 0) && ((cur->gtFlags & GTF_VAR_USEASG) == 0);
@@ -2846,7 +2846,7 @@ void Compiler::fgInterBlockLocalVarLiveness()
28462846
}
28472847
else
28482848
{
2849-
for (GenTree* cur = stmt->GetRootNode()->gtPrev; cur != nullptr;)
2849+
for (GenTree* cur = stmt->GetTreeListEnd(); cur != nullptr;)
28502850
{
28512851
assert(cur->OperIsLocal() || cur->OperIsLocalAddr());
28522852
if (!fgComputeLifeLocal(life, keepAliveVars, cur))
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
// Generated by Fuzzlyn v1.5 on 2023-01-22 16:00:16
5+
// Run on Arm64 Windows
6+
// Seed: 17286164302317655577
7+
// Reduced from 117.6 KiB to 0.4 KiB in 00:02:13
8+
// Hits JIT assert in Release:
9+
// Assertion failed '!m_rootNode->OperIsLocal() && !m_rootNode->OperIsLocalAddr()' in 'Program:Main(Fuzzlyn.ExecutionServer.IRuntime)' during 'Morph - Structs/AddrExp' (IL size 83; hash 0xade6b36b; FullOpts)
10+
//
11+
// File: D:\a\_work\1\s\src\coreclr\jit\lclmorph.cpp Line: 34
12+
//
13+
public interface I0
14+
{
15+
}
16+
17+
public struct S0 : I0
18+
{
19+
public sbyte F0;
20+
public S0 M17(I0 arg0, ulong arg1)
21+
{
22+
return this;
23+
}
24+
}
25+
26+
public class Runtime_81018
27+
{
28+
public static ulong s_2;
29+
public static int Main()
30+
{
31+
var vr6 = new S0();
32+
var vr7 = new S0();
33+
new S0().M17(new S0().M17(vr7, 0).M17(vr6, s_2), s_2);
34+
return 100;
35+
}
36+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<Optimize>True</Optimize>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<Compile Include="$(MSBuildProjectName).cs" />
8+
</ItemGroup>
9+
</Project>

0 commit comments

Comments
 (0)