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

Reply via email to