diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 818d18dff427f0..c04398aaae220d 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -33,6 +33,51 @@ bool IntegralRange::Contains(int64_t value) const return (lowerBound <= value) && (value <= upperBound); } +//------------------------------------------------------------------------ +// GetRange: Compute the {int32 lo;int32 hi} range for a given tree node. +// +// Arguments: +// comp - the Compiler object. +// tree - the tree node to compute the range for. +// block - the BasicBlock in which "tree" is being evaluated. +// assertions - the set of assertions to consider when computing the range. Can be null. +// fast - fast is when we only use VN and assertions. slow is when we perform an SSA walk to compute the range. +// +// Return Value: +// Range of possible values for "tree" based on the given assertions and block context. +// An unknown range if the range cannot be determined, or if the computation exceeds the visit budget. +// +static Range GetRange(Compiler* comp, GenTree* tree, BasicBlock* block, ASSERT_VALARG_TP assertions, bool fast = true) +{ + assert(block != nullptr); + assert(tree != nullptr); + + int budget = 64; +#ifdef DEBUG + // JIT stress: always take the slow, SSA-based range walk (with a larger budget) to maximize + // coverage of TryGetRange and shake out correctness issues in the range computation. + if (comp->compStressCompile(Compiler::STRESS_GET_RANGE, 50)) + { + fast = false; + budget = 512; + } +#endif + + if (fast) + { + return RangeCheck::GetRangeFromAssertions(comp, tree, + !BitVecOps::MayBeUninit(assertions) ? assertions + : block->bbAssertionIn); + } + + Range range = Limit(Limit::keUndef); + if (comp->GetRangeCheck(budget)->TryGetRange(block, tree, &range)) + { + return range; + } + return Limit(Limit::keUnknown); +} + //------------------------------------------------------------------------ // SymbolicToRealValue: Convert a symbolic value to a 64-bit signed integer. // @@ -4020,18 +4065,11 @@ void Compiler::optAssertionProp_RangeProperties(ASSERT_VALARG_TP assertions, } // Let's see if MergeEdgeAssertions can help us: - Range rng = RangeCheck::GetRangeFromAssertions(this, tree, assertions); - + Range rng = GetRange(this, tree, block, assertions, /*fast*/ true); if (rng.IsConstantRange()) { - if (rng.LowerLimit().GetConstant() >= 0) - { - *isKnownNonNegative = true; - } - if ((rng.LowerLimit().GetConstant() > 0) || (rng.UpperLimit().GetConstant() < 0)) - { - *isKnownNonZero = true; - } + *isKnownNonNegative |= rng.LowerLimit().GetConstant() >= 0; + *isKnownNonZero |= (rng.LowerLimit().GetConstant() > 0) || (rng.UpperLimit().GetConstant() < 0); } } @@ -4043,11 +4081,15 @@ void Compiler::optAssertionProp_RangeProperties(ASSERT_VALARG_TP assertions, // assertions - set of live assertions // tree - the MUL/ADD/SUB node to optimize // stmt - statement containing MUL/ADD/SUB +// block - the block containing the statement // // Returns: // Updated MUL/ADD/SUB node, or nullptr // -GenTree* Compiler::optAssertionProp_AddMulSub(ASSERT_VALARG_TP assertions, GenTreeOp* tree, Statement* stmt) +GenTree* Compiler::optAssertionProp_AddMulSub(ASSERT_VALARG_TP assertions, + GenTreeOp* tree, + Statement* stmt, + BasicBlock* block) { assert(tree->OperIs(GT_MUL, GT_ADD, GT_SUB)); @@ -4056,8 +4098,8 @@ GenTree* Compiler::optAssertionProp_AddMulSub(ASSERT_VALARG_TP assertions, GenTr GenTree* op1 = tree->gtGetOp1(); GenTree* op2 = tree->gtGetOp2(); - Range op1Rng = RangeCheck::GetRangeFromAssertions(this, op1, assertions); - Range op2Rng = RangeCheck::GetRangeFromAssertions(this, op2, assertions); + Range op1Rng = GetRange(this, op1, block, assertions, /*fast*/ true); + Range op2Rng = GetRange(this, op2, block, assertions, /*fast*/ true); if (op1Rng.IsConstantRange() && op2Rng.IsConstantRange()) { @@ -4441,9 +4483,9 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, { Range relopRange = RangeCheck::GetRangeFromAssertions(this, tree, assertions); + int relopResult; if (relopRange.IsConstantRange()) { - int relopResult; if (!relopRange.IsSingleValueConstant(&relopResult)) { // Retry by obtaining operand ranges individually. This accounts for cases where the @@ -4457,12 +4499,23 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, } else { - Range op1Range = RangeCheck::GetRangeFromAssertions(this, op1, assertions); - Range op2Range = RangeCheck::GetRangeFromAssertions(this, op2, assertions); + Range op1Range = GetRange(this, op1, block, assertions, /*fast*/ true); + Range op2Range = GetRange(this, op2, block, assertions, /*fast*/ true); relopRange = RangeOps::EvalRelop(tree->OperGet(), tree->IsUnsigned(), op1Range, op2Range); } } + // Perform a slow, SSA-based range check analysis. + if (!relopRange.IsSingleValueConstant(&relopResult) && + // The few checks below ensure this analysis + // is only performed when beneficial according to SPMI, keeping the TP impact low. + op1->TypeIs(TYP_INT) && op2->IsIntCnsFitsInI32() && tree->OperIs(GT_LE, GT_LT, GT_GE, GT_GT)) + { + Range op1Rng = GetRange(this, op1, block, assertions, /*fast*/ false); + Range op2Rng = GetRange(this, op2, block, assertions, /*fast*/ false); + relopRange = RangeOps::EvalRelop(tree->OperGet(), tree->IsUnsigned(), op1Rng, op2Rng); + } + if (relopRange.IsSingleValueConstant(&relopResult)) { assert((relopResult == 0) || (relopResult == 1)); @@ -4890,7 +4943,7 @@ GenTree* Compiler::optAssertionProp_Cast(ASSERT_VALARG_TP assertions, if (castToTypeRange.IsConstantRange()) { GenTree* castOp = cast->CastOp(); - Range castOpRng = RangeCheck::GetRangeFromAssertions(this, castOp, assertions); + Range castOpRng = GetRange(this, castOp, block, assertions, /*fast*/ true); if (castOpRng.IsConstantRange()) { @@ -5456,7 +5509,10 @@ GenTree* Compiler::optAssertionProp_Call(ASSERT_VALARG_TP assertions, GenTreeCal * * Given a tree with a bounds check, remove it if it has already been checked in the program flow. */ -GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt) +GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, + GenTree* tree, + Statement* stmt, + BasicBlock* block) { if (optLocalAssertionProp) { @@ -5618,8 +5674,8 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree } // Let's see if we can remove the bounds check based on the ranges. - Range idxRng = RangeCheck::GetRangeFromAssertions(this, arrBndsChkIdx, assertions); - Range lenRng = RangeCheck::GetRangeFromAssertions(this, arrBndsChkLen, assertions); + Range idxRng = GetRange(this, arrBndsChkIdx, block, assertions, /*fast*/ true); + Range lenRng = GetRange(this, arrBndsChkLen, block, assertions, /*fast*/ true); if (idxRng.IsConstantRange() && lenRng.IsConstantRange()) { @@ -5772,7 +5828,7 @@ GenTree* Compiler::optAssertionProp(ASSERT_VALARG_TP assertions, GenTree* tree, case GT_SUB: case GT_MUL: case GT_ADD: - return optAssertionProp_AddMulSub(assertions, tree->AsOp(), stmt); + return optAssertionProp_AddMulSub(assertions, tree->AsOp(), stmt, block); case GT_MOD: case GT_DIV: @@ -5798,7 +5854,7 @@ GenTree* Compiler::optAssertionProp(ASSERT_VALARG_TP assertions, GenTree* tree, return optAssertionProp_Ind(assertions, tree, stmt); case GT_BOUNDS_CHECK: - return optAssertionProp_BndsChk(assertions, tree, stmt); + return optAssertionProp_BndsChk(assertions, tree, stmt, block); case GT_COMMA: return optAssertionProp_Comma(assertions, tree, stmt); diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index fb55f2b4e7ba5a..ba939c2769e3de 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -9060,9 +9060,9 @@ class Compiler bool optCanPropLclVar; RangeCheck* optRangeCheck = nullptr; - RangeCheck* GetRangeCheck(); public: + RangeCheck* GetRangeCheck(int customBudget = 0); void optVnNonNullPropCurStmt(BasicBlock* block, Statement* stmt, GenTree* tree); fgWalkResult optVNBasedFoldCurStmt(BasicBlock* block, Statement* stmt, GenTree* parent, GenTree* tree); GenTree* optVNConstantPropOnJTrue(BasicBlock* block, GenTree* test); @@ -9142,14 +9142,17 @@ class Compiler GenTree* optAssertionProp_LocalStore(ASSERT_VALARG_TP assertions, GenTreeLclVarCommon* store, Statement* stmt); GenTree* optAssertionProp_BlockStore(ASSERT_VALARG_TP assertions, GenTreeBlk* store, Statement* stmt); GenTree* optAssertionProp_ModDiv(ASSERT_VALARG_TP assertions, GenTreeOp* tree, Statement* stmt, BasicBlock* block); - GenTree* optAssertionProp_AddMulSub(ASSERT_VALARG_TP assertions, GenTreeOp* tree, Statement* stmt); + GenTree* optAssertionProp_AddMulSub(ASSERT_VALARG_TP assertions, + GenTreeOp* tree, + Statement* stmt, + BasicBlock* block); GenTree* optAssertionProp_Return(ASSERT_VALARG_TP assertions, GenTreeOp* ret, Statement* stmt); GenTree* optAssertionProp_Ind(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt); GenTree* optAssertionProp_Cast(ASSERT_VALARG_TP assertions, GenTreeCast* cast, Statement* stmt, BasicBlock* block); GenTree* optAssertionProp_Call(ASSERT_VALARG_TP assertions, GenTreeCall* call, Statement* stmt); GenTree* optAssertionProp_RelOp(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt, BasicBlock* block); GenTree* optAssertionProp_Comma(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt); - GenTree* optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt); + GenTree* optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt, BasicBlock* block); GenTree* optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt, @@ -11547,6 +11550,7 @@ class Compiler STRESS_MODE(IF_CONVERSION_INNER_LOOPS) \ STRESS_MODE(POISON_IMPLICIT_BYREFS) \ STRESS_MODE(THREE_OPT_LAYOUT) \ + STRESS_MODE(GET_RANGE) /* Force slow SSA-based range walk in GetRange */ \ STRESS_MODE(COUNT) enum compStressArea diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index 4249bbf2d98bac..529043a0e730c6 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -23,27 +23,28 @@ PhaseStatus Compiler::rangeCheckPhase() return madeChanges ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING; } +// Max stack depth (path length) in walking the UD chain. +static const int MAX_SEARCH_DEPTH = 100; + +// Max nodes to visit in the UD chain for the current method being compiled. +static const int MAX_VISIT_BUDGET = 8192; + //------------------------------------------------------------------------ // GetRangeCheck: get the RangeCheck instance // // Returns: // The range check object // -RangeCheck* Compiler::GetRangeCheck() +RangeCheck* Compiler::GetRangeCheck(int customBudget) { if (optRangeCheck == nullptr) { optRangeCheck = new (this, CMK_Generic) RangeCheck(this); } + optRangeCheck->SetBudget(customBudget > 0 ? customBudget : MAX_VISIT_BUDGET); return optRangeCheck; } -// Max stack depth (path length) in walking the UD chain. -static const int MAX_SEARCH_DEPTH = 100; - -// Max nodes to visit in the UD chain for the current method being compiled. -static const int MAX_VISIT_BUDGET = 8192; - // RangeCheck constructor. RangeCheck::RangeCheck(Compiler* pCompiler) : m_preferredBound(ValueNumStore::NoVN) @@ -259,11 +260,9 @@ void RangeCheck::OptimizeRangeCheck(BasicBlock* block, Statement* stmt, GenTree* ValueNum arrLenVn = m_compiler->optConservativeNormalVN(bndsChk->GetArrayLength()); - m_preferredBound = arrLenVn; - // Get the range for this index. Range range = Range(Limit(Limit::keUndef)); - if (!TryGetRange(block, treeIndex, &range)) + if (!TryGetRange(block, treeIndex, &range, arrLenVn)) { JITDUMP("Failed to get range\n"); return; @@ -437,6 +436,14 @@ bool RangeCheck::IsMonotonicallyIncreasing(GenTree* expr, bool rejectNegativeCon // Given a lclvar use, try to find the lclvar's defining store and its containing block. LclSsaVarDsc* RangeCheck::GetSsaDefStore(GenTreeLclVarCommon* lclUse) { + // RangeCheck does not understand reads through LCL_FLD nodes: a LCL_FLD use reads a + // sub-range of the local (a different offset and/or a narrower type), so the value + // produced by the (full-width) definition does not describe the value being read. + if (lclUse->OperIs(GT_LCL_FLD)) + { + return nullptr; + } + unsigned ssaNum = lclUse->GetSsaNum(); if (ssaNum == SsaConfig::RESERVED_SSA_NUM) @@ -2333,15 +2340,25 @@ void Indent(int indent) // TryGetRange: Try to obtain the range of an expression. // // Arguments: -// block - the block that contains `expr`; -// expr - expression to compute the range for; -// pRange - [Out] range of the expression; +// block - the block that contains `expr`; +// expr - expression to compute the range for; +// pRange - [Out] range of the expression; +// preferredBoundVN - a value number of the preferred bound. // // Return Value: // false if the range is unknown or determined to overflow. // -bool RangeCheck::TryGetRange(BasicBlock* block, GenTree* expr, Range* pRange) +bool RangeCheck::TryGetRange(BasicBlock* block, GenTree* expr, Range* pRange, ValueNum preferredBoundVN) { + // Fast path for constants + if (expr->IsIntCnsFitsInI32()) + { + *pRange = Limit(Limit::keConstant, (int)expr->AsIntCon()->IconValue()); + return true; + } + + m_preferredBound = preferredBoundVN; + // Reset the maps. ClearRangeMap(); ClearSearchPath(); diff --git a/src/coreclr/jit/rangecheck.h b/src/coreclr/jit/rangecheck.h index ed905c77cc9ac0..18e1b12fa2c84c 100644 --- a/src/coreclr/jit/rangecheck.h +++ b/src/coreclr/jit/rangecheck.h @@ -814,11 +814,16 @@ class RangeCheck // Constructor RangeCheck(Compiler* pCompiler); + void SetBudget(int budget) + { + m_nVisitBudget = budget; + } + // Entry point to optimize range checks in the method. Assumes value numbering // and assertion prop phases are completed. bool OptimizeRangeChecks(); - bool TryGetRange(BasicBlock* block, GenTree* expr, Range* pRange); + bool TryGetRange(BasicBlock* block, GenTree* expr, Range* pRange, ValueNum preferredBoundVN = ValueNumStore::NoVN); // Cheaper version of TryGetRange that is based only on incoming assertions. static Range GetRangeFromAssertions(Compiler* comp, GenTree* tree, ASSERT_VALARG_TP assertions, int budget = 10);