dotnet / dotnet/runtime

JIT: (bug) Strength reduction zeroes an intermediate local that a preserved `checked` overflow check still reads, suppressing OverflowException

Open
#133,755 1 comment 0 reactions 0 assignees View on GitHub
area-CodeGen-coreclr
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Minimal repro

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

public class Program
{
public static int s_sink;

[MethodImpl(MethodImplOptions.NoInlining)]
public static void Consume(int v) { s_sink += v; }

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static void Test(int n, int seed)
{
int i = seed;
for (int c = 0; c < n; c++)
{
int t = i * 2; // 800000000
Consume(checked(t * 3)); // 2400000000 -> must throw
Consume(checked(t * 3));
i++;
}
}

public static int Main()
{
try
{
Test(4, 400000000);
Console.WriteLine("NO EXCEPTION");
}
catch (OverflowException)
{
Console.WriteLine("OverflowException");
}
return 100;
}
}
```

### Expected

`OverflowException` (printed with `DOTNET_JitEnableStrengthReduction=0`)

### Actual

`NO EXCEPTION` — the loop runs to completion and `Consume` is called with 0.

### Notes

`ExpandStoredCursors` retires the store `t = i * 2` and rewrites its data to zero (inductionvariableopts.cpp:2557), but the cursor replacement at inductionvariableopts.cpp:2529 deliberately keeps the throwing `MUL ovfl` via `gtExtractSideEffList`, and that retained node still reads `t`. The preserved overflow check therefore evaluates `0 * 3`. JitDump confirms: `[000010] was the data of store [000011]; expanded to 1 new cursors, and will replace with a store of 0` followed by `Replacing [000010] with a zero constant`.

Contributor guide

Open the contributing guide

Research direction

Start with the repro and JIT dump, comparing behavior with DOTNET_JitEnableStrengthReduction=0. Inspect ExpandStoredCursors in inductionvariableopts.cpp around lines 2529 and 2557, especially the retained checked MUL and the replacement of the intermediate store. Done means the repro consistently throws OverflowException and does not pass 0 to Consume when strength reduction is enabled.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.