[JIT] X64/ARM64 - `CAST` removals for small types on `ADD`, `SUB`, `MUL`, `AND`,...
authorWill Smith <lol.tihan@gmail.com>
Tue, 31 Jan 2023 17:19:06 +0000 (09:19 -0800)
committerGitHub <noreply@github.com>
Tue, 31 Jan 2023 17:19:06 +0000 (09:19 -0800)
21 files changed:
src/coreclr/jit/compiler.h
src/coreclr/jit/flowgraph.cpp
src/tests/JIT/opt/Add/IntAdd.cs [new file with mode: 0644]
src/tests/JIT/opt/Add/IntAdd.csproj [new file with mode: 0644]
src/tests/JIT/opt/And/IntAnd.cs
src/tests/JIT/opt/Divide/Regressions/Regression1.cs [new file with mode: 0644]
src/tests/JIT/opt/Divide/Regressions/Regression1.csproj [new file with mode: 0644]
src/tests/JIT/opt/Or/IntOr.cs [new file with mode: 0644]
src/tests/JIT/opt/Or/IntOr.csproj [new file with mode: 0644]
src/tests/JIT/opt/Regressions/Regression1.cs [new file with mode: 0644]
src/tests/JIT/opt/Regressions/Regression1.csproj [new file with mode: 0644]
src/tests/JIT/opt/Regressions/Regression2.cs [new file with mode: 0644]
src/tests/JIT/opt/Regressions/Regression2.csproj [new file with mode: 0644]
src/tests/JIT/opt/Regressions/Regression3.cs [new file with mode: 0644]
src/tests/JIT/opt/Regressions/Regression3.csproj [new file with mode: 0644]
src/tests/JIT/opt/Regressions/Regression4.cs [new file with mode: 0644]
src/tests/JIT/opt/Regressions/Regression4.csproj [new file with mode: 0644]
src/tests/JIT/opt/Regressions/Regression5.cs [new file with mode: 0644]
src/tests/JIT/opt/Regressions/Regression5.csproj [new file with mode: 0644]
src/tests/JIT/opt/Subtract/IntSubtract.cs [new file with mode: 0644]
src/tests/JIT/opt/Subtract/IntSubtract.csproj [new file with mode: 0644]

index 220059c..e49916d 100644 (file)
@@ -4783,6 +4783,8 @@ public:
     // lowering that is distributed between fgMorph and the lowering phase of LSRA.
     PhaseStatus fgSimpleLowering();
 
+    bool fgSimpleLowerCastOfSmpOp(LIR::Range& range, GenTreeCast* cast);
+
 #if FEATURE_LOOP_ALIGN
     PhaseStatus placeLoopAlignInstructions();
 #endif
index 0bad2bc..40c72bf 100644 (file)
@@ -2989,6 +2989,15 @@ PhaseStatus Compiler::fgSimpleLowering()
                 }
 #endif // FEATURE_FIXED_OUT_ARGS
 
+                case GT_CAST:
+                {
+                    if (tree->AsCast()->CastOp()->OperIsSimple() && fgSimpleLowerCastOfSmpOp(range, tree->AsCast()))
+                    {
+                        madeChanges = true;
+                    }
+                    break;
+                }
+
                 default:
                 {
                     // No other operators need processing.
@@ -3047,6 +3056,91 @@ PhaseStatus Compiler::fgSimpleLowering()
     return madeChanges ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING;
 }
 
+//------------------------------------------------------------------------
+// fgSimpleLowerCastOfSmpOp: Optimization to remove CAST nodes from operands of some simple ops that are safe to do so
+// since the upper bits do not affect the lower bits, and result of the simple op is zero/sign-extended via a CAST.
+// Example:
+//      CAST(ADD(CAST(x), CAST(y))) transforms to CAST(ADD(x, y))
+//
+// Returns:
+//      True or false, representing changes were made.
+//
+// Notes:
+//      This optimization could be done in morph, but it cannot because there are correctness
+//      problems with NOLs (normalized-on-load locals) and how they are handled in VN.
+//      Simple put, you cannot remove a CAST from CAST(LCL_VAR{nol}) in HIR.
+//
+//      Because the optimization happens after rationalization, turning into LIR, it is safe to remove the CAST.
+//
+bool Compiler::fgSimpleLowerCastOfSmpOp(LIR::Range& range, GenTreeCast* cast)
+{
+    GenTree*  castOp     = cast->CastOp();
+    var_types castToType = cast->CastToType();
+    var_types srcType    = castOp->TypeGet();
+
+    assert(castOp->OperIsSimple());
+
+    if (opts.OptimizationDisabled())
+        return false;
+
+    if (cast->gtOverflow())
+        return false;
+
+    if (castOp->OperMayOverflow() && castOp->gtOverflow())
+        return false;
+
+    // Only optimize if the castToType is a small integer type.
+    // Only optimize if the srcType is an integer type.
+    if (!varTypeIsSmall(castToType) || !varTypeIsIntegral(srcType))
+        return false;
+
+    // These are the only safe ops where the CAST is not necessary for the inputs.
+    if (castOp->OperIs(GT_ADD, GT_SUB, GT_MUL, GT_AND, GT_XOR, GT_OR, GT_NOT, GT_NEG))
+    {
+        bool madeChanges = false;
+
+        if (castOp->gtGetOp1()->OperIs(GT_CAST))
+        {
+            GenTreeCast* op1 = castOp->gtGetOp1()->AsCast();
+
+            if (!op1->gtOverflow() && (genActualType(op1->CastOp()) == genActualType(srcType)) &&
+                (castToType == op1->CastToType()))
+            {
+                // Removes the cast.
+                castOp->AsOp()->gtOp1 = op1->CastOp();
+                range.Remove(op1);
+                madeChanges = true;
+            }
+        }
+
+        if (castOp->OperIsBinary() && castOp->gtGetOp2()->OperIs(GT_CAST))
+        {
+            GenTreeCast* op2 = castOp->gtGetOp2()->AsCast();
+
+            if (!op2->gtOverflow() && (genActualType(op2->CastOp()) == genActualType(srcType)) &&
+                (castToType == op2->CastToType()))
+            {
+                // Removes the cast.
+                castOp->AsOp()->gtOp2 = op2->CastOp();
+                range.Remove(op2);
+                madeChanges = true;
+            }
+        }
+
+#ifdef DEBUG
+        if (madeChanges)
+        {
+            JITDUMP("Lower - Cast of Simple Op %s:\n", GenTree::OpName(cast->OperGet()));
+            DISPTREE(cast);
+        }
+#endif // DEBUG
+
+        return madeChanges;
+    }
+
+    return false;
+}
+
 /*****************************************************************************************************
  *
  *  Function to return the last basic block in the main part of the function. With funclets, it is
diff --git a/src/tests/JIT/opt/Add/IntAdd.cs b/src/tests/JIT/opt/Add/IntAdd.cs
new file mode 100644 (file)
index 0000000..158e54a
--- /dev/null
@@ -0,0 +1,150 @@
+// 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.Runtime.CompilerServices;
+
+namespace CodeGenTests
+{
+    static class IntAdd
+    {
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static sbyte Int8_Add(sbyte x, sbyte y)
+        {
+            // X64-NOT: movsx
+
+            // X64:      add
+            // X64-NEXT: movsx
+
+            return (sbyte)(x + y);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static byte UInt8_Add(byte x, byte y)
+        {
+            // X64-NOT: movzx
+
+            // X64:      add
+            // X64-NEXT: movzx
+
+            return (byte)(x + y);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static short Int16_Add(short x, short y)
+        {
+            // X64-NOT: movsx
+
+            // X64:      add
+            // X64-NEXT: movsx
+
+            // X64-NOT: cwde
+
+            return (short)(x + y);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static ushort UInt16_Add(ushort x, ushort y)
+        {
+            // X64-NOT: movzx
+
+            // X64:      add
+            // X64-NEXT: movzx
+
+            return (ushort)(x + y);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int Int32_Add(int x, int y)
+        {
+            // X64: lea
+
+            return x + y;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static uint UInt32_Add(uint x, uint y)
+        {
+            // X64: lea
+
+            return x + y;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static long Int64_Add(long x, long y)
+        {
+            // X64: lea
+
+            return x + y;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static ulong UInt64_Add(ulong x, ulong y)
+        {
+            // X64: lea
+
+            return x + y;
+        }
+
+        static int Main()
+        {
+            // Int8
+            if (Int8_Add(SByte.MaxValue, 15) != -114)
+                return 0;
+
+            if (Int8_Add(15, SByte.MaxValue) != -114)
+                return 0;
+
+            // UInt8
+            if (UInt8_Add(Byte.MaxValue, 15) != 14)
+                return 0;
+
+            if (UInt8_Add(15, Byte.MaxValue) != 14)
+                return 0;
+
+            // Int16
+            if (Int16_Add(Int16.MaxValue, 15) != -32754)
+                return 0;
+
+            if (Int16_Add(15, Int16.MaxValue) != -32754)
+                return 0;
+
+            // UInt16
+            if (UInt16_Add(UInt16.MaxValue, 15) != 14)
+                return 0;
+
+            if (UInt16_Add(15, UInt16.MaxValue) != 14)
+                return 0;
+
+            // Int32
+            if (Int32_Add(Int32.MaxValue, 15) != -2147483634)
+                return 0;
+
+            if (Int32_Add(15, Int32.MaxValue) != -2147483634)
+                return 0;
+
+            // UInt32
+            if (UInt32_Add(UInt32.MaxValue, 15) != 14)
+                return 0;
+
+            if (UInt32_Add(15, UInt32.MaxValue) != 14)
+                return 0;
+
+            // Int64
+            if (Int64_Add(Int64.MaxValue, 15) != -9223372036854775794)
+                return 0;
+
+            if (Int64_Add(15, Int64.MaxValue) != -9223372036854775794)
+                return 0;
+
+            // UInt64
+            if (UInt64_Add(UInt64.MaxValue, 15) != 14)
+                return 0;
+
+            if (UInt64_Add(15, UInt64.MaxValue) != 14)
+                return 0;
+
+            return 100;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/tests/JIT/opt/Add/IntAdd.csproj b/src/tests/JIT/opt/Add/IntAdd.csproj
new file mode 100644 (file)
index 0000000..42a89c8
--- /dev/null
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+  </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>
index 80fb37b..1f2e227 100644 (file)
@@ -9,6 +9,59 @@ namespace CodeGenTests
     class IntAnd
     {
         [MethodImpl(MethodImplOptions.NoInlining)]
+        static void SideEffect()
+        {
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static bool Test_UInt32_UInt32_And(uint x, uint y)
+        {
+            // X64-NOT: movzx
+
+            // We expect 'and reg8, reg8'.
+            // X64: and {{[a-z]+[l|b]}}, {{[a-z]+[l|b]}}
+
+            if ((byte)(x & y) == 0)
+            {
+                SideEffect();
+                return true;
+            }
+            return false;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static bool Test_UInt32_UInt32_CastByte_And(uint x, uint y)
+        {
+            // X64-NOT: movzx
+
+            // We expect 'and reg8, reg8'.
+            // X64: and {{[a-z]+[l|b]}}, {{[a-z]+[l|b]}}
+
+            if ((byte)((byte)x & y) == 0)
+            {
+                SideEffect();
+                return true;
+            }
+            return false;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static bool Test_UInt32_UInt32_CastByte_CastByte_And(uint x, uint y)
+        {
+            // X64-NOT: movzx
+
+            // We expect 'and reg8, reg8'.
+            // X64: and {{[a-z]+[l|b]}}, {{[a-z]+[l|b]}}
+
+            if ((byte)((byte)x & (byte)y) == 0)
+            {
+                SideEffect();
+                return true;
+            }
+            return false;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
         static uint Test_And_UInt32_MaxValue(uint i)
         {
             // X64: mov
@@ -19,6 +72,108 @@ namespace CodeGenTests
 
         static int Main()
         {
+            // No CastByte
+            if (!Test_UInt32_UInt32_And(0b1000_0000_0000_0000_0000_0000_0000_0000, 0b0000_0000_0000_0000_0000_0000_0000_0001))
+                return 0;
+
+            if (!Test_UInt32_UInt32_And(0b0000_0000_0000_0000_0000_0000_0000_0001, 0b1000_0000_0000_0000_0000_0000_0000_0000))
+                return 0;
+
+            if (!Test_UInt32_UInt32_And(0b1000_0000_0000_0000_0000_0000_0000_0000, 0b1000_0000_0000_0000_0000_0000_0000_0000))
+                return 0;
+
+            if (!Test_UInt32_UInt32_And(0b1000_0000_0000_0000_0000_0000_0000_0000, 0b0000_0000_0000_0000_0000_0000_0000_0000))
+                return 0;
+
+            if (!Test_UInt32_UInt32_And(0b0000_0000_0000_0000_0000_0000_0000_0000, 0b1000_0000_0000_0000_0000_0000_0000_0000))
+                return 0;
+
+            if (Test_UInt32_UInt32_And(0b0000_0000_0000_0000_0000_0000_0000_0001, 0b0000_0000_0000_0000_0000_0000_0000_0001))
+                return 0;
+
+            if (!Test_UInt32_UInt32_And(0b0000_0000_0000_0000_0000_0000_0000_0010, 0b0000_0000_0000_0000_0000_0000_0000_0001))
+                return 0;
+
+            if (Test_UInt32_UInt32_And(0b0000_0000_0000_0000_0000_0000_0000_0010, 0b0000_0000_0000_0000_0000_0000_0000_0010))
+                return 0;
+
+            if (Test_UInt32_UInt32_And(0b1000_0000_0000_0000_0000_0000_0000_0010, 0b1000_0000_0000_0000_0000_0000_0000_0010))
+                return 0;
+
+            if (Test_UInt32_UInt32_And(0b0010_0000_0000_0000_0000_0000_0000_0010, 0b1000_0000_0000_0000_0000_0000_0000_0010))
+                return 0;
+
+            if (Test_UInt32_UInt32_And(0b1000_0000_0000_0000_0000_0000_0000_0010, 0b0010_0000_0000_0000_0000_0000_0000_0010))
+                return 0;
+
+            // CastByte
+            if (!Test_UInt32_UInt32_CastByte_And(0b1000_0000_0000_0000_0000_0000_0000_0000, 0b0000_0000_0000_0000_0000_0000_0000_0001))
+                return 0;
+
+            if (!Test_UInt32_UInt32_CastByte_And(0b0000_0000_0000_0000_0000_0000_0000_0001, 0b1000_0000_0000_0000_0000_0000_0000_0000))
+                return 0;
+
+            if (!Test_UInt32_UInt32_CastByte_And(0b1000_0000_0000_0000_0000_0000_0000_0000, 0b1000_0000_0000_0000_0000_0000_0000_0000))
+                return 0;
+
+            if (!Test_UInt32_UInt32_CastByte_And(0b1000_0000_0000_0000_0000_0000_0000_0000, 0b0000_0000_0000_0000_0000_0000_0000_0000))
+                return 0;
+
+            if (!Test_UInt32_UInt32_CastByte_And(0b0000_0000_0000_0000_0000_0000_0000_0000, 0b1000_0000_0000_0000_0000_0000_0000_0000))
+                return 0;
+
+            if (Test_UInt32_UInt32_CastByte_And(0b0000_0000_0000_0000_0000_0000_0000_0001, 0b0000_0000_0000_0000_0000_0000_0000_0001))
+                return 0;
+
+            if (!Test_UInt32_UInt32_CastByte_And(0b0000_0000_0000_0000_0000_0000_0000_0010, 0b0000_0000_0000_0000_0000_0000_0000_0001))
+                return 0;
+
+            if (Test_UInt32_UInt32_CastByte_And(0b0000_0000_0000_0000_0000_0000_0000_0010, 0b0000_0000_0000_0000_0000_0000_0000_0010))
+                return 0;
+
+            if (Test_UInt32_UInt32_CastByte_And(0b1000_0000_0000_0000_0000_0000_0000_0010, 0b1000_0000_0000_0000_0000_0000_0000_0010))
+                return 0;
+
+            if (Test_UInt32_UInt32_CastByte_And(0b0010_0000_0000_0000_0000_0000_0000_0010, 0b1000_0000_0000_0000_0000_0000_0000_0010))
+                return 0;
+
+            if (Test_UInt32_UInt32_CastByte_And(0b1000_0000_0000_0000_0000_0000_0000_0010, 0b0010_0000_0000_0000_0000_0000_0000_0010))
+                return 0;
+
+            // CastByte_CastByte
+            if (!Test_UInt32_UInt32_CastByte_CastByte_And(0b1000_0000_0000_0000_0000_0000_0000_0000, 0b0000_0000_0000_0000_0000_0000_0000_0001))
+                return 0;
+
+            if (!Test_UInt32_UInt32_CastByte_CastByte_And(0b0000_0000_0000_0000_0000_0000_0000_0001, 0b1000_0000_0000_0000_0000_0000_0000_0000))
+                return 0;
+
+            if (!Test_UInt32_UInt32_CastByte_CastByte_And(0b1000_0000_0000_0000_0000_0000_0000_0000, 0b1000_0000_0000_0000_0000_0000_0000_0000))
+                return 0;
+
+            if (!Test_UInt32_UInt32_CastByte_CastByte_And(0b1000_0000_0000_0000_0000_0000_0000_0000, 0b0000_0000_0000_0000_0000_0000_0000_0000))
+                return 0;
+
+            if (!Test_UInt32_UInt32_CastByte_CastByte_And(0b0000_0000_0000_0000_0000_0000_0000_0000, 0b1000_0000_0000_0000_0000_0000_0000_0000))
+                return 0;
+
+            if (Test_UInt32_UInt32_CastByte_CastByte_And(0b0000_0000_0000_0000_0000_0000_0000_0001, 0b0000_0000_0000_0000_0000_0000_0000_0001))
+                return 0;
+
+            if (!Test_UInt32_UInt32_CastByte_CastByte_And(0b0000_0000_0000_0000_0000_0000_0000_0010, 0b0000_0000_0000_0000_0000_0000_0000_0001))
+                return 0;
+
+            if (Test_UInt32_UInt32_CastByte_CastByte_And(0b0000_0000_0000_0000_0000_0000_0000_0010, 0b0000_0000_0000_0000_0000_0000_0000_0010))
+                return 0;
+
+            if (Test_UInt32_UInt32_CastByte_CastByte_And(0b1000_0000_0000_0000_0000_0000_0000_0010, 0b1000_0000_0000_0000_0000_0000_0000_0010))
+                return 0;
+
+            if (Test_UInt32_UInt32_CastByte_CastByte_And(0b0010_0000_0000_0000_0000_0000_0000_0010, 0b1000_0000_0000_0000_0000_0000_0000_0010))
+                return 0;
+
+            if (Test_UInt32_UInt32_CastByte_CastByte_And(0b1000_0000_0000_0000_0000_0000_0000_0010, 0b0010_0000_0000_0000_0000_0000_0000_0010))
+                return 0;
+                
             if (Test_And_UInt32_MaxValue(1234) != 1234)
                 return 0;
 
diff --git a/src/tests/JIT/opt/Divide/Regressions/Regression1.cs b/src/tests/JIT/opt/Divide/Regressions/Regression1.cs
new file mode 100644 (file)
index 0000000..0e6b0bf
--- /dev/null
@@ -0,0 +1,257 @@
+// 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.Runtime.CompilerServices;
+
+public class Program
+{
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static ushort GetUShortValue()
+    {
+        return 24648;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static int GetByteMaxValue()
+    {
+        return byte.MaxValue;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static int GetSByteMaxValue()
+    {
+        return sbyte.MaxValue;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static uint Test1(ushort vr6)
+    {
+        ushort vr3 = 1;
+        ushort vr4 = (ushort)~vr3;
+        return (byte)((byte)vr6 / (byte)((byte)vr4 | 1));
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static uint Test2()
+    {
+        ushort vr3 = 1;
+        ushort vr4 = (ushort)~vr3;
+        ushort vr6 = 24648;
+        return (byte)((byte)vr6 / (byte)((byte)vr4 | 1));
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static ushort Test3()
+    {
+        ushort vr3 = 1;
+        ushort vr4 = (ushort)~vr3;
+        return (byte)((byte)vr4 | 1);
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static uint Test4()
+    {
+        ushort vr1 = 24648;
+        return (byte)((byte)vr1 / GetByteMaxValue());
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static int Test5()
+    {
+        ushort vr3 = 1;
+        ushort vr4 = (ushort)~vr3;
+        ushort vr6 = 24648;
+        return (sbyte)((sbyte)vr6 / (sbyte)((sbyte)vr4 | 1));
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static uint Test6()
+    {
+        return (byte)((byte)GetUShortValue() / GetByteMaxValue());
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static uint Test7()
+    {
+        ushort vr1 = 24648;
+        return (byte)((byte)vr1 / GetByteMaxValue());
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static uint Test8()
+    {
+        ushort vr1 = GetUShortValue();
+        return (byte)((byte)vr1 / GetByteMaxValue());
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static int Test9()
+    {
+        ushort vr1 = 24648;
+        return (sbyte)((sbyte)vr1 / GetSByteMaxValue());
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static int Test10(ushort vr6)
+    {
+        ushort vr3 = 1;
+        ushort vr4 = (ushort)~vr3;
+        return (sbyte)((sbyte)vr6 / (sbyte)((sbyte)vr4 | 1));
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static ushort Test11(int v)
+    {
+        return (ushort)((ushort)1 / (ushort)v);
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static short Test12(int v)
+    {
+        return (short)((short)1 / (short)v);
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static ushort Test13(int v)
+    {
+        return (ushort)((ushort)v / 2);
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static ushort Test14(int v1, int v2)
+    {
+        return (ushort)((ushort)v1 / (ushort)v2);
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static short Test15()
+    {
+        short y = short.MinValue;
+        return unchecked((short)(y / -1));
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static short Test16(short y)
+    {
+        return unchecked((short)(y / -1));
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static short Test17()
+    {
+        try
+        {
+            short y = short.MinValue;
+            return checked((short)(y / -1));
+        }
+        catch (ArithmeticException)
+        {
+            return 456;
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static short Test18(int v)
+    {
+        return (short)((short)v / 2);
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static short Test19(int x, int y)
+    {
+        return (short)((short)x / (short)y);
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static short Test20(short x, short y)
+    {
+        return (short)(x / y);
+    }
+
+    public static int Main()
+    {
+        var result1 = Test1(24648);
+        var result2 = Test2();
+        var result3 = Test3();
+        var result4 = Test4();
+        var result5 = Test5();
+        var result6 = Test6();
+        var result7 = Test7();
+        var result8 = Test8();
+        var result9 = Test9();
+        var result10 = Test10(24648);
+        var result11 = Test11(0x10001);
+        var result12 = Test12(0x10001);
+        var result13 = Test13(0x10000);
+        var result14 = Test14(1, 0x10001);
+        var result15 = Test15();
+        var result16 = Test16(short.MinValue);
+        var result17 = Test17();
+        var result18 = Test18(0x10000);
+        var result19 = Test19(0x10000, 2);
+        var result20 = Test20(0, 2);
+
+        if (result1 != 0)
+            return 0;
+
+        if (result2 != 0)
+            return 0;
+
+        if (result3 != 255)
+            return 0;
+
+        if (result4 != 0)
+            return 0;
+
+        if (result5 != -72)
+            return 0;
+
+        if (result6 != 0)
+            return 0;
+
+        if (result7 != 0)
+            return 0;
+
+        if (result8 != 0)
+            return 0;
+
+        if (result9 != 0)
+            return 0;
+
+        if (result10 != -72)
+            return 0;
+
+        if (result11 != 1)
+            return 0;
+
+        if (result12 != 1)
+            return 0;
+
+        if (result13 != 0)
+            return 0;
+
+        if (result14 != 1)
+            return 0;
+
+        if (result15 != -32768)
+            return 0;
+
+        if (result16 != -32768)
+            return 0;
+
+        if (result17 != 456)
+            return 0;
+
+        if (result18 != 0)
+            return 0;
+
+        if (result19 != 0)
+            return 0;
+
+        if (result20 != 0)
+            return 0;
+
+        return 100;
+    }
+}
diff --git a/src/tests/JIT/opt/Divide/Regressions/Regression1.csproj b/src/tests/JIT/opt/Divide/Regressions/Regression1.csproj
new file mode 100644 (file)
index 0000000..f3e1cbd
--- /dev/null
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+  </PropertyGroup>
+  <PropertyGroup>
+    <DebugType>None</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildProjectName).cs" />
+  </ItemGroup>
+</Project>
diff --git a/src/tests/JIT/opt/Or/IntOr.cs b/src/tests/JIT/opt/Or/IntOr.cs
new file mode 100644 (file)
index 0000000..7ea7d89
--- /dev/null
@@ -0,0 +1,112 @@
+// 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.Runtime.CompilerServices;
+
+namespace CodeGenTests
+{
+    class IntOr
+    {
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static void SideEffect()
+        {
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static bool Test_UInt32_UInt32_CastByte_Or(uint x, uint y)
+        {
+            // X64-NOT: movzx
+
+            // We expect 'or reg8, reg8'.
+            // X64: or {{[a-z]+[l|b]}}, {{[a-z]+[l|b]}}
+
+            if ((byte)((byte)x | y) == 0)
+            {
+                SideEffect();
+                return true;
+            }
+            return false;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static bool Test_UInt32_ByRef_CastByte_CastByte_Or(uint x, ref uint y)
+        {
+            // X64-NOT: movzx
+
+            // We expect 'or reg8, mem8'.
+            // X64: or {{[a-z]+[l|b]}}, byte ptr
+
+            if ((byte)((byte)x | (byte)y) == 0)
+            {
+                SideEffect();
+                return true;
+            }
+            return false;
+        }
+
+        static int Main()
+        {
+            uint leftMostBit  = 0b10000000000000000000000000000000;
+            uint rightMostBit = 0b00000000000000000000000000000001;
+            uint noBits       = 0b00000000000000000000000000000000;
+
+            if (!Test_UInt32_UInt32_CastByte_Or(leftMostBit, leftMostBit))
+                return 0;
+
+            if (Test_UInt32_UInt32_CastByte_Or(leftMostBit, rightMostBit))
+                return 0;
+
+            if (!Test_UInt32_UInt32_CastByte_Or(leftMostBit, noBits))
+                return 0;
+
+            if (Test_UInt32_UInt32_CastByte_Or(rightMostBit, leftMostBit))
+                return 0;
+
+            if (Test_UInt32_UInt32_CastByte_Or(rightMostBit, rightMostBit))
+                return 0;
+
+            if (Test_UInt32_UInt32_CastByte_Or(rightMostBit, noBits))
+                return 0;
+
+            if (!Test_UInt32_UInt32_CastByte_Or(noBits, leftMostBit))
+                return 0;
+
+            if (Test_UInt32_UInt32_CastByte_Or(noBits, rightMostBit))
+                return 0;
+
+            if (!Test_UInt32_UInt32_CastByte_Or(noBits, noBits))
+                return 0;
+
+            // ByRef
+            if (!Test_UInt32_ByRef_CastByte_CastByte_Or(leftMostBit, ref leftMostBit))
+                return 0;
+
+            if (Test_UInt32_ByRef_CastByte_CastByte_Or(leftMostBit, ref rightMostBit))
+                return 0;
+
+            if (!Test_UInt32_ByRef_CastByte_CastByte_Or(leftMostBit, ref noBits))
+                return 0;
+
+            if (Test_UInt32_ByRef_CastByte_CastByte_Or(rightMostBit, ref leftMostBit))
+                return 0;
+
+            if (Test_UInt32_ByRef_CastByte_CastByte_Or(rightMostBit, ref rightMostBit))
+                return 0;
+
+            if (Test_UInt32_ByRef_CastByte_CastByte_Or(rightMostBit, ref noBits))
+                return 0;
+
+            if (!Test_UInt32_ByRef_CastByte_CastByte_Or(noBits, ref leftMostBit))
+                return 0;
+
+            if (Test_UInt32_ByRef_CastByte_CastByte_Or(noBits, ref rightMostBit))
+                return 0;
+
+            if (!Test_UInt32_ByRef_CastByte_CastByte_Or(noBits, ref noBits))
+                return 0;
+
+            return 100;
+        }
+    }
+}
diff --git a/src/tests/JIT/opt/Or/IntOr.csproj b/src/tests/JIT/opt/Or/IntOr.csproj
new file mode 100644 (file)
index 0000000..42a89c8
--- /dev/null
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+  </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>
diff --git a/src/tests/JIT/opt/Regressions/Regression1.cs b/src/tests/JIT/opt/Regressions/Regression1.cs
new file mode 100644 (file)
index 0000000..d6961c7
--- /dev/null
@@ -0,0 +1,72 @@
+// 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.Runtime.CompilerServices;
+
+public class C0
+{
+   public short F0;
+   public bool F1;
+   public sbyte F2;
+   public short F3;
+   public C0(sbyte f2, short f3)
+   {
+       F2 = f2;
+       F3 = f3;
+   }
+}
+
+public class Program
+{
+   public static IRuntime s_rt;
+   public static int[][] s_13 = new int[][] { new int[] { 0 } };
+
+   public static int Main()
+   {
+       s_rt = new Runtime();
+       var result = M74(0);
+
+       if (result != -1)
+           return 0;
+
+       return 100;
+   }
+
+   [MethodImpl(MethodImplOptions.NoInlining)]
+   public static short M74(short arg1)
+   {
+       try
+       {
+           M75();
+       }
+       finally
+       {
+           C0 var5 = new C0(0, 1);
+           int var6 = s_13[0][0];
+           arg1 = var5.F3;
+           s_rt.WriteLine(var5.F0);
+           s_rt.WriteLine(var5.F1);
+           s_rt.WriteLine(var5.F2);
+       }
+
+       arg1 = (short)~arg1;
+       arg1++;
+       return arg1;
+   }
+
+   public static sbyte[] M75()
+   {
+       return default(sbyte[]);
+   }
+}
+
+public interface IRuntime
+{
+   void WriteLine<T>(T value);
+}
+
+public class Runtime : IRuntime
+{
+   public void WriteLine<T>(T value) { }
+}
diff --git a/src/tests/JIT/opt/Regressions/Regression1.csproj b/src/tests/JIT/opt/Regressions/Regression1.csproj
new file mode 100644 (file)
index 0000000..c053e59
--- /dev/null
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+  </PropertyGroup>
+  <PropertyGroup>
+    <DebugType>None</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildProjectName).cs" />
+
+    <CLRTestEnvironmentVariable Include="DOTNET_TieredCompilation" Value="0" />
+    <CLRTestEnvironmentVariable Include="DOTNET_JITMinOpts" Value="0" />
+  </ItemGroup>
+</Project>
diff --git a/src/tests/JIT/opt/Regressions/Regression2.cs b/src/tests/JIT/opt/Regressions/Regression2.cs
new file mode 100644 (file)
index 0000000..5e8f36a
--- /dev/null
@@ -0,0 +1,77 @@
+// 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.Runtime.CompilerServices;
+
+public class Program
+{
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static void Add_SmallType_Correctness()
+    {
+        for (int i = 0; i < ushort.MaxValue + 1; i++)
+        {
+            for (int j = 0; j < ushort.MaxValue + 1; j++)
+            {
+                if ((byte)(i + j) != (byte)((byte)i + (byte)j))
+                {
+                    throw new Exception();
+                }
+            }
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static void Sub_SmallType_Correctness()
+    {
+        for (int i = 0; i < ushort.MaxValue + 1; i++)
+        {
+            for (int j = 0; j < ushort.MaxValue + 1; j++)
+            {
+                if ((byte)(i - j) != (byte)((byte)i - (byte)j))
+                {
+                    throw new Exception();
+                }
+            }
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static void Mul_SmallType_Correctness()
+    {
+        for (int i = 0; i < ushort.MaxValue + 1; i++)
+        {
+            for (int j = 0; j < ushort.MaxValue + 1; j++)
+            {
+                if ((byte)(i * j) != (byte)((byte)i * (byte)j))
+                {
+                    throw new Exception();
+                }
+            }
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static void Div_SmallType_Correctness(int i, int j)
+    {
+        if ((byte)(i / j) != (byte)((byte)i / (byte)j))
+        {
+            throw new Exception();
+        }
+    }
+
+    static int Main()
+    {
+        Add_SmallType_Correctness();
+        Sub_SmallType_Correctness();
+        Mul_SmallType_Correctness();
+
+        try
+        {
+            Div_SmallType_Correctness(2, 256);
+        }
+        catch(DivideByZeroException) {}
+
+        return 100;
+    }
+}
diff --git a/src/tests/JIT/opt/Regressions/Regression2.csproj b/src/tests/JIT/opt/Regressions/Regression2.csproj
new file mode 100644 (file)
index 0000000..675d36d
--- /dev/null
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <DisableProjectBuild Condition="'$(RuntimeFlavor)' == 'Mono'">true</DisableProjectBuild>
+  </PropertyGroup>
+  <PropertyGroup>
+    <DebugType>None</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildProjectName).cs" />
+  </ItemGroup>
+</Project>
diff --git a/src/tests/JIT/opt/Regressions/Regression3.cs b/src/tests/JIT/opt/Regressions/Regression3.cs
new file mode 100644 (file)
index 0000000..78b6910
--- /dev/null
@@ -0,0 +1,71 @@
+// 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.Runtime.CompilerServices;
+
+public class C1
+{
+    public sbyte F1;
+    public byte F2;
+}
+
+public struct S0
+{
+    public ulong F0;
+}
+
+public class Program
+{
+    public static IRuntime s_rt;
+    public static bool s_25;
+    public static short[] s_42;
+    public static S0[][] s_43 = new S0[][]{new S0[]{new S0()}};
+    public static int Main()
+    {
+        CollectibleALC alc = new CollectibleALC();
+        System.Reflection.Assembly asm = alc.LoadFromAssemblyPath(System.Reflection.Assembly.GetExecutingAssembly().Location);
+        System.Reflection.MethodInfo mi = asm.GetType(typeof(Program).FullName).GetMethod(nameof(MainInner));
+        System.Type runtimeTy = asm.GetType(typeof(Runtime).FullName);
+        mi.Invoke(null, new object[]{System.Activator.CreateInstance(runtimeTy)});
+        return 100;
+    }
+
+    public static void MainInner(IRuntime rt)
+    {
+        s_rt = rt;
+        var vr3 = new C1();
+        C1 vr8 = new C1();
+        bool vr10 = vr8.F2 == vr8.F2;
+        M6(vr3, vr10);
+    }
+
+    public static void M60(ref sbyte arg1, ref short[] arg2)
+    {
+    }
+
+    public static void M6(C1 argThis, bool arg0)
+    {
+        arg0 = s_25;
+        M60(ref argThis.F1, ref s_42);
+        if (arg0 && arg0)
+        {
+            throw new Exception();
+        }
+    }
+}
+
+public interface IRuntime
+{
+}
+
+public class Runtime : IRuntime
+{
+}
+
+public class CollectibleALC : System.Runtime.Loader.AssemblyLoadContext
+{
+    public CollectibleALC(): base(true)
+    {
+    }
+}
diff --git a/src/tests/JIT/opt/Regressions/Regression3.csproj b/src/tests/JIT/opt/Regressions/Regression3.csproj
new file mode 100644 (file)
index 0000000..675d36d
--- /dev/null
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <DisableProjectBuild Condition="'$(RuntimeFlavor)' == 'Mono'">true</DisableProjectBuild>
+  </PropertyGroup>
+  <PropertyGroup>
+    <DebugType>None</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildProjectName).cs" />
+  </ItemGroup>
+</Project>
diff --git a/src/tests/JIT/opt/Regressions/Regression4.cs b/src/tests/JIT/opt/Regressions/Regression4.cs
new file mode 100644 (file)
index 0000000..b39ff25
--- /dev/null
@@ -0,0 +1,71 @@
+// 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.Runtime.CompilerServices;
+
+public class Program
+{
+    public static IRuntime s_rt;
+    public static int s_58;
+    public static uint s_66;
+    public static byte[] s_126 = new byte[] { 0 };
+    public static int Main()
+    {
+        CollectibleALC alc = new CollectibleALC();
+        System.Reflection.Assembly asm = alc.LoadFromAssemblyPath(System.Reflection.Assembly.GetExecutingAssembly().Location);
+        System.Reflection.MethodInfo mi = asm.GetType(typeof(Program).FullName).GetMethod(nameof(MainInner));
+        System.Type runtimeTy = asm.GetType(typeof(Runtime).FullName);
+        int count = (int)mi.Invoke(null, new object[] { System.Activator.CreateInstance(runtimeTy) });
+
+        if (count != 2)
+            return 0;
+
+        return 100;
+    }
+
+    public static int MainInner(IRuntime rt)
+    {
+        s_rt = rt;
+        var vr3 = s_126[0];
+        M59(vr3, false, ref s_66);
+
+        return s_rt.Count;
+    }
+
+    public static void M59(byte arg2, bool arg3, ref uint arg4)
+    {
+        for (int var0 = 0; var0 < 2; var0++)
+        {
+            byte var3 = arg2;
+            arg3 = 1 <= s_58;
+            s_rt.WriteLine("c_480", var3);
+            if ((arg3 || arg3))
+            {
+                short vr12 = default(short);
+                arg4 = (uint)vr12;
+                int vr13 = default(int);
+                s_rt.WriteLine("c_439", vr13);
+            }
+        }
+    }
+}
+
+public interface IRuntime
+{
+    int Count { get; }
+    void WriteLine<T>(string site, T value);
+}
+
+public class Runtime : IRuntime
+{
+    public int Count { get; set; }
+    public void WriteLine<T>(string site, T value) => Count++;
+}
+
+public class CollectibleALC : System.Runtime.Loader.AssemblyLoadContext
+{
+    public CollectibleALC() : base(true)
+    {
+    }
+}
diff --git a/src/tests/JIT/opt/Regressions/Regression4.csproj b/src/tests/JIT/opt/Regressions/Regression4.csproj
new file mode 100644 (file)
index 0000000..675d36d
--- /dev/null
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <DisableProjectBuild Condition="'$(RuntimeFlavor)' == 'Mono'">true</DisableProjectBuild>
+  </PropertyGroup>
+  <PropertyGroup>
+    <DebugType>None</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildProjectName).cs" />
+  </ItemGroup>
+</Project>
diff --git a/src/tests/JIT/opt/Regressions/Regression5.cs b/src/tests/JIT/opt/Regressions/Regression5.cs
new file mode 100644 (file)
index 0000000..346b250
--- /dev/null
@@ -0,0 +1,57 @@
+// 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.Runtime.CompilerServices;
+
+public class Program
+{
+    public static IRuntime s_rt;
+    public static bool[,] s_32 = new bool[,] { { false } };
+    public static sbyte[] s_53 = new sbyte[] { 0 };
+    public static ushort s_62;
+    public static int Main()
+    {
+        s_rt = new Runtime();
+        var vr9 = s_32[0, 0];
+        var vr10 = M76(ref s_62, vr9);
+
+        if (s_53[0] != 0)
+            return 0;
+
+        return 100;
+    }
+
+    public static ushort M76(ref ushort arg0, bool arg1)
+    {
+        byte var17 = default(byte);
+        if (!arg1)
+        {
+            s_rt.WriteLine(0);
+            bool[] var14 = new bool[] { false };
+            arg1 = var14[0];
+            s_rt.WriteLine(var14[0]);
+            s_rt.WriteLine(var17);
+            if (arg1 && arg1)
+            {
+                s_53[0] += 1;
+            }
+
+            bool[][] var18 = new bool[][] { new bool[] { true }, new bool[] { true }, new bool[] { true } };
+        }
+
+        return arg0;
+    }
+}
+
+public interface IRuntime
+{
+    void WriteLine<T>(T value);
+}
+
+public class Runtime : IRuntime
+{
+    public void WriteLine<T>(T value)
+    {
+    }
+}
diff --git a/src/tests/JIT/opt/Regressions/Regression5.csproj b/src/tests/JIT/opt/Regressions/Regression5.csproj
new file mode 100644 (file)
index 0000000..f3e1cbd
--- /dev/null
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+  </PropertyGroup>
+  <PropertyGroup>
+    <DebugType>None</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildProjectName).cs" />
+  </ItemGroup>
+</Project>
diff --git a/src/tests/JIT/opt/Subtract/IntSubtract.cs b/src/tests/JIT/opt/Subtract/IntSubtract.cs
new file mode 100644 (file)
index 0000000..fd9e804
--- /dev/null
@@ -0,0 +1,166 @@
+// 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.Runtime.CompilerServices;
+
+namespace CodeGenTests
+{
+    static class IntSubtract
+    {
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static sbyte Int8_Subtract(sbyte x, sbyte y)
+        {
+            // X64-NOT: movsx
+
+            // X64:      sub
+            // X64-NEXT: movsx
+
+            return (sbyte)(x - y);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static byte UInt8_Subtract(byte x, byte y)
+        {
+            // X64-NOT: movzx
+
+            // X64:      sub
+            // X64-NEXT: movzx
+
+            return (byte)(x - y);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static short Int16_Subtract(short x, short y)
+        {
+            // X64-NOT: movsx
+
+            // X64:      sub
+            // X64-NEXT: movsx
+
+            // X64-NOT: cwde
+
+            return (short)(x - y);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static ushort UInt16_Subtract(ushort x, ushort y)
+        {
+            // X64-NOT: movzx
+
+            // X64:      sub
+            // X64-NEXT: movzx
+
+            return (ushort)(x - y);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static int Int32_Subtract(int x, int y)
+        {
+            // X64-NOT: movsx
+            
+            // X64: sub
+            
+            // X64-NOT: movsx
+
+            return x - y;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static uint UInt32_Subtract(uint x, uint y)
+        {
+            // X64-NOT: movzx
+            
+            // X64: sub
+            
+            // X64-NOT: movzx
+
+            return x - y;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static long Int64_Subtract(long x, long y)
+        {
+            // X64-NOT: movsx
+            
+            // X64: sub
+            
+            // X64-NOT: movsx
+
+            return x - y;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static ulong UInt64_Subtract(ulong x, ulong y)
+        {
+            // X64-NOT: movzx
+            
+            // X64: sub
+            
+            // X64-NOT: movzx
+
+            return x - y;
+        }
+
+        static int Main()
+        {
+            // Int8
+            if (Int8_Subtract(SByte.MinValue, 15) != 113)
+                return 0;
+
+            if (Int8_Subtract(15, SByte.MaxValue) != -112)
+                return 0;
+
+            // UInt8
+            if (UInt8_Subtract(Byte.MinValue, 15) != 241)
+                return 0;
+
+            if (UInt8_Subtract(15, Byte.MaxValue) != 16)
+                return 0;
+
+            // Int16
+            if (Int16_Subtract(Int16.MinValue, 15) != 32753)
+                return 0;
+
+            if (Int16_Subtract(15, Int16.MaxValue) != -32752)
+                return 0;
+
+            // UInt16
+            if (UInt16_Subtract(UInt16.MinValue, 15) != 65521)
+                return 0;
+
+            if (UInt16_Subtract(15, UInt16.MaxValue) != 16)
+                return 0;
+
+            // Int32
+            if (Int32_Subtract(Int32.MinValue, 15) != 2147483633)
+                return 0;
+
+            if (Int32_Subtract(15, Int32.MaxValue) != -2147483632)
+                return 0;
+
+            // UInt32
+            if (UInt32_Subtract(UInt32.MinValue, 15) != 4294967281)
+                return 0;
+
+            if (UInt32_Subtract(15, UInt32.MaxValue) != 16)
+                return 0;
+
+            // Int64
+            if (Int64_Subtract(Int64.MinValue, 15) != 9223372036854775793)
+                return 0;
+
+            if (Int64_Subtract(15, Int64.MaxValue) != -9223372036854775792)
+                return 0;
+
+            // UInt64
+            if (UInt64_Subtract(UInt64.MinValue, 15) != 18446744073709551601)
+                return 0;
+
+            if (UInt64_Subtract(15, UInt64.MaxValue) != 16)
+                return 0;
+
+            return 100;
+        }
+    }
+}
diff --git a/src/tests/JIT/opt/Subtract/IntSubtract.csproj b/src/tests/JIT/opt/Subtract/IntSubtract.csproj
new file mode 100644 (file)
index 0000000..42a89c8
--- /dev/null
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+  </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>