Lowering::ContainBlockStoreAddress accepts addrMode->Offset() == INT32_MAX - size (it rejects only >), while genCodeFor{Init,Cp}BlkUnroll assert the strict srcOffset/dstOffset < (INT32_MAX - size). A contained LEA(base + (INT32_MAX - size)) feeding an unrolled STORE_BLK therefore trips the assert in a Checked JIT.
Minimal Repro
using System;
using System.Runtime.CompilerServices;
public struct S3 { public byte A, B, C; }
public static class P
{
// element offset = 16 + 715827876*3 = 2147483644 == int.MaxValue - 3
const int Idx = 715827876;
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static void StoreCopy(S3[] arr, S3 v) => arr[Idx] = v;
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static void StoreInit(S3[] arr) => arr[Idx] = default;
public static void Main(string[] args)
{
var a = new S3[1];
if (args.Length == 0)
{
try { StoreInit(a); } catch (IndexOutOfRangeException) { Console.WriteLine("ok2"); }
}
else
{
try { StoreCopy(a, default); } catch (IndexOutOfRangeException) { Console.WriteLine("ok1"); }
}
}
}
set DOTNET_TieredCompilation=0
corerun out\blkdisp.dll :: STORE_BLK (init) -> genCodeForInitBlkUnroll
corerun out\blkdisp.dll x :: STORE_BLK (copy) -> genCodeForCpBlkUnroll
(The two methods are split by args because the first one jitted aborts the process.)
Expected
and
Actual
Assert failure(PID 30132, Thread: 33480): Assertion failed 'dstOffset < (INT32_MAX - static_cast<int>(size))'
in 'P:StoreInit(S3[])' during 'Generate code' (IL size 18; hash 0xe8a9310d; FullOpts)
File: C:\prj\runtime-main3\src\coreclr\jit\codegenxarch.cpp:3219
Assert failure(PID 43972, Thread: 31304): Assertion failed 'dstOffset < (INT32_MAX - static_cast<int>(size))'
in 'P:StoreCopy(S3[],S3)' during 'Generate code' (IL size 13; hash 0x2433ddfe; FullOpts)
File: C:\prj\runtime-main3\src\coreclr\jit\codegenxarch.cpp:3550
DOTNET_JitDump=StoreInit shows the contained boundary address mode right before the assert:
N017 ( 1, 5) [000014] -c---+----- t14 = * LEA(b+2147483644) byref REG NA
N021 ( 17, 25) [000004] nA-XG+----- * STORE_BLK struct<S3, 3> (init) (Unroll) REG NA $145
Notes
- Off-by-one between
lowerxarch.cpp:405 (>) and codegenxarch.cpp:3219/:3549-3550 (<); either lowering should reject >= or the asserts should use <=.
- Over-strict assert, not a codegen bug: the largest displacement actually emitted is
offset + size - 1 <= INT32_MAX - 1, so release codegen is correct (verified on .NET 10.0.12, which prints ok2/ok1).
- Checked JIT, optimized codegen, x64/x86 only; with
DOTNET_JITMinOpts=1 no containment happens and both methods run fine.
main @ b44cd904110a27d96ea83621e94332d55150d482; not a regression, the mismatch is present in the .NET 10 sources too.
Lowering::ContainBlockStoreAddressacceptsaddrMode->Offset() == INT32_MAX - size(it rejects only>), whilegenCodeFor{Init,Cp}BlkUnrollassert the strictsrcOffset/dstOffset < (INT32_MAX - size). A containedLEA(base + (INT32_MAX - size))feeding an unrolledSTORE_BLKtherefore trips the assert in a Checked JIT.Minimal Repro
(The two methods are split by
argsbecause the first one jitted aborts the process.)Expected
and
Actual
DOTNET_JitDump=StoreInitshows the contained boundary address mode right before the assert:Notes
lowerxarch.cpp:405(>) andcodegenxarch.cpp:3219/:3549-3550(<); either lowering should reject>=or the asserts should use<=.offset + size - 1 <= INT32_MAX - 1, so release codegen is correct (verified on .NET 10.0.12, which printsok2/ok1).DOTNET_JITMinOpts=1no containment happens and both methods run fine.main@b44cd904110a27d96ea83621e94332d55150d482; not a regression, the mismatch is present in the .NET 10 sources too.