If a counted loop's exit test is stored into a local and the loop then branches on that local, loop unrolling hits a noway_assert. Checked builds assert. Release builds silently fall back to MinOpts for the whole method, including Tier1 rejits.
Minimal Repro
using System;
using System.Runtime.CompilerServices;
public class Program
{
public static void Main()
{
Console.WriteLine(Test(new int[] { 1, 2, 3, 4 }));
}
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static int Test(int[] a)
{
int sum = 0;
int i = 0;
bool b;
do
{
sum += a[i];
i++;
b = i < 4;
} while (b);
return b ? -1 : sum;
}
}
Expected
Prints 10, and Test is compiled with full optimizations.
Actual
Checked:
Assertion failed '!"Bad precondition in Compiler::optUnrollLoops()"' in 'Program:Test(int[]):int' during 'Unroll loops' (IL size 29; hash 0xf8b2f420; FullOpts)
File: src\coreclr\jit\optimizer.cpp:1450
Release prints 10, but DOTNET_JitDisasm=Test shows ; MinOpts code (or ; compiling with minopt for Tier1).
Regression?
Yes, it regressed in .NET 11. .NET 8, 9, and 10 compile Test with full optimizations. .NET 11 RC2 falls back to MinOpts, both for FullOpts and for Tier1.
Notes
optExtractTestIncr accepts the "test evaluated into temp" shape (STORE_LCL_VAR V03 (LT V02, 4) followed by JTRUE (NE V03, 0)), so iterInfo.TestTree points to the LT node. optTryUnrollLoop still requires TestBlock->lastStmt()->GetRootNode()->gtGetOp1() == TestTree, and that check fails because the operand is the NE node. The code should return false here instead of hitting the noway_assert, or it should handle this shape.
If a counted loop's exit test is stored into a local and the loop then branches on that local, loop unrolling hits a
noway_assert. Checked builds assert. Release builds silently fall back to MinOpts for the whole method, including Tier1 rejits.Minimal Repro
Expected
Prints
10, andTestis compiled with full optimizations.Actual
Checked:
Release prints
10, butDOTNET_JitDisasm=Testshows; MinOpts code(or; compiling with minoptfor Tier1).Regression?
Yes, it regressed in .NET 11. .NET 8, 9, and 10 compile
Testwith full optimizations. .NET 11 RC2 falls back to MinOpts, both for FullOpts and for Tier1.Notes
optExtractTestIncraccepts the "test evaluated into temp" shape (STORE_LCL_VAR V03 (LT V02, 4)followed byJTRUE (NE V03, 0)), soiterInfo.TestTreepoints to theLTnode.optTryUnrollLoopstill requiresTestBlock->lastStmt()->GetRootNode()->gtGetOp1() == TestTree, and that check fails because the operand is theNEnode. The code should returnfalsehere instead of hitting thenoway_assert, or it should handle this shape.