@@ -45,6 +45,22 @@ void ArrIndex::PrintBoundsCheckNodes(unsigned dim /* = -1 */)
45
45
}
46
46
}
47
47
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
+
48
64
#endif // DEBUG
49
65
50
66
// --------------------------------------------------------------------------------------------------
@@ -115,6 +131,20 @@ GenTree* LC_Array::ToGenTree(Compiler* comp, BasicBlock* bb)
115
131
return nullptr ;
116
132
}
117
133
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
+
118
148
// --------------------------------------------------------------------------------------------------
119
149
// ToGenTree - Convert an "identifier" into a GenTree node.
120
150
//
@@ -138,6 +168,8 @@ GenTree* LC_Ident::ToGenTree(Compiler* comp, BasicBlock* bb)
138
168
return comp->gtNewLclvNode (lclNum, comp->lvaTable [lclNum].lvType );
139
169
case ArrAccess:
140
170
return arrAccess.ToGenTree (comp, bb);
171
+ case SpanAccess:
172
+ return spanAccess.ToGenTree (comp);
141
173
case Null:
142
174
return comp->gtNewIconNode (0 , TYP_REF);
143
175
case ClassHandle:
@@ -1080,6 +1112,10 @@ bool Compiler::optDeriveLoopCloningConditions(FlowGraphNaturalLoop* loop, LoopCl
1080
1112
JitExpandArrayStack<LcOptInfo*>* optInfos = context->GetLoopOptInfo (loop->GetIndex ());
1081
1113
assert (optInfos->Size () > 0 );
1082
1114
1115
+ // If we have spans, that means we have to be careful about the stride (see below).
1116
+ //
1117
+ bool hasSpans = false ;
1118
+
1083
1119
// We only need to check for iteration behavior if we have array checks.
1084
1120
//
1085
1121
bool checkIterationBehavior = false ;
@@ -1094,6 +1130,11 @@ bool Compiler::optDeriveLoopCloningConditions(FlowGraphNaturalLoop* loop, LoopCl
1094
1130
checkIterationBehavior = true ;
1095
1131
break ;
1096
1132
1133
+ case LcOptInfo::LcSpan:
1134
+ checkIterationBehavior = true ;
1135
+ hasSpans = true ;
1136
+ break ;
1137
+
1097
1138
case LcOptInfo::LcTypeTest:
1098
1139
{
1099
1140
LcTypeTestOptInfo* ttInfo = optInfo->AsLcTypeTestOptInfo ();
@@ -1154,23 +1195,37 @@ bool Compiler::optDeriveLoopCloningConditions(FlowGraphNaturalLoop* loop, LoopCl
1154
1195
}
1155
1196
1156
1197
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
+ }
1158
1204
1159
1205
// We already know that this is either increasing or decreasing loop and the
1160
1206
// stride is (> 0) or (< 0). Here, just take the abs() value and check if it
1161
1207
// is beyond the limit.
1162
1208
int stride = abs (iterInfo->IterConst ());
1163
1209
1164
- if (stride >= 58 )
1210
+ static_assert_no_msg (INT32_MAX >= CORINFO_Array_MaxLength);
1211
+ if (stride >= (INT32_MAX - (CORINFO_Array_MaxLength - 1 ) + 1 ))
1165
1212
{
1166
- // Array.MaxLength can have maximum of 0X7FFFFFC7 elements, so make sure
1213
+ // Array.MaxLength can have maximum of 0x7fffffc7 elements, so make sure
1167
1214
// the stride increment doesn't overflow or underflow the index. Hence,
1168
1215
// the maximum stride limit is set to
1169
1216
// (int.MaxValue - (Array.MaxLength - 1) + 1), which is
1170
1217
// (0X7fffffff - 0x7fffffc7 + 2) = 0x3a or 58.
1171
1218
return false ;
1172
1219
}
1173
1220
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
+
1174
1229
LC_Ident ident;
1175
1230
// Init conditions
1176
1231
if (iterInfo->HasConstInit )
@@ -1313,6 +1368,15 @@ bool Compiler::optDeriveLoopCloningConditions(FlowGraphNaturalLoop* loop, LoopCl
1313
1368
context->EnsureArrayDerefs (loop->GetIndex ())->Push (array);
1314
1369
}
1315
1370
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 ;
1316
1380
case LcOptInfo::LcMdArray:
1317
1381
{
1318
1382
LcMdArrayOptInfo* mdArrInfo = optInfo->AsLcMdArrayOptInfo ();
@@ -1455,10 +1519,6 @@ bool Compiler::optComputeDerefConditions(FlowGraphNaturalLoop* loop, LoopCloneCo
1455
1519
JitExpandArrayStack<LC_Array>* const arrayDeref = context->EnsureArrayDerefs (loop->GetIndex ());
1456
1520
JitExpandArrayStack<LC_Ident>* const objDeref = context->EnsureObjDerefs (loop->GetIndex ());
1457
1521
1458
- // We currently expect to have at least one of these.
1459
- //
1460
- assert ((arrayDeref->Size () != 0 ) || (objDeref->Size () != 0 ));
1461
-
1462
1522
// Generate the array dereference checks.
1463
1523
//
1464
1524
// For each array in the dereference list, construct a tree,
@@ -1679,6 +1739,39 @@ void Compiler::optPerformStaticOptimizations(FlowGraphNaturalLoop* loop,
1679
1739
DBEXEC (dynamicPath, optDebugLogLoopCloning (arrIndexInfo->arrIndex .useBlock , arrIndexInfo->stmt ));
1680
1740
}
1681
1741
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 ;
1682
1775
case LcOptInfo::LcMdArray:
1683
1776
// TODO-CQ: CLONE: Implement.
1684
1777
break ;
@@ -1913,7 +2006,6 @@ BasicBlock* Compiler::optInsertLoopChoiceConditions(LoopCloneContext* contex
1913
2006
BasicBlock* insertAfter)
1914
2007
{
1915
2008
JITDUMP (" Inserting loop " FMT_LP " loop choice conditions\n " , loop->GetIndex ());
1916
- assert (context->HasBlockConditions (loop->GetIndex ()));
1917
2009
assert (slowPreheader != nullptr );
1918
2010
1919
2011
if (context->HasBlockConditions (loop->GetIndex ()))
@@ -2087,9 +2179,6 @@ void Compiler::optCloneLoop(FlowGraphNaturalLoop* loop, LoopCloneContext* contex
2087
2179
// ...
2088
2180
// slowPreheader --> slowHeader
2089
2181
//
2090
- // We should always have block conditions.
2091
-
2092
- assert (context->HasBlockConditions (loop->GetIndex ()));
2093
2182
2094
2183
// If any condition is false, go to slowPreheader (which branches or falls through to header of the slow loop).
2095
2184
BasicBlock* slowHeader = nullptr ;
@@ -2272,6 +2361,44 @@ bool Compiler::optExtractArrIndex(GenTree* tree, ArrIndex* result, unsigned lhsN
2272
2361
return true ;
2273
2362
}
2274
2363
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
+
2275
2402
// ---------------------------------------------------------------------------------------------------------------
2276
2403
// optReconstructArrIndexHelp: Helper function for optReconstructArrIndex. See that function for more details.
2277
2404
//
@@ -2535,6 +2662,30 @@ Compiler::fgWalkResult Compiler::optCanOptimizeByLoopCloning(GenTree* tree, Loop
2535
2662
return WALK_SKIP_SUBTREES;
2536
2663
}
2537
2664
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
+
2538
2689
if (info->cloneForGDVTests && tree->OperIs (GT_JTRUE))
2539
2690
{
2540
2691
JITDUMP (" ...GDV considering [%06u]\n " , dspTreeID (tree));
0 commit comments