Skip to content
Draft
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
35 changes: 35 additions & 0 deletions src/coreclr/jit/assertionprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,10 @@ void Compiler::optPrintAssertion(const AssertionDsc& curAssertion, AssertionInde
curAssertion.GetOp2().GetCheckedBoundConstant());
break;

case O2K_VN:
printf("VN " FMT_VN "", curAssertion.GetOp2().GetVN());
break;

default:
unreached();
break;
Expand Down Expand Up @@ -1421,6 +1425,13 @@ AssertionIndex Compiler::optAddAssertion(const AssertionDsc& newAssertion)
mayHaveDuplicates |= optAssertionHasAssertionsForVN(addOpVN, /* addIfNotFound */ canAddNewAssertions);
}
}
else if (newAssertion.GetOp2().KindIs(O2K_VN))
{
// For VN <relop> VN assertions, register op2's VN too so consumers can find
// the assertion when iterating from the op2 side.
mayHaveDuplicates |= optAssertionHasAssertionsForVN(newAssertion.GetOp2().GetVN(),
/* addIfNotFound */ canAddNewAssertions);
}

if (mayHaveDuplicates)
{
Expand Down Expand Up @@ -1550,6 +1561,12 @@ void Compiler::optDebugCheckAssertion(const AssertionDsc& assertion) const
assert(optLocalAssertionProp);
break;

case O2K_VN:
assert(!optLocalAssertionProp);
assert(assertion.GetOp1().KindIs(O1K_VN));
assert(assertion.IsRelop());
break;

case O2K_ZEROOBJ:
// We only make these assertion for stores (not control flow).
assert(assertion.KindIs(OAK_EQUAL));
Expand Down Expand Up @@ -1807,6 +1824,24 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree)
return idx;
}

// "X relop Y" where neither side is a constant nor a checked bound.
// For now, we only create such assertions for signed comparisons of int32 (and smaller, after promotion).
// This widens what global assertion prop can reason about: e.g. "b > a" combined with "a > 10"
// can be used to deduce "b > 10".
//
// To keep table pressure under control, we only create the assertion if at least one of the
// operands already has assertions registered. Otherwise the new assertion has no other facts
// it can chain with and is unlikely to enable any deduction, while still consuming a slot
// (and potentially crowding out useful ones).
if (!isUnsignedRelop && (op1VN != op2VN) && !vnStore->IsVNConstant(op1VN) && !vnStore->IsVNConstant(op2VN) &&
(optAssertionHasAssertionsForVN(op1VN) || optAssertionHasAssertionsForVN(op2VN)))
{
AssertionDsc dsc = AssertionDsc::CreateRelopVN(this, relopFunc, op1VN, op2VN);
AssertionIndex idx = optAddAssertion(dsc);
optCreateComplementaryAssertion(idx);
return idx;
}
Comment thread
EgorBo marked this conversation as resolved.

return NO_ASSERTION_INDEX;
}

Expand Down
26 changes: 24 additions & 2 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -7984,7 +7984,8 @@ class Compiler
// nor it implies that it's never negative.
O2K_ZEROOBJ,
O2K_SUBRANGE,
O2K_CONST_VEC
O2K_CONST_VEC,
O2K_VN, // op2 is an arbitrary value number (used for VN <relop> VN assertions in global prop).
};

struct AssertionDsc
Expand Down Expand Up @@ -8130,7 +8131,7 @@ class Compiler
ValueNum GetVN() const
{
assert(!m_compiler->optLocalAssertionProp);
assert(KindIs(O2K_CONST_INT, O2K_CONST_DOUBLE, O2K_ZEROOBJ, O2K_CONST_VEC));
assert(KindIs(O2K_CONST_INT, O2K_CONST_DOUBLE, O2K_ZEROOBJ, O2K_CONST_VEC, O2K_VN));
assert(m_vn != ValueNumStore::NoVN);
return m_vn;
}
Expand Down Expand Up @@ -8481,6 +8482,9 @@ class Compiler
case O2K_SUBRANGE:
return GetOp2().GetIntegralRange().Equals(that.GetOp2().GetIntegralRange());

case O2K_VN:
return GetOp2().GetVN() == that.GetOp2().GetVN();

default:
assert(!"Unexpected value for GetOp2().m_kind in AssertionDsc.");
break;
Expand Down Expand Up @@ -8740,6 +8744,24 @@ class Compiler
dsc.m_op2.m_icon.m_iconVal = cns;
return dsc;
}

// Create "op1VN <relop> op2VN" assertion where both operands are arbitrary value numbers
// (used by global assertion prop for VN-to-VN signed comparisons of int32 and smaller).
static AssertionDsc CreateRelopVN(const Compiler* comp, VNFunc relop, ValueNum op1VN, ValueNum op2VN)
{
assert(!comp->optLocalAssertionProp);
assert(op1VN != ValueNumStore::NoVN);
assert(op2VN != ValueNumStore::NoVN);
assert(op1VN != op2VN);

AssertionDsc dsc = CreateEmptyAssertion(comp);
dsc.m_assertionKind = FromVNFunc(relop);
dsc.m_op1.m_kind = O1K_VN;
dsc.m_op1.m_vn = op1VN;
dsc.m_op2.m_kind = O2K_VN;
dsc.m_op2.m_vn = op2VN;
return dsc;
}
};

protected:
Expand Down
108 changes: 107 additions & 1 deletion src/coreclr/jit/rangecheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -938,7 +938,12 @@ Range RangeCheck::GetRangeFromAssertionsWorker(
result = phiRange;
}

MergeEdgeAssertions(comp, num, ValueNumStore::NoVN, assertions, &result, false);
// MergeEdgeAssertionsWorker may recursively call back to GetRangeFromAssertionsWorker for other VN-to-VN assertion
// lookups.
int edgeAssertionsBudget = min(4, budget);
MergeEdgeAssertionsWorker(comp, num, ValueNumStore::NoVN, assertions, &result, /* canUseCheckedBounds */ false,
edgeAssertionsBudget, visited);

assert(result.IsConstantRange());
return result;
}
Expand Down Expand Up @@ -1044,6 +1049,37 @@ void RangeCheck::MergeEdgeAssertions(Compiler* comp,
ASSERT_VALARG_TP assertions,
Range* pRange,
bool canUseCheckedBounds)
{
// Public entry point: no shared visited set; create a fresh one and use a small recursion
// budget for VN-to-VN assertion lookups.
ValueNumStore::SmallValueNumSet visited;
MergeEdgeAssertionsWorker(comp, normalLclVN, preferredBoundVN, assertions, pRange, canUseCheckedBounds,
/* budget */ 4, &visited);
}

//------------------------------------------------------------------------
// MergeEdgeAssertionsWorker: Worker for MergeEdgeAssertions that takes a visited set and recursion
// budget for VN-to-VN assertion lookups.
//
// Arguments:
// comp - the compiler instance
// normalLclVN - the value number to look for assertions for
// preferredBoundVN - when this VN is set, it will be given preference over constant limits
// assertions - the assertions to use
// pRange - the range to tighten with assertions
// canUseCheckedBounds - true if we can use checked bounds assertions (cache)
// budget - the remaining budget for recursive VN-to-VN assertion lookups
// visited - the set of value numbers already visited in the current search path to prevent infinite
// recursion
//
void RangeCheck::MergeEdgeAssertionsWorker(Compiler* comp,
ValueNum normalLclVN,
ValueNum preferredBoundVN,
ASSERT_VALARG_TP assertions,
Range* pRange,
bool canUseCheckedBounds,
int budget,
ValueNumStore::SmallValueNumSet* visited)
{
Range assertedRange = Range(Limit(Limit::keUnknown));
if (BitVecOps::IsEmpty(comp->apTraits, assertions))
Expand Down Expand Up @@ -1343,6 +1379,76 @@ void RangeCheck::MergeEdgeAssertions(Compiler* comp,
continue;
}
}
// Current assertion is of the form "X <relop> Y" where both X and Y are arbitrary VNs
// We try to derive a bound on normalLclVN by recursively computing the range of the other operand.
//
// Example:
//
// if (a > 10 && a < 100)
// {
// if (normalLclVN > a) // a is an unknown VN with [11..99] range derived from assertions.
// {
//
else if (curAssertion.IsRelop() && curAssertion.GetOp2().KindIs(Compiler::O2K_VN) &&
(curAssertion.GetOp1().GetVN() == normalLclVN || curAssertion.GetOp2().GetVN() == normalLclVN))
{
ValueNum op1VN = curAssertion.GetOp1().GetVN();
ValueNum op2VN = curAssertion.GetOp2().GetVN();

cmpOper = Compiler::AssertionDsc::ToCompareOper(curAssertion.GetKind(), &isUnsigned);
if (isUnsigned)
{
continue;
}

ValueNum otherVN;
if (op1VN == normalLclVN)
{
// Assertion is "normalLclVN <cmpOper> otherVN" - keep cmpOper as-is.
otherVN = op2VN;
}
else
{
// Assertion is "otherVN <cmpOper> normalLclVN" - swap to get
// "normalLclVN <swappedCmpOper> otherVN".
assert(op2VN == normalLclVN);
otherVN = op1VN;
cmpOper = GenTree::SwapRelop(cmpOper);
}

if (budget <= 0)
{
continue;
}

budget--;
Range otherRange = GetRangeFromAssertionsWorker(comp, otherVN, assertions, budget, visited);
if (!otherRange.IsConstantRange())
{
continue;
}

// Derive a constant limit for normalLclVN from the constant range of otherVN.
// We use the most useful bound for each direction of the comparison.
int derivedLimit;
switch (cmpOper)
{
case GT_LT:
case GT_LE:
// normalLclVN < otherVN (or <=) => normalLclVN <(=) otherVN.UpperLimit
derivedLimit = otherRange.UpperLimit().GetConstant();
break;
case GT_GT:
case GT_GE:
// normalLclVN > otherVN (or >=) => normalLclVN >(=) otherVN.LowerLimit
derivedLimit = otherRange.LowerLimit().GetConstant();
break;

default:
continue;
}
limit = Limit(Limit::keConstant, derivedLimit);
}
// Current assertion is not supported, ignore it
else
{
Expand Down
12 changes: 12 additions & 0 deletions src/coreclr/jit/rangecheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,18 @@ class RangeCheck
Range* pRange,
bool canUseCheckedBounds = true);

// Internal worker used by GetRangeFromAssertionsWorker: same as the public overload
// but threads a recursion budget and visited set so that VN-to-VN assertions can be
// resolved by recursing into GetRangeFromAssertionsWorker without unbounded work.
static void MergeEdgeAssertionsWorker(Compiler* comp,
ValueNum num,
ValueNum preferredBoundVN,
ASSERT_VALARG_TP assertions,
Range* pRange,
bool canUseCheckedBounds,
int budget,
ValueNumStore::SmallValueNumSet* visited);

// The maximum possible value of the given "limit". If such a value could not be determined
// return "false". For example: CORINFO_Array_MaxLength for array length.
bool GetLimitMax(Limit& limit, int* pMax);
Expand Down
Loading