Skip to content
Open
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
100 changes: 78 additions & 22 deletions src/coreclr/jit/assertionprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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));

Expand All @@ -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())
{
Expand Down Expand Up @@ -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
Expand All @@ -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));
Expand Down Expand Up @@ -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())
{
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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())
{
Expand Down Expand Up @@ -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:
Expand All @@ -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);
Expand Down
10 changes: 7 additions & 3 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
45 changes: 31 additions & 14 deletions src/coreclr/jit/rangecheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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();
Expand Down
7 changes: 6 additions & 1 deletion src/coreclr/jit/rangecheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading