-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Handle overlapped groups of bounds checks #112660
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,17 +48,18 @@ | |
| // Arguments: | ||
| // comp - The compiler instance | ||
| // statement - The statement containing the bounds check | ||
| // bndChkNode - The bounds check node | ||
| // bndChkParentNode - The parent node of the bounds check node (either null or COMMA) | ||
| // statementIdx - The index of the statement in the block | ||
| // bndChk - The bounds check node (its use edge) | ||
| // | ||
| // Return Value: | ||
| // true if the initialization was successful, false otherwise. | ||
| // | ||
| bool BoundsCheckInfo::Initialize(const Compiler* comp, Statement* statement, GenTree** bndChk) | ||
| bool BoundsCheckInfo::Initialize(const Compiler* comp, Statement* statement, int statementIdx, GenTree** bndChk) | ||
| { | ||
| assert((bndChk != nullptr) && ((*bndChk) != nullptr)); | ||
|
|
||
| stmt = statement; | ||
| stmtIdx = statementIdx; | ||
| bndChkUse = bndChk; | ||
| idxVN = comp->vnStore->VNConservativeNormalValue(BndChk()->GetIndex()->gtVNPair); | ||
| lenVN = comp->vnStore->VNConservativeNormalValue(BndChk()->GetArrayLength()->gtVNPair); | ||
|
|
@@ -98,16 +99,26 @@ bool BoundsCheckInfo::Initialize(const Compiler* comp, Statement* statement, Gen | |
| // RemoveBoundsChk - Remove the given bounds check from the statement and the block. | ||
| // | ||
| // Arguments: | ||
| // comp - compiler instance | ||
| // check - bounds check node to remove | ||
| // comma - check's parent node (either null or COMMA) | ||
| // stmt - statement containing the bounds check | ||
| // comp - compiler instance | ||
| // treeUse - the bounds check node to remove (its use edge) | ||
| // stmt - the statement containing the bounds check | ||
| // | ||
| static void RemoveBoundsChk(Compiler* comp, GenTree** treeUse, Statement* stmt) | ||
| { | ||
| JITDUMP("Before RemoveBoundsChk:\n"); | ||
| DISPTREE(*treeUse); | ||
|
|
||
| // If we deal with GT_ARR_LEN(lcl), we can unmark the array length node | ||
| // as having an NRE. Technically, this should be done by the assertion prop, | ||
| // but not today, see https://github.com/dotnet/runtime/pull/93531 | ||
| // | ||
| GenTreeBoundsChk* bndsChk = (*treeUse)->AsBoundsChk(); | ||
| GenTree* arrObj = bndsChk->GetArray(); | ||
| if ((arrObj != nullptr) && ((arrObj->gtFlags & GTF_ALL_EFFECT) == 0)) | ||
| { | ||
| bndsChk->GetArrayLength()->gtFlags &= ~GTF_EXCEPT; | ||
| } | ||
|
|
||
| GenTree* sideEffList = nullptr; | ||
| comp->gtExtractSideEffList(*treeUse, &sideEffList, GTF_SIDE_EFFECT, /*ignoreRoot*/ true); | ||
| *treeUse = (sideEffList != nullptr) ? sideEffList : comp->gtNewNothingNode(); | ||
|
|
@@ -155,18 +166,21 @@ static void RemoveBoundsChk(Compiler* comp, GenTree** treeUse, Statement* stmt) | |
| // comp - The compiler instance | ||
| // block - The block to clone | ||
| // bndChkStack - The stack of bounds checks to clone | ||
| // lastStmt - The last statement in the block (the block is split after this statement) | ||
| // | ||
| // Return Value: | ||
| // The block containing the fast path. | ||
| // | ||
| static BasicBlock* optRangeCheckCloning_DoClone(Compiler* comp, BasicBlock* block, BoundsCheckInfoStack* bndChkStack) | ||
| static BasicBlock* optRangeCheckCloning_DoClone(Compiler* comp, | ||
| BasicBlock* block, | ||
| BoundsCheckInfoStack* bndChkStack, | ||
| Statement* lastStmt) | ||
| { | ||
| assert(block != nullptr); | ||
| assert(bndChkStack->Height() > 0); | ||
|
|
||
| // The bound checks are in the execution order (top of the stack is the last check) | ||
| BoundsCheckInfo firstCheck = bndChkStack->Bottom(); | ||
| BoundsCheckInfo lastCheck = bndChkStack->Top(); | ||
| BasicBlock* prevBb = block; | ||
|
|
||
| // First, split the block at the first bounds check using gtSplitTree (via fgSplitBlockBeforeTree): | ||
|
|
@@ -187,7 +201,7 @@ static BasicBlock* optRangeCheckCloning_DoClone(Compiler* comp, BasicBlock* bloc | |
| // Now split the block at the last bounds check using fgSplitBlockAfterStatement: | ||
| // TODO-RangeCheckCloning: call gtSplitTree for lastBndChkStmt as well, to cut off | ||
| // the stuff we don't have to clone. | ||
| BasicBlock* lastBb = comp->fgSplitBlockAfterStatement(fastpathBb, lastCheck.stmt); | ||
| BasicBlock* lastBb = comp->fgSplitBlockAfterStatement(fastpathBb, lastStmt); | ||
|
|
||
| DebugInfo debugInfo = fastpathBb->firstStmt()->GetDebugInfo(); | ||
|
|
||
|
|
@@ -351,14 +365,15 @@ static BasicBlock* optRangeCheckCloning_DoClone(Compiler* comp, BasicBlock* bloc | |
| assert(BasicBlock::sameEHRegion(prevBb, fallbackBb)); | ||
| assert(BasicBlock::sameEHRegion(prevBb, lastBb)); | ||
|
|
||
| return fastpathBb; | ||
| return fastpathBb->Prev(); | ||
| } | ||
|
|
||
| // A visitor to record all the bounds checks in a statement in the execution order | ||
| class BoundsChecksVisitor final : public GenTreeVisitor<BoundsChecksVisitor> | ||
| { | ||
| Statement* m_stmt; | ||
| ArrayStack<BoundCheckLocation>* m_boundsChks; | ||
| int m_stmtIdx; | ||
|
|
||
| public: | ||
| enum | ||
|
|
@@ -368,10 +383,14 @@ class BoundsChecksVisitor final : public GenTreeVisitor<BoundsChecksVisitor> | |
| UseExecutionOrder = true | ||
| }; | ||
|
|
||
| BoundsChecksVisitor(Compiler* compiler, Statement* stmt, ArrayStack<BoundCheckLocation>* bndChkLocations) | ||
| BoundsChecksVisitor(Compiler* compiler, | ||
| Statement* stmt, | ||
| int stmtIdx, | ||
| ArrayStack<BoundCheckLocation>* bndChkLocations) | ||
| : GenTreeVisitor(compiler) | ||
| , m_stmt(stmt) | ||
| , m_boundsChks(bndChkLocations) | ||
| , m_stmtIdx(stmtIdx) | ||
| { | ||
| } | ||
|
|
||
|
|
@@ -389,7 +408,7 @@ class BoundsChecksVisitor final : public GenTreeVisitor<BoundsChecksVisitor> | |
| { | ||
| if ((*use)->OperIs(GT_BOUNDS_CHECK)) | ||
| { | ||
| m_boundsChks->Push(BoundCheckLocation(m_stmt, use)); | ||
| m_boundsChks->Push(BoundCheckLocation(m_stmt, use, m_stmtIdx)); | ||
| } | ||
| return fgWalkResult::WALK_CONTINUE; | ||
| } | ||
|
|
@@ -401,6 +420,7 @@ class BoundsChecksVisitor final : public GenTreeVisitor<BoundsChecksVisitor> | |
| // the bounds checks. | ||
| // | ||
| // Arguments: | ||
| // comp - The compiler instance | ||
| // bndChks - The stack of bounds checks | ||
| // | ||
| // Return Value: | ||
|
|
@@ -499,8 +519,10 @@ PhaseStatus Compiler::optRangeCheckCloning() | |
| bndChkLocations.Reset(); | ||
| bndChkMap.RemoveAll(); | ||
|
|
||
| int stmtIdx = -1; | ||
| for (Statement* const stmt : block->Statements()) | ||
| { | ||
| stmtIdx++; | ||
| if (block->HasTerminator() && (stmt == block->lastStmt())) | ||
| { | ||
| // TODO-RangeCheckCloning: Splitting these blocks at the last statements | ||
|
|
@@ -510,7 +532,7 @@ PhaseStatus Compiler::optRangeCheckCloning() | |
|
|
||
| // Now just record all the bounds checks in the block (in the execution order) | ||
| // | ||
| BoundsChecksVisitor visitor(this, stmt, &bndChkLocations); | ||
| BoundsChecksVisitor visitor(this, stmt, stmtIdx, &bndChkLocations); | ||
| visitor.WalkTree(stmt->GetRootNodePointer(), nullptr); | ||
| } | ||
|
|
||
|
|
@@ -528,7 +550,7 @@ PhaseStatus Compiler::optRangeCheckCloning() | |
| { | ||
| BoundCheckLocation loc = bndChkLocations.Bottom(i); | ||
| BoundsCheckInfo bci{}; | ||
| if (bci.Initialize(this, loc.stmt, loc.bndChkUse)) | ||
| if (bci.Initialize(this, loc.stmt, loc.stmtIdx, loc.bndChkUse)) | ||
| { | ||
| IdxLenPair key(bci.idxVN, bci.lenVN); | ||
| BoundsCheckInfoStack** value = bndChkMap.LookupPointerOrAdd(key, nullptr); | ||
|
|
@@ -552,34 +574,69 @@ PhaseStatus Compiler::optRangeCheckCloning() | |
| } | ||
|
|
||
| // Now choose the largest group of bounds checks (the one with the most checks) | ||
| BoundsCheckInfoStack* largestGroup = nullptr; | ||
| // BoundsCheckInfoStack* largestGroup = nullptr; | ||
EgorBo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ArrayStack<BoundsCheckInfoStack*> groups(getAllocator(CMK_RangeCheckCloning)); | ||
|
|
||
| for (BoundsCheckInfoMap::Node* keyValuePair : BoundsCheckInfoMap::KeyValueIteration(&bndChkMap)) | ||
| { | ||
| ArrayStack<BoundsCheckInfo>* value = keyValuePair->GetValue(); | ||
| if ((largestGroup == nullptr) || (value->Height() > largestGroup->Height())) | ||
| if ((value->Height() >= MIN_CHECKS_PER_GROUP) && !DoesComplexityExceed(this, value)) | ||
| { | ||
| if (DoesComplexityExceed(this, value)) | ||
| { | ||
| continue; | ||
| } | ||
| largestGroup = value; | ||
| groups.Push(value); | ||
| } | ||
| } | ||
|
|
||
| if (largestGroup == nullptr) | ||
| if (groups.Height() == 0) | ||
| { | ||
| JITDUMP("No suitable group of bounds checks in the block - bail out.\n"); | ||
| continue; | ||
| } | ||
|
|
||
| if (largestGroup->Height() < MIN_CHECKS_PER_GROUP) | ||
| // We have multiple groups of bounds checks in the block. | ||
| // let's pick a group that appears first in the block and the one whose last bounds check | ||
| // appears last in the block. | ||
| // | ||
| BoundsCheckInfoStack* firstGroup = groups.Top(); | ||
| BoundsCheckInfoStack* lastGroup = groups.Top(); | ||
| for (int i = 0; i < groups.Height(); i++) | ||
| { | ||
| JITDUMP("Not enough bounds checks in the largest group - bail out.\n"); | ||
| continue; | ||
| BoundsCheckInfoStack* group = groups.Bottom(i); | ||
| int firstStmt = group->Bottom().stmtIdx; | ||
| int secondStmt = group->Top().stmtIdx; | ||
| if (firstStmt < firstGroup->Bottom().stmtIdx) | ||
| { | ||
| firstGroup = group; | ||
| } | ||
| if (secondStmt > lastGroup->Top().stmtIdx) | ||
| { | ||
| lastGroup = group; | ||
| } | ||
| } | ||
|
|
||
| JITDUMP("Cloning bounds checks in " FMT_BB "\n", block->bbNum); | ||
| block = optRangeCheckCloning_DoClone(this, block, largestGroup); | ||
| // We're going to clone for the first group. | ||
| // But let's see if we can extend the end of the group so future iterations | ||
| // can fit more groups in the same block. | ||
| // | ||
| Statement* lastStmt = firstGroup->Top().stmt; | ||
|
|
||
| int firstGroupStarts = firstGroup->Bottom().stmtIdx; | ||
| int firstGroupEnds = firstGroup->Top().stmtIdx; | ||
| int lastGroupStarts = lastGroup->Bottom().stmtIdx; | ||
| int lastGroupEnds = lastGroup->Top().stmtIdx; | ||
|
|
||
| // The only requirement is that both groups must overlap - we don't want to | ||
| // end up cloning unrelated statements between them (not a correctness issue, | ||
| // just a heuristic to avoid cloning too much). | ||
| // | ||
| if (firstGroupEnds < lastGroupEnds && firstGroupEnds >= lastGroupStarts) | ||
| { | ||
| lastStmt = lastGroup->Top().stmt; | ||
| } | ||
|
|
||
| JITDUMP("Cloning bounds checks in " FMT_BB " from " FMT_STMT " to " FMT_STMT "\n", block->bbNum, | ||
| firstGroup->Bottom().stmt->GetID(), lastStmt->GetID()); | ||
|
|
||
| block = optRangeCheckCloning_DoClone(this, block, firstGroup, lastStmt); | ||
|
||
| modified = true; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't really correct. The only thing we know here is that the conservative normal VN is the same as the first bounds check's conservative normal VN, but the array length here could still be something like
0 * (1 / 0) + arr.Length(obviously a constructed example) where this code would get rid of theDivisionByZeroException.Maybe something like
would get the cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, even previous code didn't catch anything, so let me just remove this part