dotnet / dotnet/runtime

JIT: (bug) LEA address-mode folding silently deletes a nested `checked` multiply (missing `OverflowException`)

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

Description

`CodeGen::genCreateAddrMode` checks `gtOverflow()` on the outermost `GT_MUL` it folds, but the inner scale-folding loops use only `GenTree::GetScaledIndex()`, which ignores `GTF_OVERFLOW`. `Lowering::TryCreateAddrMode` therefore folds `b + checked(i * 2) * 4` into `LEA(b + i*8)` and deletes the `MUL_ovfl` node, so the mandated `OverflowException` is never thrown.

### Minimal Repro

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

class Program
{
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
static long Test(long b, long i) => b + checked(i * 2) * 4;

static void Main()
{
try { Console.WriteLine(Test(100, 0x4000000000000000)); }
catch (OverflowException) { Console.WriteLine("OverflowException"); }
}
}
```

No JIT env vars required.

### Expected

```
OverflowException
```

(`0x4000000000000000 * 2` overflows `Int64`, so the `mul.ovf` must throw.)

### Actual

```
100
```

```asm
; Program:Test(long,long):long (FullOpts)
lea rax, [rcx+8*rdx]
ret
; Total bytes of code 5
```

The `imul`/`jo`/`CORINFO_HELP_OVERFLOW` sequence is gone. `DOTNET_JitDump=Test` shows lowering deleting the checked multiply:

```
Removing unused node:
N004 ( 9, 8) [000004] ---X-+----- * MUL_ovfl long $141 <<<< checked multiply deleted
New addressing mode node:
N007 ( 13, 12) [000008] -----+----- * LEA(b+(i*8)+0) long
```

### Notes

- `src/coreclr/jit/codegencommon.cpp` ~L1596 (op1) and ~L1672 (op2): `while ((rv2->OperIs(GT_MUL) || rv2->OperIs(GT_LSH)) && (argScale = rv2->GetScaledIndex()) != 0)` swallows `rv2` even when `rv2->gtOverflow()`. A fix is `&& !rv2->gtOverflow()` in both loops, or making `GetScaledIndex()` return 0 for overflow-checking `GT_MUL`.
- Also repros with reversed operand order, with `int`/`ulong`, and for other scale pairs (e.g. `checked(i * 4) * 2`) — any nested pair whose product is a legal x64 LEA scale.
- The `int` variant repros even with `DOTNET_JITMinOpts=1`, since `Lowering::LowerAdd` calls `TryCreateAddrMode` unconditionally on xarch.
- `main` @ `b44cd904110a27d96ea83621e94332d55150d482`, windows-x64 Checked corerun; also repros on released .NET 10.0.12, so long-standing rather than a .NET 11 regression.

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.