@@ -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