https://gcc.gnu.org/g:771f488a90f748613940a62de40b57d1131b2fcd
commit r17-4209-g771f488a90f748613940a62de40b57d1131b2fcd Author: Dominic P <[email protected]> Date: Sun Sep 13 15:40:23 2026 -0600 [PATCH v2 1/2] match.pd: fold A - MIN (B, C) to A + MAX (-B, -C) 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]> Diff: --- gcc/match.pd | 9 +++++ gcc/testsuite/gcc.dg/torture/minmax-negate-1.c | 52 +++++++++++++++++++++++++ gcc/testsuite/gcc.dg/tree-ssa/minmax-negate-1.c | 36 +++++++++++++++++ gcc/testsuite/gcc.dg/tree-ssa/minmax-negate-2.c | 26 +++++++++++++ gcc/testsuite/gcc.dg/tree-ssa/minmax-negate-3.c | 48 +++++++++++++++++++++++ 5 files changed, 171 insertions(+) diff --git a/gcc/match.pd b/gcc/match.pd index 213b118da06a..6728fc68fc13 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 000000000000..b8176f31a4a3 --- /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 000000000000..677c195fe301 --- /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 000000000000..dcf842bc2c79 --- /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 000000000000..562e76bfde9f --- /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 } } } } */
