diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 922aafc61f9013..a0d97cb1589f0f 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -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); } } diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index 47dfe474d4a8e9..8ec2b30ccd4fa0 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -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: { @@ -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(funcApp.m_func); + } + + result = RangeOps::EvalRelop(cmpOper, isUnsigned, r1, r2); + } break; + } case VNF_LeadingZeroCount: case VNF_TrailingZeroCount: @@ -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); @@ -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; diff --git a/src/coreclr/jit/rangecheck.h b/src/coreclr/jit/rangecheck.h index 272eeb784a5335..575ffcd8dba2f9 100644 --- a/src/coreclr/jit/rangecheck.h +++ b/src/coreclr/jit/rangecheck.h @@ -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 @@ -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()" @@ -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()); @@ -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)); } } @@ -668,12 +673,12 @@ 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; @@ -681,21 +686,32 @@ struct RangeOps 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; @@ -703,7 +719,8 @@ struct RangeOps 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)); } }; @@ -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.