Skip to content

Commit 254b55a

Browse files
authored
Enable Loop Cloning for Spans (#113575)
1 parent bb84123 commit 254b55a

File tree

6 files changed

+250
-11
lines changed

6 files changed

+250
-11
lines changed

src/coreclr/jit/clrjit.natvis

+1
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ Documentation for VS debugger format specifiers: https://learn.microsoft.com/vis
266266
<Expand>
267267
<ExpandedItem Condition="optType == LcOptInfo::OptType::LcJaggedArray">(LcJaggedArrayOptInfo*)this,nd</ExpandedItem>
268268
<ExpandedItem Condition="optType == LcOptInfo::OptType::LcMdArray">(LcMdArrayOptInfo*)this,nd</ExpandedItem>
269+
<ExpandedItem Condition="optType == LcOptInfo::OptType::LcSpan">(LcSpanOptInfo*)this,nd</ExpandedItem>
269270
</Expand>
270271
</Type>
271272

src/coreclr/jit/compiler.h

+1
Original file line numberDiff line numberDiff line change
@@ -8157,6 +8157,7 @@ class Compiler
81578157

81588158
bool optIsStackLocalInvariant(FlowGraphNaturalLoop* loop, unsigned lclNum);
81598159
bool optExtractArrIndex(GenTree* tree, ArrIndex* result, unsigned lhsNum, bool* topLevelIsFinal);
8160+
bool optExtractSpanIndex(GenTree* tree, SpanIndex* result);
81608161
bool optReconstructArrIndexHelp(GenTree* tree, ArrIndex* result, unsigned lhsNum, bool* topLevelIsFinal);
81618162
bool optReconstructArrIndex(GenTree* tree, ArrIndex* result);
81628163
bool optIdentifyLoopOptInfo(FlowGraphNaturalLoop* loop, LoopCloneContext* context);

src/coreclr/jit/importercalls.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -3671,6 +3671,8 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd,
36713671
case NI_System_Span_get_Item:
36723672
case NI_System_ReadOnlySpan_get_Item:
36733673
{
3674+
optMethodFlags |= OMF_HAS_ARRAYREF;
3675+
36743676
// Have index, stack pointer-to Span<T> s on the stack. Expand to:
36753677
//
36763678
// For Span<T>

src/coreclr/jit/loopcloning.cpp

+162-11
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,22 @@ void ArrIndex::PrintBoundsCheckNodes(unsigned dim /* = -1 */)
4545
}
4646
}
4747

48+
//--------------------------------------------------------------------------------------------------
49+
// Print: debug print an SpanIndex struct in form: `V01[V02]`.
50+
//
51+
void SpanIndex::Print()
52+
{
53+
printf("V%02d[V%02d]", lenLcl, indLcl);
54+
}
55+
56+
//--------------------------------------------------------------------------------------------------
57+
// PrintBoundsCheckNode: - debug print an SpanIndex struct bounds check node tree id
58+
//
59+
void SpanIndex::PrintBoundsCheckNode()
60+
{
61+
Compiler::printTreeID(bndsChk);
62+
}
63+
4864
#endif // DEBUG
4965

5066
//--------------------------------------------------------------------------------------------------
@@ -115,6 +131,20 @@ GenTree* LC_Array::ToGenTree(Compiler* comp, BasicBlock* bb)
115131
return nullptr;
116132
}
117133

134+
//--------------------------------------------------------------------------------------------------
135+
// ToGenTree: Convert a Span.Length operation into a GenTree node.
136+
//
137+
// Arguments:
138+
// comp - Compiler instance to allocate trees
139+
//
140+
// Return Values:
141+
// Returns the gen tree representation for Span.Length
142+
//
143+
GenTree* LC_Span::ToGenTree(Compiler* comp)
144+
{
145+
return comp->gtNewLclvNode(spanIndex->lenLcl, comp->lvaTable[spanIndex->lenLcl].lvType);
146+
}
147+
118148
//--------------------------------------------------------------------------------------------------
119149
// ToGenTree - Convert an "identifier" into a GenTree node.
120150
//
@@ -138,6 +168,8 @@ GenTree* LC_Ident::ToGenTree(Compiler* comp, BasicBlock* bb)
138168
return comp->gtNewLclvNode(lclNum, comp->lvaTable[lclNum].lvType);
139169
case ArrAccess:
140170
return arrAccess.ToGenTree(comp, bb);
171+
case SpanAccess:
172+
return spanAccess.ToGenTree(comp);
141173
case Null:
142174
return comp->gtNewIconNode(0, TYP_REF);
143175
case ClassHandle:
@@ -1080,6 +1112,10 @@ bool Compiler::optDeriveLoopCloningConditions(FlowGraphNaturalLoop* loop, LoopCl
10801112
JitExpandArrayStack<LcOptInfo*>* optInfos = context->GetLoopOptInfo(loop->GetIndex());
10811113
assert(optInfos->Size() > 0);
10821114

1115+
// If we have spans, that means we have to be careful about the stride (see below).
1116+
//
1117+
bool hasSpans = false;
1118+
10831119
// We only need to check for iteration behavior if we have array checks.
10841120
//
10851121
bool checkIterationBehavior = false;
@@ -1094,6 +1130,11 @@ bool Compiler::optDeriveLoopCloningConditions(FlowGraphNaturalLoop* loop, LoopCl
10941130
checkIterationBehavior = true;
10951131
break;
10961132

1133+
case LcOptInfo::LcSpan:
1134+
checkIterationBehavior = true;
1135+
hasSpans = true;
1136+
break;
1137+
10971138
case LcOptInfo::LcTypeTest:
10981139
{
10991140
LcTypeTestOptInfo* ttInfo = optInfo->AsLcTypeTestOptInfo();
@@ -1154,23 +1195,37 @@ bool Compiler::optDeriveLoopCloningConditions(FlowGraphNaturalLoop* loop, LoopCl
11541195
}
11551196

11561197
const bool isIncreasingLoop = iterInfo->IsIncreasingLoop();
1157-
assert(isIncreasingLoop || iterInfo->IsDecreasingLoop());
1198+
if (!isIncreasingLoop && !iterInfo->IsDecreasingLoop())
1199+
{
1200+
// Normally, we reject weird-looking loops in optIsLoopClonable, but it's not the case
1201+
// when we have both GDVs and array checks inside such loops.
1202+
return false;
1203+
}
11581204

11591205
// We already know that this is either increasing or decreasing loop and the
11601206
// stride is (> 0) or (< 0). Here, just take the abs() value and check if it
11611207
// is beyond the limit.
11621208
int stride = abs(iterInfo->IterConst());
11631209

1164-
if (stride >= 58)
1210+
static_assert_no_msg(INT32_MAX >= CORINFO_Array_MaxLength);
1211+
if (stride >= (INT32_MAX - (CORINFO_Array_MaxLength - 1) + 1))
11651212
{
1166-
// Array.MaxLength can have maximum of 0X7FFFFFC7 elements, so make sure
1213+
// Array.MaxLength can have maximum of 0x7fffffc7 elements, so make sure
11671214
// the stride increment doesn't overflow or underflow the index. Hence,
11681215
// the maximum stride limit is set to
11691216
// (int.MaxValue - (Array.MaxLength - 1) + 1), which is
11701217
// (0X7fffffff - 0x7fffffc7 + 2) = 0x3a or 58.
11711218
return false;
11721219
}
11731220

1221+
// We don't know exactly whether we might be dealing with a Span<T> or not,
1222+
// but if we suspect we are, we need to be careful about the stride:
1223+
// As Span<>.Length can be INT32_MAX unlike arrays.
1224+
if (hasSpans && (stride > 1))
1225+
{
1226+
return false;
1227+
}
1228+
11741229
LC_Ident ident;
11751230
// Init conditions
11761231
if (iterInfo->HasConstInit)
@@ -1313,6 +1368,15 @@ bool Compiler::optDeriveLoopCloningConditions(FlowGraphNaturalLoop* loop, LoopCl
13131368
context->EnsureArrayDerefs(loop->GetIndex())->Push(array);
13141369
}
13151370
break;
1371+
case LcOptInfo::LcSpan:
1372+
{
1373+
LcSpanOptInfo* spanInfo = optInfo->AsLcSpanOptInfo();
1374+
LC_Span spanLen(&spanInfo->spanIndex);
1375+
LC_Ident spanLenIdent = LC_Ident::CreateSpanAccess(spanLen);
1376+
LC_Condition cond(opLimitCondition, LC_Expr(ident), LC_Expr(spanLenIdent));
1377+
context->EnsureConditions(loop->GetIndex())->Push(cond);
1378+
}
1379+
break;
13161380
case LcOptInfo::LcMdArray:
13171381
{
13181382
LcMdArrayOptInfo* mdArrInfo = optInfo->AsLcMdArrayOptInfo();
@@ -1455,10 +1519,6 @@ bool Compiler::optComputeDerefConditions(FlowGraphNaturalLoop* loop, LoopCloneCo
14551519
JitExpandArrayStack<LC_Array>* const arrayDeref = context->EnsureArrayDerefs(loop->GetIndex());
14561520
JitExpandArrayStack<LC_Ident>* const objDeref = context->EnsureObjDerefs(loop->GetIndex());
14571521

1458-
// We currently expect to have at least one of these.
1459-
//
1460-
assert((arrayDeref->Size() != 0) || (objDeref->Size() != 0));
1461-
14621522
// Generate the array dereference checks.
14631523
//
14641524
// For each array in the dereference list, construct a tree,
@@ -1679,6 +1739,39 @@ void Compiler::optPerformStaticOptimizations(FlowGraphNaturalLoop* loop,
16791739
DBEXEC(dynamicPath, optDebugLogLoopCloning(arrIndexInfo->arrIndex.useBlock, arrIndexInfo->stmt));
16801740
}
16811741
break;
1742+
case LcOptInfo::LcSpan:
1743+
{
1744+
LcSpanOptInfo* spanIndexInfo = optInfo->AsLcSpanOptInfo();
1745+
compCurBB = spanIndexInfo->spanIndex.useBlock;
1746+
GenTree* bndsChkNode = spanIndexInfo->spanIndex.bndsChk;
1747+
1748+
#ifdef DEBUG
1749+
if (verbose)
1750+
{
1751+
printf("Remove bounds check ");
1752+
printTreeID(bndsChkNode->gtGetOp1());
1753+
printf(" for " FMT_STMT ", ", spanIndexInfo->stmt->GetID());
1754+
spanIndexInfo->spanIndex.Print();
1755+
printf(", bounds check nodes: ");
1756+
spanIndexInfo->spanIndex.PrintBoundsCheckNode();
1757+
printf("\n");
1758+
}
1759+
#endif // DEBUG
1760+
1761+
if (bndsChkNode->gtGetOp1()->OperIs(GT_BOUNDS_CHECK))
1762+
{
1763+
optRemoveCommaBasedRangeCheck(bndsChkNode, spanIndexInfo->stmt);
1764+
}
1765+
else
1766+
{
1767+
JITDUMP(" Bounds check already removed\n");
1768+
1769+
// If the bounds check node isn't there, it better have been converted to a GT_NOP.
1770+
assert(bndsChkNode->gtGetOp1()->OperIs(GT_NOP));
1771+
}
1772+
DBEXEC(dynamicPath, optDebugLogLoopCloning(spanIndexInfo->spanIndex.useBlock, spanIndexInfo->stmt));
1773+
}
1774+
break;
16821775
case LcOptInfo::LcMdArray:
16831776
// TODO-CQ: CLONE: Implement.
16841777
break;
@@ -1913,7 +2006,6 @@ BasicBlock* Compiler::optInsertLoopChoiceConditions(LoopCloneContext* contex
19132006
BasicBlock* insertAfter)
19142007
{
19152008
JITDUMP("Inserting loop " FMT_LP " loop choice conditions\n", loop->GetIndex());
1916-
assert(context->HasBlockConditions(loop->GetIndex()));
19172009
assert(slowPreheader != nullptr);
19182010

19192011
if (context->HasBlockConditions(loop->GetIndex()))
@@ -2087,9 +2179,6 @@ void Compiler::optCloneLoop(FlowGraphNaturalLoop* loop, LoopCloneContext* contex
20872179
// ...
20882180
// slowPreheader --> slowHeader
20892181
//
2090-
// We should always have block conditions.
2091-
2092-
assert(context->HasBlockConditions(loop->GetIndex()));
20932182

20942183
// If any condition is false, go to slowPreheader (which branches or falls through to header of the slow loop).
20952184
BasicBlock* slowHeader = nullptr;
@@ -2272,6 +2361,44 @@ bool Compiler::optExtractArrIndex(GenTree* tree, ArrIndex* result, unsigned lhsN
22722361
return true;
22732362
}
22742363

2364+
//---------------------------------------------------------------------------------------------------------------
2365+
// optExtractSpanIndex: Try to extract the Span element access from "tree".
2366+
//
2367+
// Arguments:
2368+
// tree - the tree to be checked if it is the Span [] operation.
2369+
// result - the extracted information is updated in result.
2370+
//
2371+
// Return Value:
2372+
// Returns true if Span index can be extracted, else, return false.
2373+
//
2374+
// Notes:
2375+
// The way loop cloning works for Span is that we don't actually know (or care)
2376+
// if it's a Span or an array, we just extract index and length locals out
2377+
/// of the GT_BOUNDS_CHECK node. The fact that the length is a local var
2378+
/// allows us to not worry about array/span dereferencing.
2379+
//
2380+
bool Compiler::optExtractSpanIndex(GenTree* tree, SpanIndex* result)
2381+
{
2382+
// Bounds checks are almost always wrapped in a comma node
2383+
// and are the first operand.
2384+
if (!tree->OperIs(GT_COMMA) || !tree->gtGetOp1()->OperIs(GT_BOUNDS_CHECK))
2385+
{
2386+
return false;
2387+
}
2388+
2389+
GenTreeBoundsChk* arrBndsChk = tree->gtGetOp1()->AsBoundsChk();
2390+
if (!arrBndsChk->GetIndex()->OperIs(GT_LCL_VAR) || !arrBndsChk->GetArrayLength()->OperIs(GT_LCL_VAR))
2391+
{
2392+
return false;
2393+
}
2394+
2395+
result->lenLcl = arrBndsChk->GetArrayLength()->AsLclVarCommon()->GetLclNum();
2396+
result->indLcl = arrBndsChk->GetIndex()->AsLclVarCommon()->GetLclNum();
2397+
result->bndsChk = tree;
2398+
result->useBlock = compCurBB;
2399+
return true;
2400+
}
2401+
22752402
//---------------------------------------------------------------------------------------------------------------
22762403
// optReconstructArrIndexHelp: Helper function for optReconstructArrIndex. See that function for more details.
22772404
//
@@ -2535,6 +2662,30 @@ Compiler::fgWalkResult Compiler::optCanOptimizeByLoopCloning(GenTree* tree, Loop
25352662
return WALK_SKIP_SUBTREES;
25362663
}
25372664

2665+
SpanIndex spanIndex = SpanIndex();
2666+
if (info->cloneForArrayBounds && optExtractSpanIndex(tree, &spanIndex))
2667+
{
2668+
// Check that the span's length local variable is invariant within the loop body.
2669+
if (!optIsStackLocalInvariant(info->loop, spanIndex.lenLcl))
2670+
{
2671+
JITDUMP("Span.Length V%02d is not loop invariant\n", spanIndex.lenLcl);
2672+
return WALK_SKIP_SUBTREES;
2673+
}
2674+
2675+
unsigned iterVar = info->context->GetLoopIterInfo(info->loop->GetIndex())->IterVar;
2676+
if (spanIndex.indLcl == iterVar)
2677+
{
2678+
// Update the loop context.
2679+
info->context->EnsureLoopOptInfo(info->loop->GetIndex())
2680+
->Push(new (this, CMK_LoopOpt) LcSpanOptInfo(spanIndex, info->stmt));
2681+
}
2682+
else
2683+
{
2684+
JITDUMP("Induction V%02d is not used as index\n", iterVar);
2685+
}
2686+
return WALK_SKIP_SUBTREES;
2687+
}
2688+
25382689
if (info->cloneForGDVTests && tree->OperIs(GT_JTRUE))
25392690
{
25402691
JITDUMP("...GDV considering [%06u]\n", dspTreeID(tree));

0 commit comments

Comments
 (0)