Skip to content
19 changes: 9 additions & 10 deletions src/coreclr/jit/assertionprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4330,19 +4330,18 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions,
return optAssertionProp_Update(newTree, tree, stmt);
}

ValueNum op1VN = vnStore->VNConservativeNormalValue(op1->gtVNPair);
ValueNum op2VN = vnStore->VNConservativeNormalValue(op2->gtVNPair);

if (op1->TypeIs(TYP_INT) && op2->TypeIs(TYP_INT))
// See if we can fold the relop based on range information.
// We don't need the op1->TypeIs(TYP_INT) check, but it seems to improve the TP quite a bit.
if (op1->TypeIs(TYP_INT))
{
Range rng1 = RangeCheck::GetRangeFromAssertions(this, op1VN, assertions);
Range rng2 = RangeCheck::GetRangeFromAssertions(this, op2VN, assertions);
ValueNum relopVN = vnStore->VNConservativeNormalValue(tree->gtVNPair);
Range relopRange = RangeCheck::GetRangeFromAssertions(this, relopVN, assertions);

RangeOps::RelationKind kind = RangeOps::EvalRelop(tree->OperGet(), tree->IsUnsigned(), rng1, rng2);
if ((kind != RangeOps::RelationKind::Unknown))
int relopResult;
if (relopRange.IsSingleValueConstant(&relopResult))
{
newTree = kind == RangeOps::RelationKind::AlwaysTrue ? gtNewTrue() : gtNewFalse();
newTree = gtWrapWithSideEffects(newTree, tree, GTF_ALL_EFFECT);
assert((relopResult == 0) || (relopResult == 1));
newTree = gtWrapWithSideEffects(relopResult == 1 ? gtNewTrue() : gtNewFalse(), tree, GTF_ALL_EFFECT);
return optAssertionProp_Update(newTree, tree, stmt);
}
}
Expand Down
110 changes: 72 additions & 38 deletions src/coreclr/jit/rangecheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -678,41 +678,32 @@ Range RangeCheck::GetRangeFromAssertions(Compiler* comp, ValueNum num, ASSERT_VA
switch (funcApp.m_func)
{
case VNF_Cast:
// The logic matches IntegralRange::ForCastOutput for small types.
{
var_types castToType;
bool srcIsUnsigned;
comp->vnStore->GetCastOperFromVN(funcApp.m_args[1], &castToType, &srcIsUnsigned);

// GetRangeFromType returns a non-constant range if it can't be represented with Range
Range castToTypeRange = GetRangeFromType(castToType);
if (castToTypeRange.IsConstantRange())
{
var_types castToType;
bool srcIsUnsigned;
comp->vnStore->GetCastOperFromVN(funcApp.m_args[1], &castToType, &srcIsUnsigned);
switch (castToType)
result = castToTypeRange;

// Now see if we can do better by looking at the cast source.
// if its range is within the castTo range, we can use that (and the cast is basically a no-op).
if (comp->vnStore->TypeOfVN(funcApp.m_args[0]) == TYP_INT)
{
case TYP_UBYTE:
result.lLimit = Limit(Limit::keConstant, UINT8_MIN);
result.uLimit = Limit(Limit::keConstant, UINT8_MAX);
break;

case TYP_BYTE:
result.lLimit = Limit(Limit::keConstant, INT8_MIN);
result.uLimit = Limit(Limit::keConstant, INT8_MAX);
break;

case TYP_USHORT:
result.lLimit = Limit(Limit::keConstant, UINT16_MIN);
result.uLimit = Limit(Limit::keConstant, UINT16_MAX);
break;

case TYP_SHORT:
result.lLimit = Limit(Limit::keConstant, INT16_MIN);
result.uLimit = Limit(Limit::keConstant, INT16_MAX);
break;

default:
break;
Range castOpRange = GetRangeFromAssertions(comp, funcApp.m_args[0], assertions, --budget);
if (castOpRange.IsConstantRange() &&
(castOpRange.LowerLimit().GetConstant() >= castToTypeRange.LowerLimit().GetConstant()) &&
(castOpRange.UpperLimit().GetConstant() <= castToTypeRange.UpperLimit().GetConstant()))
{
result = castOpRange;
}
}

// If we wanted to be more precise, we could also try to get the range of the source
// and if it's smaller than the cast range, use that.
}
break;
}
break;

case VNF_NEG:
{
Expand Down Expand Up @@ -784,13 +775,45 @@ Range RangeCheck::GetRangeFromAssertions(Compiler* comp, ValueNum num, ASSERT_VA
case VNF_GE_UN:
case VNF_LT:
case VNF_LT_UN:
case VNF_LE_UN:
case VNF_LE:
case VNF_LE_UN:
case VNF_EQ:
case VNF_NE:
{
// These always return 0 or 1 (range is [0..1])
result.lLimit = Limit(Limit::keConstant, 0);
result.uLimit = Limit(Limit::keConstant, 1);

// But maybe we can do better and determine if they are always true or always false,
// hence, return [1..1] or [0..0]
if ((comp->vnStore->TypeOfVN(funcApp.m_args[0]) == TYP_INT) &&
(comp->vnStore->TypeOfVN(funcApp.m_args[1]) == TYP_INT))
{
Range r1 = GetRangeFromAssertions(comp, funcApp.m_args[0], assertions, --budget);
Range r2 = GetRangeFromAssertions(comp, funcApp.m_args[1], assertions, --budget);

bool isUnsigned = true;
genTreeOps cmpOper;

// Normalize the unsigned comparison operators.
if (funcApp.m_func == VNF_GT_UN)
cmpOper = GT_GT;
else if (funcApp.m_func == VNF_GE_UN)
cmpOper = GT_GE;
else if (funcApp.m_func == VNF_LT_UN)
cmpOper = GT_LT;
else if (funcApp.m_func == VNF_LE_UN)
cmpOper = GT_LE;
else
{
isUnsigned = false;
cmpOper = static_cast<genTreeOps>(funcApp.m_func);
}

result = RangeOps::EvalRelop(cmpOper, isUnsigned, r1, r2);
}
break;
}

case VNF_LeadingZeroCount:
case VNF_TrailingZeroCount:
Expand All @@ -805,9 +828,15 @@ Range RangeCheck::GetRangeFromAssertions(Compiler* comp, ValueNum num, ASSERT_VA
}
}

// If it was evaluated to a single constant value by now, return it.
// We can't do better anyway.
if (result.IsSingleValueConstant())
{
return result;
}

Range phiRange = Range(Limit(Limit::keUndef));
if (comp->optVisitReachingAssertions(num,
[comp, &phiRange, &budget](ValueNum reachingVN, ASSERT_TP reachingAssertions) {
auto visitor = [comp, &phiRange, &budget](ValueNum reachingVN, ASSERT_TP reachingAssertions) {
// call GetRangeFromAssertions for each reaching VN using reachingAssertions
Range edgeRange = GetRangeFromAssertions(comp, reachingVN, reachingAssertions, --budget);

Expand All @@ -817,10 +846,15 @@ Range RangeCheck::GetRangeFromAssertions(Compiler* comp, ValueNum num, ASSERT_VA

// if any edge produces a non-constant range, we abort further processing
// We also give up if the range is full, as it won't help tighten the result.
return edgeRange.IsConstantRange() && !edgeRange.IsFullRange() ? Compiler::AssertVisit::Continue
: Compiler::AssertVisit::Abort;
}) == Compiler::AssertVisit::Continue &&
!phiRange.IsUndef())
if (edgeRange.IsConstantRange() && !edgeRange.IsFullRange())
{
return Compiler::AssertVisit::Continue;
}

return Compiler::AssertVisit::Abort;
};

if (comp->optVisitReachingAssertions(num, visitor) == Compiler::AssertVisit::Continue && !phiRange.IsUndef())
{
assert(phiRange.IsConstantRange());
result = phiRange;
Expand Down
57 changes: 37 additions & 20 deletions src/coreclr/jit/rangecheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,19 @@ struct Range
return lLimit.IsConstant() && uLimit.IsConstant() && (lLimit.GetConstant() == INT32_MIN) &&
(uLimit.GetConstant() == INT32_MAX);
}

bool IsSingleValueConstant(int* cns = nullptr) const
{
if (lLimit.IsConstant() && uLimit.IsConstant() && (lLimit.GetConstant() == uLimit.GetConstant()))
{
if (cns != nullptr)
{
*cns = lLimit.GetConstant();
}
return true;
}
return false;
}
};

// Helpers for operations performed on ranges
Expand Down Expand Up @@ -620,13 +633,6 @@ struct RangeOps
return result;
}

enum class RelationKind
{
AlwaysTrue,
AlwaysFalse,
Unknown
};

//------------------------------------------------------------------------
// EvalRelop: Evaluate the relation between two ranges for the given relop
// Example: "x >= y" is AlwaysTrue when "x.LowerLimit() >= y.UpperLimit()"
Expand All @@ -638,11 +644,9 @@ struct RangeOps
// y - The right range
//
// Returns:
// AlwaysTrue when the given relop always evaluates to true for the given ranges
// AlwaysFalse when the given relop always evaluates to false for the given ranges
// Otherwise Unknown
// Either [0..0] (AlwaysFalse), [1..1] (AlwaysTrue), or [0..1] (all relops are guaranteed to return 0 or 1)
//
static RelationKind EvalRelop(const genTreeOps relop, bool isUnsigned, const Range& x, const Range& y)
static Range EvalRelop(const genTreeOps relop, bool isUnsigned, const Range& x, const Range& y)
{
assert(x.IsValid());
assert(y.IsValid());
Expand All @@ -655,10 +659,11 @@ struct RangeOps
// For unsigned comparisons, we only support non-negative ranges.
if (isUnsigned)
{
if (!xLower.IsConstant() || !yUpper.IsConstant() || (xLower.GetConstant() < 0) ||
if (!xLower.IsConstant() || !yLower.IsConstant() || (xLower.GetConstant() < 0) ||
(yLower.GetConstant() < 0))
{
return RelationKind::Unknown;
// Relops always return either 0 or 1.
return Range(Limit(Limit::keConstant, 0), Limit(Limit::keConstant, 1));
}
}

Expand All @@ -668,42 +673,54 @@ struct RangeOps
case GT_LT:
if (xLower.IsConstant() && yUpper.IsConstant() && (xLower.GetConstant() >= yUpper.GetConstant()))
{
return relop == GT_GE ? RelationKind::AlwaysTrue : RelationKind::AlwaysFalse;
return Range(Limit(Limit::keConstant, relop == GT_GE ? 1 : 0));
}

if (xUpper.IsConstant() && yLower.IsConstant() && (xUpper.GetConstant() < yLower.GetConstant()))
{
return relop == GT_GE ? RelationKind::AlwaysFalse : RelationKind::AlwaysTrue;
return Range(Limit(Limit::keConstant, relop == GT_GE ? 0 : 1));
}
break;

case GT_GT:
case GT_LE:
if (xLower.IsConstant() && yUpper.IsConstant() && (xLower.GetConstant() > yUpper.GetConstant()))
{
return relop == GT_GT ? RelationKind::AlwaysTrue : RelationKind::AlwaysFalse;
return Range(Limit(Limit::keConstant, relop == GT_GT ? 1 : 0));
}

if (xUpper.IsConstant() && yLower.IsConstant() && (xUpper.GetConstant() <= yLower.GetConstant()))
{
return relop == GT_GT ? RelationKind::AlwaysFalse : RelationKind::AlwaysTrue;
return Range(Limit(Limit::keConstant, relop == GT_GT ? 0 : 1));
}
break;

case GT_EQ:
case GT_NE:
// If the ranges do not overlap, then EQ is always false, NE is always true.
// Example: x = [6..10], y = [0..5] -> EQ is always false, NE is always true.
if ((xLower.IsConstant() && yUpper.IsConstant() && (xLower.GetConstant() > yUpper.GetConstant())) ||
(xUpper.IsConstant() && yLower.IsConstant() && (xUpper.GetConstant() < yLower.GetConstant())))
{
return relop == GT_EQ ? RelationKind::AlwaysFalse : RelationKind::AlwaysTrue;
return Range(Limit(Limit::keConstant, relop == GT_EQ ? 0 : 1));
}

// If both ranges are single constant and equal, then EQ is always true, NE is always false.
// Example: x = [5..5], y = [5..5] -> EQ is always true, NE is always false.
if (x.LowerLimit().IsConstant() && y.LowerLimit().IsConstant() &&
x.LowerLimit().Equals(x.UpperLimit()) && y.LowerLimit().Equals(y.UpperLimit()) &&
x.LowerLimit().GetConstant() == y.LowerLimit().GetConstant())
{
return Range(Limit(Limit::keConstant, relop == GT_EQ ? 1 : 0));
}
break;

default:
assert(!"unknown comparison operator");
break;
}
return RelationKind::Unknown;
// Relops always return either 0 or 1.
return Range(Limit(Limit::keConstant, 0), Limit(Limit::keConstant, 1));
}
};

Expand Down Expand Up @@ -744,7 +761,7 @@ class RangeCheck
Range GetRangeWorker(BasicBlock* block, GenTree* expr, bool monIncreasing DEBUGARG(int indent));

// Compute the range from the given type
Range GetRangeFromType(var_types type);
static Range GetRangeFromType(var_types type);

// Given the local variable, first find the definition of the local and find the range of the rhs.
// Helper for GetRangeWorker.
Expand Down
Loading