Skip to content

Commit 1bebdb0

Browse files
authored
JIT: Generalize strategy for finding addrecs (#99048)
When looking at a PHI, utilize a strategy where a symbolic node representing the recurrence is inserted in the cache, after which the analysis is invoked recursively. The created SCEV can afterwards be turned into a recurrence by considering the symbolic node to be a recursive occurrence.
1 parent 8b8d1ce commit 1bebdb0

File tree

4 files changed

+241
-35
lines changed

4 files changed

+241
-35
lines changed

src/coreclr/jit/compiler.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9717,6 +9717,7 @@ JITDBGAPI void __cdecl cScev(Compiler* comp, Scev* scev)
97179717
else
97189718
{
97199719
scev->Dump(comp);
9720+
printf("\n");
97209721
}
97219722
}
97229723

src/coreclr/jit/inductionvariableopts.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,7 @@ PhaseStatus Compiler::optInductionVariables()
656656
initStmt->GetNextStmt());
657657
}
658658

659-
JITDUMP(" Replacing in the loop; %d statements with appearences\n", ivUses.Height());
659+
JITDUMP(" Replacing in the loop; %d statements with appearances\n", ivUses.Height());
660660
for (int i = 0; i < ivUses.Height(); i++)
661661
{
662662
Statement* stmt = ivUses.Bottom(i);

src/coreclr/jit/scev.cpp

Lines changed: 172 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,7 @@
5555
// straightforward translation from JIT IR into the SCEV IR. Creating the add
5656
// recurrences requires paying attention to the structure of PHIs, and
5757
// disambiguating the values coming from outside the loop and the values coming
58-
// from the backedges. Currently only simplistic add recurrences that do not
59-
// require recursive analysis are supported. These simplistic add recurrences
60-
// are always on the form i = i + k.
58+
// from the backedges.
6159
//
6260

6361
#include "jitpch.h"
@@ -208,7 +206,7 @@ void Scev::Dump(Compiler* comp)
208206
// ResetForLoop.
209207
//
210208
ScalarEvolutionContext::ScalarEvolutionContext(Compiler* comp)
211-
: m_comp(comp), m_cache(comp->getAllocator(CMK_LoopIVOpts))
209+
: m_comp(comp), m_cache(comp->getAllocator(CMK_LoopIVOpts)), m_ephemeralCache(comp->getAllocator(CMK_LoopIVOpts))
212210
{
213211
}
214212

@@ -471,34 +469,34 @@ Scev* ScalarEvolutionContext::AnalyzeNew(BasicBlock* block, GenTree* tree, int d
471469

472470
assert(ssaDsc->GetBlock() != nullptr);
473471

474-
// We currently do not handle complicated addrecs. We can do this
475-
// by inserting a symbolic node in the cache and analyzing while it
476-
// is part of the cache. It would allow us to model
477-
//
478-
// int i = 0;
479-
// while (i < n)
480-
// {
481-
// int j = i + 1;
482-
// ...
483-
// i = j;
484-
// }
485-
// => <L, 0, 1>
486-
//
487-
// and chains of recurrences, such as
488-
//
489-
// int i = 0;
490-
// int j = 0;
491-
// while (i < n)
492-
// {
493-
// j++;
494-
// i += j;
495-
// }
496-
// => <L, 0, <L, 1, 1>>
497-
//
498-
// The main issue is that it requires cache invalidation afterwards
499-
// and turning the recursive result into an addrec.
500-
//
501-
return CreateSimpleAddRec(store, enterScev, ssaDsc->GetBlock(), ssaDsc->GetDefNode()->Data());
472+
Scev* simpleAddRec = CreateSimpleAddRec(store, enterScev, ssaDsc->GetBlock(), ssaDsc->GetDefNode()->Data());
473+
if (simpleAddRec != nullptr)
474+
{
475+
return simpleAddRec;
476+
}
477+
478+
ScevConstant* symbolicAddRec = NewConstant(data->TypeGet(), 0xdeadbeef);
479+
m_ephemeralCache.Emplace(store, symbolicAddRec);
480+
481+
Scev* result;
482+
if (m_usingEphemeralCache)
483+
{
484+
result = Analyze(ssaDsc->GetBlock(), ssaDsc->GetDefNode()->Data(), depth + 1);
485+
}
486+
else
487+
{
488+
m_usingEphemeralCache = true;
489+
result = Analyze(ssaDsc->GetBlock(), ssaDsc->GetDefNode()->Data(), depth + 1);
490+
m_usingEphemeralCache = false;
491+
m_ephemeralCache.RemoveAll();
492+
}
493+
494+
if (result == nullptr)
495+
{
496+
return nullptr;
497+
}
498+
499+
return MakeAddRecFromRecursiveScev(enterScev, result, symbolicAddRec);
502500
}
503501
case GT_CAST:
504502
{
@@ -611,6 +609,138 @@ Scev* ScalarEvolutionContext::CreateSimpleAddRec(GenTreeLclVarCommon* headerStor
611609
return NewAddRec(enterScev, stepScev);
612610
}
613611

612+
//------------------------------------------------------------------------
613+
// ExtractAddOperands: Extract all operands of potentially nested add
614+
// operations.
615+
//
616+
// Parameters:
617+
// binop - The binop representing an add
618+
// operands - Array stack to add the operands to
619+
//
620+
void ScalarEvolutionContext::ExtractAddOperands(ScevBinop* binop, ArrayStack<Scev*>& operands)
621+
{
622+
assert(binop->OperIs(ScevOper::Add));
623+
624+
if (binop->Op1->OperIs(ScevOper::Add))
625+
{
626+
ExtractAddOperands(static_cast<ScevBinop*>(binop->Op1), operands);
627+
}
628+
else
629+
{
630+
operands.Push(binop->Op1);
631+
}
632+
633+
if (binop->Op2->OperIs(ScevOper::Add))
634+
{
635+
ExtractAddOperands(static_cast<ScevBinop*>(binop->Op2), operands);
636+
}
637+
else
638+
{
639+
operands.Push(binop->Op2);
640+
}
641+
}
642+
643+
//------------------------------------------------------------------------
644+
// MakeAddRecFromRecursiveScev: Given a recursive SCEV and a symbolic SCEV
645+
// whose appearances represent an occurrence of the full SCEV, create a
646+
// non-recursive add-rec from it.
647+
//
648+
// Parameters:
649+
// startScev - The start value of the addrec
650+
// scev - The scev
651+
// recursiveScev - A symbolic node whose appearance represents the value of "scev"
652+
//
653+
// Returns:
654+
// A non-recursive addrec
655+
//
656+
Scev* ScalarEvolutionContext::MakeAddRecFromRecursiveScev(Scev* startScev, Scev* scev, Scev* recursiveScev)
657+
{
658+
if (!scev->OperIs(ScevOper::Add))
659+
{
660+
return nullptr;
661+
}
662+
663+
ArrayStack<Scev*> addOperands(m_comp->getAllocator(CMK_LoopIVOpts));
664+
ExtractAddOperands(static_cast<ScevBinop*>(scev), addOperands);
665+
666+
assert(addOperands.Height() >= 2);
667+
668+
int numAppearances = 0;
669+
for (int i = 0; i < addOperands.Height(); i++)
670+
{
671+
Scev* addOperand = addOperands.Bottom(i);
672+
if (addOperand == recursiveScev)
673+
{
674+
numAppearances++;
675+
}
676+
else
677+
{
678+
ScevVisit result = addOperand->Visit([=](Scev* node) {
679+
if (node == recursiveScev)
680+
{
681+
return ScevVisit::Abort;
682+
}
683+
684+
return ScevVisit::Continue;
685+
});
686+
687+
if (result == ScevVisit::Abort)
688+
{
689+
// We do not handle nested occurrences. Some of these may be representable, some won't.
690+
return nullptr;
691+
}
692+
}
693+
}
694+
695+
if (numAppearances == 0)
696+
{
697+
// TODO-CQ: We currently cannot handle cases like
698+
// i = arr.Length;
699+
// j = i - 1;
700+
// i = j;
701+
// while (true) { ...; j = i - 1; i = j; }
702+
//
703+
// These cases can arise from loop structures like "for (int i =
704+
// arr.Length; --i >= 0;)" when Roslyn emits a "sub; dup; stloc"
705+
// sequence, and local prop + loop inversion converts the duplicated
706+
// local into a fully fledged IV.
707+
// In this case we see that i = <L, [i from outside loop], -1>, but for
708+
// j we will see <L, [i from outside loop], -1> + (-1) in this function
709+
// as the value coming around the backedge, and we cannot reconcile
710+
// this.
711+
//
712+
return nullptr;
713+
}
714+
715+
if (numAppearances > 1)
716+
{
717+
// Multiple occurrences -- cannot be represented as an addrec
718+
// (corresponds to a geometric progression).
719+
return nullptr;
720+
}
721+
722+
Scev* step = nullptr;
723+
for (int i = 0; i < addOperands.Height(); i++)
724+
{
725+
Scev* addOperand = addOperands.Bottom(i);
726+
if (addOperand == recursiveScev)
727+
{
728+
continue;
729+
}
730+
731+
if (step == nullptr)
732+
{
733+
step = addOperand;
734+
}
735+
else
736+
{
737+
step = NewBinop(ScevOper::Add, step, addOperand);
738+
}
739+
}
740+
741+
return NewAddRec(startScev, step);
742+
}
743+
614744
//------------------------------------------------------------------------
615745
// Analyze: Analyze the specified tree in the specified block.
616746
//
@@ -653,15 +783,23 @@ const int SCALAR_EVOLUTION_ANALYSIS_MAX_DEPTH = 64;
653783
Scev* ScalarEvolutionContext::Analyze(BasicBlock* block, GenTree* tree, int depth)
654784
{
655785
Scev* result;
656-
if (!m_cache.Lookup(tree, &result))
786+
if (!m_cache.Lookup(tree, &result) && (!m_usingEphemeralCache || !m_ephemeralCache.Lookup(tree, &result)))
657787
{
658788
if (depth >= SCALAR_EVOLUTION_ANALYSIS_MAX_DEPTH)
659789
{
660790
return nullptr;
661791
}
662792

663793
result = AnalyzeNew(block, tree, depth);
664-
m_cache.Set(tree, result);
794+
795+
if (m_usingEphemeralCache)
796+
{
797+
m_ephemeralCache.Set(tree, result, ScalarEvolutionMap::Overwrite);
798+
}
799+
else
800+
{
801+
m_cache.Set(tree, result);
802+
}
665803
}
666804

667805
return result;

src/coreclr/jit/scev.h

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ static bool ScevOperIs(ScevOper oper, ScevOper operFirst, Args... operTail)
3737
return oper == operFirst || ScevOperIs(oper, operTail...);
3838
}
3939

40+
enum class ScevVisit
41+
{
42+
Abort,
43+
Continue,
44+
};
45+
4046
struct Scev
4147
{
4248
const ScevOper Oper;
@@ -62,6 +68,8 @@ struct Scev
6268
#ifdef DEBUG
6369
void Dump(Compiler* comp);
6470
#endif
71+
template <typename TVisitor>
72+
ScevVisit Visit(TVisitor visitor);
6573
};
6674

6775
struct ScevConstant : Scev
@@ -119,6 +127,57 @@ struct ScevAddRec : Scev
119127
INDEBUG(FlowGraphNaturalLoop* const Loop);
120128
};
121129

130+
//------------------------------------------------------------------------
131+
// Scev::Visit: Recursively visit all SCEV nodes in the SCEV tree.
132+
//
133+
// Parameters:
134+
// visitor - Callback with signature Scev* -> ScevVisit.
135+
//
136+
// Returns:
137+
// ScevVisit::Abort if "visitor" aborted, otherwise ScevVisit::Continue.
138+
//
139+
// Remarks:
140+
// The visit is done in preorder.
141+
//
142+
template <typename TVisitor>
143+
ScevVisit Scev::Visit(TVisitor visitor)
144+
{
145+
if (visitor(this) == ScevVisit::Abort)
146+
return ScevVisit::Abort;
147+
148+
switch (Oper)
149+
{
150+
case ScevOper::Constant:
151+
case ScevOper::Local:
152+
break;
153+
case ScevOper::ZeroExtend:
154+
case ScevOper::SignExtend:
155+
return static_cast<ScevUnop*>(this)->Op1->Visit(visitor);
156+
case ScevOper::Add:
157+
case ScevOper::Mul:
158+
case ScevOper::Lsh:
159+
{
160+
ScevBinop* binop = static_cast<ScevBinop*>(this);
161+
if (binop->Op1->Visit(visitor) == ScevVisit::Abort)
162+
return ScevVisit::Abort;
163+
164+
return binop->Op2->Visit(visitor);
165+
}
166+
case ScevOper::AddRec:
167+
{
168+
ScevAddRec* addrec = static_cast<ScevAddRec*>(this);
169+
if (addrec->Start->Visit(visitor) == ScevVisit::Abort)
170+
return ScevVisit::Abort;
171+
172+
return addrec->Step->Visit(visitor);
173+
}
174+
default:
175+
unreached();
176+
}
177+
178+
return ScevVisit::Continue;
179+
}
180+
122181
typedef JitHashTable<GenTree*, JitPtrKeyFuncs<GenTree>, Scev*> ScalarEvolutionMap;
123182

124183
// Scalar evolution is analyzed in the context of a single loop, and are
@@ -130,14 +189,22 @@ class ScalarEvolutionContext
130189
FlowGraphNaturalLoop* m_loop = nullptr;
131190
ScalarEvolutionMap m_cache;
132191

192+
// During analysis of PHIs we insert a symbolic node representing the
193+
// "recurrence"; we use this cache to be able to invalidate things that end
194+
// up depending on the symbolic node quickly.
195+
ScalarEvolutionMap m_ephemeralCache;
196+
bool m_usingEphemeralCache = false;
197+
133198
Scev* Analyze(BasicBlock* block, GenTree* tree, int depth);
134199
Scev* AnalyzeNew(BasicBlock* block, GenTree* tree, int depth);
135200
Scev* CreateSimpleAddRec(GenTreeLclVarCommon* headerStore,
136201
ScevLocal* start,
137202
BasicBlock* stepDefBlock,
138203
GenTree* stepDefData);
204+
Scev* MakeAddRecFromRecursiveScev(Scev* start, Scev* scev, Scev* recursiveScev);
139205
Scev* CreateSimpleInvariantScev(GenTree* tree);
140206
Scev* CreateScevForConstant(GenTreeIntConCommon* tree);
207+
void ExtractAddOperands(ScevBinop* add, ArrayStack<Scev*>& operands);
141208

142209
public:
143210
ScalarEvolutionContext(Compiler* comp);

0 commit comments

Comments
 (0)