Skip to content

Commit 4b0b723

Browse files
authored
Add binary VN funcs to TryGetRangeFromAssertions (#123233)
[diffs](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1261349&view=ms.vss-build-web.run-extensions-tab) Fixes #122288 Also, enables more branch foldings based on assertions - basically, mimics what SSA-based GetRange does, but based only on VNs. I recommend reviewing with whitespaces disabled.
1 parent 5142090 commit 4b0b723

File tree

4 files changed

+199
-266
lines changed

4 files changed

+199
-266
lines changed

src/coreclr/jit/assertionprop.cpp

Lines changed: 8 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -3970,61 +3970,18 @@ void Compiler::optAssertionProp_RangeProperties(ASSERT_VALARG_TP assertions,
39703970
// Let's see if MergeEdgeAssertions can help us:
39713971
if (tree->TypeIs(TYP_INT))
39723972
{
3973-
// See if (X + CNS) is known to be non-negative
3974-
if (tree->OperIs(GT_ADD) && tree->gtGetOp2()->IsIntCnsFitsInI32())
3973+
Range rng = Range(Limit(Limit::keUnknown));
3974+
if (RangeCheck::TryGetRangeFromAssertions(this, treeVN, assertions, &rng))
39753975
{
3976-
Range rng = Range(Limit(Limit::keUnknown));
3977-
ValueNum vn = vnStore->VNConservativeNormalValue(tree->gtGetOp1()->gtVNPair);
3978-
if (!RangeCheck::TryGetRangeFromAssertions(this, vn, assertions, &rng))
3979-
{
3980-
return;
3981-
}
3982-
3983-
int cns = static_cast<int>(tree->gtGetOp2()->AsIntCon()->IconValue());
3984-
3985-
if ((rng.LowerLimit().IsConstant() && !rng.LowerLimit().AddConstant(cns)) ||
3986-
(rng.UpperLimit().IsConstant() && !rng.UpperLimit().AddConstant(cns)))
3987-
{
3988-
// Add cns to both bounds if they are constants. Make sure the addition doesn't overflow.
3989-
return;
3990-
}
3991-
3992-
if (rng.LowerLimit().IsConstant())
3976+
Limit lowerBound = rng.LowerLimit();
3977+
assert(lowerBound.IsConstant());
3978+
if (lowerBound.GetConstant() >= 0)
39933979
{
3994-
// E.g. "X + -8" when X's range is [8..unknown]
3995-
// it's safe to say "X + -8" is non-negative
3996-
if ((rng.LowerLimit().GetConstant() == 0))
3997-
{
3998-
*isKnownNonNegative = true;
3999-
}
4000-
4001-
// E.g. "X + 8" when X's range is [0..CNS]
4002-
// Here we have to check the upper bound as well to avoid overflow
4003-
if ((rng.LowerLimit().GetConstant() > 0) && rng.UpperLimit().IsConstant() &&
4004-
rng.UpperLimit().GetConstant() > rng.LowerLimit().GetConstant())
4005-
{
4006-
*isKnownNonNegative = true;
4007-
*isKnownNonZero = true;
4008-
}
3980+
*isKnownNonNegative = true;
40093981
}
4010-
}
4011-
else
4012-
{
4013-
Range rng = Range(Limit(Limit::keUnknown));
4014-
if (RangeCheck::TryGetRangeFromAssertions(this, treeVN, assertions, &rng))
3982+
if (lowerBound.GetConstant() > 0)
40153983
{
4016-
Limit lowerBound = rng.LowerLimit();
4017-
if (lowerBound.IsConstant())
4018-
{
4019-
if (lowerBound.GetConstant() >= 0)
4020-
{
4021-
*isKnownNonNegative = true;
4022-
}
4023-
if (lowerBound.GetConstant() > 0)
4024-
{
4025-
*isKnownNonZero = true;
4026-
}
4027-
}
3984+
*isKnownNonZero = true;
40283985
}
40293986
}
40303987
}
@@ -4461,36 +4418,6 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions,
44614418
return optAssertionProp_Update(newTree, tree, stmt);
44624419
}
44634420
}
4464-
4465-
// If op1VN is actually ADD(X, CNS), we can try peeling the constant offset and adjusting op2Cns accordingly.
4466-
// It's a bit more complicated for unsigned comparisons, so only do it for signed ones for now.
4467-
//
4468-
if (!tree->IsUnsigned())
4469-
{
4470-
ValueNum peeledOp1VN = op1VN;
4471-
int peeledOffset = 0;
4472-
vnStore->PeelOffsetsI32(&peeledOp1VN, &peeledOffset);
4473-
4474-
if (peeledOffset != 0)
4475-
{
4476-
Range peeledOffsetRng = Range(Limit(Limit::keConstant, peeledOffset));
4477-
Range peeledOp1Rng = Range(Limit(Limit::keUnknown));
4478-
if (RangeCheck::TryGetRangeFromAssertions(this, peeledOp1VN, assertions, &peeledOp1Rng))
4479-
{
4480-
// Subtract handles overflow internally.
4481-
rng2 = RangeOps::Subtract(rng2, peeledOffsetRng);
4482-
4483-
RangeOps::RelationKind kind =
4484-
RangeOps::EvalRelop(tree->OperGet(), tree->IsUnsigned(), peeledOp1Rng, rng2);
4485-
if ((kind != RangeOps::RelationKind::Unknown))
4486-
{
4487-
newTree = kind == RangeOps::RelationKind::AlwaysTrue ? gtNewTrue() : gtNewFalse();
4488-
newTree = gtWrapWithSideEffects(newTree, tree, GTF_ALL_EFFECT);
4489-
return optAssertionProp_Update(newTree, tree, stmt);
4490-
}
4491-
}
4492-
}
4493-
}
44944421
}
44954422

44964423
// Else check if we have an equality check involving a local or an indir

src/coreclr/jit/rangecheck.cpp

Lines changed: 99 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,9 @@ bool RangeCheck::TryGetRangeFromAssertions(Compiler* comp, ValueNum num, ASSERT_
673673
return true;
674674
}
675675

676+
// Start with the widest possible constant range.
677+
Range result = Range(Limit(Limit::keConstant, INT32_MIN), Limit(Limit::keConstant, INT32_MAX));
678+
676679
VNFuncApp funcApp;
677680
if (comp->vnStore->GetVNFunc(num, &funcApp))
678681
{
@@ -687,44 +690,127 @@ bool RangeCheck::TryGetRangeFromAssertions(Compiler* comp, ValueNum num, ASSERT_
687690
switch (castToType)
688691
{
689692
case TYP_UBYTE:
690-
pRange->lLimit = Limit(Limit::keConstant, UINT8_MIN);
691-
pRange->uLimit = Limit(Limit::keConstant, UINT8_MAX);
693+
result.lLimit = Limit(Limit::keConstant, UINT8_MIN);
694+
result.uLimit = Limit(Limit::keConstant, UINT8_MAX);
692695
break;
693696

694697
case TYP_BYTE:
695-
pRange->lLimit = Limit(Limit::keConstant, INT8_MIN);
696-
pRange->uLimit = Limit(Limit::keConstant, INT8_MAX);
698+
result.lLimit = Limit(Limit::keConstant, INT8_MIN);
699+
result.uLimit = Limit(Limit::keConstant, INT8_MAX);
697700
break;
698701

699702
case TYP_USHORT:
700-
pRange->lLimit = Limit(Limit::keConstant, UINT16_MIN);
701-
pRange->uLimit = Limit(Limit::keConstant, UINT16_MAX);
703+
result.lLimit = Limit(Limit::keConstant, UINT16_MIN);
704+
result.uLimit = Limit(Limit::keConstant, UINT16_MAX);
702705
break;
703706

704707
case TYP_SHORT:
705-
pRange->lLimit = Limit(Limit::keConstant, INT16_MIN);
706-
pRange->uLimit = Limit(Limit::keConstant, INT16_MAX);
708+
result.lLimit = Limit(Limit::keConstant, INT16_MIN);
709+
result.uLimit = Limit(Limit::keConstant, INT16_MAX);
707710
break;
708711

709712
default:
710713
break;
711714
}
715+
716+
// If we wanted to be more precise, we could also try to get the range of the source
717+
// and if it's smaller than the cast range, use that.
712718
}
713719
break;
714720

721+
case VNF_LSH:
722+
case VNF_ADD:
723+
case VNF_MUL:
724+
case VNF_AND:
725+
case VNF_OR:
726+
case VNF_RSH:
727+
case VNF_RSZ:
728+
case VNF_UMOD:
729+
{
730+
// Get ranges of both operands and perform the same operation on the ranges.
731+
Range r1 = Range(Limit(Limit::keUnknown));
732+
Range r2 = Range(Limit(Limit::keUnknown));
733+
if (TryGetRangeFromAssertions(comp, funcApp.m_args[0], assertions, &r1) &&
734+
TryGetRangeFromAssertions(comp, funcApp.m_args[1], assertions, &r2))
735+
{
736+
Range binOpResult = Range(Limit(Limit::keUnknown));
737+
switch (funcApp.m_func)
738+
{
739+
case VNF_ADD:
740+
binOpResult = RangeOps::Add(r1, r2);
741+
break;
742+
case VNF_MUL:
743+
binOpResult = RangeOps::Multiply(r1, r2);
744+
break;
745+
case VNF_AND:
746+
binOpResult = RangeOps::And(r1, r2);
747+
break;
748+
case VNF_OR:
749+
binOpResult = RangeOps::Or(r1, r2);
750+
break;
751+
case VNF_LSH:
752+
binOpResult = RangeOps::ShiftLeft(r1, r2);
753+
break;
754+
case VNF_RSH:
755+
binOpResult = RangeOps::ShiftRight(r1, r2, /*logical*/ false);
756+
break;
757+
case VNF_RSZ:
758+
binOpResult = RangeOps::ShiftRight(r1, r2, /*logical*/ true);
759+
break;
760+
case VNF_UMOD:
761+
binOpResult = RangeOps::UnsignedMod(r1, r2);
762+
break;
763+
default:
764+
unreached();
765+
}
766+
767+
if (binOpResult.IsConstantRange())
768+
{
769+
result = binOpResult;
770+
}
771+
// if the result is unknown (or may overflow), we'll just analyze the binop itself based on the
772+
// assertions
773+
}
774+
break;
775+
}
776+
777+
case VNF_MDARR_LENGTH:
715778
case VNF_ARR_LENGTH:
716-
pRange->lLimit = Limit(Limit::keConstant, 0);
717-
pRange->uLimit = Limit(Limit::keConstant, CORINFO_Array_MaxLength);
779+
result.lLimit = Limit(Limit::keConstant, 0);
780+
result.uLimit = Limit(Limit::keConstant, CORINFO_Array_MaxLength);
781+
break;
782+
783+
case VNF_GT:
784+
case VNF_GT_UN:
785+
case VNF_GE:
786+
case VNF_GE_UN:
787+
case VNF_LT:
788+
case VNF_LT_UN:
789+
case VNF_LE_UN:
790+
case VNF_LE:
791+
case VNF_EQ:
792+
case VNF_NE:
793+
result.lLimit = Limit(Limit::keConstant, 0);
794+
result.uLimit = Limit(Limit::keConstant, 1);
795+
break;
796+
797+
case VNF_LeadingZeroCount:
798+
case VNF_TrailingZeroCount:
799+
case VNF_PopCount:
800+
// We can be a bit more precise here if we want to
801+
result.lLimit = Limit(Limit::keConstant, 0);
802+
result.uLimit = Limit(Limit::keConstant, 64);
718803
break;
719804

720805
default:
721806
break;
722807
}
723808
}
724809

725-
MergeEdgeAssertions(comp, num, ValueNumStore::NoVN, assertions, pRange, false);
726-
assert(pRange->IsValid());
727-
return !pRange->LowerLimit().IsUnknown() || !pRange->UpperLimit().IsUnknown();
810+
MergeEdgeAssertions(comp, num, ValueNumStore::NoVN, assertions, &result, false);
811+
assert(result.IsConstantRange());
812+
*pRange = result;
813+
return true;
728814
}
729815

730816
//------------------------------------------------------------------------

0 commit comments

Comments
 (0)