From dc4e0bbe0cc8376970711748ec4937f0ca449223 Mon Sep 17 00:00:00 2001 From: Jonathan Davies Date: Mon, 13 Oct 2025 13:47:00 +0000 Subject: [PATCH 1/4] arm64: Optimise x < 0 and x >= 0 - Fixes #83562 - Add tests for previous work - Add tbz/tbnz support for short and sbyte --- src/coreclr/jit/lower.cpp | 20 +- .../opt/InstructionCombining/CompareZero.cs | 389 ++++++++++++++++++ .../InstructionCombining/CompareZero.csproj | 17 + 3 files changed, 424 insertions(+), 2 deletions(-) create mode 100644 src/tests/JIT/opt/InstructionCombining/CompareZero.cs create mode 100644 src/tests/JIT/opt/InstructionCombining/CompareZero.csproj diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 73597ea88edffa..19a213c703ff98 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -4555,11 +4555,27 @@ GenTree* Lowering::LowerJTrue(GenTreeOp* jtrue) else if (cond->OperIs(GT_LT, GT_GE) && !cond->IsUnsigned() && relopOp2->IsIntegralConst(0)) { // Codegen will use tbnz or tbz in codegen which do not affect the flag register + var_types op1Type = genActualType(relopOp1); + + // Remove cast to sbyte or short and instead check negative bit for those types. + if (relopOp1->OperIs(GT_CAST)) + { + GenTreeCast* cast = relopOp1->AsCast(); + if ((cast->gtCastType == TYP_BYTE || cast->gtCastType == TYP_SHORT) && !cast->gtOverflow()) + { + LIR::Use use; + op1Type = cast->gtCastType; + GenTree* castOp = cast->CastOp(); + BlockRange().TryGetUse(cast, &use); + use.ReplaceWith(castOp); + BlockRange().Remove(cast); + relopOp1 = castOp; + } + } newOper = GT_JTEST; cc = cond->OperIs(GT_LT) ? GenCondition(GenCondition::NE) : GenCondition(GenCondition::EQ); // x < 0 => (x & signBit) != 0. Update the constant to be the sign bit. - relopOp2->AsIntConCommon()->SetIntegralValue( - (static_cast(1) << (8 * genTypeSize(genActualType(relopOp1)) - 1))); + relopOp2->AsIntConCommon()->SetIntegralValue((static_cast(1) << (8 * genTypeSize(op1Type) - 1))); } else if (cond->OperIs(GT_TEST_EQ, GT_TEST_NE) && isPow2(relopOp2->AsIntCon()->IconValue())) { diff --git a/src/tests/JIT/opt/InstructionCombining/CompareZero.cs b/src/tests/JIT/opt/InstructionCombining/CompareZero.cs new file mode 100644 index 00000000000000..4b32f3a882bf01 --- /dev/null +++ b/src/tests/JIT/opt/InstructionCombining/CompareZero.cs @@ -0,0 +1,389 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using Xunit; + +namespace TestCompareZero +{ + public class Program + { + [MethodImpl(MethodImplOptions.NoInlining)] + [Fact] + public static int CheckCompareZero() + { + bool fail = false; + + if (!CompareLtZeroLong(-4)) + { + fail = true; + } + + if (!CompareLeZeroLong(0)) + { + fail = true; + } + + if (!CompareGtZeroLong(2)) + { + fail = true; + } + + if (!CompareGeZeroLong(0)) + { + fail = true; + } + + if (!CompareLtZeroInt(-4)) + { + fail = true; + } + + if (!CompareLeZeroInt(0)) + { + fail = true; + } + + if (!CompareGtZeroInt(2)) + { + fail = true; + } + + if (!CompareGeZeroInt(0)) + { + fail = true; + } + + if (!CompareLtZeroShort(-4)) + { + fail = true; + } + + if (!CompareLeZeroShort(-1)) + { + fail = true; + } + + if (!CompareGtZeroShort(2)) + { + fail = true; + } + + if (!CompareGeZeroShort(10)) + { + fail = true; + } + + if (!CompareLtZeroSByte(-4)) + { + fail = true; + } + + if (!CompareLeZeroSByte(-1)) + { + fail = true; + } + + if (!CompareGtZeroSByte(2)) + { + fail = true; + } + + if (!CompareGeZeroSByte(5)) + { + fail = true; + } + + CompareLtZeroJumpLong(-4); + CompareLeZeroJumpLong(0); + CompareGtZeroJumpLong(3); + CompareGeZeroJumpLong(10); + + CompareLtZeroJumpInt(-4); + CompareLeZeroJumpInt(-4); + CompareGtZeroJumpInt(1); + CompareGeZeroJumpInt(1); + + CompareLtZeroJumpShort(-1); + CompareLeZeroJumpShort(-2); + CompareGtZeroJumpShort(2); + CompareGeZeroJumpShort(12); + + CompareLtZeroJumpSByte(-1); + CompareLeZeroJumpSByte(-1); + CompareGtZeroJumpSByte(3); + CompareGeZeroJumpSByte(0); + + if (fail) + { + return 101; + } + return 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CompareLtZeroLong(long a) + { + //ARM64-FULL-LINE: lsr {{x[0-9]+}}, {{x[0-9]+}}, #63 + return a < 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CompareLeZeroLong(long a) + { + //ARM64-FULL-LINE: cmp {{x[0-9]+}}, #0 + //ARM64-FULL-LINE: cset {{x[0-9]+}}, le + return a <= 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CompareGtZeroLong(long a) + { + //ARM64-FULL-LINE: cmp {{x[0-9]+}}, #0 + //ARM64-FULL-LINE: cset {{x[0-9]+}}, gt + return a > 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CompareGeZeroLong(long a) + { + //ARM64-FULL-LINE: cmp {{x[0-9]+}}, #0 + //ARM64-FULL-LINE: cset {{x[0-9]+}}, ge + return a >= 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CompareLtZeroInt(int a) + { + //ARM64-FULL-LINE: lsr {{w[0-9]+}}, {{w[0-9]+}}, #31 + return a < 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CompareLeZeroInt(int a) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #0 + //ARM64-FULL-LINE: cset {{x[0-9]+}}, le + return a <= 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CompareGtZeroInt(int a) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #0 + //ARM64-FULL-LINE: cset {{x[0-9]+}}, gt + return a > 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CompareGeZeroInt(int a) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #0 + //ARM64-FULL-LINE: cset {{x[0-9]+}}, ge + return a >= 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CompareLtZeroShort(short a) + { + //ARM64-FULL-LINE: sxth {{w[0-9]+}}, {{w[0-9]+}} + //ARM64-FULL-LINE: lsr {{w[0-9]+}}, {{w[0-9]+}}, #31 + return a < 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CompareLeZeroShort(short a) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #0 + //ARM64-FULL-LINE: cset {{x[0-9]+}}, le + return a <= 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CompareGtZeroShort(short a) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #0 + //ARM64-FULL-LINE: cset {{x[0-9]+}}, gt + return a > 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CompareGeZeroShort(short a) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #0 + //ARM64-FULL-LINE: cset {{x[0-9]+}}, ge + return a >= 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CompareLtZeroSByte(sbyte a) + { + //ARM64-FULL-LINE: sxtb {{w[0-9]+}}, {{w[0-9]+}} + //ARM64-FULL-LINE: lsr {{w[0-9]+}}, {{w[0-9]+}}, #31 + return a < 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CompareLeZeroSByte(sbyte a) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #0 + //ARM64-FULL-LINE: cset {{x[0-9]+}}, le + return a <= 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CompareGtZeroSByte(sbyte a) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #0 + //ARM64-FULL-LINE: cset {{x[0-9]+}}, gt + return a > 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CompareGeZeroSByte(sbyte a) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #0 + //ARM64-FULL-LINE: cset {{x[0-9]+}}, ge + return a >= 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CompareLtZeroJumpLong(long a) + { + //ARM64-FULL-LINE: tbz {{x[0-9]+}}, #63, G_M{{[0-9]+}}_IG{{[0-9]+}} + if (a < 0) + foo(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CompareLeZeroJumpLong(long a) + { + //ARM64-FULL-LINE: cmp {{x[0-9]+}}, #0 + //ARM64-FULL-LINE: bgt G_M{{[0-9]+}}_IG{{[0-9]+}} + if (a <= 0) + foo(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CompareGtZeroJumpLong(long a) + { + //ARM64-FULL-LINE: cmp {{x[0-9]+}}, #0 + //ARM64-FULL-LINE: ble G_M{{[0-9]+}}_IG{{[0-9]+}} + if (a > 0) + foo(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CompareGeZeroJumpLong(long a) + { + //ARM64-FULL-LINE: tbnz {{x[0-9]+}}, #63, G_M{{[0-9]+}}_IG{{[0-9]+}} + if (a >= 0) + foo(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CompareLtZeroJumpInt(int a) + { + //ARM64-FULL-LINE: tbz {{w[0-9]+}}, #31, G_M{{[0-9]+}}_IG{{[0-9]+}} + if (a < 0) + foo(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CompareLeZeroJumpInt(int a) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #0 + //ARM64-FULL-LINE: bgt G_M{{[0-9]+}}_IG{{[0-9]+}} + if (a <= 0) + foo(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CompareGtZeroJumpInt(int a) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #0 + //ARM64-FULL-LINE: ble G_M{{[0-9]+}}_IG{{[0-9]+}} + if (a > 0) + foo(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CompareGeZeroJumpInt(int a) + { + //ARM64-FULL-LINE: tbnz {{w[0-9]+}}, #31, G_M{{[0-9]+}}_IG{{[0-9]+}} + if (a >= 0) + foo(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CompareLtZeroJumpShort(short a) + { + //ARM64-FULL-LINE: tbz {{w[0-9]+}}, #15, G_M{{[0-9]+}}_IG{{[0-9]+}} + if (a < 0) + foo(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CompareLeZeroJumpShort(short a) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #0 + //ARM64-FULL-LINE: bgt G_M{{[0-9]+}}_IG{{[0-9]+}} + if (a <= 0) + foo(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CompareGtZeroJumpShort(short a) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #0 + //ARM64-FULL-LINE: ble G_M{{[0-9]+}}_IG{{[0-9]+}} + if (a > 0) + foo(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CompareGeZeroJumpShort(short a) + { + //ARM64-FULL-LINE: tbnz {{w[0-9]+}}, #15, G_M{{[0-9]+}}_IG{{[0-9]+}} + if (a >= 0) + foo(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CompareLtZeroJumpSByte(sbyte a) + { + //ARM64-FULL-LINE: tbz {{w[0-9]+}}, #7, G_M{{[0-9]+}}_IG{{[0-9]+}} + if (a < 0) + foo(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CompareLeZeroJumpSByte(sbyte a) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #0 + //ARM64-FULL-LINE: bgt G_M{{[0-9]+}}_IG{{[0-9]+}} + if (a <= 0) + foo(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CompareGtZeroJumpSByte(sbyte a) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #0 + //ARM64-FULL-LINE: ble G_M{{[0-9]+}}_IG{{[0-9]+}} + if (a > 0) + foo(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CompareGeZeroJumpSByte(sbyte a) + { + //ARM64-FULL-LINE: tbnz {{w[0-9]+}}, #7, G_M{{[0-9]+}}_IG{{[0-9]+}} + if (a >= 0) + foo(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void foo() {} + } +} diff --git a/src/tests/JIT/opt/InstructionCombining/CompareZero.csproj b/src/tests/JIT/opt/InstructionCombining/CompareZero.csproj new file mode 100644 index 00000000000000..0096cd445113d7 --- /dev/null +++ b/src/tests/JIT/opt/InstructionCombining/CompareZero.csproj @@ -0,0 +1,17 @@ + + + + true + + + None + True + + + + true + + + + + From c5871760ecff89d5d324dafc4f94d2cdf8401ffc Mon Sep 17 00:00:00 2001 From: Jonathan Davies Date: Tue, 14 Oct 2025 14:07:57 +0000 Subject: [PATCH 2/4] Set gtOp1 instead of using ReplaceWith --- src/coreclr/jit/lower.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 19a213c703ff98..6146eb1b2add0d 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -4563,11 +4563,9 @@ GenTree* Lowering::LowerJTrue(GenTreeOp* jtrue) GenTreeCast* cast = relopOp1->AsCast(); if ((cast->gtCastType == TYP_BYTE || cast->gtCastType == TYP_SHORT) && !cast->gtOverflow()) { - LIR::Use use; op1Type = cast->gtCastType; GenTree* castOp = cast->CastOp(); - BlockRange().TryGetUse(cast, &use); - use.ReplaceWith(castOp); + cond->AsOp()->gtOp1 = castOp; BlockRange().Remove(cast); relopOp1 = castOp; } From 1b307f6d183f9b98a71adadb5e24ed47da3b6e04 Mon Sep 17 00:00:00 2001 From: Jonathan Davies Date: Tue, 14 Oct 2025 14:25:38 +0000 Subject: [PATCH 3/4] Fix formatting --- src/coreclr/jit/lower.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 6146eb1b2add0d..f057a043b3ea6d 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -4563,8 +4563,8 @@ GenTree* Lowering::LowerJTrue(GenTreeOp* jtrue) GenTreeCast* cast = relopOp1->AsCast(); if ((cast->gtCastType == TYP_BYTE || cast->gtCastType == TYP_SHORT) && !cast->gtOverflow()) { - op1Type = cast->gtCastType; - GenTree* castOp = cast->CastOp(); + op1Type = cast->gtCastType; + GenTree* castOp = cast->CastOp(); cond->AsOp()->gtOp1 = castOp; BlockRange().Remove(cast); relopOp1 = castOp; From 041aeac34362771ff1171a5857b08b842070351d Mon Sep 17 00:00:00 2001 From: Jonathan Davies Date: Tue, 21 Oct 2025 08:55:02 +0000 Subject: [PATCH 4/4] Fix indent --- src/tests/JIT/opt/InstructionCombining/CompareZero.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/JIT/opt/InstructionCombining/CompareZero.cs b/src/tests/JIT/opt/InstructionCombining/CompareZero.cs index 4b32f3a882bf01..ccd3488b0ebfcf 100644 --- a/src/tests/JIT/opt/InstructionCombining/CompareZero.cs +++ b/src/tests/JIT/opt/InstructionCombining/CompareZero.cs @@ -357,7 +357,7 @@ static void CompareLtZeroJumpSByte(sbyte a) foo(); } - [MethodImpl(MethodImplOptions.NoInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] static void CompareLeZeroJumpSByte(sbyte a) { //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #0