Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 47 additions & 12 deletions src/coreclr/jit/assertionprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1529,7 +1529,10 @@ void Compiler::optDebugCheckAssertion(const AssertionDsc& assertion) const
case O2K_VN_ADD_CNS:
assert(!optLocalAssertionProp);
assert(assertion.GetOp1().KindIs(O1K_VN));
assert(assertion.IsRelop());
// Most O2K_VN_ADD_CNS assertions are ordered relops ("i <relop> bnd + cns"), but
// we also create equality assertions against a checked bound (e.g. "i != arr.Length")
// for use by RangeCheck.
assert(assertion.IsRelop() || assertion.CanPropEqualOrNotEqual());
break;

case O2K_ZEROOBJ:
Expand Down Expand Up @@ -1682,6 +1685,7 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree)
}

bool isUnsignedRelop;
bool isEqualityRelop = false;
if (relopFuncApp.FuncIs(VNF_LE, VNF_LT, VNF_GE, VNF_GT))
{
isUnsignedRelop = false;
Expand All @@ -1690,10 +1694,20 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree)
{
isUnsignedRelop = true;
}
else if (relopFuncApp.FuncIs(VNF_EQ, VNF_NE))
{
// Equality relops against a checked bound (e.g. "i != arr.Length") are
// useful for RangeCheck to tighten ranges on loop back-edges. They flow
// through the CheckedBound paths below; other equality assertions
// (against constants, locals, type handles, etc.) are handled in
// optAssertionGenJtrue.
isUnsignedRelop = false;
isEqualityRelop = true;
}
else
{
// Not a relop we're interested in.
// Assertions for NE/EQ are handled elsewhere.
// Assertions for EQ/NE not against a checked bound are handled elsewhere.
return NO_ASSERTION_INDEX;
}

Expand All @@ -1710,21 +1724,42 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree)
// "CheckedBnd <relop> X"
if (!isUnsignedRelop && vnStore->IsVNCheckedBound(op1VN))
{
// Move the checked bound to the right side for simplicity
relopFunc = ValueNumStore::SwapRelop(relopFunc);
AssertionDsc dsc = AssertionDsc::CreateCompareCheckedBound(this, relopFunc, op2VN, op1VN, 0);
AssertionIndex idx = optAddAssertion(dsc);
optCreateComplementaryAssertion(idx);
return idx;
// For equality relops where the non-bound side is a constant (e.g. "len != 0"), the
// LCLVAR-based equality assertion created below by optAssertionGenJtrue is strictly more
// useful than a CompareCheckedBound form -- downstream consumers (folding bounds checks,
// proving "len > 0" after a "len != 0" test) only recognize the LCLVAR form. Skip the new
// assertion in that case and let the LCLVAR path produce it.
if (!(isEqualityRelop && vnStore->IsVNConstant(op2VN)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be omitted if AssertionDsc::CreateCompareCheckedBound becomes smarter and switches automatically from O2K_VN_ADD_CNS to O2K_INT_CONST when the vn is a constant

{
// Move the checked bound to the right side for simplicity
relopFunc = ValueNumStore::SwapRelop(relopFunc);
AssertionDsc dsc = AssertionDsc::CreateCompareCheckedBound(this, relopFunc, op2VN, op1VN, 0);
AssertionIndex idx = optAddAssertion(dsc);
optCreateComplementaryAssertion(idx);
return idx;
}
}

// "X <relop> CheckedBnd"
if (!isUnsignedRelop && vnStore->IsVNCheckedBound(op2VN))
{
AssertionDsc dsc = AssertionDsc::CreateCompareCheckedBound(this, relopFunc, op1VN, op2VN, 0);
AssertionIndex idx = optAddAssertion(dsc);
optCreateComplementaryAssertion(idx);
return idx;
// Symmetric guard: leave constant-vs-CheckedBound equality assertions to the LCLVAR path.
if (!(isEqualityRelop && vnStore->IsVNConstant(op1VN)))
{
AssertionDsc dsc = AssertionDsc::CreateCompareCheckedBound(this, relopFunc, op1VN, op2VN, 0);
AssertionIndex idx = optAddAssertion(dsc);
optCreateComplementaryAssertion(idx);
return idx;
}
}

// The remaining "(CheckedBnd + CNS) <relop> X" cases are only useful when the
// comparison is ordered (LT/LE/GT/GE). For equality relops we don't produce
// CheckedBoundAddConst-shaped assertions; the consumers (RangeCheck) only
// tighten ranges from equality assertions whose RHS is the bound itself.
if (isEqualityRelop)
{
return NO_ASSERTION_INDEX;
}

// "(CheckedBnd + CNS) <relop> X"
Expand Down
42 changes: 42 additions & 0 deletions src/coreclr/jit/rangecheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1442,6 +1442,48 @@ void RangeCheck::MergeEdgeAssertionsWorker(Compiler* comp
}
}
}
// Current assertion is of the form "X != (BoundVN + 0)" or "X == (BoundVN + 0)"
// where BoundVN is a length-like checked bound (e.g. an array length). These are
// generated from loop exit tests written as "i != arr.Length"; we can tighten the
// induction variable's range when its current upper/lower limit equals the bound.
else if (canUseCheckedBounds && curAssertion.KindIs(Compiler::OAK_EQUAL, Compiler::OAK_NOT_EQUAL) &&
(curAssertion.GetOp1().GetVN() == normalLclVN) &&
curAssertion.GetOp2().KindIs(Compiler::O2K_VN_ADD_CNS) && (curAssertion.GetOp2().GetCns() == 0) &&
comp->vnStore->IsVNCheckedBound(curAssertion.GetOp2().GetVN()))
{
const ValueNum boundVN = curAssertion.GetOp2().GetVN();

if (curAssertion.KindIs(Compiler::OAK_EQUAL))
{
// X == bound: range tightens to exactly [bound, bound].
limit = Limit(Limit::keBinOpArray, boundVN, 0);
cmpOper = GT_EQ;
Comment thread
EgorBo marked this conversation as resolved.
}
else
{
// X != bound: only useful when pRange's upper or lower limit already equals
// the bound. Tighten:
// [lo, bound] -> [lo, bound - 1]
// [bound, hi] -> [bound + 1, hi]
if (pRange->UpperLimit().IsBinOpArray() && (pRange->UpperLimit().vn == boundVN) &&

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect [bound, ..] assertions aren't useful generally, but it's fine to keep

(pRange->UpperLimit().GetConstant() == 0))
{
limit = Limit(Limit::keBinOpArray, boundVN, -1);
cmpOper = GT_LE;
}
else if (pRange->LowerLimit().IsBinOpArray() && (pRange->LowerLimit().vn == boundVN) &&
(pRange->LowerLimit().GetConstant() == 0))
{
limit = Limit(Limit::keBinOpArray, boundVN, 1);
cmpOper = GT_GE;
}
else
{
// Nothing to deduce from this assertion at this site.
continue;
}
}
}
// Current assertion asserts a bounds check does not throw
else if (curAssertion.IsBoundsCheckNoThrow())
{
Expand Down
Loading