Skip to content

Commit 6022adf

Browse files
authored
Use rangecheck in assertprop (dotnet#112766)
1 parent fb67dba commit 6022adf

File tree

3 files changed

+129
-38
lines changed

3 files changed

+129
-38
lines changed

src/coreclr/jit/assertionprop.cpp

+70-6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1111
*/
1212

1313
#include "jitpch.h"
14+
#include "rangecheck.h"
1415
#ifdef _MSC_VER
1516
#pragma hdrstop
1617
#endif
@@ -4125,6 +4126,69 @@ void Compiler::optAssertionProp_RangeProperties(ASSERT_VALARG_TP assertions,
41254126
}
41264127
}
41274128
}
4129+
4130+
if (*isKnownNonZero && *isKnownNonNegative)
4131+
{
4132+
return;
4133+
}
4134+
4135+
// Let's see if MergeEdgeAssertions can help us:
4136+
if (tree->TypeIs(TYP_INT))
4137+
{
4138+
// See if (X + CNS) is known to be non-negative
4139+
if (tree->OperIs(GT_ADD) && tree->gtGetOp2()->IsIntCnsFitsInI32())
4140+
{
4141+
Range rng = Range(Limit(Limit::keDependent));
4142+
ValueNum vn = vnStore->VNConservativeNormalValue(tree->gtGetOp1()->gtVNPair);
4143+
RangeCheck::MergeEdgeAssertions(this, vn, ValueNumStore::NoVN, assertions, &rng, false);
4144+
4145+
int cns = static_cast<int>(tree->gtGetOp2()->AsIntCon()->IconValue());
4146+
rng.LowerLimit().AddConstant(cns);
4147+
4148+
if ((rng.LowerLimit().IsConstant() && !rng.LowerLimit().AddConstant(cns)) ||
4149+
(rng.UpperLimit().IsConstant() && !rng.UpperLimit().AddConstant(cns)))
4150+
{
4151+
// Add cns to both bounds if they are constants. Make sure the addition doesn't overflow.
4152+
return;
4153+
}
4154+
4155+
if (rng.LowerLimit().IsConstant())
4156+
{
4157+
// E.g. "X + -8" when X's range is [8..unknown]
4158+
// it's safe to say "X + -8" is non-negative
4159+
if ((rng.LowerLimit().GetConstant() == 0))
4160+
{
4161+
*isKnownNonNegative = true;
4162+
}
4163+
4164+
// E.g. "X + 8" when X's range is [0..CNS]
4165+
// Here we have to check the upper bound as well to avoid overflow
4166+
if ((rng.LowerLimit().GetConstant() > 0) && rng.UpperLimit().IsConstant() &&
4167+
rng.UpperLimit().GetConstant() > rng.LowerLimit().GetConstant())
4168+
{
4169+
*isKnownNonNegative = true;
4170+
*isKnownNonZero = true;
4171+
}
4172+
}
4173+
}
4174+
else
4175+
{
4176+
Range rng = Range(Limit(Limit::keDependent));
4177+
RangeCheck::MergeEdgeAssertions(this, treeVN, ValueNumStore::NoVN, assertions, &rng, false);
4178+
Limit lowerBound = rng.LowerLimit();
4179+
if (lowerBound.IsConstant())
4180+
{
4181+
if (lowerBound.GetConstant() >= 0)
4182+
{
4183+
*isKnownNonNegative = true;
4184+
}
4185+
if (lowerBound.GetConstant() > 0)
4186+
{
4187+
*isKnownNonZero = true;
4188+
}
4189+
}
4190+
}
4191+
}
41284192
}
41294193

41304194
//------------------------------------------------------------------------
@@ -4851,12 +4915,6 @@ GenTree* Compiler::optAssertionProp_Cast(ASSERT_VALARG_TP assertions, GenTreeCas
48514915
// Skip over a GT_COMMA node(s), if necessary to get to the lcl.
48524916
GenTree* lcl = op1->gtEffectiveVal();
48534917

4854-
// If we don't have a cast of a LCL_VAR then bail.
4855-
if (!lcl->OperIs(GT_LCL_VAR))
4856-
{
4857-
return nullptr;
4858-
}
4859-
48604918
// Try and see if we can make this cast into a cheaper zero-extending version
48614919
// if the input is known to be non-negative.
48624920
if (!cast->IsUnsigned() && genActualTypeIsInt(lcl) && cast->TypeIs(TYP_LONG) && (TARGET_POINTER_SIZE == 8))
@@ -4870,6 +4928,12 @@ GenTree* Compiler::optAssertionProp_Cast(ASSERT_VALARG_TP assertions, GenTreeCas
48704928
}
48714929
}
48724930

4931+
// If we don't have a cast of a LCL_VAR then bail.
4932+
if (!lcl->OperIs(GT_LCL_VAR))
4933+
{
4934+
return nullptr;
4935+
}
4936+
48734937
IntegralRange range = IntegralRange::ForCastInput(cast);
48744938
AssertionIndex index = optAssertionIsSubrange(lcl, range, assertions);
48754939
if (index != NO_ASSERTION_INDEX)

src/coreclr/jit/rangecheck.cpp

+53-31
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ void RangeCheck::OptimizeRangeCheck(BasicBlock* block, Statement* stmt, GenTree*
251251
{
252252
JITDUMP("Looking for array size assertions for: " FMT_VN "\n", arrLenVn);
253253
Range arrLength = Range(Limit(Limit::keDependent));
254-
MergeEdgeAssertions(arrLenVn, block->bbAssertionIn, &arrLength);
254+
MergeEdgeAssertions(m_pCompiler, arrLenVn, arrLenVn, block->bbAssertionIn, &arrLength);
255255
if (arrLength.lLimit.IsConstant())
256256
{
257257
arrSize = arrLength.lLimit.GetConstant();
@@ -640,20 +640,28 @@ void RangeCheck::MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP
640640

641641
LclSsaVarDsc* ssaData = m_pCompiler->lvaGetDesc(lcl)->GetPerSsaData(lcl->GetSsaNum());
642642
ValueNum normalLclVN = m_pCompiler->vnStore->VNConservativeNormalValue(ssaData->m_vnPair);
643-
MergeEdgeAssertions(normalLclVN, assertions, pRange);
643+
ValueNum arrLenVN = m_pCompiler->vnStore->VNConservativeNormalValue(m_pCurBndsChk->GetArrayLength()->gtVNPair);
644+
MergeEdgeAssertions(m_pCompiler, normalLclVN, arrLenVN, assertions, pRange);
644645
}
645646

646647
//------------------------------------------------------------------------
647648
// MergeEdgeAssertions: Merge assertions on the edge flowing into the block about a variable
648649
//
649650
// Arguments:
650-
// normalLclVN - the value number to look for assertions for
651-
// assertions - the assertions to use
652-
// pRange - the range to tighten with assertions
651+
// comp - the compiler instance
652+
// normalLclVN - the value number to look for assertions for
653+
// preferredBoundVN - when this VN is set, it will be given preference over constant limits
654+
// assertions - the assertions to use
655+
// pRange - the range to tighten with assertions
653656
//
654-
void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP assertions, Range* pRange)
657+
void RangeCheck::MergeEdgeAssertions(Compiler* comp,
658+
ValueNum normalLclVN,
659+
ValueNum preferredBoundVN,
660+
ASSERT_VALARG_TP assertions,
661+
Range* pRange,
662+
bool log)
655663
{
656-
if (BitVecOps::IsEmpty(m_pCompiler->apTraits, assertions))
664+
if (BitVecOps::IsEmpty(comp->apTraits, assertions))
657665
{
658666
return;
659667
}
@@ -663,14 +671,14 @@ void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP asse
663671
return;
664672
}
665673

666-
// Walk through the "assertions" to check if the apply.
667-
BitVecOps::Iter iter(m_pCompiler->apTraits, assertions);
674+
// Walk through the "assertions" to check if they apply.
675+
BitVecOps::Iter iter(comp->apTraits, assertions);
668676
unsigned index = 0;
669677
while (iter.NextElem(&index))
670678
{
671679
AssertionIndex assertionIndex = GetAssertionIndex(index);
672680

673-
Compiler::AssertionDsc* curAssertion = m_pCompiler->optGetAssertion(assertionIndex);
681+
Compiler::AssertionDsc* curAssertion = comp->optGetAssertion(assertionIndex);
674682

675683
Limit limit(Limit::keUndef);
676684
genTreeOps cmpOper = GT_NONE;
@@ -683,7 +691,7 @@ void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP asse
683691
ValueNumStore::CompareCheckedBoundArithInfo info;
684692

685693
// Get i, len, cns and < as "info."
686-
m_pCompiler->vnStore->GetCompareCheckedBoundArithInfo(curAssertion->op1.vn, &info);
694+
comp->vnStore->GetCompareCheckedBoundArithInfo(curAssertion->op1.vn, &info);
687695

688696
// If we don't have the same variable we are comparing against, bail.
689697
if (normalLclVN != info.cmpOp)
@@ -697,12 +705,12 @@ void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP asse
697705
}
698706

699707
// If the operand that operates on the bound is not constant, then done.
700-
if (!m_pCompiler->vnStore->IsVNInt32Constant(info.arrOp))
708+
if (!comp->vnStore->IsVNInt32Constant(info.arrOp))
701709
{
702710
continue;
703711
}
704712

705-
int cons = m_pCompiler->vnStore->ConstantValue<int>(info.arrOp);
713+
int cons = comp->vnStore->ConstantValue<int>(info.arrOp);
706714
limit = Limit(Limit::keBinOpArray, info.vnBound, info.arrOper == GT_SUB ? -cons : cons);
707715
cmpOper = (genTreeOps)info.cmpOper;
708716
}
@@ -712,7 +720,7 @@ void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP asse
712720
ValueNumStore::CompareCheckedBoundArithInfo info;
713721

714722
// Get the info as "i", "<" and "len"
715-
m_pCompiler->vnStore->GetCompareCheckedBound(curAssertion->op1.vn, &info);
723+
comp->vnStore->GetCompareCheckedBound(curAssertion->op1.vn, &info);
716724

717725
// If we don't have the same variable we are comparing against, bail.
718726
if (normalLclVN == info.cmpOp)
@@ -736,7 +744,7 @@ void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP asse
736744
ValueNumStore::ConstantBoundInfo info;
737745

738746
// Get the info as "i", "<" and "100"
739-
m_pCompiler->vnStore->GetConstantBoundInfo(curAssertion->op1.vn, &info);
747+
comp->vnStore->GetConstantBoundInfo(curAssertion->op1.vn, &info);
740748

741749
// If we don't have the same variable we are comparing against, bail.
742750
if (normalLclVN != info.cmpOpVN)
@@ -756,10 +764,10 @@ void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP asse
756764
continue;
757765
}
758766

759-
int cnstLimit = m_pCompiler->vnStore->CoercedConstantValue<int>(curAssertion->op2.vn);
767+
int cnstLimit = comp->vnStore->CoercedConstantValue<int>(curAssertion->op2.vn);
760768

761769
if ((cnstLimit == 0) && (curAssertion->assertionKind == Compiler::OAK_NOT_EQUAL) &&
762-
m_pCompiler->vnStore->IsVNCheckedBound(curAssertion->op1.vn))
770+
comp->vnStore->IsVNCheckedBound(curAssertion->op1.vn))
763771
{
764772
// we have arr.Len != 0, so the length must be atleast one
765773
limit = Limit(Limit::keConstant, 1);
@@ -804,31 +812,31 @@ void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP asse
804812

805813
// Make sure the assertion is of the form != 0 or == 0 if it isn't a constant assertion.
806814
if (!isConstantAssertion && (curAssertion->assertionKind != Compiler::OAK_NO_THROW) &&
807-
(curAssertion->op2.vn != m_pCompiler->vnStore->VNZeroForType(TYP_INT)))
815+
(curAssertion->op2.vn != comp->vnStore->VNZeroForType(TYP_INT)))
808816
{
809817
continue;
810818
}
811819
#ifdef DEBUG
812-
if (m_pCompiler->verbose)
820+
if (comp->verbose)
813821
{
814-
m_pCompiler->optPrintAssertion(curAssertion, assertionIndex);
822+
comp->optPrintAssertion(curAssertion, assertionIndex);
815823
}
816824
#endif
817825

818826
// Limits are sometimes made with the form vn + constant, where vn is a known constant
819827
// see if we can simplify this to just a constant
820-
if (limit.IsBinOpArray() && m_pCompiler->vnStore->IsVNInt32Constant(limit.vn))
828+
if (limit.IsBinOpArray() && comp->vnStore->IsVNInt32Constant(limit.vn))
821829
{
822-
Limit tempLimit = Limit(Limit::keConstant, m_pCompiler->vnStore->ConstantValue<int>(limit.vn));
830+
Limit tempLimit = Limit(Limit::keConstant, comp->vnStore->ConstantValue<int>(limit.vn));
823831
if (tempLimit.AddConstant(limit.cns))
824832
{
825833
limit = tempLimit;
826834
}
827835
}
828836

829-
ValueNum arrLenVN = m_pCompiler->vnStore->VNConservativeNormalValue(m_pCurBndsChk->GetArrayLength()->gtVNPair);
837+
ValueNum arrLenVN = preferredBoundVN;
830838

831-
if (m_pCompiler->vnStore->IsVNConstant(arrLenVN))
839+
if (comp->vnStore->IsVNConstant(arrLenVN))
832840
{
833841
// Set arrLenVN to NoVN; this will make it match the "vn" recorded on
834842
// constant limits (where we explicitly track the constant and don't
@@ -917,7 +925,10 @@ void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP asse
917925

918926
if (limit.vn != arrLenVN)
919927
{
920-
JITDUMP("Array length VN did not match arrLen=" FMT_VN ", limit=" FMT_VN "\n", arrLenVN, limit.vn);
928+
if (log)
929+
{
930+
JITDUMP("Array length VN did not match arrLen=" FMT_VN ", limit=" FMT_VN "\n", arrLenVN, limit.vn);
931+
}
921932
continue;
922933
}
923934

@@ -927,7 +938,10 @@ void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP asse
927938
// Incoming limit doesn't tighten the existing upper limit.
928939
if (limCns >= curCns)
929940
{
930-
JITDUMP("Bound limit %d doesn't tighten current bound %d\n", limCns, curCns);
941+
if (log)
942+
{
943+
JITDUMP("Bound limit %d doesn't tighten current bound %d\n", limCns, curCns);
944+
}
931945
continue;
932946
}
933947
}
@@ -954,8 +968,12 @@ void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP asse
954968

955969
case GT_GT:
956970
case GT_GE:
957-
pRange->lLimit = limit;
958-
// it doesn't matter if it's isUnsigned or not here - it's not negative anyway.
971+
// GT/GE being unsigned creates a non-contiguous range which we can't represent
972+
// using single Range object.
973+
if (!isUnsigned)
974+
{
975+
pRange->lLimit = limit;
976+
}
959977
break;
960978

961979
case GT_EQ:
@@ -967,9 +985,13 @@ void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP asse
967985
// All other 'cmpOper' kinds leave lLimit/uLimit unchanged
968986
break;
969987
}
970-
JITDUMP("The range after edge merging:");
971-
JITDUMP(pRange->ToString(m_pCompiler));
972-
JITDUMP("\n");
988+
989+
if (log)
990+
{
991+
JITDUMP("The range after edge merging:");
992+
JITDUMP(pRange->ToString(comp));
993+
JITDUMP("\n");
994+
}
973995
}
974996
}
975997

src/coreclr/jit/rangecheck.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,12 @@ class RangeCheck
686686
void MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP assertions, Range* pRange);
687687

688688
// Inspect the assertions about the current ValueNum to refine pRange
689-
void MergeEdgeAssertions(ValueNum num, ASSERT_VALARG_TP assertions, Range* pRange);
689+
static void MergeEdgeAssertions(Compiler* comp,
690+
ValueNum num,
691+
ValueNum preferredBoundVN,
692+
ASSERT_VALARG_TP assertions,
693+
Range* pRange,
694+
bool log = true);
690695

691696
// The maximum possible value of the given "limit". If such a value could not be determined
692697
// return "false". For example: CORINFO_Array_MaxLength for array length.

0 commit comments

Comments
 (0)