Skip to content

Commit 0f0a9ec

Browse files
committed
Analyze reaching PHI in GetRangeFromAssertions (more branch foldings)
1 parent 4b0b723 commit 0f0a9ec

File tree

4 files changed

+189
-174
lines changed

4 files changed

+189
-174
lines changed

src/coreclr/jit/assertionprop.cpp

Lines changed: 18 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -3970,19 +3970,14 @@ void Compiler::optAssertionProp_RangeProperties(ASSERT_VALARG_TP assertions,
39703970
// Let's see if MergeEdgeAssertions can help us:
39713971
if (tree->TypeIs(TYP_INT))
39723972
{
3973-
Range rng = Range(Limit(Limit::keUnknown));
3974-
if (RangeCheck::TryGetRangeFromAssertions(this, treeVN, assertions, &rng))
3973+
Range rng = RangeCheck::GetRangeFromAssertions(this, treeVN, assertions);
3974+
if (rng.LowerLimit().GetConstant() >= 0)
39753975
{
3976-
Limit lowerBound = rng.LowerLimit();
3977-
assert(lowerBound.IsConstant());
3978-
if (lowerBound.GetConstant() >= 0)
3979-
{
3980-
*isKnownNonNegative = true;
3981-
}
3982-
if (lowerBound.GetConstant() > 0)
3983-
{
3984-
*isKnownNonZero = true;
3985-
}
3976+
*isKnownNonNegative = true;
3977+
}
3978+
if (rng.LowerLimit().GetConstant() > 0)
3979+
{
3980+
*isKnownNonZero = true;
39863981
}
39873982
}
39883983
}
@@ -4250,68 +4245,6 @@ GenTree* Compiler::optAssertionProp_RelOp(ASSERT_VALARG_TP assertions,
42504245
return optAssertionPropLocal_RelOp(assertions, tree, stmt);
42514246
}
42524247

4253-
//--------------------------------------------------------------------------------
4254-
// optVisitReachingAssertions: given a vn, call the specified callback function on all
4255-
// the assertions that reach it via PHI definitions if any.
4256-
//
4257-
// Arguments:
4258-
// vn - The vn to visit all the reaching assertions for
4259-
// argVisitor - The callback function to call on the vn and its reaching assertions
4260-
//
4261-
// Return Value:
4262-
// AssertVisit::Aborted - an argVisitor returned AssertVisit::Abort, we stop the walk and return
4263-
// AssertVisit::Continue - all argVisitor returned AssertVisit::Continue
4264-
//
4265-
template <typename TAssertVisitor>
4266-
Compiler::AssertVisit Compiler::optVisitReachingAssertions(ValueNum vn, TAssertVisitor argVisitor)
4267-
{
4268-
VNPhiDef phiDef;
4269-
if (!vnStore->GetPhiDef(vn, &phiDef))
4270-
{
4271-
// We assume that the caller already checked assertions for the current block, so we're
4272-
// interested only in assertions for PHI definitions.
4273-
return AssertVisit::Abort;
4274-
}
4275-
4276-
LclSsaVarDsc* ssaDef = lvaGetDesc(phiDef.LclNum)->GetPerSsaData(phiDef.SsaDef);
4277-
GenTreeLclVarCommon* node = ssaDef->GetDefNode();
4278-
assert(node->IsPhiDefn());
4279-
4280-
// Keep track of the set of phi-preds
4281-
//
4282-
BitVecTraits traits(fgBBNumMax + 1, this);
4283-
BitVec visitedBlocks = BitVecOps::MakeEmpty(&traits);
4284-
4285-
for (GenTreePhi::Use& use : node->Data()->AsPhi()->Uses())
4286-
{
4287-
GenTreePhiArg* phiArg = use.GetNode()->AsPhiArg();
4288-
const ValueNum phiArgVN = vnStore->VNConservativeNormalValue(phiArg->gtVNPair);
4289-
ASSERT_TP assertions = optGetEdgeAssertions(ssaDef->GetBlock(), phiArg->gtPredBB);
4290-
if (argVisitor(phiArgVN, assertions) == AssertVisit::Abort)
4291-
{
4292-
// The visitor wants to abort the walk.
4293-
return AssertVisit::Abort;
4294-
}
4295-
BitVecOps::AddElemD(&traits, visitedBlocks, phiArg->gtPredBB->bbNum);
4296-
}
4297-
4298-
// Verify the set of phi-preds covers the set of block preds
4299-
//
4300-
for (BasicBlock* const pred : ssaDef->GetBlock()->PredBlocks())
4301-
{
4302-
if (!BitVecOps::IsMember(&traits, visitedBlocks, pred->bbNum))
4303-
{
4304-
JITDUMP("... optVisitReachingAssertions in " FMT_BB ": pred " FMT_BB " not a phi-pred\n",
4305-
ssaDef->GetBlock()->bbNum, pred->bbNum);
4306-
4307-
// We missed examining a block pred. Fail the phi inference.
4308-
//
4309-
return AssertVisit::Abort;
4310-
}
4311-
}
4312-
return AssertVisit::Continue;
4313-
}
4314-
43154248
//------------------------------------------------------------------------
43164249
// optAssertionProp: try and optimize a relop via assertion propagation
43174250
//
@@ -4400,23 +4333,17 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions,
44004333
ValueNum op1VN = vnStore->VNConservativeNormalValue(op1->gtVNPair);
44014334
ValueNum op2VN = vnStore->VNConservativeNormalValue(op2->gtVNPair);
44024335

4403-
// See if we can fold "X relop CNS" using TryGetRangeFromAssertions.
4404-
int op2cns;
4405-
if (op1->TypeIs(TYP_INT) && op2->TypeIs(TYP_INT) && vnStore->IsVNIntegralConstant(op2VN, &op2cns))
4336+
if (op1->TypeIs(TYP_INT) && op2->TypeIs(TYP_INT))
44064337
{
4407-
// NOTE: we can call TryGetRangeFromAssertions for op2 as well if we want, but it's not cheap.
4408-
Range rng1 = Range(Limit(Limit::keUnknown));
4409-
Range rng2 = Range(Limit(Limit::keConstant, op2cns));
4338+
Range rng1 = RangeCheck::GetRangeFromAssertions(this, op1VN, assertions);
4339+
Range rng2 = RangeCheck::GetRangeFromAssertions(this, op2VN, assertions);
44104340

4411-
if (RangeCheck::TryGetRangeFromAssertions(this, op1VN, assertions, &rng1))
4341+
RangeOps::RelationKind kind = RangeOps::EvalRelop(tree->OperGet(), tree->IsUnsigned(), rng1, rng2);
4342+
if ((kind != RangeOps::RelationKind::Unknown))
44124343
{
4413-
RangeOps::RelationKind kind = RangeOps::EvalRelop(tree->OperGet(), tree->IsUnsigned(), rng1, rng2);
4414-
if ((kind != RangeOps::RelationKind::Unknown))
4415-
{
4416-
newTree = kind == RangeOps::RelationKind::AlwaysTrue ? gtNewTrue() : gtNewFalse();
4417-
newTree = gtWrapWithSideEffects(newTree, tree, GTF_ALL_EFFECT);
4418-
return optAssertionProp_Update(newTree, tree, stmt);
4419-
}
4344+
newTree = kind == RangeOps::RelationKind::AlwaysTrue ? gtNewTrue() : gtNewFalse();
4345+
newTree = gtWrapWithSideEffects(newTree, tree, GTF_ALL_EFFECT);
4346+
return optAssertionProp_Update(newTree, tree, stmt);
44204347
}
44214348
}
44224349

@@ -5346,10 +5273,10 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree
53465273
std::swap(funcApp.m_args[0], funcApp.m_args[1]);
53475274
}
53485275

5349-
Range rng = Range(Limit(Limit::keUnknown));
5350-
if ((funcApp.m_args[0] == vnCurLen) && vnStore->IsVNInt32Constant(funcApp.m_args[1]) &&
5351-
RangeCheck::TryGetRangeFromAssertions(this, vnCurLen, assertions, &rng) && rng.LowerLimit().IsConstant())
5276+
Range(Limit(Limit::keUnknown));
5277+
if ((funcApp.m_args[0] == vnCurLen) && vnStore->IsVNInt32Constant(funcApp.m_args[1]))
53525278
{
5279+
Range rng = RangeCheck::GetRangeFromAssertions(this, vnCurLen, assertions);
53535280
// Lower known limit of ArrLen:
53545281
const int lenLowerLimit = rng.LowerLimit().GetConstant();
53555282

src/coreclr/jit/compiler.h

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8129,8 +8129,68 @@ class Compiler
81298129
Continue,
81308130
Abort,
81318131
};
8132+
8133+
//--------------------------------------------------------------------------------
8134+
// optVisitReachingAssertions: given a vn, call the specified callback function on all
8135+
// the assertions that reach it via PHI definitions if any.
8136+
//
8137+
// Arguments:
8138+
// vn - The vn to visit all the reaching assertions for
8139+
// argVisitor - The callback function to call on the vn and its reaching assertions
8140+
//
8141+
// Return Value:
8142+
// AssertVisit::Aborted - an argVisitor returned AssertVisit::Abort, we stop the walk and return
8143+
// AssertVisit::Continue - all argVisitor returned AssertVisit::Continue
8144+
//
81328145
template <typename TAssertVisitor>
8133-
AssertVisit optVisitReachingAssertions(ValueNum vn, TAssertVisitor argVisitor);
8146+
AssertVisit optVisitReachingAssertions(ValueNum vn, TAssertVisitor argVisitor)
8147+
{
8148+
VNPhiDef phiDef;
8149+
if (!vnStore->GetPhiDef(vn, &phiDef))
8150+
{
8151+
// We assume that the caller already checked assertions for the current block, so we're
8152+
// interested only in assertions for PHI definitions.
8153+
return AssertVisit::Abort;
8154+
}
8155+
8156+
LclSsaVarDsc* ssaDef = lvaGetDesc(phiDef.LclNum)->GetPerSsaData(phiDef.SsaDef);
8157+
GenTreeLclVarCommon* node = ssaDef->GetDefNode();
8158+
assert(node->IsPhiDefn());
8159+
8160+
// Keep track of the set of phi-preds
8161+
//
8162+
BitVecTraits traits(fgBBNumMax + 1, this);
8163+
BitVec visitedBlocks = BitVecOps::MakeEmpty(&traits);
8164+
8165+
for (GenTreePhi::Use& use : node->Data()->AsPhi()->Uses())
8166+
{
8167+
GenTreePhiArg* phiArg = use.GetNode()->AsPhiArg();
8168+
const ValueNum phiArgVN = vnStore->VNConservativeNormalValue(phiArg->gtVNPair);
8169+
ASSERT_TP assertions = optGetEdgeAssertions(ssaDef->GetBlock(), phiArg->gtPredBB);
8170+
if (argVisitor(phiArgVN, assertions) == AssertVisit::Abort)
8171+
{
8172+
// The visitor wants to abort the walk.
8173+
return AssertVisit::Abort;
8174+
}
8175+
BitVecOps::AddElemD(&traits, visitedBlocks, phiArg->gtPredBB->bbNum);
8176+
}
8177+
8178+
// Verify the set of phi-preds covers the set of block preds
8179+
//
8180+
for (BasicBlock* const pred : ssaDef->GetBlock()->PredBlocks())
8181+
{
8182+
if (!BitVecOps::IsMember(&traits, visitedBlocks, pred->bbNum))
8183+
{
8184+
JITDUMP("... optVisitReachingAssertions in " FMT_BB ": pred " FMT_BB " not a phi-pred\n",
8185+
ssaDef->GetBlock()->bbNum, pred->bbNum);
8186+
8187+
// We missed examining a block pred. Fail the phi inference.
8188+
//
8189+
return AssertVisit::Abort;
8190+
}
8191+
}
8192+
return AssertVisit::Continue;
8193+
}
81348194

81358195
void optAssertionProp_RangeProperties(ASSERT_VALARG_TP assertions,
81368196
GenTree* tree,

src/coreclr/jit/rangecheck.cpp

Lines changed: 74 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -637,29 +637,30 @@ void RangeCheck::MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP
637637
}
638638

639639
//------------------------------------------------------------------------
640-
// TryGetRangeFromAssertions: Cheaper version of TryGetRange that is based purely on assertions
640+
// GetRangeFromAssertions: Cheaper version of TryGetRange that is based purely on assertions
641641
// and does not require a full range analysis based on SSA.
642642
//
643643
// Arguments:
644644
// comp - the compiler instance
645645
// num - the value number to analyze range for
646646
// assertions - the assertions to use
647-
// pRange - the range to tighten with assertions
648647
//
649648
// Return Value:
650-
// True if the range was successfully computed
649+
// The computed range
651650
//
652-
bool RangeCheck::TryGetRangeFromAssertions(Compiler* comp, ValueNum num, ASSERT_VALARG_TP assertions, Range* pRange)
651+
Range RangeCheck::GetRangeFromAssertions(Compiler* comp, ValueNum num, ASSERT_VALARG_TP assertions, int budget)
653652
{
654-
assert(pRange != nullptr);
655-
assert(pRange->LowerLimit().IsUnknown());
656-
assert(pRange->UpperLimit().IsUnknown());
653+
// Start with the widest possible constant range.
654+
Range result = Range(Limit(Limit::keConstant, INT32_MIN), Limit(Limit::keConstant, INT32_MAX));
657655

658-
if (num == ValueNumStore::NoVN)
656+
if ((num == ValueNumStore::NoVN) || (budget <= 0))
659657
{
660-
return false;
658+
return result;
661659
}
662660

661+
// Currently, we only handle int32 and smaller integer types.
662+
assert(varTypeIsInt(comp->vnStore->TypeOfVN(num)) || varTypeIsSmall(comp->vnStore->TypeOfVN(num)));
663+
663664
//
664665
// First, let's see if we can tighten the range based on VN information.
665666
//
@@ -668,14 +669,9 @@ bool RangeCheck::TryGetRangeFromAssertions(Compiler* comp, ValueNum num, ASSERT_
668669
int cns;
669670
if (comp->vnStore->IsVNIntegralConstant(num, &cns))
670671
{
671-
pRange->lLimit = Limit(Limit::keConstant, cns);
672-
pRange->uLimit = Limit(Limit::keConstant, cns);
673-
return true;
672+
return Range(Limit(Limit::keConstant, cns));
674673
}
675674

676-
// Start with the widest possible constant range.
677-
Range result = Range(Limit(Limit::keConstant, INT32_MIN), Limit(Limit::keConstant, INT32_MAX));
678-
679675
VNFuncApp funcApp;
680676
if (comp->vnStore->GetVNFunc(num, &funcApp))
681677
{
@@ -718,6 +714,16 @@ bool RangeCheck::TryGetRangeFromAssertions(Compiler* comp, ValueNum num, ASSERT_
718714
}
719715
break;
720716

717+
case VNF_NEG:
718+
{
719+
Range r1 = GetRangeFromAssertions(comp, funcApp.m_args[0], assertions, --budget);
720+
Range unaryOpResult = RangeOps::Negate(r1);
721+
722+
// We can use the result only if it never overflows.
723+
result = unaryOpResult.IsConstantRange() ? unaryOpResult : result;
724+
break;
725+
}
726+
721727
case VNF_LSH:
722728
case VNF_ADD:
723729
case VNF_MUL:
@@ -728,49 +734,41 @@ bool RangeCheck::TryGetRangeFromAssertions(Compiler* comp, ValueNum num, ASSERT_
728734
case VNF_UMOD:
729735
{
730736
// Get ranges of both operands and perform the same operation on the ranges.
731-
Range r1 = Range(Limit(Limit::keUnknown));
732-
Range r2 = Range(Limit(Limit::keUnknown));
733-
if (TryGetRangeFromAssertions(comp, funcApp.m_args[0], assertions, &r1) &&
734-
TryGetRangeFromAssertions(comp, funcApp.m_args[1], assertions, &r2))
737+
Range r1 = GetRangeFromAssertions(comp, funcApp.m_args[0], assertions, --budget);
738+
Range r2 = GetRangeFromAssertions(comp, funcApp.m_args[1], assertions, --budget);
739+
Range binOpResult = Range(Limit(Limit::keUnknown));
740+
switch (funcApp.m_func)
735741
{
736-
Range binOpResult = Range(Limit(Limit::keUnknown));
737-
switch (funcApp.m_func)
738-
{
739-
case VNF_ADD:
740-
binOpResult = RangeOps::Add(r1, r2);
741-
break;
742-
case VNF_MUL:
743-
binOpResult = RangeOps::Multiply(r1, r2);
744-
break;
745-
case VNF_AND:
746-
binOpResult = RangeOps::And(r1, r2);
747-
break;
748-
case VNF_OR:
749-
binOpResult = RangeOps::Or(r1, r2);
750-
break;
751-
case VNF_LSH:
752-
binOpResult = RangeOps::ShiftLeft(r1, r2);
753-
break;
754-
case VNF_RSH:
755-
binOpResult = RangeOps::ShiftRight(r1, r2, /*logical*/ false);
756-
break;
757-
case VNF_RSZ:
758-
binOpResult = RangeOps::ShiftRight(r1, r2, /*logical*/ true);
759-
break;
760-
case VNF_UMOD:
761-
binOpResult = RangeOps::UnsignedMod(r1, r2);
762-
break;
763-
default:
764-
unreached();
765-
}
766-
767-
if (binOpResult.IsConstantRange())
768-
{
769-
result = binOpResult;
770-
}
771-
// if the result is unknown (or may overflow), we'll just analyze the binop itself based on the
772-
// assertions
742+
case VNF_ADD:
743+
binOpResult = RangeOps::Add(r1, r2);
744+
break;
745+
case VNF_MUL:
746+
binOpResult = RangeOps::Multiply(r1, r2);
747+
break;
748+
case VNF_AND:
749+
binOpResult = RangeOps::And(r1, r2);
750+
break;
751+
case VNF_OR:
752+
binOpResult = RangeOps::Or(r1, r2);
753+
break;
754+
case VNF_LSH:
755+
binOpResult = RangeOps::ShiftLeft(r1, r2);
756+
break;
757+
case VNF_RSH:
758+
binOpResult = RangeOps::ShiftRight(r1, r2, /*logical*/ false);
759+
break;
760+
case VNF_RSZ:
761+
binOpResult = RangeOps::ShiftRight(r1, r2, /*logical*/ true);
762+
break;
763+
case VNF_UMOD:
764+
binOpResult = RangeOps::UnsignedMod(r1, r2);
765+
break;
766+
default:
767+
unreached();
773768
}
769+
770+
// We can use the result only if it never overflows.
771+
result = binOpResult.IsConstantRange() ? binOpResult : result;
774772
break;
775773
}
776774

@@ -807,10 +805,28 @@ bool RangeCheck::TryGetRangeFromAssertions(Compiler* comp, ValueNum num, ASSERT_
807805
}
808806
}
809807

808+
Range phiRange = Range(Limit(Limit::keUndef));
809+
if (comp->optVisitReachingAssertions(num,
810+
[comp, &phiRange, &budget](ValueNum reachingVN, ASSERT_TP reachingAssertions) {
811+
// call GetRangeFromAssertions for each reaching VN using reachingAssertions
812+
Range edgeRange = GetRangeFromAssertions(comp, reachingVN, reachingAssertions, --budget);
813+
814+
// If phiRange is not yet set, set it to the first edgeRange
815+
// else merge it with the new edgeRange. Example: [10..100] U [50..150] = [10..150]
816+
phiRange = phiRange.LowerLimit().IsUndef() ? edgeRange : RangeOps::Merge(phiRange, edgeRange, false);
817+
818+
// if any edge produces a non-constant range, we abort further processing
819+
// We also give up if the range is full, as it won't help tighten the result.
820+
return edgeRange.IsConstantRange() && !edgeRange.IsFullRange() ? Compiler::AssertVisit::Continue
821+
: Compiler::AssertVisit::Abort;
822+
}) == Compiler::AssertVisit::Continue)
823+
{
824+
result = phiRange;
825+
}
826+
810827
MergeEdgeAssertions(comp, num, ValueNumStore::NoVN, assertions, &result, false);
811828
assert(result.IsConstantRange());
812-
*pRange = result;
813-
return true;
829+
return result;
814830
}
815831

816832
//------------------------------------------------------------------------

0 commit comments

Comments
 (0)