Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/coreclr/jit/compiler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}
Comment thread
tannergooding marked this conversation as resolved.
Comment thread
tannergooding marked this conversation as resolved.

return (offset < UINT16_MAX) && (offset < lvaLclExactSize(lclNum));
}

Expand Down
28 changes: 16 additions & 12 deletions src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint16_t>(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<uint16_t>(consVal))
{
ClrSafeInt<uint16_t> newOffset =
ClrSafeInt<uint16_t>(lclAddrNode->GetLclOffs()) + ClrSafeInt<uint16_t>(consVal);
assert(!newOffset.IsOverflow());
unsigned newOffset = lclAddrNode->GetLclOffs() + static_cast<unsigned>(consVal);

lclAddrNode->SetOper(GT_LCL_ADDR);
lclAddrNode->AsLclFld()->SetLclOffs(newOffset.Value());
assert(lvaGetDesc(lclAddrNode)->lvDoNotEnregister);
if (FitsIn<uint16_t>(newOffset) && IsValidLclAddr(lclAddrNode->GetLclNum(), newOffset))
{
lclAddrNode->SetOper(GT_LCL_ADDR);
lclAddrNode->AsLclFld()->SetLclOffs(static_cast<uint16_t>(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;
}
}
}

Expand Down
53 changes: 53 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_126584/Runtime_126584.cs
Original file line number Diff line number Diff line change
@@ -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<bool, long>)method.CreateDelegate(typeof(Func<bool, long>));
Assert.Equal(0, func(true));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Needed for CLRTestEnvironmentVariable -->
<RequiresProcessIsolation>true</RequiresProcessIsolation>
<CLRTestPriority>1</CLRTestPriority>
Comment thread
tannergooding marked this conversation as resolved.
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />

<!-- Force the MinOpts path where the emitter encoding limit was hit. -->
<CLRTestEnvironmentVariable Include="DOTNET_JITMinOpts" Value="1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(TestLibraryProjectPath)" />
</ItemGroup>
</Project>
Loading