Skip to content
Closed
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
10 changes: 5 additions & 5 deletions src/coreclr/jit/lowerxarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7771,11 +7771,11 @@ void Lowering::ContainCheckCast(GenTreeCast* node)
else if (m_compiler->opts.Tier0OptimizationEnabled() && varTypeIsIntegral(castOp) &&
varTypeIsIntegral(castToType))
{
// Most integral casts can be re-expressed as loads, except those that would be changing the sign.
if (!varTypeIsSmall(castOp) || (varTypeIsUnsigned(castOp) == node->IsZeroExtending()))
{
srcIsContainable = true;
}
// Small-typed casts can always be re-expressed as memory loads at the cast's own
// extension direction; the load has a single use (containment implies it), so
// the load's own extension mode is irrelevant. This folds patterns like
// "movzx r, byte ptr [mem]; movsx r, r8" into a single "movsx r, byte ptr [mem]".
Comment on lines +7774 to +7777
srcIsContainable = true;
}

if (srcIsContainable)
Expand Down
70 changes: 70 additions & 0 deletions src/tests/JIT/opt/Casts/SignExtLoad.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// Regression tests for https://github.com/dotnet/runtime/issues/69472:
// a small-typed load fed to a sign-extending cast should emit a single
// signed-load instruction, not a zero-extending load followed by a separate
// sign-extension. Contained-source lowering in ContainCheckCast used to
// refuse to fold when the load's small-type signedness didn't match the
// cast's extension direction.

using System;
using System.Runtime.CompilerServices;
using Xunit;

namespace CodeGenTests
{
public class SignExtLoad
{
[MethodImpl(MethodImplOptions.NoInlining)]
static int UByteSpanIndexed(ReadOnlySpan<byte> s, int i)
{
// X64: movsx
// X64-NOT: movzx
return (sbyte)s[i];
}

[MethodImpl(MethodImplOptions.NoInlining)]
static int CharArrayIndexed(char[] a, int i)
{
// X64: movsx
// X64-NOT: movzx
return (short)a[i];
}

[MethodImpl(MethodImplOptions.NoInlining)]
static int ByteArrayIndexed(byte[] a, int i)
{
// X64: movsx
// X64-NOT: movzx
return (sbyte)a[i];
}

[MethodImpl(MethodImplOptions.NoInlining)]
static int UShortArrayIndexed(ushort[] a, int i)
{
// X64: movsx
// X64-NOT: movzx
return (short)a[i];
}

[Fact]
public static int TestEntryPoint()
{
byte[] bytes = new byte[] { 1, 0xFF };
char[] chars = new char[] { 'A', '\uFFFF' };
ushort[] ushorts = new ushort[] { 1, 0xFFFF };

if (UByteSpanIndexed(bytes, 0) != 1) return 0;
if (UByteSpanIndexed(bytes, 1) != -1) return 0;
if (ByteArrayIndexed(bytes, 0) != 1) return 0;
if (ByteArrayIndexed(bytes, 1) != -1) return 0;
if (CharArrayIndexed(chars, 0) != 'A') return 0;
if (CharArrayIndexed(chars, 1) != -1) return 0;
if (UShortArrayIndexed(ushorts, 0) != 1) return 0;
if (UShortArrayIndexed(ushorts, 1) != -1) return 0;

return 100;
}
}
}
18 changes: 18 additions & 0 deletions src/tests/JIT/opt/Casts/SignExtLoad.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Needed for CLRTestEnvironmentVariable -->
<RequiresProcessIsolation>true</RequiresProcessIsolation>
</PropertyGroup>
<PropertyGroup>
<DebugType>None</DebugType>
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs">
<HasDisasmCheck>true</HasDisasmCheck>
</Compile>

<CLRTestEnvironmentVariable Include="DOTNET_TieredCompilation" Value="0" />
<CLRTestEnvironmentVariable Include="DOTNET_JITMinOpts" Value="0" />
</ItemGroup>
</Project>
Loading