Skip to content

Commit f545368

Browse files
authored
JIT: Establish loop invariant base case based on IR (#97182)
Avoid having a cross-phase dependency on loop inversion here. Instead, validate that the condition is an actual zero-trip test.
1 parent 9429e43 commit f545368

File tree

6 files changed

+132
-28
lines changed

6 files changed

+132
-28
lines changed

src/coreclr/jit/compiler.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1958,7 +1958,7 @@ struct NaturalLoopIterInfo
19581958
unsigned IterVar = BAD_VAR_NUM;
19591959

19601960
#ifdef DEBUG
1961-
// Tree that initializes induction varaible outside the loop.
1961+
// Tree that initializes induction variable outside the loop.
19621962
// Only valid if HasConstInit is true.
19631963
GenTree* InitTree = nullptr;
19641964
#endif
@@ -2096,6 +2096,8 @@ class FlowGraphNaturalLoop
20962096
void MatchInit(NaturalLoopIterInfo* info, BasicBlock* initBlock, GenTree* init);
20972097
bool MatchLimit(unsigned iterVar, GenTree* test, NaturalLoopIterInfo* info);
20982098
bool CheckLoopConditionBaseCase(BasicBlock* initBlock, NaturalLoopIterInfo* info);
2099+
bool IsZeroTripTest(BasicBlock* initBlock, NaturalLoopIterInfo* info);
2100+
bool InitBlockEntersLoopOnTrue(BasicBlock* initBlock);
20992101
template<typename T>
21002102
static bool EvaluateRelop(T op1, T op2, genTreeOps oper);
21012103
public:

src/coreclr/jit/flowgraph.cpp

Lines changed: 128 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5029,7 +5029,7 @@ bool FlowGraphNaturalLoop::AnalyzeIteration(NaturalLoopIterInfo* info)
50295029

50305030
if (!CheckLoopConditionBaseCase(initBlock, info))
50315031
{
5032-
JITDUMP(" Loop condition is not always true\n");
5032+
JITDUMP(" Loop condition may not be true on the first iteration\n");
50335033
return false;
50345034
}
50355035

@@ -5263,11 +5263,10 @@ bool FlowGraphNaturalLoop::EvaluateRelop(T op1, T op2, genTreeOps oper)
52635263

52645264
//------------------------------------------------------------------------
52655265
// CheckLoopConditionBaseCase: Verify that the loop condition is true when the
5266-
// loop is entered (or validated immediately on entry).
5266+
// loop is entered.
52675267
//
52685268
// Returns:
5269-
// True if we could prove that the condition is true at all interesting
5270-
// points in the loop.
5269+
// True if we could prove that the condition is true on entry.
52715270
//
52725271
// Remarks:
52735272
// Currently handles the following cases:
@@ -5306,18 +5305,137 @@ bool FlowGraphNaturalLoop::CheckLoopConditionBaseCase(BasicBlock* initBlock, Nat
53065305
}
53075306

53085307
// Do we have a zero-trip test?
5309-
if (initBlock->KindIs(BBJ_COND))
5308+
if (initBlock->KindIs(BBJ_COND) && IsZeroTripTest(initBlock, info))
53105309
{
5311-
GenTree* enteringJTrue = initBlock->lastStmt()->GetRootNode();
5312-
assert(enteringJTrue->OperIs(GT_JTRUE));
5313-
if (enteringJTrue->gtGetOp1()->OperIsCompare() && ((enteringJTrue->gtGetOp1()->gtFlags & GTF_RELOP_ZTT) != 0))
5310+
return true;
5311+
}
5312+
5313+
return false;
5314+
}
5315+
5316+
//------------------------------------------------------------------------
5317+
// IsZeroTripTest: Check whether `initBlock`, a BBJ_COND block that enters the
5318+
// loop in one case and not in the other, implies that the loop invariant is
5319+
// true on entry.
5320+
//
5321+
// Returns:
5322+
// True if we could prove that the loop invariant is true on entry through
5323+
// "initBlock".
5324+
//
5325+
bool FlowGraphNaturalLoop::IsZeroTripTest(BasicBlock* initBlock, NaturalLoopIterInfo* info)
5326+
{
5327+
assert(initBlock->KindIs(BBJ_COND));
5328+
GenTree* enteringJTrue = initBlock->lastStmt()->GetRootNode();
5329+
assert(enteringJTrue->OperIs(GT_JTRUE));
5330+
GenTree* relop = enteringJTrue->gtGetOp1();
5331+
if (!relop->OperIsCmpCompare())
5332+
{
5333+
return false;
5334+
}
5335+
5336+
// Technically optExtractInitTestIncr only handles the "false"
5337+
// entry case, and preheader creation should ensure that that's the
5338+
// only time we'll see a BBJ_COND init block. However, it does not
5339+
// hurt to let this logic be correct by construction.
5340+
bool enterOnTrue = InitBlockEntersLoopOnTrue(initBlock);
5341+
5342+
JITDUMP(" Init block " FMT_BB " enters the loop when condition [%06u] evaluates to %s\n", initBlock->bbNum,
5343+
Compiler::dspTreeID(relop), enterOnTrue ? "true" : "false");
5344+
5345+
GenTree* limitCandidate;
5346+
genTreeOps oper;
5347+
5348+
if (relop->gtGetOp1()->OperIsScalarLocal() && (relop->gtGetOp1()->AsLclVarCommon()->GetLclNum() == info->IterVar))
5349+
{
5350+
JITDUMP(" op1 is the iteration variable\n");
5351+
oper = relop->gtOper;
5352+
limitCandidate = relop->gtGetOp2();
5353+
}
5354+
else if (relop->gtGetOp2()->OperIsScalarLocal() &&
5355+
(relop->gtGetOp2()->AsLclVarCommon()->GetLclNum() == info->IterVar))
5356+
{
5357+
JITDUMP(" op2 is the iteration variable\n");
5358+
oper = GenTree::SwapRelop(relop->gtOper);
5359+
limitCandidate = relop->gtGetOp1();
5360+
}
5361+
else
5362+
{
5363+
JITDUMP(" Relop does not involve iteration variable\n");
5364+
return false;
5365+
}
5366+
5367+
if (!enterOnTrue)
5368+
{
5369+
oper = GenTree::ReverseRelop(oper);
5370+
}
5371+
5372+
// Here we want to prove that [iterVar] [oper] [limitCandidate] implies
5373+
// [iterVar] [info->IterOper()] [info->Limit()]. Currently we just do the
5374+
// simple exact checks, but this could be improved. Note that using
5375+
// `GenTree::Compare` for the limits is ok for a "same value" check for the
5376+
// limited shapes of limits we recognize.
5377+
//
5378+
if ((relop->IsUnsigned() != info->TestTree->IsUnsigned()) || (oper != info->TestOper()) ||
5379+
!GenTree::Compare(limitCandidate, info->Limit()))
5380+
{
5381+
JITDUMP(" Condition guarantees V%02u %s%s [%06u], but invariant requires V%02u %s%s [%06u]\n", info->IterVar,
5382+
relop->IsUnsigned() ? "(uns) " : "", GenTree::OpName(oper), Compiler::dspTreeID(limitCandidate),
5383+
info->IterVar, info->TestTree->IsUnsigned() ? "(uns) " : "", GenTree::OpName(info->TestOper()),
5384+
Compiler::dspTreeID(info->Limit()));
5385+
return false;
5386+
}
5387+
5388+
JITDUMP(" Condition is established before entry at [%06u]\n", Compiler::dspTreeID(enteringJTrue->gtGetOp1()));
5389+
return true;
5390+
}
5391+
5392+
//------------------------------------------------------------------------
5393+
// InitBlockEntersLoopOnTrue: Determine whether a BBJ_COND init block enters the
5394+
// loop in the false or true case.
5395+
//
5396+
// Parameters:
5397+
// initBlock - A BBJ_COND block that is assumed to dominate the loop, and
5398+
// only enter the loop in one of the two cases.
5399+
//
5400+
// Returns:
5401+
// True if the loop is entered if the condition evaluates to true; otherwise false.
5402+
//
5403+
// Remarks:
5404+
// Handles only limited cases (optExtractInitTestIncr ensures that we see
5405+
// only limited cases).
5406+
//
5407+
bool FlowGraphNaturalLoop::InitBlockEntersLoopOnTrue(BasicBlock* initBlock)
5408+
{
5409+
assert(initBlock->KindIs(BBJ_COND));
5410+
5411+
if (initBlock->FalseTargetIs(GetHeader()))
5412+
{
5413+
return false;
5414+
}
5415+
5416+
if (initBlock->TrueTargetIs(GetHeader()))
5417+
{
5418+
return true;
5419+
}
5420+
5421+
// `optExtractInitTestIncr` may look at preds of preds to find an init
5422+
// block, so try a little bit harder. Today this always happens since we
5423+
// always have preheaders created in the places we call
5424+
// FlowGraphNaturalLoop::AnalyzeIteration.
5425+
for (FlowEdge* enterEdge : EntryEdges())
5426+
{
5427+
BasicBlock* entering = enterEdge->getSourceBlock();
5428+
if (initBlock->FalseTargetIs(entering))
5429+
{
5430+
return false;
5431+
}
5432+
if (initBlock->TrueTargetIs(entering))
53145433
{
5315-
JITDUMP(" Condition is established before entry at [%06u]\n",
5316-
Compiler::dspTreeID(enteringJTrue->gtGetOp1()));
53175434
return true;
53185435
}
53195436
}
53205437

5438+
assert(!"Could not find init block enter side");
53215439
return false;
53225440
}
53235441

src/coreclr/jit/gentree.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,6 @@ enum GenTreeFlags : unsigned int
503503

504504
GTF_RELOP_NAN_UN = 0x80000000, // GT_<relop> -- Is branch taken if ops are NaN?
505505
GTF_RELOP_JMP_USED = 0x40000000, // GT_<relop> -- result of compare used for jump or ?:
506-
GTF_RELOP_ZTT = 0x08000000, // GT_<relop> -- Loop test cloned for converting while-loops into do-while
507506
// with explicit "loop test" in the header block.
508507

509508
GTF_RET_MERGED = 0x80000000, // GT_RETURN -- This is a return generated during epilog merging.

src/coreclr/jit/loopcloning.cpp

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,17 +1841,6 @@ bool Compiler::optIsLoopClonable(FlowGraphNaturalLoop* loop, LoopCloneContext* c
18411841
return false;
18421842
}
18431843

1844-
// TODO-Quirk: Can be removed, loop invariant is validated by
1845-
// `FlowGraphNaturalLoop::AnalyzeIteration`.
1846-
if (!iterInfo->TestTree->OperIsCompare() || ((iterInfo->TestTree->gtFlags & GTF_RELOP_ZTT) == 0))
1847-
{
1848-
JITDUMP("Loop cloning: rejecting loop " FMT_LP
1849-
". Loop inversion NOT present, loop test [%06u] may not protect "
1850-
"entry from head.\n",
1851-
loop->GetIndex(), iterInfo->TestTree->gtTreeID);
1852-
return false;
1853-
}
1854-
18551844
#ifdef DEBUG
18561845
const unsigned ivLclNum = iterInfo->IterVar;
18571846
GenTree* const op1 = iterInfo->Iterator();

src/coreclr/jit/optimizer.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2156,11 +2156,6 @@ bool Compiler::optInvertWhileLoop(BasicBlock* block)
21562156
assert(originalCompareTree->OperIsCompare());
21572157
assert(clonedCompareTree->OperIsCompare());
21582158

2159-
// Flag compare and cloned copy so later we know this loop
2160-
// has a proper zero trip test.
2161-
originalCompareTree->gtFlags |= GTF_RELOP_ZTT;
2162-
clonedCompareTree->gtFlags |= GTF_RELOP_ZTT;
2163-
21642159
// The original test branches to remain in the loop. The
21652160
// new cloned test will branch to avoid the loop. So the
21662161
// cloned compare needs to reverse the branch condition.

src/tests/JIT/Regression/JitBlue/Runtime_95315/Runtime_95315.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public class Runtime_95315
1111
[Fact]
1212
public static void TestEntryPoint()
1313
{
14+
// Make sure 'Bar' tiers up completely...
1415
for (int i = 0; i < 4; i++)
1516
{
1617
for (int j = 0; j < 200; j++)

0 commit comments

Comments
 (0)