Skip to content

Commit 7cf676f

Browse files
authored
Bounds checks: make MergeEdgeAssertions more precise (#113233)
1 parent 8a9d492 commit 7cf676f

File tree

2 files changed

+88
-103
lines changed

2 files changed

+88
-103
lines changed

src/coreclr/jit/rangecheck.cpp

Lines changed: 86 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -651,13 +651,10 @@ bool RangeCheck::TryGetRangeFromAssertions(Compiler* comp, ValueNum num, ASSERT_
651651
// assertions - the assertions to use
652652
// pRange - the range to tighten with assertions
653653
//
654-
void RangeCheck::MergeEdgeAssertions(Compiler* comp,
655-
ValueNum normalLclVN,
656-
ValueNum preferredBoundVN,
657-
ASSERT_VALARG_TP assertions,
658-
Range* pRange,
659-
bool log)
654+
void RangeCheck::MergeEdgeAssertions(
655+
Compiler* comp, ValueNum normalLclVN, ValueNum preferredBoundVN, ASSERT_VALARG_TP assertions, Range* pRange)
660656
{
657+
Range assertedRange = Range(Limit(Limit::keUnknown));
661658
if (BitVecOps::IsEmpty(comp->apTraits, assertions))
662659
{
663660
return;
@@ -897,90 +894,15 @@ void RangeCheck::MergeEdgeAssertions(Compiler* comp,
897894
continue;
898895
}
899896

900-
// Skip if it doesn't tighten the current bound:
901-
if (pRange->uLimit.IsConstant() && ((cmpOper == GT_LE) || (cmpOper == GT_LT)))
902-
{
903-
if (!limit.IsConstant() && (limit.vn != arrLenVN))
904-
{
905-
// If our new limit is not constant and doesn't represent the array's length - bail out.
906-
// NOTE: it's fine to replace the current constant limit with a non-constant arrLenVN.
907-
continue;
908-
}
909-
if (limit.IsConstant() && (limit.cns > pRange->uLimit.cns))
910-
{
911-
// The new constant limit doesn't tighten the current constant bound.
912-
// E.g. current is "X < 10" and the new one is "X < 100"
913-
continue;
914-
}
915-
}
916-
// Same for the lower bound:
917-
if (pRange->lLimit.IsConstant() && ((cmpOper == GT_GE) || (cmpOper == GT_GT)))
918-
{
919-
if (!limit.IsConstant() && (limit.vn != arrLenVN))
920-
{
921-
// If our new limit is not constant and doesn't represent the array's length - bail out.
922-
// NOTE: it's fine to replace the current constant limit with a non-constant arrLenVN.
923-
continue;
924-
}
925-
if (limit.IsConstant() && (limit.cns < pRange->lLimit.cns))
926-
{
927-
// The new constant limit doesn't tighten the current constant bound.
928-
// E.g. current is "X > 10" and the new one is "X > 5"
929-
continue;
930-
}
931-
}
932-
933-
// Check if the incoming limit from assertions tightens the existing upper limit.
934-
if (pRange->uLimit.IsBinOpArray() && (pRange->uLimit.vn == arrLenVN))
935-
{
936-
// We have checked the current range's (pRange's) upper limit is either of the form:
937-
// length + cns
938-
// and length == the bndsChkCandidate's arrLen
939-
//
940-
// We want to check if the incoming limit tightens the bound, and for that
941-
// we need to make sure that incoming limit is also on the same length (or
942-
// length + cns) and not some other length.
943-
944-
if (limit.vn != arrLenVN)
945-
{
946-
if (log)
947-
{
948-
JITDUMP("Array length VN did not match arrLen=" FMT_VN ", limit=" FMT_VN "\n", arrLenVN, limit.vn);
949-
}
950-
continue;
951-
}
952-
953-
int curCns = pRange->uLimit.cns;
954-
int limCns = limit.IsBinOpArray() ? limit.cns : 0;
955-
956-
// Incoming limit doesn't tighten the existing upper limit.
957-
if (limCns >= curCns)
958-
{
959-
if (log)
960-
{
961-
JITDUMP("Bound limit %d doesn't tighten current bound %d\n", limCns, curCns);
962-
}
963-
continue;
964-
}
965-
}
966-
else
967-
{
968-
// Current range's upper bound is not "length + cns" and the
969-
// incoming limit is not on the same length as the bounds check candidate.
970-
// So we could skip this assertion. But in cases, of Dependent or Unknown
971-
// type of upper limit, the incoming assertion still tightens the upper
972-
// bound to a saner value. So do not skip the assertion.
973-
}
974-
975897
// cmpOp (loop index i) cmpOper len +/- cns
976898
switch (cmpOper)
977899
{
978900
case GT_LT:
979901
case GT_LE:
980-
pRange->uLimit = limit;
902+
assertedRange.uLimit = limit;
981903
if (isUnsigned)
982904
{
983-
pRange->lLimit = Limit(Limit::keConstant, 0);
905+
assertedRange.lLimit = Limit(Limit::keConstant, 0);
984906
}
985907
break;
986908

@@ -990,31 +912,98 @@ void RangeCheck::MergeEdgeAssertions(Compiler* comp,
990912
// using single Range object.
991913
if (!isUnsigned)
992914
{
993-
pRange->lLimit = limit;
994-
// INT32_MAX as the upper limit is better than UNKNOWN for a constant lower limit.
995-
if (limit.IsConstant() && pRange->UpperLimit().IsUnknown())
996-
{
997-
pRange->uLimit = Limit(Limit::keConstant, INT32_MAX);
998-
}
915+
assertedRange.lLimit = limit;
999916
}
1000917
break;
1001918

1002919
case GT_EQ:
1003-
pRange->uLimit = limit;
1004-
pRange->lLimit = limit;
920+
assertedRange.uLimit = limit;
921+
assertedRange.lLimit = limit;
1005922
break;
1006923

1007924
default:
1008925
// All other 'cmpOper' kinds leave lLimit/uLimit unchanged
1009926
break;
1010927
}
1011928

1012-
if (log)
1013-
{
1014-
JITDUMP("The range after edge merging:");
1015-
JITDUMP(pRange->ToString(comp));
1016-
JITDUMP("\n");
1017-
}
929+
// We have two ranges - we need to merge (tighten) them.
930+
931+
auto tightenLimit = [](Limit l1, Limit l2, ValueNum preferredBound, bool isLower) -> Limit {
932+
// 1) One of the limits is undef, unknown or dependent
933+
if (l1.IsUndef() || l2.IsUndef())
934+
{
935+
// Anything is better than undef.
936+
return l1.IsUndef() ? l2 : l1;
937+
}
938+
if (l1.IsUnknown() || l2.IsUnknown())
939+
{
940+
// Anything is better than unknown.
941+
return l1.IsUnknown() ? l2 : l1;
942+
}
943+
if (l1.IsDependent() || l2.IsDependent())
944+
{
945+
// Anything is better than dependent.
946+
return l1.IsDependent() ? l2 : l1;
947+
}
948+
949+
// 2) Both limits are constants
950+
if (l1.IsConstant() && l2.IsConstant())
951+
{
952+
// isLower: whatever is higher is better.
953+
// !isLower: whatever is lower is better.
954+
return isLower ? (l1.cns > l2.cns ? l1 : l2) : (l1.cns < l2.cns ? l1 : l2);
955+
}
956+
957+
// 3) Both limits are BinOpArray (which is "arrLen + cns")
958+
if (l1.IsBinOpArray() && l2.IsBinOpArray())
959+
{
960+
// If one of them is preferredBound and the other is not, use the preferredBound.
961+
if (preferredBound != ValueNumStore::NoVN)
962+
{
963+
if ((l1.vn == preferredBound) && (l2.vn != preferredBound))
964+
{
965+
return l1;
966+
}
967+
if ((l2.vn == preferredBound) && (l1.vn != preferredBound))
968+
{
969+
return l2;
970+
}
971+
}
972+
973+
// Otherwise, just use the one with the higher/lower constant.
974+
// even if they use different arrLen.
975+
return isLower ? (l1.cns > l2.cns ? l1 : l2) : (l1.cns < l2.cns ? l1 : l2);
976+
}
977+
978+
// 4) One of the limits is a constant and the other is BinOpArray
979+
if ((l1.IsConstant() && l2.IsBinOpArray()) || (l2.IsConstant() && l1.IsBinOpArray()))
980+
{
981+
// l1 - BinOpArray, l2 - constant
982+
if (l1.IsConstant())
983+
{
984+
std::swap(l1, l2);
985+
}
986+
987+
if (((preferredBound == ValueNumStore::NoVN) || (l1.vn != preferredBound)))
988+
{
989+
// if we don't have a preferred bound,
990+
// or it doesn't match l1.vn, use the constant (l2).
991+
return l2;
992+
}
993+
994+
// Otherwise, prefer the BinOpArray(preferredBound) over the constant.
995+
return l1;
996+
}
997+
unreached();
998+
};
999+
1000+
JITDUMP("Tightening pRange: [%s] with assertedRange: [%s] into ", pRange->ToString(comp),
1001+
assertedRange.ToString(comp));
1002+
1003+
pRange->lLimit = tightenLimit(assertedRange.lLimit, pRange->lLimit, preferredBoundVN, true);
1004+
pRange->uLimit = tightenLimit(assertedRange.uLimit, pRange->uLimit, preferredBoundVN, false);
1005+
1006+
JITDUMP("[%s]\n", pRange->ToString(comp));
10181007
}
10191008
}
10201009

src/coreclr/jit/rangecheck.h

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -728,12 +728,8 @@ class RangeCheck
728728
void MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP assertions, Range* pRange);
729729

730730
// Inspect the assertions about the current ValueNum to refine pRange
731-
static void MergeEdgeAssertions(Compiler* comp,
732-
ValueNum num,
733-
ValueNum preferredBoundVN,
734-
ASSERT_VALARG_TP assertions,
735-
Range* pRange,
736-
bool log = true);
731+
static void MergeEdgeAssertions(
732+
Compiler* comp, ValueNum num, ValueNum preferredBoundVN, ASSERT_VALARG_TP assertions, Range* pRange);
737733

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

0 commit comments

Comments
 (0)