Skip to content

Commit 03324cd

Browse files
EgorBojakobbotsch
andauthored
Handle overlapped groups of bounds checks (#112660)
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
1 parent ca710ec commit 03324cd

File tree

2 files changed

+84
-30
lines changed

2 files changed

+84
-30
lines changed

src/coreclr/jit/rangecheckcloning.cpp

Lines changed: 77 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,18 @@
4848
// Arguments:
4949
// comp - The compiler instance
5050
// statement - The statement containing the bounds check
51-
// bndChkNode - The bounds check node
52-
// bndChkParentNode - The parent node of the bounds check node (either null or COMMA)
51+
// statementIdx - The index of the statement in the block
52+
// bndChk - The bounds check node (its use edge)
5353
//
5454
// Return Value:
5555
// true if the initialization was successful, false otherwise.
5656
//
57-
bool BoundsCheckInfo::Initialize(const Compiler* comp, Statement* statement, GenTree** bndChk)
57+
bool BoundsCheckInfo::Initialize(const Compiler* comp, Statement* statement, int statementIdx, GenTree** bndChk)
5858
{
5959
assert((bndChk != nullptr) && ((*bndChk) != nullptr));
6060

6161
stmt = statement;
62+
stmtIdx = statementIdx;
6263
bndChkUse = bndChk;
6364
idxVN = comp->vnStore->VNConservativeNormalValue(BndChk()->GetIndex()->gtVNPair);
6465
lenVN = comp->vnStore->VNConservativeNormalValue(BndChk()->GetArrayLength()->gtVNPair);
@@ -98,10 +99,9 @@ bool BoundsCheckInfo::Initialize(const Compiler* comp, Statement* statement, Gen
9899
// RemoveBoundsChk - Remove the given bounds check from the statement and the block.
99100
//
100101
// Arguments:
101-
// comp - compiler instance
102-
// check - bounds check node to remove
103-
// comma - check's parent node (either null or COMMA)
104-
// stmt - statement containing the bounds check
102+
// comp - compiler instance
103+
// treeUse - the bounds check node to remove (its use edge)
104+
// stmt - the statement containing the bounds check
105105
//
106106
static void RemoveBoundsChk(Compiler* comp, GenTree** treeUse, Statement* stmt)
107107
{
@@ -155,18 +155,21 @@ static void RemoveBoundsChk(Compiler* comp, GenTree** treeUse, Statement* stmt)
155155
// comp - The compiler instance
156156
// block - The block to clone
157157
// bndChkStack - The stack of bounds checks to clone
158+
// lastStmt - The last statement in the block (the block is split after this statement)
158159
//
159160
// Return Value:
160-
// The block containing the fast path.
161+
// The next block to visit after the cloning.
161162
//
162-
static BasicBlock* optRangeCheckCloning_DoClone(Compiler* comp, BasicBlock* block, BoundsCheckInfoStack* bndChkStack)
163+
static BasicBlock* optRangeCheckCloning_DoClone(Compiler* comp,
164+
BasicBlock* block,
165+
BoundsCheckInfoStack* bndChkStack,
166+
Statement* lastStmt)
163167
{
164168
assert(block != nullptr);
165169
assert(bndChkStack->Height() > 0);
166170

167171
// The bound checks are in the execution order (top of the stack is the last check)
168172
BoundsCheckInfo firstCheck = bndChkStack->Bottom();
169-
BoundsCheckInfo lastCheck = bndChkStack->Top();
170173
BasicBlock* prevBb = block;
171174

172175
// First, split the block at the first bounds check using gtSplitTree (via fgSplitBlockBeforeTree):
@@ -187,7 +190,7 @@ static BasicBlock* optRangeCheckCloning_DoClone(Compiler* comp, BasicBlock* bloc
187190
// Now split the block at the last bounds check using fgSplitBlockAfterStatement:
188191
// TODO-RangeCheckCloning: call gtSplitTree for lastBndChkStmt as well, to cut off
189192
// the stuff we don't have to clone.
190-
BasicBlock* lastBb = comp->fgSplitBlockAfterStatement(fastpathBb, lastCheck.stmt);
193+
BasicBlock* lastBb = comp->fgSplitBlockAfterStatement(fastpathBb, lastStmt);
191194

192195
DebugInfo debugInfo = fastpathBb->firstStmt()->GetDebugInfo();
193196

@@ -359,6 +362,7 @@ class BoundsChecksVisitor final : public GenTreeVisitor<BoundsChecksVisitor>
359362
{
360363
Statement* m_stmt;
361364
ArrayStack<BoundCheckLocation>* m_boundsChks;
365+
int m_stmtIdx;
362366

363367
public:
364368
enum
@@ -368,10 +372,14 @@ class BoundsChecksVisitor final : public GenTreeVisitor<BoundsChecksVisitor>
368372
UseExecutionOrder = true
369373
};
370374

371-
BoundsChecksVisitor(Compiler* compiler, Statement* stmt, ArrayStack<BoundCheckLocation>* bndChkLocations)
375+
BoundsChecksVisitor(Compiler* compiler,
376+
Statement* stmt,
377+
int stmtIdx,
378+
ArrayStack<BoundCheckLocation>* bndChkLocations)
372379
: GenTreeVisitor(compiler)
373380
, m_stmt(stmt)
374381
, m_boundsChks(bndChkLocations)
382+
, m_stmtIdx(stmtIdx)
375383
{
376384
}
377385

@@ -389,7 +397,7 @@ class BoundsChecksVisitor final : public GenTreeVisitor<BoundsChecksVisitor>
389397
{
390398
if ((*use)->OperIs(GT_BOUNDS_CHECK))
391399
{
392-
m_boundsChks->Push(BoundCheckLocation(m_stmt, use));
400+
m_boundsChks->Push(BoundCheckLocation(m_stmt, use, m_stmtIdx));
393401
}
394402
return fgWalkResult::WALK_CONTINUE;
395403
}
@@ -401,6 +409,7 @@ class BoundsChecksVisitor final : public GenTreeVisitor<BoundsChecksVisitor>
401409
// the bounds checks.
402410
//
403411
// Arguments:
412+
// comp - The compiler instance
404413
// bndChks - The stack of bounds checks
405414
//
406415
// Return Value:
@@ -499,8 +508,10 @@ PhaseStatus Compiler::optRangeCheckCloning()
499508
bndChkLocations.Reset();
500509
bndChkMap.RemoveAll();
501510

511+
int stmtIdx = -1;
502512
for (Statement* const stmt : block->Statements())
503513
{
514+
stmtIdx++;
504515
if (block->HasTerminator() && (stmt == block->lastStmt()))
505516
{
506517
// TODO-RangeCheckCloning: Splitting these blocks at the last statements
@@ -510,7 +521,7 @@ PhaseStatus Compiler::optRangeCheckCloning()
510521

511522
// Now just record all the bounds checks in the block (in the execution order)
512523
//
513-
BoundsChecksVisitor visitor(this, stmt, &bndChkLocations);
524+
BoundsChecksVisitor visitor(this, stmt, stmtIdx, &bndChkLocations);
514525
visitor.WalkTree(stmt->GetRootNodePointer(), nullptr);
515526
}
516527

@@ -528,7 +539,7 @@ PhaseStatus Compiler::optRangeCheckCloning()
528539
{
529540
BoundCheckLocation loc = bndChkLocations.Bottom(i);
530541
BoundsCheckInfo bci{};
531-
if (bci.Initialize(this, loc.stmt, loc.bndChkUse))
542+
if (bci.Initialize(this, loc.stmt, loc.stmtIdx, loc.bndChkUse))
532543
{
533544
IdxLenPair key(bci.idxVN, bci.lenVN);
534545
BoundsCheckInfoStack** value = bndChkMap.LookupPointerOrAdd(key, nullptr);
@@ -552,34 +563,72 @@ PhaseStatus Compiler::optRangeCheckCloning()
552563
}
553564

554565
// Now choose the largest group of bounds checks (the one with the most checks)
555-
BoundsCheckInfoStack* largestGroup = nullptr;
566+
ArrayStack<BoundsCheckInfoStack*> groups(getAllocator(CMK_RangeCheckCloning));
567+
556568
for (BoundsCheckInfoMap::Node* keyValuePair : BoundsCheckInfoMap::KeyValueIteration(&bndChkMap))
557569
{
558570
ArrayStack<BoundsCheckInfo>* value = keyValuePair->GetValue();
559-
if ((largestGroup == nullptr) || (value->Height() > largestGroup->Height()))
571+
if ((value->Height() >= MIN_CHECKS_PER_GROUP) && !DoesComplexityExceed(this, value))
560572
{
561-
if (DoesComplexityExceed(this, value))
562-
{
563-
continue;
564-
}
565-
largestGroup = value;
573+
groups.Push(value);
566574
}
567575
}
568576

569-
if (largestGroup == nullptr)
577+
if (groups.Height() == 0)
570578
{
571579
JITDUMP("No suitable group of bounds checks in the block - bail out.\n");
572580
continue;
573581
}
574582

575-
if (largestGroup->Height() < MIN_CHECKS_PER_GROUP)
583+
// We have multiple groups of bounds checks in the block.
584+
// let's pick a group that appears first in the block and the one whose last bounds check
585+
// appears last in the block.
586+
//
587+
BoundsCheckInfoStack* firstGroup = groups.Top();
588+
BoundsCheckInfoStack* lastGroup = groups.Top();
589+
for (int i = 0; i < groups.Height(); i++)
576590
{
577-
JITDUMP("Not enough bounds checks in the largest group - bail out.\n");
578-
continue;
591+
BoundsCheckInfoStack* group = groups.Bottom(i);
592+
int firstStmt = group->Bottom().stmtIdx;
593+
int secondStmt = group->Top().stmtIdx;
594+
if (firstStmt < firstGroup->Bottom().stmtIdx)
595+
{
596+
firstGroup = group;
597+
}
598+
if (secondStmt > lastGroup->Top().stmtIdx)
599+
{
600+
lastGroup = group;
601+
}
602+
}
603+
604+
// We're going to clone for the first group.
605+
// But let's see if we can extend the end of the group so future iterations
606+
// can fit more groups in the same block.
607+
//
608+
Statement* lastStmt = firstGroup->Top().stmt;
609+
610+
int firstGroupStarts = firstGroup->Bottom().stmtIdx;
611+
int firstGroupEnds = firstGroup->Top().stmtIdx;
612+
int lastGroupStarts = lastGroup->Bottom().stmtIdx;
613+
int lastGroupEnds = lastGroup->Top().stmtIdx;
614+
615+
// The only requirement is that both groups must overlap - we don't want to
616+
// end up cloning unrelated statements between them (not a correctness issue,
617+
// just a heuristic to avoid cloning too much).
618+
//
619+
if (firstGroupEnds < lastGroupEnds && firstGroupEnds >= lastGroupStarts)
620+
{
621+
lastStmt = lastGroup->Top().stmt;
579622
}
580623

581-
JITDUMP("Cloning bounds checks in " FMT_BB "\n", block->bbNum);
582-
block = optRangeCheckCloning_DoClone(this, block, largestGroup);
624+
JITDUMP("Cloning bounds checks in " FMT_BB " from " FMT_STMT " to " FMT_STMT "\n", block->bbNum,
625+
firstGroup->Bottom().stmt->GetID(), lastStmt->GetID());
626+
627+
BasicBlock* nextBbToVisit = optRangeCheckCloning_DoClone(this, block, firstGroup, lastStmt);
628+
assert(nextBbToVisit != nullptr);
629+
// optRangeCheckCloning_DoClone wants us to visit nextBbToVisit next
630+
block = nextBbToVisit->Prev();
631+
assert(block != nullptr);
583632
modified = true;
584633
}
585634

src/coreclr/jit/rangecheckcloning.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,18 @@ struct BoundCheckLocation
2222
{
2323
Statement* stmt;
2424
GenTree** bndChkUse;
25+
int stmtIdx;
2526

26-
BoundCheckLocation(Statement* stmt, GenTree** bndChkUse)
27+
BoundCheckLocation(Statement* stmt, GenTree** bndChkUse, int stmtIdx)
2728
: stmt(stmt)
2829
, bndChkUse(bndChkUse)
30+
, stmtIdx(stmtIdx)
2931
{
3032
assert(stmt != nullptr);
3133
assert((bndChkUse != nullptr));
3234
assert((*bndChkUse) != nullptr);
3335
assert((*bndChkUse)->OperIs(GT_BOUNDS_CHECK));
36+
assert(stmtIdx >= 0);
3437
}
3538
};
3639

@@ -41,17 +44,19 @@ struct BoundsCheckInfo
4144
ValueNum lenVN;
4245
ValueNum idxVN;
4346
int offset;
47+
int stmtIdx;
4448

4549
BoundsCheckInfo()
4650
: stmt(nullptr)
4751
, bndChkUse(nullptr)
4852
, lenVN(ValueNumStore::NoVN)
4953
, idxVN(ValueNumStore::NoVN)
5054
, offset(0)
55+
, stmtIdx(0)
5156
{
5257
}
5358

54-
bool Initialize(const Compiler* comp, Statement* statement, GenTree** bndChkUse);
59+
bool Initialize(const Compiler* comp, Statement* statement, int statementIdx, GenTree** bndChkUse);
5560

5661
GenTreeBoundsChk* BndChk() const
5762
{

0 commit comments

Comments
 (0)