-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Update GetRangeFromAssertions to handle some basic TYP_LONG scenarios where it FitsIn<int32_t> #128906
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
base: main
Are you sure you want to change the base?
Update GetRangeFromAssertions to handle some basic TYP_LONG scenarios where it FitsIn<int32_t> #128906
Changes from 2 commits
f72c56d
492a936
707aa55
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 |
|---|---|---|
|
|
@@ -300,6 +300,10 @@ bool IntegralRange::Contains(int64_t value) const | |
| { | ||
| rangeType = TYP_USHORT; | ||
| } | ||
| else if ((elementCount == 32) && varTypeIsLong(rangeType)) | ||
| { | ||
| return {SymbolicIntegerValue::Zero, UpperBoundForType(TYP_UINT)}; | ||
| } | ||
| break; | ||
| } | ||
|
|
||
|
|
@@ -4070,17 +4074,20 @@ void Compiler::optAssertionProp_RangeProperties(ASSERT_VALARG_TP assertions, | |
| } | ||
|
|
||
| // Let's see if MergeEdgeAssertions can help us: | ||
| if (genActualType(tree) == TYP_INT) | ||
| if (varTypeIsIntegral(tree)) | ||
| { | ||
| Range rng = RangeCheck::GetRangeFromAssertions(this, treeVN, assertions); | ||
| assert(rng.IsConstantRange()); | ||
| if (rng.LowerLimit().GetConstant() >= 0) | ||
| { | ||
| *isKnownNonNegative = true; | ||
| } | ||
| if ((rng.LowerLimit().GetConstant() > 0) || (rng.UpperLimit().GetConstant() < 0)) | ||
| Range rng = RangeCheck::GetRangeFromAssertions(this, tree->TypeGet(), treeVN, assertions); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really don't like the fact we need to pass type. It should be evaluated from VN, shouldn't it?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The issue is the initial i.e, handling producing a range if no VN exists, which previously relied on the fact we would only ever have We'd have to have no VN produce
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so can we return just
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could, but then that will regress That might be fine, since most things should have VNs, but it might also not be since we lose VN info in various places or may not have it for new nodes. I could restrict it to just |
||
|
|
||
| if (rng.IsConstantRange()) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's unfortunate we broke the contract for GetRangeFromAssertions to always return a constant range. My opinion we shouldn't do it and instead either upgrade Range to TYP_LONG or introduce a new GetRangeFromAssertions for 64-bit ranges
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is a lot of unnecessary code duplication and keeping the same overall handling regardless. That is, it doesn't change what assertionprop has to handle here, rather it just forces it to two paths one calling With this approach, we have a single method handling both and the caller just has to handle the fact it might not be constant, rather than having to own dispatch to the right method and still handle that nuance anyways.
This is the eventual goal, but I'm trying to get this done incrementally and in a way that is easier to test, review, and handle. Fully handling Namely, it involves extending |
||
| { | ||
| *isKnownNonZero = true; | ||
| if (rng.LowerLimit().GetConstant() >= 0) | ||
| { | ||
| *isKnownNonNegative = true; | ||
| } | ||
| if ((rng.LowerLimit().GetConstant() > 0) || (rng.UpperLimit().GetConstant() < 0)) | ||
| { | ||
| *isKnownNonZero = true; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -4101,14 +4108,17 @@ GenTree* Compiler::optAssertionProp_AddMulSub(ASSERT_VALARG_TP assertions, GenTr | |
| { | ||
| assert(tree->OperIs(GT_MUL, GT_ADD, GT_SUB)); | ||
|
|
||
| if (!optLocalAssertionProp && tree->TypeIs(TYP_INT) && tree->gtOverflow()) | ||
| if (!optLocalAssertionProp && varTypeIsIntegral(tree) && tree->gtOverflow()) | ||
| { | ||
| GenTree* op1 = tree->gtGetOp1(); | ||
| GenTree* op2 = tree->gtGetOp2(); | ||
|
|
||
| Range op1Rng = RangeCheck::GetRangeFromAssertions(this, optConservativeNormalVN(op1), assertions); | ||
| Range op2Rng = RangeCheck::GetRangeFromAssertions(this, optConservativeNormalVN(op2), assertions); | ||
| Range op1Rng = | ||
| RangeCheck::GetRangeFromAssertions(this, op1->TypeGet(), optConservativeNormalVN(op1), assertions); | ||
| Range op2Rng = | ||
| RangeCheck::GetRangeFromAssertions(this, op2->TypeGet(), optConservativeNormalVN(op2), assertions); | ||
| Range result = Limit(Limit::keUnknown); | ||
|
|
||
| if (tree->OperIs(GT_MUL)) | ||
| { | ||
| result = RangeOps::Multiply(op1Rng, op2Rng, tree->IsUnsigned()); | ||
|
|
@@ -4481,10 +4491,10 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, | |
| } | ||
|
|
||
| // See if we can fold the relop based on range information. | ||
| // We don't need the op1->TypeIs(TYP_INT) check, but it seems to improve the TP quite a bit. | ||
| if (op1->TypeIs(TYP_INT)) | ||
| // We don't need the varTypeIsIntegral(op1) check, but it seems to improve the TP quite a bit. | ||
| if (varTypeIsIntegral(op1)) | ||
| { | ||
| Range relopRange = RangeCheck::GetRangeFromAssertions(this, relopVN, assertions); | ||
| Range relopRange = RangeCheck::GetRangeFromAssertions(this, tree->TypeGet(), relopVN, assertions); | ||
|
|
||
| int relopResult; | ||
| if (!relopRange.IsSingleValueConstant(&relopResult)) | ||
|
|
@@ -4500,8 +4510,8 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, | |
| } | ||
| else | ||
| { | ||
| Range op1Range = RangeCheck::GetRangeFromAssertions(this, op1VN, assertions); | ||
| Range op2Range = RangeCheck::GetRangeFromAssertions(this, op2VN, assertions); | ||
| Range op1Range = RangeCheck::GetRangeFromAssertions(this, op1->TypeGet(), op1VN, assertions); | ||
| Range op2Range = RangeCheck::GetRangeFromAssertions(this, op2->TypeGet(), op2VN, assertions); | ||
| relopRange = RangeOps::EvalRelop(tree->OperGet(), tree->IsUnsigned(), op1Range, op2Range); | ||
| } | ||
| } | ||
|
|
@@ -4928,29 +4938,33 @@ GenTree* Compiler::optAssertionProp_Cast(ASSERT_VALARG_TP assertions, | |
| if (FitsIn<int>(castLo) && FitsIn<int>(castHi)) | ||
| { | ||
| Range castToTypeRange = Range(Limit(Limit::keConstant, (int)castLo), Limit(Limit::keConstant, (int)castHi)); | ||
| if (castToTypeRange.IsConstantRange() && (genActualType(cast->CastOp()) == TYP_INT)) | ||
| { | ||
| ValueNum castOpVN = optConservativeNormalVN(cast->CastOp()); | ||
| Range castOpRng = RangeCheck::GetRangeFromAssertions(this, castOpVN, assertions); | ||
| assert(castOpRng.IsConstantRange()); | ||
|
|
||
| int castFromLo = castOpRng.LowerLimit().GetConstant(); | ||
| int castFromHi = castOpRng.UpperLimit().GetConstant(); | ||
| int castToLo = castToTypeRange.LowerLimit().GetConstant(); | ||
| int castToHi = castToTypeRange.UpperLimit().GetConstant(); | ||
| GenTree* castOp = cast->CastOp(); | ||
| if (castToTypeRange.IsConstantRange() && varTypeIsIntegral(castOp)) | ||
| { | ||
| ValueNum castOpVN = optConservativeNormalVN(castOp); | ||
| Range castOpRng = RangeCheck::GetRangeFromAssertions(this, castOp->TypeGet(), castOpVN, assertions); | ||
|
|
||
| if ((castFromLo >= castToLo) && (castFromHi <= castToHi)) | ||
| if (castOpRng.IsConstantRange()) | ||
| { | ||
| if (canDropCast) | ||
| int castFromLo = castOpRng.LowerLimit().GetConstant(); | ||
| int castFromHi = castOpRng.UpperLimit().GetConstant(); | ||
| int castToLo = castToTypeRange.LowerLimit().GetConstant(); | ||
| int castToHi = castToTypeRange.UpperLimit().GetConstant(); | ||
|
|
||
| if ((castFromLo >= castToLo) && (castFromHi <= castToHi)) | ||
| { | ||
| JITDUMP("Removing cast %06u as redundant based on VN assertions.\n", dspTreeID(cast)); | ||
| return optAssertionProp_Update(cast->CastOp(), cast, stmt); | ||
| } | ||
| if (canDropCast) | ||
| { | ||
| JITDUMP("Removing cast %06u as redundant based on VN assertions.\n", dspTreeID(cast)); | ||
| return optAssertionProp_Update(cast->CastOp(), cast, stmt); | ||
| } | ||
|
|
||
| assert(cast->gtOverflow()); | ||
| JITDUMP("Clearing overflow flag for cast %06u based on VN assertions.\n", dspTreeID(cast)); | ||
| cast->ClearOverflow(); | ||
| return optAssertionProp_Update(cast, cast, stmt); | ||
| assert(cast->gtOverflow()); | ||
| JITDUMP("Clearing overflow flag for cast %06u based on VN assertions.\n", dspTreeID(cast)); | ||
| cast->ClearOverflow(); | ||
| return optAssertionProp_Update(cast, cast, stmt); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -5519,9 +5533,11 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree | |
| } | ||
| #endif // FEATURE_ENABLE_NO_RANGE_CHECKS | ||
|
|
||
| GenTreeBoundsChk* arrBndsChk = tree->AsBoundsChk(); | ||
| ValueNum vnCurIdx = vnStore->VNConservativeNormalValue(arrBndsChk->GetIndex()->gtVNPair); | ||
| ValueNum vnCurLen = vnStore->VNConservativeNormalValue(arrBndsChk->GetArrayLength()->gtVNPair); | ||
| GenTreeBoundsChk* arrBndsChk = tree->AsBoundsChk(); | ||
| GenTree* arrBndsChkIdx = arrBndsChk->GetIndex(); | ||
| GenTree* arrBndsChkLen = arrBndsChk->GetArrayLength(); | ||
| ValueNum vnCurIdx = vnStore->VNConservativeNormalValue(arrBndsChk->GetIndex()->gtVNPair); | ||
| ValueNum vnCurLen = vnStore->VNConservativeNormalValue(arrBndsChk->GetArrayLength()->gtVNPair); | ||
|
Comment on lines
+5543
to
+5547
|
||
|
|
||
| auto dropBoundsCheck = [&](INDEBUG(const char* reason)) -> GenTree* { | ||
| JITDUMP("\nVN based redundant (%s) bounds check assertion prop in " FMT_BB ":\n", reason, compCurBB->bbNum); | ||
|
|
@@ -5549,7 +5565,7 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree | |
|
|
||
| if ((add0 == vnCurLen) && vnStore->IsVNInt32Constant(add1)) | ||
| { | ||
| Range rng = RangeCheck::GetRangeFromAssertions(this, vnCurLen, assertions); | ||
| Range rng = RangeCheck::GetRangeFromAssertions(this, arrBndsChkLen->TypeGet(), vnCurLen, assertions); | ||
| // Lower known limit of ArrLen: | ||
| const int lenLowerLimit = rng.LowerLimit().GetConstant(); | ||
|
tannergooding marked this conversation as resolved.
Outdated
|
||
|
|
||
|
|
@@ -5582,7 +5598,7 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree | |
| assert(curAssertion.GetOp2().IsVNNeverNegative()); | ||
|
|
||
| // Do we have a previous range check involving the same 'vnLen' upper bound? | ||
| if (curAssertion.GetOp2().GetVN() == optConservativeNormalVN(arrBndsChk->GetArrayLength())) | ||
| if (curAssertion.GetOp2().GetVN() == optConservativeNormalVN(arrBndsChkLen)) | ||
| { | ||
| // Do we have the exact same lower bound 'vnIdx'? | ||
| // a[i] followed by a[i] | ||
|
|
@@ -5593,7 +5609,7 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree | |
| // Are we using zero as the index? | ||
| // It can always be considered as redundant with any previous value | ||
| // a[*] followed by a[0] | ||
| else if (vnCurIdx == vnStore->VNZeroForType(arrBndsChk->GetIndex()->TypeGet())) | ||
| else if (vnCurIdx == vnStore->VNZeroForType(arrBndsChkIdx->TypeGet())) | ||
| { | ||
| return dropBoundsCheck(INDEBUG("a[*] followed by a[0]")); | ||
| } | ||
|
|
@@ -5645,44 +5661,40 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree | |
| } | ||
|
|
||
| // Let's see if we can remove the bounds check based on the ranges. | ||
| if ((genActualType(vnStore->TypeOfVN(vnCurIdx)) == TYP_INT) && | ||
| (genActualType(vnStore->TypeOfVN(vnCurLen)) == TYP_INT)) | ||
| Range idxRng = RangeCheck::GetRangeFromAssertions(this, arrBndsChkIdx->TypeGet(), vnCurIdx, assertions); | ||
| Range lenRng = RangeCheck::GetRangeFromAssertions(this, arrBndsChkLen->TypeGet(), vnCurLen, assertions); | ||
|
|
||
| if (idxRng.IsConstantRange() && lenRng.IsConstantRange()) | ||
| { | ||
| Range idxRng = RangeCheck::GetRangeFromAssertions(this, vnCurIdx, assertions); | ||
| Range lenRng = RangeCheck::GetRangeFromAssertions(this, vnCurLen, assertions); | ||
| if (idxRng.IsConstantRange() && lenRng.IsConstantRange()) | ||
| { | ||
| int idxLo = idxRng.LowerLimit().GetConstant(); | ||
| int idxHi = idxRng.UpperLimit().GetConstant(); | ||
| int lenLo = lenRng.LowerLimit().GetConstant(); | ||
| int idxLo = idxRng.LowerLimit().GetConstant(); | ||
| int idxHi = idxRng.UpperLimit().GetConstant(); | ||
| int lenLo = lenRng.LowerLimit().GetConstant(); | ||
|
|
||
| // GT_BOUNDS_CHECK node has an implicit contract - the length node must always be non-negative. | ||
| // So we additionally tighten the lower bound of lenLo to be ">= 1" when we also have a | ||
| // "length != 0" assertion for it. | ||
| if ((idxLo == 0) && (idxHi == 0) && (lenLo <= 0)) | ||
| // GT_BOUNDS_CHECK node has an implicit contract - the length node must always be non-negative. | ||
| // So we additionally tighten the lower bound of lenLo to be ">= 1" when we also have a | ||
| // "length != 0" assertion for it. | ||
| if ((idxLo == 0) && (idxHi == 0) && (lenLo <= 0)) | ||
| { | ||
| BitVecOps::Iter iter(apTraits, assertions); | ||
| unsigned bvIndex = 0; | ||
| while (iter.NextElem(&bvIndex)) | ||
| { | ||
| BitVecOps::Iter iter(apTraits, assertions); | ||
| unsigned bvIndex = 0; | ||
| while (iter.NextElem(&bvIndex)) | ||
| const AssertionDsc& assertion = optGetAssertion(GetAssertionIndex(bvIndex)); | ||
| if (assertion.IsConstantInt32Assertion() && assertion.KindIs(OAK_NOT_EQUAL) && | ||
| (assertion.GetOp1().GetVN() == vnCurLen) && (assertion.GetOp2().GetIntConstant() == 0)) | ||
| { | ||
| const AssertionDsc& assertion = optGetAssertion(GetAssertionIndex(bvIndex)); | ||
| if (assertion.IsConstantInt32Assertion() && assertion.KindIs(OAK_NOT_EQUAL) && | ||
| (assertion.GetOp1().GetVN() == vnCurLen) && (assertion.GetOp2().GetIntConstant() == 0)) | ||
| { | ||
| lenLo = 1; | ||
| break; | ||
| } | ||
| lenLo = 1; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // index is always within [0..lenLo) --> drop bounds check | ||
| if ((idxLo >= 0) && (idxHi < lenLo)) | ||
| { | ||
| return dropBoundsCheck(INDEBUG("upper bound of index is less than lower bound of length")); | ||
| } | ||
| // index is always within [0..lenLo) --> drop bounds check | ||
| if ((idxLo >= 0) && (idxHi < lenLo)) | ||
| { | ||
| return dropBoundsCheck(INDEBUG("upper bound of index is less than lower bound of length")); | ||
| } | ||
| } | ||
|
|
||
| return nullptr; | ||
| } | ||
|
|
||
|
|
||
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.
why not just
rangeType = TYP_UINTlike above?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.
Because
LowerBoundForTypedoesn't handleTYP_UINT, onlyUpperBoundForTypedoes, and soForTypewould hit anunreached.I believe this is intentional and to avoid bugs since we shouldn't normally encounter
TYP_UINTfor anything except rare special scenarios like this