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
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
(0x4000000000000000 * 2 overflows Int64, so the mul.ovf must throw.)
Actual
; 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.
CodeGen::genCreateAddrModechecksgtOverflow()on the outermostGT_MULit folds, but the inner scale-folding loops use onlyGenTree::GetScaledIndex(), which ignoresGTF_OVERFLOW.Lowering::TryCreateAddrModetherefore foldsb + checked(i * 2) * 4intoLEA(b + i*8)and deletes theMUL_ovflnode, so the mandatedOverflowExceptionis never thrown.Minimal Repro
No JIT env vars required.
Expected
(
0x4000000000000000 * 2overflowsInt64, so themul.ovfmust throw.)Actual
The
imul/jo/CORINFO_HELP_OVERFLOWsequence is gone.DOTNET_JitDump=Testshows lowering deleting the checked multiply:Notes
src/coreclr/jit/codegencommon.cpp~L1596 (op1) and ~L1672 (op2):while ((rv2->OperIs(GT_MUL) || rv2->OperIs(GT_LSH)) && (argScale = rv2->GetScaledIndex()) != 0)swallowsrv2even whenrv2->gtOverflow(). A fix is&& !rv2->gtOverflow()in both loops, or makingGetScaledIndex()return 0 for overflow-checkingGT_MUL.int/ulong, and for other scale pairs (e.g.checked(i * 4) * 2) — any nested pair whose product is a legal x64 LEA scale.intvariant repros even withDOTNET_JITMinOpts=1, sinceLowering::LowerAddcallsTryCreateAddrModeunconditionally 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.