A halfword test takes two instructions, "uxth rD, rS; cmp rD, #0": 0xffff is
not an Arm immediate, so TST cannot do it.  "lsls rD, rS, #16" sets Z from
bits 15:0 in one, at the cost of a scratch.  combine already narrows the test
to a comparison of (subreg:HI ...) against zero; it wanted only a mode and an
insn to match it.  Found in the halfword state tests of an emulator, next to
the byte tests of the patch below.

Only EQ and NE may use the result -- after the shift N is bit 15 of the
operand and C is bit 16 -- hence CC_Zmode, in which get_arm_condition_code
maps every other code to ARM_NV.  Arm state only: in Thumb-2 uxth and cmp are
two bytes each, so there is nothing to win, and with the pattern enabled there
the corpus below is byte-identical.

This completes the byte case in <[email protected]>,
whose COMPARE cost is what lets combine accept the narrowed form; that
patch's halfword negative moves to the new test.  Over gcc.target/arm and
gcc.c-torture/compile at -O2 -marm -march=armv7-a two objects shrink by 84
bytes and none grows; -mthumb -march=armv7-a is unchanged.

Regression tested on arm-none-eabi (arm.exp, tree-ssa.exp, dg.exp; default,
cortex-a15 and cortex-m0 boards).

Assisted-by: Claude Opus 5 (Anthropic)

gcc/ChangeLog:

        * config/arm/arm.cc (arm_select_cc_mode): Select CC_Zmode for an
        HImode equality comparison against zero, as for QImode.
        * config/arm/arm.md (*comparehi_eq0): New pattern.

gcc/testsuite/ChangeLog:

        * gcc.target/arm/tst-byte-eq0-2.c: Move the halfword case out.
        * gcc.target/arm/tst-half-eq0.c: New test.
        * gcc.target/arm/tst-half-eq0-2.c: New test.

Signed-off-by: Dominic P <[email protected]>
---
 gcc/config/arm/arm.cc                         |  3 +-
 gcc/config/arm/arm.md                         | 12 +++
 gcc/testsuite/gcc.target/arm/tst-byte-eq0-2.c |  6 +-
 gcc/testsuite/gcc.target/arm/tst-half-eq0-2.c | 16 ++++
 gcc/testsuite/gcc.target/arm/tst-half-eq0.c   | 85 +++++++++++++++++++
 5 files changed, 117 insertions(+), 5 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/arm/tst-half-eq0-2.c
 create mode 100644 gcc/testsuite/gcc.target/arm/tst-half-eq0.c

diff --git a/gcc/config/arm/arm.cc b/gcc/config/arm/arm.cc
index aa44374d58c..97e94f4dc01 100644
--- a/gcc/config/arm/arm.cc
+++ b/gcc/config/arm/arm.cc
@@ -16520,7 +16520,8 @@ arm_select_cc_mode (enum rtx_code op, rtx x, rtx y)
       && CONST_INT_P (y))
     return CC_RSBmode;
 
-  if (GET_MODE (x) == QImode && (op == EQ || op == NE))
+  if ((GET_MODE (x) == QImode || GET_MODE (x) == HImode)
+      && (op == EQ || op == NE))
     return CC_Zmode;
 
   if (GET_MODE (x) == SImode && (op == LTU || op == GEU)
diff --git a/gcc/config/arm/arm.md b/gcc/config/arm/arm.md
index 4cea71b40dd..acf2b1fb7c8 100644
--- a/gcc/config/arm/arm.md
+++ b/gcc/config/arm/arm.md
@@ -5816,6 +5816,18 @@
    (set_attr "type" "logic_imm")]
 )
 
+(define_insn "*comparehi_eq0"
+  [(set (reg:CC_Z CC_REGNUM)
+       (compare:CC_Z (match_operand:HI 0 "s_register_operand" "r")
+                     (const_int 0)))
+   (clobber (match_scratch:SI 1 "=r"))]
+  "TARGET_ARM"
+  "lsl%.\\t%1, %0, #16"
+  [(set_attr "conds" "set")
+   (set_attr "predicable" "yes")
+   (set_attr "type" "alus_shift_imm")]
+)
+
 (define_expand "extendhisi2"
   [(set (match_operand:SI 0 "s_register_operand")
        (sign_extend:SI (match_operand:HI 1 "nonimmediate_operand")))]
diff --git a/gcc/testsuite/gcc.target/arm/tst-byte-eq0-2.c 
b/gcc/testsuite/gcc.target/arm/tst-byte-eq0-2.c
index 1a793c63899..ea096acc96b 100644
--- a/gcc/testsuite/gcc.target/arm/tst-byte-eq0-2.c
+++ b/gcc/testsuite/gcc.target/arm/tst-byte-eq0-2.c
@@ -3,14 +3,12 @@
 /* { dg-options "-O2" } */
 /* { dg-add-options arm_arch_v6 } */
 
-/* Only an equality may use the flags of TST, and only a byte fits its
-   immediate, so these keep the extension and the comparison.  */
+/* Only an equality may use the flags of TST, so these keep the extension
+   and the comparison.  */
 
 unsigned n1 (unsigned x, unsigned p) { unsigned char v = x; return v < 5 ? p : 
p | 2; }
 unsigned n2 (unsigned x, unsigned p) { unsigned char v = x; return v > 5 ? p : 
p | 2; }
-unsigned n3 (unsigned x, unsigned p) { unsigned short v = x; return v == 0 ? p 
| 2 : p; }
 
 /* { dg-final { scan-assembler-times "uxtb" 2 } } */
-/* { dg-final { scan-assembler-times "uxth" 1 } } */
 /* { dg-final { scan-assembler-not "tst\[ \t\]" } } */
 
diff --git a/gcc/testsuite/gcc.target/arm/tst-half-eq0-2.c 
b/gcc/testsuite/gcc.target/arm/tst-half-eq0-2.c
new file mode 100644
index 00000000000..8fa7ccdac61
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/tst-half-eq0-2.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_arm_ok } */
+/* { dg-options "-O2 -marm" } */
+
+/* Only an equality may use the flags of the shifted test: after LSLS the N
+   flag is bit 15 of the operand and the C flag is bit 16.  n4 is not a
+   halfword, and n5 is the byte case, which keeps TST.  */
+
+unsigned n1 (unsigned x, unsigned p) { unsigned short v = x; return v < 5 ? p 
: p | 2; }
+unsigned n2 (unsigned x, unsigned p) { unsigned short v = x; return v > 5 ? p 
: p | 2; }
+unsigned n3 (unsigned x, unsigned p) { short v = x; return v < 0 ? p : p | 2; }
+unsigned n4 (unsigned x, unsigned p) { return (x & 0x3ff) ? p : p | 2; }
+unsigned n5 (unsigned x, unsigned p) { unsigned char v = x; return v == 0 ? p 
: p | 2; }
+
+/* { dg-final { scan-assembler-times "tst\[ \t\]" 2 } } */
+/* { dg-final { scan-assembler-not "lsls\[ \t\]" } } */
diff --git a/gcc/testsuite/gcc.target/arm/tst-half-eq0.c 
b/gcc/testsuite/gcc.target/arm/tst-half-eq0.c
new file mode 100644
index 00000000000..58ebd739e2a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/tst-half-eq0.c
@@ -0,0 +1,85 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_arm_ok } */
+/* { dg-options "-O2 -marm" } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+
+/*
+** f1:
+**     lsls    r0, r0, #16
+**     ...
+**     bx      lr
+*/
+unsigned
+f1 (unsigned x, unsigned p)
+{
+  return (unsigned short) x ? p : p | 2;
+}
+
+/*
+** f2:
+**     lsls    r0, r0, #16
+**     ...
+**     bx      lr
+*/
+unsigned
+f2 (unsigned x, unsigned p)
+{
+  unsigned short v = x;
+  return v == 0 ? p | 2 : p;
+}
+
+/*
+** f3:
+**     lsls    r0, r0, #16
+**     ...
+**     bx      lr
+*/
+unsigned
+f3 (unsigned x, unsigned p)
+{
+  return (x & 0xffff) ? p : p | 2;
+}
+
+/*
+** f4:
+**     lsls    r0, r0, #16
+**     ...
+**     bx      lr
+*/
+unsigned
+f4 (unsigned x, unsigned p)
+{
+  short v = x;
+  return v == 0 ? p | 2 : p;
+}
+
+/*
+** f5:
+**     lsls    r0, r0, #16
+**     movne   r0, #7
+**     moveq   r0, #9
+**     bx      lr
+*/
+int
+f5 (unsigned x)
+{
+  if ((unsigned short) x)
+    return 7;
+  return 9;
+}
+
+/*
+** f6:
+**     tst     r0, #255
+**     ...
+**     bx      lr
+*/
+unsigned
+f6 (unsigned x, unsigned p)
+{
+  unsigned char v = x;
+  return v == 0 ? p | 2 : p;
+}
+
+/* { dg-final { scan-assembler-not "uxth" } } */
+/* { dg-final { scan-assembler-not "sxth" } } */
-- 
2.55.0

Reply via email to