Skip to content

Commit 9c4c520

Browse files
committed
Introduce a concept of minimum array length into range check
1 parent 3873bf5 commit 9c4c520

File tree

6 files changed

+410
-48
lines changed

6 files changed

+410
-48
lines changed

src/coreclr/src/jit/assertionprop.cpp

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1625,7 +1625,6 @@ void Compiler::optDebugCheckAssertion(AssertionDsc* assertion)
16251625
assert(assertion->op2.u1.iconFlags != 0);
16261626
break;
16271627
case O1K_LCLVAR:
1628-
case O1K_ARR_BND:
16291628
assert((lvaTable[assertion->op1.lcl.lclNum].lvType != TYP_REF) || (assertion->op2.u1.iconVal == 0));
16301629
break;
16311630
case O1K_VALUE_NUMBER:
@@ -1959,12 +1958,34 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree)
19591958
{
19601959
std::swap(op1, op2);
19611960
}
1961+
1962+
ValueNum op1VN = vnStore->VNConservativeNormalValue(op1->gtVNPair);
19621963
// If op1 is lcl and op2 is const or lcl, create assertion.
19631964
if ((op1->gtOper == GT_LCL_VAR) &&
19641965
((op2->OperKind() & GTK_CONST) || (op2->gtOper == GT_LCL_VAR))) // Fix for Dev10 851483
19651966
{
19661967
return optCreateJtrueAssertions(op1, op2, assertionKind);
19671968
}
1969+
else if (vnStore->IsVNCheckedBound(op1VN) && op2->OperIs(GT_CNS_INT))
1970+
{
1971+
AssertionDsc dsc;
1972+
dsc.assertionKind = OAK_EQUAL;
1973+
dsc.op1.vn = vnStore->VNConservativeNormalValue(relop->gtVNPair);
1974+
dsc.op1.kind = O1K_ARR_BND;
1975+
dsc.op1.bnd.vnIdx = vnStore->VNForIntCon(op2->AsIntCon()->IntegralValue() - 1);
1976+
dsc.op1.bnd.vnLen = op1VN;
1977+
dsc.op2.vn = vnStore->VNConservativeNormalValue(op2->gtVNPair);
1978+
dsc.op2.kind = O2K_CONST_INT;
1979+
dsc.op2.u1.iconFlags = 0;
1980+
dsc.op2.u1.iconVal = 0;
1981+
1982+
AssertionIndex index = optAddAssertion(&dsc);
1983+
if (relop->OperIs(GT_NE))
1984+
{
1985+
return AssertionInfo::ForNextEdge(index);
1986+
}
1987+
return index;
1988+
}
19681989

19691990
// Check op1 and op2 for an indirection of a GT_LCL_VAR and keep it in op1.
19701991
if (((op1->gtOper != GT_IND) || (op1->AsOp()->gtOp1->gtOper != GT_LCL_VAR)) &&

src/coreclr/src/jit/rangecheck.cpp

Lines changed: 61 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "jitpch.h"
77
#include "rangecheck.h"
8+
#include "compiler.h"
89

910
// Max stack depth (path length) in walking the UD chain.
1011
static const int MAX_SEARCH_DEPTH = 100;
@@ -60,7 +61,7 @@ int RangeCheck::GetArrLength(ValueNum vn)
6061
}
6162

6263
// Check if the computed range is within bounds.
63-
bool RangeCheck::BetweenBounds(Range& range, int lower, GenTree* upper)
64+
bool RangeCheck::BetweenBounds(Range& range, int lower, ValueNum uLimitVN, int arrSize DEBUGARG(GenTree* upper))
6465
{
6566
#ifdef DEBUG
6667
if (m_pCompiler->verbose)
@@ -73,10 +74,8 @@ bool RangeCheck::BetweenBounds(Range& range, int lower, GenTree* upper)
7374

7475
ValueNumStore* vnStore = m_pCompiler->vnStore;
7576

76-
// Get the VN for the upper limit.
77-
ValueNum uLimitVN = vnStore->VNConservativeNormalValue(upper->gtVNPair);
78-
7977
#ifdef DEBUG
78+
assert(vnStore->VNConservativeNormalValue(upper->gtVNPair));
8079
JITDUMP(FMT_VN " upper bound is: ", uLimitVN);
8180
if (m_pCompiler->verbose)
8281
{
@@ -85,26 +84,7 @@ bool RangeCheck::BetweenBounds(Range& range, int lower, GenTree* upper)
8584
JITDUMP("\n");
8685
#endif
8786

88-
int arrSize = 0;
89-
90-
if (vnStore->IsVNConstant(uLimitVN))
91-
{
92-
ssize_t constVal = -1;
93-
unsigned iconFlags = 0;
94-
95-
if (m_pCompiler->optIsTreeKnownIntValue(true, upper, &constVal, &iconFlags))
96-
{
97-
arrSize = (int)constVal;
98-
}
99-
}
100-
else if (vnStore->IsVNArrLen(uLimitVN))
101-
{
102-
// Get the array reference from the length.
103-
ValueNum arrRefVN = vnStore->GetArrForLenVn(uLimitVN);
104-
// Check if array size can be obtained.
105-
arrSize = vnStore->GetNewArrSize(arrRefVN);
106-
}
107-
else if (!vnStore->IsVNCheckedBound(uLimitVN))
87+
if ((arrSize <= 0) && !vnStore->IsVNCheckedBound(uLimitVN))
10888
{
10989
// If the upper limit is not length, then bail.
11090
return false;
@@ -231,6 +211,16 @@ void RangeCheck::OptimizeRangeCheck(BasicBlock* block, Statement* stmt, GenTree*
231211
#endif // FEATURE_SIMD
232212
{
233213
arrSize = GetArrLength(arrLenVn);
214+
if (arrSize <= 0)
215+
{
216+
// see if there are any assertions about the array size we can use
217+
Range arrLength = Range(Limit(Limit::keDependent));
218+
MergeEdgeAssertions(arrLenVn, block->bbAssertionIn, &arrLength);
219+
if (arrLength.lLimit.IsConstant())
220+
{
221+
arrSize = arrLength.lLimit.GetConstant();
222+
}
223+
}
234224
}
235225

236226
JITDUMP("ArrSize for lengthVN:%03X = %d\n", arrLenVn, arrSize);
@@ -286,7 +276,7 @@ void RangeCheck::OptimizeRangeCheck(BasicBlock* block, Statement* stmt, GenTree*
286276
}
287277

288278
// Is the range between the lower and upper bound values.
289-
if (BetweenBounds(range, 0, bndsChk->gtArrLen))
279+
if (BetweenBounds(range, 0, arrLenVn, arrSize DEBUGARG(bndsChk->gtArrLen)))
290280
{
291281
JITDUMP("[RangeCheck::OptimizeRangeCheck] Between bounds\n");
292282
m_pCompiler->optRemoveRangeCheck(treeParent, stmt);
@@ -532,7 +522,6 @@ void RangeCheck::SetDef(UINT64 hash, Location* loc)
532522
}
533523
#endif
534524

535-
// Merge assertions on the edge flowing into the block about a variable.
536525
void RangeCheck::MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP assertions, Range* pRange)
537526
{
538527
if (BitVecOps::IsEmpty(m_pCompiler->apTraits, assertions))
@@ -544,6 +533,20 @@ void RangeCheck::MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP
544533
{
545534
return;
546535
}
536+
537+
LclVarDsc* varDsc = m_pCompiler->lvaGetDesc(lcl);
538+
if (varDsc->CanBeReplacedWithItsField(m_pCompiler))
539+
{
540+
varDsc = m_pCompiler->lvaGetDesc(varDsc->lvFieldLclStart);
541+
}
542+
LclSsaVarDsc* ssaData = varDsc->GetPerSsaData(lcl->GetSsaNum());
543+
ValueNum normalLclVN = m_pCompiler->vnStore->VNConservativeNormalValue(ssaData->m_vnPair);
544+
MergeEdgeAssertions(normalLclVN, assertions, pRange);
545+
}
546+
547+
// Merge assertions on the edge flowing into the block about a variable.
548+
void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP assertions, Range* pRange)
549+
{
547550
// Walk through the "assertions" to check if the apply.
548551
BitVecOps::Iter iter(m_pCompiler->apTraits, assertions);
549552
unsigned index = 0;
@@ -556,14 +559,6 @@ void RangeCheck::MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP
556559
Limit limit(Limit::keUndef);
557560
genTreeOps cmpOper = GT_NONE;
558561

559-
LclVarDsc* varDsc = m_pCompiler->lvaGetDesc(lcl);
560-
if (varDsc->CanBeReplacedWithItsField(m_pCompiler))
561-
{
562-
varDsc = m_pCompiler->lvaGetDesc(varDsc->lvFieldLclStart);
563-
}
564-
LclSsaVarDsc* ssaData = varDsc->GetPerSsaData(lcl->GetSsaNum());
565-
ValueNum normalLclVN = m_pCompiler->vnStore->VNConservativeNormalValue(ssaData->m_vnPair);
566-
567562
// Current assertion is of the form (i < len - cns) != 0
568563
if (curAssertion->IsCheckedBoundArithBound())
569564
{
@@ -602,13 +597,20 @@ void RangeCheck::MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP
602597
m_pCompiler->vnStore->GetCompareCheckedBound(curAssertion->op1.vn, &info);
603598

604599
// If we don't have the same variable we are comparing against, bail.
605-
if (normalLclVN != info.cmpOp)
600+
if (normalLclVN == info.cmpOp)
601+
{
602+
cmpOper = (genTreeOps)info.cmpOper;
603+
limit = Limit(Limit::keBinOpArray, info.vnBound, 0);
604+
}
605+
else if (info.vnBound == info.vnBound)
606+
{
607+
cmpOper = GenTree::SwapRelop((genTreeOps)info.cmpOper);
608+
limit = Limit(Limit::keBinOpArray, info.cmpOp, 0);
609+
}
610+
else
606611
{
607612
continue;
608613
}
609-
610-
limit = Limit(Limit::keBinOpArray, info.vnBound, 0);
611-
cmpOper = (genTreeOps)info.cmpOper;
612614
}
613615
// Current assertion is of the form (i < 100) != 0
614616
else if (curAssertion->IsConstantBound())
@@ -627,6 +629,11 @@ void RangeCheck::MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP
627629
limit = Limit(Limit::keConstant, info.constVal);
628630
cmpOper = (genTreeOps)info.cmpOper;
629631
}
632+
else if (IsConstantAssertion(curAssertion, normalLclVN))
633+
{
634+
limit = Limit(Limit::keConstant, m_pCompiler->vnStore->ConstantValue<int>(curAssertion->op2.vn));
635+
cmpOper = GT_EQ;
636+
}
630637
// Current assertion is not supported, ignore it
631638
else
632639
{
@@ -636,7 +643,8 @@ void RangeCheck::MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP
636643
assert(limit.IsBinOpArray() || limit.IsConstant());
637644

638645
// Make sure the assertion is of the form != 0 or == 0.
639-
if (curAssertion->op2.vn != m_pCompiler->vnStore->VNZeroForType(TYP_INT))
646+
if ((curAssertion->op2.vn != m_pCompiler->vnStore->VNZeroForType(TYP_INT)) &&
647+
(cmpOper != GT_EQ))
640648
{
641649
continue;
642650
}
@@ -647,6 +655,13 @@ void RangeCheck::MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP
647655
}
648656
#endif
649657

658+
// Limits are sometimes made with the form vn + constant, where vn is a known constant
659+
// see if we can simplify this to just a constant
660+
if (limit.IsBinOpArray() && m_pCompiler->vnStore->IsVNInt32Constant(limit.vn))
661+
{
662+
limit = Limit(Limit::keConstant, m_pCompiler->vnStore->ConstantValue<int>(limit.vn) + limit.cns);
663+
}
664+
650665
ValueNum arrLenVN = m_pCompiler->vnStore->VNConservativeNormalValue(m_pCurBndsChk->gtArrLen->gtVNPair);
651666

652667
if (m_pCompiler->vnStore->IsVNConstant(arrLenVN))
@@ -663,6 +678,7 @@ void RangeCheck::MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP
663678
// (i < length) != 0
664679
// (i < 100) == 0
665680
// (i < 100) != 0
681+
// i == 100
666682
//
667683
// At this point, we have detected that op1.vn is (i < length) or (i < length + cns) or
668684
// (i < 100) and the op2.vn is 0.
@@ -673,12 +689,12 @@ void RangeCheck::MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP
673689
// If we have an assertion of the form == 0 (i.e., equals false), then reverse relop.
674690
// The relop has to be reversed because we have: (i < length) is false which is the same
675691
// as (i >= length).
676-
if (curAssertion->assertionKind == Compiler::OAK_EQUAL)
692+
if ((curAssertion->assertionKind == Compiler::OAK_EQUAL) && (cmpOper != GT_EQ))
677693
{
678694
cmpOper = GenTree::ReverseRelop(cmpOper);
679695
}
680696

681-
// Bounds are inclusive, so add -1 for upper bound when "<". But make sure we won't overflow.
697+
// Bounds are inclusive, so add -1 for upper bound when "<". But make sure we won't underflow.
682698
if (cmpOper == GT_LT && !limit.AddConstant(-1))
683699
{
684700
continue;
@@ -744,6 +760,10 @@ void RangeCheck::MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP
744760
pRange->lLimit = limit;
745761
break;
746762

763+
case GT_EQ:
764+
pRange->uLimit = limit;
765+
pRange->lLimit = limit;
766+
747767
default:
748768
// All other 'cmpOper' kinds leave lLimit/uLimit unchanged
749769
break;

src/coreclr/src/jit/rangecheck.h

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ struct Limit
165165
}
166166
return false;
167167
}
168+
168169
#ifdef DEBUG
169170
const char* ToString(CompAllocator alloc)
170171
{
@@ -376,6 +377,21 @@ struct RangeOps
376377
result.uLimit = r2hi;
377378
}
378379
}
380+
if (r1lo.IsBinOpArray() && r2lo.IsBinOpArray() && r1lo.vn == r2lo.vn)
381+
{
382+
result.lLimit = r2lo.GetConstant() < r1lo.GetConstant() ? r2lo : r1lo;
383+
}
384+
385+
if (r1lo.IsConstant() && r1lo.GetConstant() >= 0 && r2lo.IsBinOpArray() &&
386+
r2lo.GetConstant() >= r1lo.GetConstant())
387+
{
388+
result.lLimit = r1lo;
389+
}
390+
else if (r2lo.IsConstant() && r2lo.GetConstant() >= 0 && r1lo.IsBinOpArray() &&
391+
r1lo.GetConstant() >= r2lo.GetConstant())
392+
{
393+
result.lLimit = r2lo;
394+
}
379395
return result;
380396
}
381397
};
@@ -438,7 +454,7 @@ class RangeCheck
438454
// assumes that the lower range is resolved and upper range is symbolic as in an
439455
// increasing loop.
440456
// TODO-CQ: This is not general enough.
441-
bool BetweenBounds(Range& range, int lower, GenTree* upper);
457+
bool BetweenBounds(Range& range, int lower, ValueNum uLimitVN, int arrSize DEBUGARG(GenTree* upper));
442458

443459
// Entry point to optimize range checks in the block. Assumes value numbering
444460
// and assertion prop phases are completed.
@@ -474,6 +490,8 @@ class RangeCheck
474490
// refine the "pRange" value.
475491
void MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP assertions, Range* pRange);
476492

493+
void MergeEdgeAssertions(ValueNum num, ASSERT_VALARG_TP, Range* pRange);
494+
477495
// The maximum possible value of the given "limit." If such a value could not be determined
478496
// return "false." For example: ARRLEN_MAX for array length.
479497
bool GetLimitMax(Limit& limit, int* pMax);
@@ -519,6 +537,13 @@ class RangeCheck
519537

520538
GenTreeBoundsChk* m_pCurBndsChk;
521539

540+
// Is the given assertion a constant assertion with the given vn
541+
bool IsConstantAssertion(Compiler::AssertionDsc* dsc, ValueNum vn)
542+
{
543+
return (dsc->assertionKind == Compiler::OAK_EQUAL) && (dsc->op1.vn == vn)
544+
&& (m_pCompiler->vnStore->IsVNInt32Constant(dsc->op2.vn));
545+
}
546+
522547
// Get the cached overflow values.
523548
OverflowMap* GetOverflowMap();
524549
OverflowMap* m_pOverflowMap;

src/libraries/System.Private.CoreLib/src/System/String.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -487,13 +487,9 @@ public char[] ToCharArray(int startIndex, int length)
487487
[NonVersionable]
488488
public static bool IsNullOrEmpty([NotNullWhen(false)] string? value)
489489
{
490-
// Using 0u >= (uint)value.Length rather than
491-
// value.Length == 0 as it will elide the bounds check to
492-
// the first char: value[0] if that is performed following the test
493-
// for the same test cost.
494490
// Ternary operator returning true/false prevents redundant asm generation:
495491
// https://github.com/dotnet/runtime/issues/4207
496-
return (value == null || 0u >= (uint)value.Length) ? true : false;
492+
return (value == null || 0 == value.Length) ? true : false;
497493
}
498494

499495
public static bool IsNullOrWhiteSpace([NotNullWhen(false)] string? value)

0 commit comments

Comments
 (0)