Skip to content

Commit 823cd67

Browse files
authored
JIT: Improve and fix StaysWithinManagedObject (#105108)
- For string accesses we also produce `ARR_ADDR`, so we must take care to use `GenTreeArrAddr::GetFirstElemOffset` instead of hardcoding `OFFSETOF__CORINFO_Array__data` - There are cases where VN is fully able to prove that bound < ARR_LENGTH(vn), specifically when the array is stored in a static readonly field. In those cases everything reduces to constants, so allow VN to try to prove it but fall back to our manual logic otherwise. - Rephrase the fallback as a VN test as well. In a standard `for (;i < arr.Length;)` loop we have a bound on the backedge of the shape `ARR_LENGTH(array) - 1`. The previous strategy was to syntactically check if the LHS was such an array length on the same array as the base of the add recurrence. Instead of doing that, we can ask more generally for any shape `x - c` whether we know that `x <= ARR_LENGTH(array)`. In the usual case of `x == ARR_LENGTH(array)` this is trivially true and VN knows that. However, there are other cases where this is provable by RBO due to a dominating compare; particularly loop cloning introduces these dominating compares when cloning loops of the shape `for (; i < n;)`. This fixes #105087.
1 parent ae4bffa commit 823cd67

File tree

3 files changed

+55
-42
lines changed

3 files changed

+55
-42
lines changed

src/coreclr/jit/inductionvariableopts.cpp

Lines changed: 49 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1849,17 +1849,18 @@ bool StrengthReductionContext::StaysWithinManagedObject(ArrayStack<CursorInfo>*
18491849
Scev* baseScev = addRec->Start->PeelAdditions(&offset);
18501850
offset = static_cast<target_ssize_t>(offset);
18511851

1852-
// We only support arrays here. To strength reduce Span<T> accesses we need
1853-
// additional properies on the range designated by a Span<T> that we
1854-
// currently do not specify, or we need to prove that the byref we may form
1855-
// in the IV update would have been formed anyway by the loop.
1852+
// We only support arrays and strings here. To strength reduce Span<T>
1853+
// accesses we need additional properies on the range designated by a
1854+
// Span<T> that we currently do not specify, or we need to prove that the
1855+
// byref we may form in the IV update would have been formed anyway by the
1856+
// loop.
18561857
if (!baseScev->OperIs(ScevOper::Local) || !baseScev->TypeIs(TYP_REF))
18571858
{
18581859
return false;
18591860
}
18601861

1861-
// Now use the fact that we keep ARR_ADDRs in the IR when we have array
1862-
// accesses.
1862+
// Now use the fact that we keep ARR_ADDRs in the IR when we have
1863+
// array/string accesses.
18631864
GenTreeArrAddr* arrAddr = nullptr;
18641865
for (int i = 0; i < cursors->Height(); i++)
18651866
{
@@ -1890,7 +1891,7 @@ bool StrengthReductionContext::StaysWithinManagedObject(ArrayStack<CursorInfo>*
18901891
ScevLocal* local = (ScevLocal*)baseScev;
18911892

18921893
ValueNumPair vnp = m_scevContext.MaterializeVN(baseScev);
1893-
if (vnp.GetConservative() == ValueNumStore::NoVN)
1894+
if (!vnp.BothDefined())
18941895
{
18951896
return false;
18961897
}
@@ -1901,53 +1902,68 @@ bool StrengthReductionContext::StaysWithinManagedObject(ArrayStack<CursorInfo>*
19011902
return false;
19021903
}
19031904

1904-
// We have a non-null array. Check that the 'start' offset looks fine.
1905-
// TODO: We could also use assertions on the length of the array. E.g. if
1906-
// we know the length of the array is > 3, then we can allow the add rec to
1907-
// have a later start. Maybe range check can be used?
1908-
if ((offset < 0) || (offset > (int64_t)OFFSETOF__CORINFO_Array__data))
1905+
// We have a non-null array/string. Check that the 'start' offset looks
1906+
// fine. TODO: We could also use assertions on the length of the
1907+
// array/string. E.g. if we know the length of the array is > 3, then we
1908+
// can allow the add rec to have a later start. Maybe range check can be
1909+
// used?
1910+
if ((offset < 0) || (offset > arrAddr->GetFirstElemOffset()))
19091911
{
19101912
return false;
19111913
}
19121914

1913-
// Now see if we have a bound that guarantees that we iterate less than the
1914-
// array length's times.
1915+
// Now see if we have a bound that guarantees that we iterate fewer times
1916+
// than the array/string's length.
1917+
ValueNum arrLengthVN = m_comp->vnStore->VNForFunc(TYP_INT, VNF_ARR_LENGTH, vnp.GetLiberal());
1918+
19151919
for (int i = 0; i < m_backEdgeBounds.Height(); i++)
19161920
{
1917-
// TODO: EvaluateRelop ought to be powerful enough to prove something
1918-
// like bound < ARR_LENGTH(vn), but it is not able to prove that
1919-
// currently, even for bound = ARR_LENGTH(vn) - 1 (common case).
19201921
Scev* bound = m_backEdgeBounds.Bottom(i);
1922+
if (!bound->TypeIs(TYP_INT))
1923+
{
1924+
// Currently cannot handle bounds that aren't 32 bit.
1925+
continue;
1926+
}
19211927

1922-
int64_t boundOffset;
1923-
Scev* boundBase = bound->PeelAdditions(&boundOffset);
1924-
1925-
if (bound->TypeIs(TYP_INT))
1928+
// In some cases VN is powerful enough to evaluate bound <
1929+
// ARR_LENGTH(vn) directly.
1930+
ValueNumPair boundVN = m_scevContext.MaterializeVN(bound);
1931+
if (boundVN.GetLiberal() != ValueNumStore::NoVN)
19261932
{
1927-
boundOffset = static_cast<int32_t>(boundOffset);
1933+
ValueNum relop = m_comp->vnStore->VNForFunc(TYP_INT, VNF_LT_UN, boundVN.GetLiberal(), arrLengthVN);
1934+
if (m_scevContext.EvaluateRelop(relop) == RelopEvaluationResult::True)
1935+
{
1936+
return true;
1937+
}
19281938
}
19291939

1940+
// In common cases VN cannot prove the above, so try a little bit
1941+
// harder. In this case we know the bound doesn't overflow
1942+
// (conceptually the bound is an arbitrary precision integer and a
1943+
// negative bound does not make sense).
1944+
int64_t boundOffset;
1945+
Scev* boundBase = bound->PeelAdditions(&boundOffset);
1946+
1947+
boundOffset = static_cast<int32_t>(boundOffset);
19301948
if (boundOffset >= 0)
19311949
{
19321950
// If we take the backedge >= the array length times, then we would
19331951
// advance the addrec past the end.
19341952
continue;
19351953
}
19361954

1955+
// Now try to prove that boundBase <= ARR_LENGTH(vn). Commonly
1956+
// boundBase == ARR_LENGTH(vn) exactly, but it may also have a
1957+
// dominating subsuming compare before the loop due to loop cloning.
19371958
ValueNumPair boundBaseVN = m_scevContext.MaterializeVN(boundBase);
1938-
1939-
VNFuncApp vnf;
1940-
if (!m_comp->vnStore->GetVNFunc(boundBaseVN.GetConservative(), &vnf))
1941-
{
1942-
continue;
1943-
}
1944-
1945-
if ((vnf.m_func != VNF_ARR_LENGTH) || (vnf.m_args[0] != vnp.GetConservative()))
1959+
if (boundBaseVN.GetLiberal() != ValueNumStore::NoVN)
19461960
{
1947-
continue;
1961+
ValueNum relop = m_comp->vnStore->VNForFunc(TYP_INT, VNF_LE, boundBaseVN.GetLiberal(), arrLengthVN);
1962+
if (m_scevContext.EvaluateRelop(relop) == RelopEvaluationResult::True)
1963+
{
1964+
return true;
1965+
}
19481966
}
1949-
1950-
return true;
19511967
}
19521968

19531969
return false;

src/coreclr/jit/scev.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1501,16 +1501,13 @@ RelopEvaluationResult ScalarEvolutionContext::EvaluateRelop(ValueNum vn)
15011501
}
15021502

15031503
// Evaluate by using dominators and RBO's logic.
1504+
assert(m_comp->m_domTree != nullptr);
15041505
//
15051506
// TODO-CQ: Using assertions could be stronger given its dataflow, but it
15061507
// is not convenient to use (optVNConstantPropOnJTrue does not actually
15071508
// make any use of assertions to evaluate conditionals, so it seems like
15081509
// the logic does not actually exist anywhere.)
15091510
//
1510-
if (m_comp->m_domTree == nullptr)
1511-
{
1512-
m_comp->m_domTree = FlowGraphDominatorTree::Build(m_comp->m_dfsTree);
1513-
}
15141511

15151512
for (BasicBlock* idom = m_loop->GetHeader()->bbIDom; idom != nullptr; idom = idom->bbIDom)
15161513
{

src/coreclr/jit/scev.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -239,10 +239,9 @@ class ScalarEvolutionContext
239239
Scev* CreateScevForConstant(GenTreeIntConCommon* tree);
240240
void ExtractAddOperands(ScevBinop* add, ArrayStack<Scev*>& operands);
241241

242-
VNFunc MapRelopToVNFunc(genTreeOps oper, bool isUnsigned);
243-
RelopEvaluationResult EvaluateRelop(ValueNum relop);
244-
bool MayOverflowBeforeExit(ScevAddRec* lhs, Scev* rhs, VNFunc exitOp);
245-
bool AddRecMayOverflow(ScevAddRec* addRec, bool signedBound, const SimplificationAssumptions& assumptions);
242+
VNFunc MapRelopToVNFunc(genTreeOps oper, bool isUnsigned);
243+
bool MayOverflowBeforeExit(ScevAddRec* lhs, Scev* rhs, VNFunc exitOp);
244+
bool AddRecMayOverflow(ScevAddRec* addRec, bool signedBound, const SimplificationAssumptions& assumptions);
246245

247246
bool Materialize(Scev* scev, bool createIR, GenTree** result, ValueNumPair* resultVN);
248247

@@ -262,7 +261,8 @@ class ScalarEvolutionContext
262261
static const SimplificationAssumptions NoAssumptions;
263262
Scev* Simplify(Scev* scev, const SimplificationAssumptions& assumptions = NoAssumptions);
264263

265-
Scev* ComputeExitNotTakenCount(BasicBlock* exiting);
264+
Scev* ComputeExitNotTakenCount(BasicBlock* exiting);
265+
RelopEvaluationResult EvaluateRelop(ValueNum relop);
266266

267267
GenTree* Materialize(Scev* scev);
268268
ValueNumPair MaterializeVN(Scev* scev);

0 commit comments

Comments
 (0)