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
@@ -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.
@@ -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")])
new file mode 100644
@@ -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);
+}
new file mode 100644
@@ -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;
+}