diff --git a/src/coreclr/jit/lowerxarch.cpp b/src/coreclr/jit/lowerxarch.cpp index 40213512b03649..0efc1db90978fc 100644 --- a/src/coreclr/jit/lowerxarch.cpp +++ b/src/coreclr/jit/lowerxarch.cpp @@ -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]". + srcIsContainable = true; } if (srcIsContainable) diff --git a/src/tests/JIT/opt/Casts/SignExtLoad.cs b/src/tests/JIT/opt/Casts/SignExtLoad.cs new file mode 100644 index 00000000000000..45e0f00a949921 --- /dev/null +++ b/src/tests/JIT/opt/Casts/SignExtLoad.cs @@ -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 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; + } + } +} diff --git a/src/tests/JIT/opt/Casts/SignExtLoad.csproj b/src/tests/JIT/opt/Casts/SignExtLoad.csproj new file mode 100644 index 00000000000000..dbc3ab7f2f9596 --- /dev/null +++ b/src/tests/JIT/opt/Casts/SignExtLoad.csproj @@ -0,0 +1,18 @@ + + + + true + + + None + True + + + + true + + + + + +