From 492d8acd09224cafded1c403566288ca630bac3a Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Fri, 10 Apr 2026 14:29:35 -0700 Subject: [PATCH] JIT: fix bug in loop cloning with down-counting loops We were not creating proper cloning conditions, so the fast path might execute in cases where it shouldn't. We need to always verify for down counting that the initial value is strictly less than the array length(s). --- src/coreclr/jit/loopcloning.cpp | 6 +- src/tests/JIT/opt/Cloning/DownCounted.cs | 72 ++++++++++++++++++++ src/tests/JIT/opt/Cloning/DownCounted.csproj | 8 +++ 3 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 src/tests/JIT/opt/Cloning/DownCounted.cs create mode 100644 src/tests/JIT/opt/Cloning/DownCounted.csproj diff --git a/src/coreclr/jit/loopcloning.cpp b/src/coreclr/jit/loopcloning.cpp index b77fdf7f99d8c4..f167f5a885a3ff 100644 --- a/src/coreclr/jit/loopcloning.cpp +++ b/src/coreclr/jit/loopcloning.cpp @@ -1334,17 +1334,17 @@ bool Compiler::optDeriveLoopCloningConditions(FlowGraphNaturalLoop* loop, LoopCl // GT_LE loop test: (start <= end) ==> (end < arrLen) // // Decreasing loops - // GT_GT loop test: (end > start) ==> (end <= arrLen) - // GT_GE loop test: (end >= start) ==> (end < arrLen) + // Always check if iter var is less than array length. genTreeOps opLimitCondition; switch (iterInfo->TestOper()) { case GT_LT: - case GT_GT: + opLimitCondition = GT_LE; break; case GT_LE: case GT_GE: + case GT_GT: opLimitCondition = GT_LT; break; default: diff --git a/src/tests/JIT/opt/Cloning/DownCounted.cs b/src/tests/JIT/opt/Cloning/DownCounted.cs new file mode 100644 index 00000000000000..50d14e99d39b9f --- /dev/null +++ b/src/tests/JIT/opt/Cloning/DownCounted.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using Xunit; + +public class DownCounted +{ + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + static bool ArrayProblem(int[] a, int n) + { + bool result = false; + for (int i = n; i > 0; i--) + { + a[i] = 44; + result |= (i == n); + } + return result; + } + + [Fact] + public static int ArrayTest() + { + int[] a = new int[100]; + int result = -1; + try + { + bool hasProblem = ArrayProblem(a, 100); + Console.WriteLine($"failed, has problem={hasProblem}"); + } + catch (IndexOutOfRangeException e) + { + Console.WriteLine("passed"); + result = 100; + } + return result; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + static bool SpanProblem(Span a, int n) + { + bool result = false; + for (int i = n; i > 0; i--) + { + a[i] = 44; + result |= (i == n); + } + return result; + } + + [Fact] + public static int SpanTest() + { + int[] a = new int[100]; + int result = -1; + try + { + bool hasProblem = SpanProblem(a, 100); + Console.WriteLine($"failed, has problem={hasProblem}"); + } + catch (IndexOutOfRangeException e) + { + Console.WriteLine("passed"); + result = 100; + } + return result; + } + + +} + diff --git a/src/tests/JIT/opt/Cloning/DownCounted.csproj b/src/tests/JIT/opt/Cloning/DownCounted.csproj new file mode 100644 index 00000000000000..de6d5e08882e86 --- /dev/null +++ b/src/tests/JIT/opt/Cloning/DownCounted.csproj @@ -0,0 +1,8 @@ + + + True + + + + +