diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 6b7b08ed31a4c2..8aad4186d05faf 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -3324,7 +3324,8 @@ inline bool Compiler::fgIsBigOffset(size_t offset) // IsValidLclAddr: Can the given local address be represented as "LCL_ADDR"? // // Local address nodes cannot point beyond the local and can only store -// 16 bits worth of offset. +// 16 bits worth of offset. Additionally, the emitter can only encode byte-sized +// offsets for locals numbered 32768 or greater. // // Arguments: // lclNum - The local's number @@ -3341,6 +3342,16 @@ inline bool Compiler::IsValidLclAddr(unsigned lclNum, unsigned offset) return (offset == 0); } #endif + + // The emitter only supports byte-sized offsets for locals numbered 32768 or greater + // (see emitLclVarAddr::initLclVarAddr). Reject larger offsets here so such accesses are + // kept as explicit address computations instead of being folded into a LCL_FLD or a + // contained LCL_ADDR, both of which the emitter would be unable to encode. + if ((lclNum >= 32768) && (offset >= 256)) + { + return false; + } + return (offset < UINT16_MAX) && (offset < lvaLclExactSize(lclNum)); } diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 57c678dcb2ef6a..8750dba2963c4d 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -10570,23 +10570,27 @@ GenTree* Compiler::fgOptimizeAddition(GenTreeOp* add) GenTreeIntCon* offsetNode = op2->AsIntCon(); ssize_t consVal = offsetNode->IconValue(); - // Note: the emitter does not expect out-of-bounds access for LCL_ADDR. - if (FitsIn(consVal) && IsValidLclAddr(lclAddrNode->GetLclNum(), (uint32_t)consVal)) + // Note: the emitter does not expect out-of-bounds access for LCL_ADDR. Validate the + // resulting offset rather than just the addend, so repeated folds cannot accumulate an + // offset the emitter is unable to encode. Both operands are bounded to [0, UINT16_MAX], + // so the addition below cannot overflow. + if (FitsIn(consVal)) { - ClrSafeInt newOffset = - ClrSafeInt(lclAddrNode->GetLclOffs()) + ClrSafeInt(consVal); - assert(!newOffset.IsOverflow()); + unsigned newOffset = lclAddrNode->GetLclOffs() + static_cast(consVal); - lclAddrNode->SetOper(GT_LCL_ADDR); - lclAddrNode->AsLclFld()->SetLclOffs(newOffset.Value()); - assert(lvaGetDesc(lclAddrNode)->lvDoNotEnregister); + if (FitsIn(newOffset) && IsValidLclAddr(lclAddrNode->GetLclNum(), newOffset)) + { + lclAddrNode->SetOper(GT_LCL_ADDR); + lclAddrNode->AsLclFld()->SetLclOffs(static_cast(newOffset)); + assert(lvaGetDesc(lclAddrNode)->lvDoNotEnregister); - lclAddrNode->SetVNsFromNode(add); + lclAddrNode->SetVNsFromNode(add); - DEBUG_DESTROY_NODE(offsetNode); - DEBUG_DESTROY_NODE(add); + DEBUG_DESTROY_NODE(offsetNode); + DEBUG_DESTROY_NODE(add); - return lclAddrNode; + return lclAddrNode; + } } } diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_126584/Runtime_126584.cs b/src/tests/JIT/Regression/JitBlue/Runtime_126584/Runtime_126584.cs new file mode 100644 index 00000000000000..82dfb57f4c64c2 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_126584/Runtime_126584.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Reflection.Emit; +using TestLibrary; +using Xunit; + +// A struct large enough that initializing it reaches offsets >= 256. +public struct Big_126584 +{ + public long A0, A1, A2, A3, A4, A5, A6, A7, A8, A9; + public long A10, A11, A12, A13, A14, A15, A16, A17, A18, A19; + public long A20, A21, A22, A23, A24, A25, A26, A27, A28, A29; + public long A30, A31, A32, A33, A34, A35, A36, A37, A38, A39; +} + +public class Runtime_126584 +{ + // The emitter can only encode byte-sized offsets for locals numbered >= 32768. A struct + // block-init of such a local reaching offset >= 256 must not be folded into the stack + // addressing form, which the emitter cannot encode. Reflection.Emit is used to build a + // method with enough locals to push the struct past that boundary without a huge source file. + [ConditionalFact(typeof(Utilities), nameof(Utilities.IsReflectionEmitSupported))] + public static void TestEntryPoint() + { + var method = new DynamicMethod("M0", typeof(long), new[] { typeof(bool) }, typeof(Runtime_126584).Module); + ILGenerator il = method.GetILGenerator(); + + const int DummyLocalCount = 33000; + for (int i = 0; i < DummyLocalCount; i++) + { + il.DeclareLocal(typeof(int)); + } + LocalBuilder big = il.DeclareLocal(typeof(Big_126584)); + + // Initialize the struct behind a branch so it isn't hoisted into the prolog. + Label skip = il.DefineLabel(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Brfalse, skip); + il.Emit(OpCodes.Ldloca, big); + il.Emit(OpCodes.Initobj, typeof(Big_126584)); + il.MarkLabel(skip); + + // Read a field past offset 255 so the access survives. + il.Emit(OpCodes.Ldloca, big); + il.Emit(OpCodes.Ldfld, typeof(Big_126584).GetField(nameof(Big_126584.A39))); + il.Emit(OpCodes.Ret); + + var func = (Func)method.CreateDelegate(typeof(Func)); + Assert.Equal(0, func(true)); + } +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_126584/Runtime_126584.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_126584/Runtime_126584.csproj new file mode 100644 index 00000000000000..1b5150958a87cf --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_126584/Runtime_126584.csproj @@ -0,0 +1,16 @@ + + + + true + 1 + + + + + + + + + + +