aarch64: Use orr/orn with asr for smax/smin against -1 [PR125640]

Message ID 20261002194353.2936951-1-gcc@gcc.dp11.uk
State New
Headers
Series aarch64: Use orr/orn with asr for smax/smin against -1 [PR125640] |

Commit Message

DominicP Oct. 2, 2026, 7:43 p.m. UTC
  MAX (x, -1) is x | (x >> (N - 1)) and MIN (x, -1) is x | ~(x >> (N - 1)),
one orr/orn with a shifted operand, but without CSSC the smax/smin expander
FAILs for anything other than 0 and we get cmp + csinv.  Since
r17-1683 (PR125641) match.pd also folds the source forms
x | (x >> 31) and x | ~(x >> 31) to MAX_EXPR/MIN_EXPR, so these now
regress from one instruction to two as well.  Accept -1 in the expander
and add a pattern next to the existing one for 0.

Assisted-by: Claude Opus 5 (Anthropic)

	PR target/125640

gcc/ChangeLog:

	* config/aarch64/aarch64.md (<optab><mode>3): Do not FAIL for
	SMAX and SMIN with -1.
	(*aarch64_<optab><mode>3_m1): New pattern.
	* config/aarch64/iterators.md (maxminorr): New code attribute.

gcc/testsuite/ChangeLog:

	* gcc.target/aarch64/sminmax-asr_2.c: New test.
	* gcc.target/aarch64/sminmax-asr_3.c: New test.

Signed-off-by: Dominic P <gcc@gcc.dp11.uk>
---
Effect at -O2 on an arm64 Linux 6.12 kernel (bcm2712_defconfig): 4
cmp + csinv pairs become orr/orn in 4 objects, none grow; .text -76 bytes
including alignment.  On a Cortex-A72 (Pi 400) dependency-chain latency
is unchanged (orr with an asr operand takes 2 cycles, as cmp + csinv
do), so this is a size win of one instruction per use.

Regression-tested with an aarch64-linux-gnu cross compiler (C testsuite under
qemu user mode), together with the aarch64 patches for PR118666 and
PR118648; no regressions.

OK for trunk?

 gcc/config/aarch64/aarch64.md                 | 17 +++-
 gcc/config/aarch64/iterators.md               |  1 +
 .../gcc.target/aarch64/sminmax-asr_2.c        | 78 +++++++++++++++++++
 .../gcc.target/aarch64/sminmax-asr_3.c        | 36 +++++++++
 4 files changed, 129 insertions(+), 3 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/aarch64/sminmax-asr_2.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/sminmax-asr_3.c
  

Patch

diff --git a/gcc/config/aarch64/aarch64.md b/gcc/config/aarch64/aarch64.md
index 7c8915ec389..21fd6c909fb 100644
--- a/gcc/config/aarch64/aarch64.md
+++ b/gcc/config/aarch64/aarch64.md
@@ -7684,8 +7684,8 @@ 
 ;; Expander for integer smin, smax, umin.  Mainly used to generate
 ;; straightforward RTL for TARGET_CSSC.  When that is not available
 ;; FAIL and let the generic expanders generate the CMP + CSEL sequences,
-;; except for the SMIN and SMAX with zero cases, for which we have a
-;; single instruction even for the base architecture.
+;; except for the SMIN and SMAX with zero and minus one cases, for which
+;; we have a single instruction even for the base architecture.
 (define_expand "<optab><mode>3"
   [(set (match_operand:GPI 0 "register_operand")
         (MAXMIN_NOUMAX:GPI
@@ -7695,7 +7695,8 @@ 
   {
     if (!TARGET_CSSC)
       {
-	if (operands[2] != CONST0_RTX (<MODE>mode)
+	if ((operands[2] != CONST0_RTX (<MODE>mode)
+	     && operands[2] != CONSTM1_RTX (<MODE>mode))
 	    || !(<CODE> == SMAX || <CODE> == SMIN))
 	  FAIL;
       }
@@ -7723,6 +7724,16 @@ 
   [(set_attr "type" "logic_shift_imm")]
 )
 
+(define_insn "*aarch64_<optab><mode>3_m1"
+  [(set (match_operand:GPI 0 "register_operand" "=r")
+        (FMAXMIN:GPI
+	  (match_operand:GPI 1 "register_operand" "r")
+	  (const_int -1)))]
+  ""
+  "<maxminorr>\\t%<w>0, %<w>1, %<w>1, asr <sizem1>";
+  [(set_attr "type" "logic_shift_imm")]
+)
+
 ;; Given that smax/smin do not specify the result when either input is NaN,
 ;; we could use either FMAXNM or FMAX for smax, and either FMINNM or FMIN
 ;; for smin.
diff --git a/gcc/config/aarch64/iterators.md b/gcc/config/aarch64/iterators.md
index 568786e3714..617a955e86d 100644
--- a/gcc/config/aarch64/iterators.md
+++ b/gcc/config/aarch64/iterators.md
@@ -3540,6 +3540,7 @@ 
 			  (umin "min")])
 
 (define_code_attr maxminand [(smax "bic") (smin "and")])
+(define_code_attr maxminorr [(smax "orr") (smin "orn")])
 
 (define_code_attr ovf_add_cmp [(umax "geu") (umin "ltu")])
 (define_code_attr udf_sub_cmp [(umax "ltu") (umin "geu")])
diff --git a/gcc/testsuite/gcc.target/aarch64/sminmax-asr_2.c b/gcc/testsuite/gcc.target/aarch64/sminmax-asr_2.c
new file mode 100644
index 00000000000..05714b217a6
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sminmax-asr_2.c
@@ -0,0 +1,78 @@ 
+/* PR target/125640 */
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-add-options check_function_bodies } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+
+#include <stdint.h>
+
+#pragma GCC target "+nocssc"
+
+#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
+#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
+
+/*
+** maxm1:
+**	orr	w0, w0, w0, asr #31
+**	ret
+*/
+int
+maxm1 (int a)
+{
+  return MAX (a, -1);
+}
+
+/*
+** minm1:
+**	orn	w0, w0, w0, asr #31
+**	ret
+*/
+int
+minm1 (int a)
+{
+  return MIN (a, -1);
+}
+
+/*
+** maxm1l:
+**	orr	x0, x0, x0, asr #63
+**	ret
+*/
+int64_t
+maxm1l (int64_t a)
+{
+  return MAX (a, -1);
+}
+
+/*
+** minm1l:
+**	orn	x0, x0, x0, asr #63
+**	ret
+*/
+int64_t
+minm1l (int64_t a)
+{
+  return MIN (a, -1);
+}
+
+/*
+** ior_splat:
+**	orr	w0, w0, w0, asr #31
+**	ret
+*/
+int
+ior_splat (int a)
+{
+  return a | (a >> 31);
+}
+
+/*
+** ior_not_splat:
+**	orn	w0, w0, w0, asr #31
+**	ret
+*/
+int
+ior_not_splat (int a)
+{
+  return a | ~(a >> 31);
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/sminmax-asr_3.c b/gcc/testsuite/gcc.target/aarch64/sminmax-asr_3.c
new file mode 100644
index 00000000000..1fe85fbd74f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sminmax-asr_3.c
@@ -0,0 +1,36 @@ 
+/* PR target/125640 */
+/* { dg-do run } */
+/* { dg-options "-O2" } */
+
+#pragma GCC target "+nocssc"
+
+#define TEST(T, N)							\
+  __attribute__ ((noipa)) T max_##N (T a) { return a > -1 ? a : -1; }	\
+  __attribute__ ((noipa)) T min_##N (T a) { return a < -1 ? a : -1; }
+
+TEST (int, si)
+TEST (long long, di)
+
+#define IMIN (-0x7fffffff - 1)
+#define LMIN (-0x7fffffffffffffffLL - 1)
+
+static const int si[][3]
+  = { { 0, 0, -1 }, { 1, 1, -1 }, { -1, -1, -1 }, { -2, -1, -2 },
+      { 0x7fffffff, 0x7fffffff, -1 }, { IMIN, -1, IMIN } };
+static const long long di[][3]
+  = { { 0, 0, -1 }, { 1, 1, -1 }, { -1, -1, -1 }, { -2, -1, -2 },
+      { 0x7fffffff, 0x7fffffff, -1 }, { IMIN, -1, IMIN },
+      { 0x80000000LL, 0x80000000LL, -1 },
+      { 0x7fffffffffffffffLL, 0x7fffffffffffffffLL, -1 }, { LMIN, -1, LMIN } };
+
+int
+main (void)
+{
+  for (unsigned i = 0; i < sizeof si / sizeof si[0]; i++)
+    if (max_si (si[i][0]) != si[i][1] || min_si (si[i][0]) != si[i][2])
+      __builtin_abort ();
+  for (unsigned i = 0; i < sizeof di / sizeof di[0]; i++)
+    if (max_di (di[i][0]) != di[i][1] || min_di (di[i][0]) != di[i][2])
+      __builtin_abort ();
+  return 0;
+}