dotnet / dotnet/runtime

JIT: (bug) constant-NaN Math.Min/Max folding drops the other argument's side effects

Open
#133,718 1 comment 0 reactions 1 assignee Claimed by @tannergooding View on GitHub
area-CodeGen-coreclr
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

```csharp
using System;
using System.Runtime.CompilerServices;

class P
{
static double[] a = new double[1];

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
static double Test(int i)
{
return Math.Min(a[i], double.NaN);
}

static void Main()
{
try
{
Console.WriteLine("NO EXCEPTION, result=" + Test(5));
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("IndexOutOfRangeException (correct)");
}
}
}
```

**Expected:** `IndexOutOfRangeException (correct)` — `a[5]` on a length-1 array must throw before `Math.Min` returns.
**Actual:** `NO EXCEPTION, result=NaN` — the array load, including its bounds check, is discarded.

Notes:
- `run.ps1 -Cs repro.cs` (csc `-optimize+`, Checked corerun x64). Compiling with `-optimize-` prints the correct `IndexOutOfRangeException`, so it is purely the optimized-JIT path.
- `src\coreclr\jit\gentree.cpp` in `gtNewSimdMinMaxNode`: the `isNaN` shortcut does `return cnsNode;` (non-Number) / `return otherNode;` (Number) with no `gtExtractSideEffList`/COMMA for the discarded operand.
- Only on the non-AVX10v2 xarch path (the AVX10v2 branch is taken earlier and is unaffected). Same shape works for `MathF.Min`, `Math.Max`, and the `MinNumber`/`MaxNumber` variants (drop the *other* operand there).

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.