Skip to content
16 changes: 10 additions & 6 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -1960,8 +1960,10 @@ struct NaturalLoopIterInfo
#endif

// Constant value that the induction variable is initialized with, outside
// the loop. Only valid if HasConstInit is true.
int ConstInitValue = 0;
// the loop. Only valid if HasConstInit is true. Width matches the IV
// (TYP_INT IV: int-sized; TYP_LONG IV: long-sized). Stored as int64_t so
// 64-bit IV constants are representable on a 32-bit JIT host.
int64_t ConstInitValue = 0;

Comment thread
AndyAyersMS marked this conversation as resolved.
// Tree that has the loop test for the induction variable.
GenTree* TestTree = nullptr;
Expand Down Expand Up @@ -2004,8 +2006,10 @@ struct NaturalLoopIterInfo

// Constant peeled from the loop limit so that the effective limit is
// `LimitBase() + LimitOffset`. Non-zero only for HasInvariantLocalLimit
// and HasArrayLengthLimit.
int LimitOffset = 0;
// and HasArrayLengthLimit. Width matches the IV: a TYP_INT IV has an
// int-sized offset; a TYP_LONG IV has a long-sized offset. Stored as
// int64_t so 64-bit offsets are representable on a 32-bit JIT host.
int64_t LimitOffset = 0;

NaturalLoopIterInfo()
: ExitedOnTrue(false)
Expand All @@ -2018,7 +2022,7 @@ struct NaturalLoopIterInfo
{
}

int IterConst();
int64_t IterConst();
genTreeOps IterOper();
var_types IterOperType();
genTreeOps TestOper();
Expand All @@ -2027,7 +2031,7 @@ struct NaturalLoopIterInfo
GenTree* Iterator();
GenTree* Limit();
GenTree* LimitBase();
int ConstLimit();
int64_t ConstLimit();
unsigned VarLimit();
bool ArrLenLimit(Compiler* comp, ArrIndex* index);

Expand Down
148 changes: 99 additions & 49 deletions src/coreclr/jit/flowgraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5754,10 +5754,10 @@ bool FlowGraphNaturalLoop::AnalyzeIteration(NaturalLoopIterInfo* info, bool allo
#ifdef DEBUG
if (comp->verbose)
{
printf(" IterVar = V%02u\n", info->IterVar);
printf(" IterVar = V%02u (%s)\n", info->IterVar, varTypeName(genActualType(info->IterTree)));

if (info->HasConstInit)
printf(" Const init with value %d (at [%06u])\n", info->ConstInitValue,
printf(" Const init with value %lld (at [%06u])\n", (long long)info->ConstInitValue,
Compiler::dspTreeID(info->InitTree));

printf(" Test is [%06u] (", Compiler::dspTreeID(info->TestTree));
Expand All @@ -5770,7 +5770,7 @@ bool FlowGraphNaturalLoop::AnalyzeIteration(NaturalLoopIterInfo* info, bool allo
if (info->HasArrayLengthLimit)
printf("array length limit ");
if (info->LimitOffset != 0)
printf("offset %d ", info->LimitOffset);
printf("offset %lld ", (long long)info->LimitOffset);
printf(")\n");
}
#endif
Expand Down Expand Up @@ -5840,7 +5840,7 @@ bool FlowGraphNaturalLoop::MatchLimit(unsigned iterVar, GenTree* test, NaturalLo
return false;
}

if (!iterOp->TypeIs(TYP_INT))
if (!iterOp->TypeIs(TYP_INT, TYP_LONG))
{
return false;
}
Comment thread
AndyAyersMS marked this conversation as resolved.
Expand All @@ -5849,33 +5849,32 @@ bool FlowGraphNaturalLoop::MatchLimit(unsigned iterVar, GenTree* test, NaturalLo
// peeling the constant into LimitOffset and form-checking the base.
// Morph canonicalizes commutative ops so that any constant operand sits on
// op2, and folds `base ± 0`, so we only need to look for `base op2-cns`.
int peeledOffset = 0;
int64_t peeledOffset = 0;
GenTree* peeledBase = nullptr;
if (limitOp->OperIs(GT_ADD, GT_SUB) && limitOp->TypeIs(TYP_INT))
if (limitOp->OperIs(GT_ADD, GT_SUB) && (limitOp->TypeGet() == limitOp->gtGetOp1()->TypeGet()))
{
GenTree* lop1 = limitOp->gtGetOp1();
GenTree* lop2 = limitOp->gtGetOp2();
if (lop2->IsCnsIntOrI() && lop2->TypeIs(TYP_INT) && !lop1->IsCnsIntOrI())
// Long constants are GT_CNS_LNG on 32-bit targets, GT_CNS_INT
// (TYP_LONG) on 64-bit; IsIntegralConst accepts both.
if (lop2->IsIntegralConst() && (lop2->TypeGet() == lop1->TypeGet()) && !lop1->IsIntegralConst())
{
ssize_t cns = lop2->AsIntCon()->IconValue();
if ((cns >= INT32_MIN) && (cns <= INT32_MAX))
int64_t cns = lop2->AsIntConCommon()->IntegralValue();
if (limitOp->OperIs(GT_SUB))
{
int signedCns = (int)cns;
if (limitOp->OperIs(GT_SUB))
// Guard against signed negation overflow at the type's minimum.
const int64_t typeMin = (lop1->TypeGet() == TYP_INT) ? (int64_t)INT32_MIN : INT64_MIN;
if (cns != typeMin)
{
// Guard against the -INT32_MIN overflow.
if (signedCns != INT32_MIN)
{
peeledOffset = -signedCns;
peeledBase = lop1;
}
}
else
{
peeledOffset = signedCns;
peeledOffset = -cns;
peeledBase = lop1;
}
}
else
{
peeledOffset = cns;
peeledBase = lop1;
}
}
}

Expand All @@ -5885,8 +5884,26 @@ bool FlowGraphNaturalLoop::MatchLimit(unsigned iterVar, GenTree* test, NaturalLo
info->LimitOffset = peeledOffset;
}

// A TYP_LONG IV often compares against an int-typed limit (e.g.
// `arr.Length`) widened via a non-overflowing CAST. Peel that cast so
// the underlying form-check sees the int operand. Only peel when any
// previously peeled LimitOffset still fits in an int32: the peeled
// base is int-typed, and downstream materialization (ToGenTree) will
// emit an int-sized offset node when added to that base.
if (iterOp->TypeIs(TYP_LONG) && limitOp->OperIs(GT_CAST))
{
GenTreeCast* cast = limitOp->AsCast();
if ((cast->CastToType() == TYP_LONG) && (genActualType(cast->CastOp()) == TYP_INT) && !cast->gtOverflow() &&
(info->LimitOffset >= INT32_MIN) && (info->LimitOffset <= INT32_MAX))
{
limitOp = cast->CastOp();
}
}
Comment thread
AndyAyersMS marked this conversation as resolved.
Comment thread
AndyAyersMS marked this conversation as resolved.

// Check what type of limit we have - constant, variable or arr-len.
if (limitOp->IsCnsIntOrI())
// IsIntegralConst accepts both GT_CNS_INT (TYP_INT/TYP_I_IMPL) and
// GT_CNS_LNG (TYP_LONG on 32-bit targets).
if (limitOp->IsIntegralConst())
{
info->HasConstLimit = true;
if ((limitOp->gtFlags & GTF_ICON_SIMD_COUNT) != 0)
Expand Down Expand Up @@ -5983,10 +6000,11 @@ bool FlowGraphNaturalLoop::FindConstInit(BasicBlock* preheader, NaturalLoopIterI
{
GenTreeLclVarCommon* store = tree->AsLclVarCommon();
GenTree* data = store->Data();
if ((store->GetLclNum() == info->IterVar) && data->IsCnsIntOrI() && data->TypeIs(TYP_INT))
if ((store->GetLclNum() == info->IterVar) && data->IsIntegralConst() &&
data->TypeIs(TYP_INT, TYP_LONG))
{
info->HasConstInit = true;
info->ConstInitValue = (int)data->AsIntCon()->IconValue();
info->ConstInitValue = data->AsIntConCommon()->IntegralValue();
INDEBUG(info->InitTree = tree);
return true;
}
Expand Down Expand Up @@ -6077,19 +6095,32 @@ bool FlowGraphNaturalLoop::CheckLoopConditionBaseCase(BasicBlock* preheader, Nat
// Is it trivially true?
if (info->HasConstInit && info->HasConstLimit)
{
int initVal = info->ConstInitValue;
int limitVal = info->ConstLimit();
int64_t initVal = info->ConstInitValue;
int64_t limitVal = info->ConstLimit();

assert(genActualType(info->TestTree->gtGetOp1()) == TYP_INT);
const var_types testType = genActualType(info->TestTree->gtGetOp1());
assert((testType == TYP_INT) || (testType == TYP_LONG));

bool isTriviallyTrue = info->TestTree->IsUnsigned()
? EvaluateRelop<uint32_t>((uint32_t)initVal, (uint32_t)limitVal, info->TestOper())
: EvaluateRelop<int32_t>(initVal, limitVal, info->TestOper());
bool isTriviallyTrue;
if (testType == TYP_INT)
{
isTriviallyTrue =
info->TestTree->IsUnsigned()
? EvaluateRelop<uint32_t>((uint32_t)(int)initVal, (uint32_t)(int)limitVal, info->TestOper())
: EvaluateRelop<int32_t>((int32_t)initVal, (int32_t)limitVal, info->TestOper());
}
else
{
isTriviallyTrue = info->TestTree->IsUnsigned()
? EvaluateRelop<uint64_t>((uint64_t)initVal, (uint64_t)limitVal, info->TestOper())
: EvaluateRelop<int64_t>((int64_t)initVal, (int64_t)limitVal, info->TestOper());
}

if (isTriviallyTrue)
{
JITDUMP(" Condition is trivially true on entry (%d %s%s %d)\n", initVal,
info->TestTree->IsUnsigned() ? "(uns)" : "", GenTree::OpName(info->TestOper()), limitVal);
JITDUMP(" Condition is trivially true on entry (%lld %s%s %lld)\n", (long long)initVal,
info->TestTree->IsUnsigned() ? "(uns)" : "", GenTree::OpName(info->TestOper()),
(long long)limitVal);
return true;
}
}
Expand Down Expand Up @@ -6814,15 +6845,17 @@ bool FlowGraphNaturalLoop::IsPostDominatedOnLoopIteration(BasicBlock* block, Bas
}

//------------------------------------------------------------------------
// IterConst: Get the constant with which the iterator is modified
// IterConst: Get the constant with which the iterator is modified.
//
// Returns:
// Constant value.
// Constant value, as a signed value sized for the IV's type. For a
// TYP_INT IV the value fits in int; for TYP_LONG the full 64 bits may
// be needed.
//
int NaturalLoopIterInfo::IterConst()
int64_t NaturalLoopIterInfo::IterConst()
{
GenTree* value = IterTree->AsLclVar()->Data();
return (int)value->gtGetOp2()->AsIntCon()->IconValue();
return value->gtGetOp2()->AsIntConCommon()->IntegralValue();
}

//------------------------------------------------------------------------
Expand All @@ -6844,7 +6877,7 @@ genTreeOps NaturalLoopIterInfo::IterOper()
//
var_types NaturalLoopIterInfo::IterOperType()
{
assert(genActualType(IterTree) == TYP_INT);
assert((genActualType(IterTree) == TYP_INT) || (genActualType(IterTree) == TYP_LONG));
return IterTree->TypeGet();
}

Expand Down Expand Up @@ -6965,20 +6998,37 @@ GenTree* NaturalLoopIterInfo::Limit()
GenTree* NaturalLoopIterInfo::LimitBase()
{
GenTree* lim = Limit();
if (LimitOffset == 0)

// Mirror MatchLimit's peels (offset first, then a long-from-int cast) so
// VarLimit / ArrLenLimit see the actual form-checked subtree.
if (LimitOffset != 0)
{
return lim;
assert(lim->OperIs(GT_ADD, GT_SUB) && lim->TypeIs(TYP_INT, TYP_LONG));
GenTree* op1 = lim->gtGetOp1();
GenTree* op2 = lim->gtGetOp2();
// IsIntegralConst handles both GT_CNS_INT and GT_CNS_LNG, matching
// the form MatchLimit peeled.
if (op2->IsIntegralConst())
{
lim = op1;
}
else
{
assert(op1->IsIntegralConst() && lim->OperIs(GT_ADD));
lim = op2;
}
}

assert(lim->OperIs(GT_ADD, GT_SUB) && lim->TypeIs(TYP_INT));
GenTree* op1 = lim->gtGetOp1();
GenTree* op2 = lim->gtGetOp2();
if (op2->IsCnsIntOrI())
if (lim->OperIs(GT_CAST) && lim->TypeIs(TYP_LONG))
{
return op1;
GenTreeCast* cast = lim->AsCast();
if ((cast->CastToType() == TYP_LONG) && (genActualType(cast->CastOp()) == TYP_INT) && !cast->gtOverflow())
{
lim = cast->CastOp();
}
}
assert(op1->IsCnsIntOrI() && lim->OperIs(GT_ADD));
return op2;

return lim;
}

//------------------------------------------------------------------------
Expand All @@ -6991,13 +7041,13 @@ GenTree* NaturalLoopIterInfo::LimitBase()
// Remarks:
// Only valid if HasConstLimit is true.
//
int NaturalLoopIterInfo::ConstLimit()
int64_t NaturalLoopIterInfo::ConstLimit()
{
assert(HasConstLimit);
assert(LimitOffset == 0);
GenTree* limit = LimitBase();
assert(limit->OperIsConst());
return (int)limit->AsIntCon()->IconValue();
assert(limit->IsIntegralConst());
return limit->AsIntConCommon()->IntegralValue();
}

//------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/jitmetadatalist.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ JITMETADATAMETRIC(GCInfoBytes, int, JIT_M
JITMETADATAMETRIC(EHClauseCount, int, 0)
JITMETADATAMETRIC(PhysicallyPromotedFields, int, 0)
JITMETADATAMETRIC(LoopsFoundDuringOpts, int, 0)
JITMETADATAMETRIC(LoopsWithLongIV, int, 0)
Comment thread
AndyAyersMS marked this conversation as resolved.
Outdated
JITMETADATAMETRIC(LoopsInverted, int, 0)
JITMETADATAMETRIC(LoopsCloned, int, 0)
JITMETADATAMETRIC(LoopsRejectedForInsufficientBenefit, int, 0)
Expand Down
Loading
Loading