A - MIN (B, C) -> A + MAX (-B, -C)
A - MAX (B, C) -> A + MIN (-B, -C)
when B and C are negatable. TYPE_OVERFLOW_UNDEFINED is required as well:
for a wrapping type negate_expr_p accepts X - Y, which can be the type
minimum. This catches A - MIN (-x, 5), which min (-A, -B) -> -max (A, B)
does not.
Bootstrapped and regression tested on x86_64-pc-linux-gnu.
Assisted-by: Claude Opus 5 (Anthropic)
gcc/ChangeLog:
* match.pd (A - MIN (B, C), A - MAX (B, C)): New patterns.
gcc/testsuite/ChangeLog:
* gcc.dg/torture/minmax-negate-1.c: New test.
* gcc.dg/tree-ssa/minmax-negate-1.c: New test.
* gcc.dg/tree-ssa/minmax-negate-2.c: New test.
* gcc.dg/tree-ssa/minmax-negate-3.c: New test.
Signed-off-by: Dominic P <[email protected]>
---
gcc/match.pd | 9 ++++
.../gcc.dg/torture/minmax-negate-1.c | 52 +++++++++++++++++++
.../gcc.dg/tree-ssa/minmax-negate-1.c | 36 +++++++++++++
.../gcc.dg/tree-ssa/minmax-negate-2.c | 26 ++++++++++
.../gcc.dg/tree-ssa/minmax-negate-3.c | 48 +++++++++++++++++
5 files changed, 171 insertions(+)
create mode 100644 gcc/testsuite/gcc.dg/torture/minmax-negate-1.c
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/minmax-negate-1.c
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/minmax-negate-2.c
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/minmax-negate-3.c
diff --git a/gcc/match.pd b/gcc/match.pd
index 3d7091e81b7..12bdf12c60f 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -2482,6 +2482,15 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
(if (!FIXED_POINT_TYPE_P (type))
(plus @0 (negate @1))))
+/* A - MIN (B, C) -> A + MAX (-B, -C)
+ A - MAX (B, C) -> A + MIN (-B, -C) if B and C are easily negatable. */
+(for minmax (min max)
+ maxmin (max min)
+ (simplify
+ (minus @0 (minmax:s negate_expr_p@1 negate_expr_p@2))
+ (if (ANY_INTEGRAL_TYPE_P (type) && TYPE_OVERFLOW_UNDEFINED (type))
+ (plus @0 (maxmin (negate @1) (negate @2))))))
+
/* 1 - a is a ^ 1 if a had a bool range. */
/* This is only enabled for gimple as sometimes
cfun is not set for the function which contains
diff --git a/gcc/testsuite/gcc.dg/torture/minmax-negate-1.c
b/gcc/testsuite/gcc.dg/torture/minmax-negate-1.c
new file mode 100644
index 00000000000..b8176f31a4a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/minmax-negate-1.c
@@ -0,0 +1,52 @@
+/* { dg-do run } */
+/* { dg-require-effective-target int32 } */
+
+/* The canonicalization must not change any value. */
+
+static int __attribute__ ((noipa))
+sub_min_cst (int a, int x)
+{
+ int nx = -x;
+ return a - (nx < 5 ? nx : 5);
+}
+
+static int __attribute__ ((noipa))
+sub_max_cst (int a, int x)
+{
+ int nx = -x;
+ return a - (nx > 5 ? nx : 5);
+}
+
+static int __attribute__ ((noipa))
+sub_min_two_uses (int a, int x, int y, int *p)
+{
+ int nx = -x;
+ int ny = -y;
+ *p = nx;
+ return a - (nx < ny ? nx : ny);
+}
+
+int
+main (void)
+{
+ static const int vs[] = { -1000000, -257, -6, -5, -4, -1, 0, 1, 4, 5, 6,
+ 257, 1000000 };
+ unsigned n = sizeof (vs) / sizeof (vs[0]);
+ for (unsigned i = 0; i < n; i++)
+ for (unsigned j = 0; j < n; j++)
+ {
+ int a = vs[i], x = vs[j], s;
+ if (sub_min_cst (a, x) != a - (-x < 5 ? -x : 5))
+ __builtin_abort ();
+ if (sub_max_cst (a, x) != a - (-x > 5 ? -x : 5))
+ __builtin_abort ();
+ for (unsigned k = 0; k < n; k++)
+ {
+ int y = vs[k];
+ if (sub_min_two_uses (a, x, y, &s) != a - (-x < -y ? -x : -y)
+ || s != -x)
+ __builtin_abort ();
+ }
+ }
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/minmax-negate-1.c
b/gcc/testsuite/gcc.dg/tree-ssa/minmax-negate-1.c
new file mode 100644
index 00000000000..677c195fe30
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/minmax-negate-1.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-forwprop1-raw" } */
+
+/* A - MIN (B, C) -> A + MAX (-B, -C) and the dual. MIN (-x, -y) on its own
+ is already folded by min (-A, -B) -> -max (A, B); these are the mixed and
+ the multiple-use forms it does not reach. */
+
+int
+sub_min_cst (int a, int x)
+{
+ int nx = -x;
+ int m = nx < 5 ? nx : 5;
+ return a - m;
+}
+
+int
+sub_max_cst (int a, int x)
+{
+ int nx = -x;
+ int m = nx > 5 ? nx : 5;
+ return a - m;
+}
+
+int
+sub_min_two_uses (int a, int x, int y, int *p)
+{
+ int nx = -x;
+ int ny = -y;
+ int m = nx < ny ? nx : ny;
+ *p = nx;
+ return a - m;
+}
+
+/* { dg-final { scan-tree-dump-not "<minus_expr," "forwprop1" } } */
+/* { dg-final { scan-tree-dump-times "<max_expr," 2 "forwprop1" } } */
+/* { dg-final { scan-tree-dump-times "<min_expr," 1 "forwprop1" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/minmax-negate-2.c
b/gcc/testsuite/gcc.dg/tree-ssa/minmax-negate-2.c
new file mode 100644
index 00000000000..dcf842bc2c7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/minmax-negate-2.c
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fwrapv -fdump-tree-optimized-raw" } */
+
+/* With a wrapping type the negation is no longer order reversing: at
+ x = INT_MIN, -x is INT_MIN again and MAX (-x, -5) picks the wrong arm.
+ Neither subtraction may go. */
+
+int
+sub_min_cst (int a, int x)
+{
+ int nx = -x;
+ int m = nx < 5 ? nx : 5;
+ return a - m;
+}
+
+int
+sub_max_cst (int a, int x)
+{
+ int nx = -x;
+ int m = nx > 5 ? nx : 5;
+ return a - m;
+}
+
+/* { dg-final { scan-tree-dump-times "<minus_expr," 2 "optimized" } } */
+/* { dg-final { scan-tree-dump-times "<min_expr," 1 "optimized" } } */
+/* { dg-final { scan-tree-dump-times "<max_expr," 1 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/minmax-negate-3.c
b/gcc/testsuite/gcc.dg/tree-ssa/minmax-negate-3.c
new file mode 100644
index 00000000000..562e76bfde9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/minmax-negate-3.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target vect_int } */
+/* { dg-options "-O3 -fdump-tree-optimized" } */
+/* { dg-additional-options "-msse2" { target ia32 } } */
+
+/* The rule reaching vector code. It matches the scalar loop body, so what is
+ left to vectorize is a vector MAX or MIN of the unnegated loads and a vector
+ add, in place of the vector negations and the vector subtract. */
+
+void
+sub_min (int *r, int *q, int *a, int *x, int *y, int n)
+{
+ for (int i = 0; i < n; i++)
+ {
+ int nx = -x[i], ny = -y[i];
+ int m = nx < ny ? nx : ny;
+ q[i] = nx;
+ r[i] = a[i] - m;
+ }
+}
+
+void
+sub_max (int *r, int *q, int *a, int *x, int *y, int n)
+{
+ for (int i = 0; i < n; i++)
+ {
+ int nx = -x[i], ny = -y[i];
+ int m = nx > ny ? nx : ny;
+ q[i] = nx;
+ r[i] = a[i] - m;
+ }
+}
+
+void
+sub_min_cst (int *r, int *a, int *x, int n)
+{
+ for (int i = 0; i < n; i++)
+ {
+ int nx = -x[i];
+ int m = nx < 5 ? nx : 5;
+ r[i] = a[i] - m;
+ }
+}
+
+/* { dg-final { scan-tree-dump-not "= vect_\[^;\]* - vect_" "optimized" } } */
+/* { dg-final { scan-tree-dump-not "MIN_EXPR <vect_\[^,\]*, \{ 5" "optimized"
} } */
+/* { dg-final { scan-tree-dump "= vect_\[^;\]* \\+ vect_" "optimized" { target
{ ! vect_no_int_min_max } } } } */
+/* { dg-final { scan-tree-dump "MAX_EXPR <vect_\[^,\]*, \{ -5" "optimized" {
target { ! vect_no_int_min_max } } } } */
--
2.55.0