@@ -1226,26 +1226,42 @@ bool Compiler::optDeriveLoopCloningConditions(FlowGraphNaturalLoop* loop, LoopCl
12261226 // is beyond the limit.
12271227 int stride = abs (iterInfo->IterConst ());
12281228
1229+ // For arrays the per-access cloning condition only bounds `limit` by
1230+ // Array.MaxLength (0x7FFFFFC7), which leaves room for the post-step IV
1231+ // up to `limit + s - 1` to fit in INT_MAX as long as `s <= 57`. Larger
1232+ // strides need an explicit overflow guard, same shape as the one used
1233+ // for spans (where Span<>.Length can reach INT_MAX even at small s).
12291234 static_assert (INT32_MAX >= CORINFO_Array_MaxLength);
1230- if (stride >= (INT32_MAX - (CORINFO_Array_MaxLength - 1 ) + 1 ))
1235+ const bool largeStride = (stride >= (INT32_MAX - (CORINFO_Array_MaxLength - 1 ) + 1 ));
1236+ const bool needsOverflowGuard = hasSpans || largeStride;
1237+
1238+ // If the loop limit is an array length, compute the underlying ArrIndex
1239+ // and queue the deref check once up front. The optional zero-trip guard,
1240+ // the optional overflow guard, and the regular limit conditions all
1241+ // reuse this single ArrIndex.
1242+ //
1243+ ArrIndex* limitArrIndex = nullptr ;
1244+ if (iterInfo->HasArrayLengthLimit )
12311245 {
1232- // Array.MaxLength can have maximum of 0x7fffffc7 elements, so make sure
1233- // the stride increment doesn't overflow or underflow the index. Hence,
1234- // the maximum stride limit is set to
1235- // (int.MaxValue - (Array.MaxLength - 1) + 1), which is
1236- // (0X7fffffff - 0x7fffffc7 + 2) = 0x3a or 58.
1237- return false ;
1246+ limitArrIndex = new (getAllocator (CMK_LoopClone)) ArrIndex (getAllocator (CMK_LoopClone));
1247+ if (!iterInfo->ArrLenLimit (this , limitArrIndex))
1248+ {
1249+ JITDUMP (" > ArrLen not matching\n " );
1250+ return false ;
1251+ }
1252+
1253+ LC_Array array (LC_Array::Jagged, limitArrIndex, LC_Array::None);
1254+ context->EnsureArrayDerefs (loop->GetIndex ())->Push (array);
12381255 }
12391256
1240- // Span<>.Length can be INT32_MAX, unlike Array.MaxLength. For an
1241- // increasing loop with stride > 1, the IV after the final in-loop
1242- // increment is at most `limit + s` (LE) or `limit + s - 1` (LT), so
1243- // a limit near INT32_MAX would wrap the IV and let the bounds-check-
1244- // stripped fast clone access memory past the span. Bound the limit
1245- // base accordingly. Decreasing loops are safe via the existing
1246- // `limit >= 0` condition plus the stride cap above. HasArrayLengthLimit
1247- // is bounded implicitly by Array.MaxLength.
1248- if (hasSpans && (stride > 1 ) && isIncreasingLoop)
1257+ // For an increasing loop with stride > 1, the IV after the final in-loop
1258+ // increment is at most `limit + s` (LE) or `limit + s - 1` (LT), so a
1259+ // limit near INT32_MAX would wrap the IV and let the bounds-check-
1260+ // stripped fast clone access memory past the array/span. Bound the limit
1261+ // base accordingly. Decreasing loops are safe via the existing `limit
1262+ // >= 0` condition (post-step IV >= -stride > INT_MIN for any non-absurd
1263+ // stride).
1264+ if ((stride > 1 ) && isIncreasingLoop && needsOverflowGuard)
12491265 {
12501266 const int adjustForLE = (iterInfo->TestOper () == GT_LE ) ? 1 : 0 ;
12511267 const int offset = iterInfo->LimitOffset ;
@@ -1257,7 +1273,7 @@ bool Compiler::optDeriveLoopCloningConditions(FlowGraphNaturalLoop* loop, LoopCl
12571273 const int limitVal = iterInfo->ConstLimit ();
12581274 if ((int64_t )limitVal > maxLimitBase64)
12591275 {
1260- JITDUMP (" > Span stride %d: const limit %d exceeds overflow bound %lld\n " , stride, limitVal,
1276+ JITDUMP (" > Stride %d: const limit %d exceeds overflow bound %lld\n " , stride, limitVal,
12611277 (long long )maxLimitBase64);
12621278 return false ;
12631279 }
@@ -1266,20 +1282,19 @@ bool Compiler::optDeriveLoopCloningConditions(FlowGraphNaturalLoop* loop, LoopCl
12661282 {
12671283 if (maxLimitBase64 >= INT32_MAX )
12681284 {
1269- // Offset already absorbs the stride; guard is vacuous.
1270- JITDUMP (" Span stride>1 overflow guard trivially holds (offset %d)\n " , offset);
1285+ JITDUMP (" Stride>1 overflow guard trivially holds (offset %d)\n " , offset);
12711286 }
12721287 else if (maxLimitBase64 < 0 )
12731288 {
1274- JITDUMP (" > Span stride %d, offset %d: overflow guard unsatisfiable\n " , stride, offset);
1289+ JITDUMP (" > Stride %d, offset %d: overflow guard unsatisfiable\n " , stride, offset);
12751290 return false ;
12761291 }
12771292 else
12781293 {
12791294 const unsigned limitLcl = iterInfo->VarLimit ();
12801295 if (!genActualTypeIsInt (lvaGetDesc (limitLcl)))
12811296 {
1282- JITDUMP (" > Span stride %d: limit var V%02u not TYP_INT-compatible\n " , stride, limitLcl);
1297+ JITDUMP (" > Stride %d: limit var V%02u not TYP_INT-compatible\n " , stride, limitLcl);
12831298 return false ;
12841299 }
12851300
@@ -1288,29 +1303,36 @@ bool Compiler::optDeriveLoopCloningConditions(FlowGraphNaturalLoop* loop, LoopCl
12881303 LC_Ident maxConstIdent = LC_Ident::CreateConst (static_cast <unsigned >(maxLimit));
12891304 LC_Condition overflowGuard (GT_LE , LC_Expr (limitVarIdent), LC_Expr (maxConstIdent));
12901305 context->EnsureConditions (loop->GetIndex ())->Push (overflowGuard);
1291- JITDUMP (" Added Span stride>1 overflow guard: V%02u <= %d\n " , limitLcl, maxLimit);
1306+ JITDUMP (" Added stride>1 overflow guard: V%02u <= %d\n " , limitLcl, maxLimit);
12921307 }
12931308 }
1294- // HasArrayLengthLimit: bounded by Array.MaxLength, no extra guard.
1295- }
1296-
1297- // If the loop limit is an array length, compute the underlying ArrIndex
1298- // and queue the deref check once up front. Both the optional zero-trip
1299- // guard below and the regular limit conditions further down reuse this
1300- // single ArrIndex to avoid duplicating the deref entry and allocation.
1301- //
1302- ArrIndex* limitArrIndex = nullptr ;
1303- if (iterInfo->HasArrayLengthLimit )
1304- {
1305- limitArrIndex = new (getAllocator (CMK_LoopClone)) ArrIndex (getAllocator (CMK_LoopClone));
1306- if (!iterInfo->ArrLenLimit (this , limitArrIndex))
1309+ else if (iterInfo->HasArrayLengthLimit && largeStride)
13071310 {
1308- JITDUMP (" > ArrLen not matching\n " );
1309- return false ;
1311+ // For stride <= 57 the implicit Array.MaxLength bound suffices;
1312+ // we fall through with no extra check. For wider strides emit a
1313+ // runtime guard on arr.Length so the fast clone only runs when
1314+ // the array is short enough that the post-step IV stays in int.
1315+ assert (limitArrIndex != nullptr );
1316+ if (maxLimitBase64 >= CORINFO_Array_MaxLength)
1317+ {
1318+ JITDUMP (" Stride>1 overflow guard trivially holds for arr.Length (offset %d)\n " , offset);
1319+ }
1320+ else if (maxLimitBase64 < 0 )
1321+ {
1322+ JITDUMP (" > Stride %d, offset %d: arr.Length overflow guard unsatisfiable\n " , stride, offset);
1323+ return false ;
1324+ }
1325+ else
1326+ {
1327+ const int maxLimit = (int )maxLimitBase64;
1328+ LC_Ident arrLenIdent =
1329+ LC_Ident::CreateArrAccess (LC_Array (LC_Array::Jagged, limitArrIndex, LC_Array::ArrLen));
1330+ LC_Ident maxConstIdent = LC_Ident::CreateConst (static_cast <unsigned >(maxLimit));
1331+ LC_Condition overflowGuard (GT_LE , LC_Expr (arrLenIdent), LC_Expr (maxConstIdent));
1332+ context->EnsureConditions (loop->GetIndex ())->Push (overflowGuard);
1333+ JITDUMP (" Added stride>1 arr.Length overflow guard: <= %d\n " , maxLimit);
1334+ }
13101335 }
1311-
1312- LC_Array array (LC_Array::Jagged, limitArrIndex, LC_Array::None);
1313- context->EnsureArrayDerefs (loop->GetIndex ())->Push (array);
13141336 }
13151337
13161338 // If AnalyzeIteration could not prove the loop condition holds on entry,
0 commit comments