The x & (x >> (PREC - 1)) -> MIN (x, 0) rules leave the branchless
min/max idiom
t = (a - b) & ((a - b) >> 31); a -= t; b += t;
half folded. Add
Y + MIN (X - Y, 0) -> MIN (X, Y) X - MIN (X - Y, 0) -> MAX (X, Y)
MIN (X + Y, Y) - Y -> MIN (X, 0)
and the MAX forms. Each needs the inner operation not to overflow, which
gimple_match_no_overflow_p takes from the type or, on GIMPLE, from ranges.
There is no :s because the idiom uses the masked value twice and both
uses fold.
x264's median-of-three predictor (common/base.h) goes from 20 to 11
instructions on x86_64 at -O2.
Bootstrapped and regression tested on x86_64-pc-linux-gnu.
Assisted-by: Claude Opus 5 (Anthropic)
gcc/ChangeLog:
* generic-match-head.cc (gimple_match_no_overflow_p): New.
* gimple-match-head.cc (gimple_match_no_overflow_p): New.
* match.pd (Y + MIN (X - Y, 0), X - MIN (X - Y, 0), Y + MAX (X - Y, 0),
X - MAX (X - Y, 0), MIN (X + Y, Y) - Y, MAX (X + Y, Y) - Y): New
patterns.
gcc/testsuite/ChangeLog:
* gcc.dg/torture/minmax-swar-1.c: New test.
* gcc.dg/tree-ssa/minmax-swar-1.c: New test.
* gcc.dg/tree-ssa/minmax-swar-2.c: New test.
* gcc.dg/tree-ssa/minmax-swar-3.c: New test.
* gcc.dg/tree-ssa/minmax-swar-4.c: New test.
* gcc.dg/tree-ssa/minmax-swar-5.c: New test.
* gcc.dg/tree-ssa/minmax-swar-6.c: New test.
* gcc.dg/tree-ssa/minmax-swar-7.c: New test.
Signed-off-by: Dominic P <[email protected]>
---
Thanks for the review. v2 splits it as you suggested.
1/2 is A - MIN (B, C) -> A + MAX (-B, -C). It needs
TYPE_OVERFLOW_UNDEFINED as well as negate_expr_p.
2/2 keeps X - MIN (X - Y, 0), because 1/2 cannot reach it: X - Y is only
negatable when the type wraps. Without that rule x264's median is 18
instructions on x86_64 at -O2, against 11 with it. MIN (X + Y, Y) - Y is
added, unsigned and -fwrapv use the range check from your 731051, and
both patches are ANY_INTEGRAL_TYPE_P with vector tests.
gcc/generic-match-head.cc | 9 +++
gcc/gimple-match-head.cc | 17 ++++
gcc/match.pd | 24 ++++++
gcc/testsuite/gcc.dg/torture/minmax-swar-1.c | 81 +++++++++++++++++++
gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-1.c | 57 +++++++++++++
gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-2.c | 25 ++++++
gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-3.c | 39 +++++++++
gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-4.c | 54 +++++++++++++
gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-5.c | 70 ++++++++++++++++
gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-6.c | 53 ++++++++++++
gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-7.c | 32 ++++++++
11 files changed, 461 insertions(+)
create mode 100644 gcc/testsuite/gcc.dg/torture/minmax-swar-1.c
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-1.c
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-2.c
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-3.c
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-4.c
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-5.c
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-6.c
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-7.c
diff --git a/gcc/generic-match-head.cc b/gcc/generic-match-head.cc
index 9e7097a038d..5f66965c84f 100644
--- a/gcc/generic-match-head.cc
+++ b/gcc/generic-match-head.cc
@@ -209,3 +209,12 @@ gimple_match_ctx (tree)
{
return NULL;
}
+
+/* Return true if CODE applied to OP0 and OP1 in TYPE cannot overflow.
+ On GENERIC there is no range query, so only TYPE can say so. */
+
+static inline bool
+gimple_match_no_overflow_p (tree_code, tree type, tree, tree, tree)
+{
+ return TYPE_OVERFLOW_UNDEFINED (type);
+}
diff --git a/gcc/gimple-match-head.cc b/gcc/gimple-match-head.cc
index 757e324cb21..b2f26c71fc9 100644
--- a/gcc/gimple-match-head.cc
+++ b/gcc/gimple-match-head.cc
@@ -535,3 +535,20 @@ gimple_match_range_of_expr (vrange &r, tree op, tree ctx =
NULL_TREE)
return false;
return !r.undefined_p ();
}
+
+/* Return true if CODE applied to OP0 and OP1 in TYPE cannot overflow,
+ either because TYPE says so or because their ranges prove it. CTX is
+ a capture passed on to gimple_match_range_of_expr. */
+
+static inline bool
+gimple_match_no_overflow_p (tree_code code, tree type, tree op0, tree op1,
+ tree ctx)
+{
+ if (TYPE_OVERFLOW_UNDEFINED (type))
+ return true;
+
+ int_range_max vr0, vr1;
+ return (gimple_match_range_of_expr (vr0, op0, ctx)
+ && gimple_match_range_of_expr (vr1, op1, ctx)
+ && range_op_handler (code).overflow_free_p (vr0, vr1));
+}
diff --git a/gcc/match.pd b/gcc/match.pd
index 12bdf12c60f..acf892e6da2 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -5565,6 +5565,30 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
(rresult @0 { build_int_cst (type, bitop == BIT_IOR_EXPR ? -1 : 0); }))))
#endif
+/* Complete the branchless min and max the rules above leave half folded,
+ and the same identity with the addition inside the select:
+
+ Y + MIN (X - Y, 0) -> MIN (X, Y) X - MIN (X - Y, 0) -> MAX (X, Y)
+ Y + MAX (X - Y, 0) -> MAX (X, Y) X - MAX (X - Y, 0) -> MIN (X, Y)
+ MIN (X + Y, Y) - Y -> MIN (X, 0) MAX (X + Y, Y) - Y -> MAX (X, 0) */
+(for minmax (min max)
+ maxmin (max min)
+ (simplify
+ (plus:c (minmax (minus@3 @0 @1) integer_zerop) @1)
+ (if (ANY_INTEGRAL_TYPE_P (type)
+ && gimple_match_no_overflow_p (MINUS_EXPR, type, @0, @1, @3))
+ (minmax @0 @1)))
+ (simplify
+ (minus @0 (minmax (minus@3 @0 @1) integer_zerop))
+ (if (ANY_INTEGRAL_TYPE_P (type)
+ && gimple_match_no_overflow_p (MINUS_EXPR, type, @0, @1, @3))
+ (maxmin @0 @1)))
+ (simplify
+ (minus (minmax:c (plus:c@3 @0 @1) @1) @1)
+ (if (ANY_INTEGRAL_TYPE_P (type)
+ && gimple_match_no_overflow_p (PLUS_EXPR, type, @0, @1, @3))
+ (minmax @0 { build_zero_cst (type); }))))
+
/* Fold `1 >> a` into `a == 0` for scalar integral types. */
(simplify
(rshift integer_onep @2)
diff --git a/gcc/testsuite/gcc.dg/torture/minmax-swar-1.c
b/gcc/testsuite/gcc.dg/torture/minmax-swar-1.c
new file mode 100644
index 00000000000..005a485c13e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/minmax-swar-1.c
@@ -0,0 +1,81 @@
+/* { dg-do run } */
+/* { dg-require-effective-target int32 } */
+
+/* The canonicalization must not change any value. Every difference formed
+ below stays inside the type, so the test is also valid under -ftrapv (where
+ the fold itself is disabled, the branchless form being the one that traps).
*/
+
+static int __attribute__ ((noipa))
+swar_min (int a, int b)
+{
+ int t = a - b;
+ return b + (t & (t >> 31));
+}
+
+static int __attribute__ ((noipa))
+swar_max (int a, int b)
+{
+ int t = a - b;
+ return a - (t & (t >> 31));
+}
+
+static int __attribute__ ((noipa))
+swar_median (int a, int b, int c)
+{
+ int t = (a - b) & ((a - b) >> 31);
+ a -= t;
+ b += t;
+ b -= (b - c) & ((b - c) >> 31);
+ b += (a - b) & ((a - b) >> 31);
+ return b;
+}
+
+static int __attribute__ ((noipa))
+clamp_lo (int x, int y)
+{
+ int s = x + y;
+ return (s < y ? s : y) - y;
+}
+
+static int __attribute__ ((noipa))
+clamp_hi (int x, int y)
+{
+ int s = x + y;
+ return (s > y ? s : y) - y;
+}
+
+static int __attribute__ ((noipa))
+ref_median (int a, int b, int c)
+{
+ int lo = a < b ? (a < c ? a : c) : (b < c ? b : c);
+ int hi = a > b ? (a > c ? a : c) : (b > c ? b : c);
+ return a + b + c - lo - hi;
+}
+
+int
+main (void)
+{
+ static const int vs[] = { -268435456, -65536, -257, -1, 0, 1, 257, 65536,
+ 268435455 };
+ 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], b = vs[j];
+ if (swar_min (a, b) != (a < b ? a : b))
+ __builtin_abort ();
+ if (swar_max (a, b) != (a > b ? a : b))
+ __builtin_abort ();
+ if (a < 268435456 && a > -268435456 && b < 268435456 && b > -268435456)
+ {
+ if (clamp_lo (a, b) != (a < 0 ? a : 0))
+ __builtin_abort ();
+ if (clamp_hi (a, b) != (a > 0 ? a : 0))
+ __builtin_abort ();
+ }
+ for (unsigned k = 0; k < n; k++)
+ if (swar_median (a, b, vs[k]) != ref_median (a, b, vs[k]))
+ __builtin_abort ();
+ }
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-1.c
b/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-1.c
new file mode 100644
index 00000000000..53bf1242cb8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-1.c
@@ -0,0 +1,57 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-forwprop1 -fdump-tree-optimized" } */
+/* { dg-require-effective-target int32 } */
+
+/* The branchless min and max that x264's x264_median writes. The existing
+ rule turns x & (x >> 31) into MIN <x, 0>; these have to finish the job. */
+
+int
+swar_min (int a, int b)
+{
+ int t = a - b;
+ return b + (t & (t >> 31));
+}
+
+int
+swar_max (int a, int b)
+{
+ int t = a - b;
+ return a - (t & (t >> 31));
+}
+
+int
+swar_min_not (int a, int b)
+{
+ int t = a - b;
+ return a - (t & ~(t >> 31));
+}
+
+int
+swar_max_not (int a, int b)
+{
+ int t = a - b;
+ return b + (t & ~(t >> 31));
+}
+
+/* Both uses of the one difference have to fold. */
+int
+swar_median (int a, int b, int c)
+{
+ int t = (a - b) & ((a - b) >> 31);
+ a -= t;
+ b += t;
+ b -= (b - c) & ((b - c) >> 31);
+ b += (a - b) & ((a - b) >> 31);
+ return b;
+}
+
+/* The existing rule leaves MIN <x, 0> and MAX <x, 0> behind, so the counts
+ alone do not say whether this patch fired; what says it is that none of the
+ clamps against zero survive. The four simple functions fold in forwprop1,
+ the median needs one more pass because its uses are chained. */
+/* { dg-final { scan-tree-dump-not "MIN_EXPR <\[^,\]*, 0>" "forwprop1" } } */
+/* { dg-final { scan-tree-dump-not "MAX_EXPR <\[^,\]*, 0>" "forwprop1" } } */
+/* { dg-final { scan-tree-dump-not "MIN_EXPR <\[^,\]*, 0>" "optimized" } } */
+/* { dg-final { scan-tree-dump-not "MAX_EXPR <\[^,\]*, 0>" "optimized" } } */
+/* { dg-final { scan-tree-dump-times "MIN_EXPR" 4 "optimized" } } */
+/* { dg-final { scan-tree-dump-times "MAX_EXPR" 4 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-2.c
b/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-2.c
new file mode 100644
index 00000000000..b600d69207e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-2.c
@@ -0,0 +1,25 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fwrapv -fdump-tree-optimized" } */
+/* { dg-require-effective-target int32 } */
+
+/* With a wrapping difference the identity fails: for a = INT_MIN, b = 1 the
+ difference wraps positive and the branchless form returns b. The inner
+ MIN <a - b, 0> is still formed, but neither function may become a plain
+ MIN or MAX of a and b. */
+
+int
+swar_min (int a, int b)
+{
+ int t = a - b;
+ return b + (t & (t >> 31));
+}
+
+int
+swar_max (int a, int b)
+{
+ int t = a - b;
+ return a - (t & (t >> 31));
+}
+
+/* { dg-final { scan-tree-dump-times "MIN_EXPR <\[^,\]*, 0>" 2 "optimized" } }
*/
+/* { dg-final { scan-tree-dump-not "MAX_EXPR" "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-3.c
b/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-3.c
new file mode 100644
index 00000000000..5061683d04c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-3.c
@@ -0,0 +1,39 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-forwprop1-raw -fdump-tree-optimized-raw" } */
+
+/* MIN (X + Y, Y) - Y -> MIN (X, 0) and MAX (X + Y, Y) - Y -> MAX (X, 0). */
+
+int
+clamp_lo (int x, int y)
+{
+ int s = x + y;
+ return (s < y ? s : y) - y;
+}
+
+int
+clamp_hi (int x, int y)
+{
+ int s = x + y;
+ return (s > y ? s : y) - y;
+}
+
+int
+clamp_lo_long (int x, int y)
+{
+ int s = x + y;
+ int m;
+ if (s < y)
+ m = s;
+ else
+ m = y;
+ return m - y;
+}
+
+/* The two written with ?: fold in forwprop1; the one written with if and else
+ needs phiopt to make the select first. */
+/* { dg-final { scan-tree-dump-times "<min_expr," 1 "forwprop1" } } */
+/* { dg-final { scan-tree-dump-times "<max_expr," 1 "forwprop1" } } */
+/* { dg-final { scan-tree-dump-not "<plus_expr," "optimized" } } */
+/* { dg-final { scan-tree-dump-not "<minus_expr," "optimized" } } */
+/* { dg-final { scan-tree-dump-times "<min_expr," 2 "optimized" } } */
+/* { dg-final { scan-tree-dump-times "<max_expr," 1 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-4.c
b/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-4.c
new file mode 100644
index 00000000000..96675c1a6a9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-4.c
@@ -0,0 +1,54 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fwrapv -fdump-tree-optimized-raw" } */
+
+/* Under -fwrapv the identities still hold where the range of the operands
+ rules the wrap out. */
+
+int
+swar_min_ranged (int a, int b)
+{
+ if (a < -1000 || a > 1000)
+ __builtin_unreachable ();
+ if (b < -1000 || b > 1000)
+ __builtin_unreachable ();
+ int t = a - b;
+ return b + (t < 0 ? t : 0);
+}
+
+int
+swar_max_ranged (int a, int b)
+{
+ if (a < -1000 || a > 1000)
+ __builtin_unreachable ();
+ if (b < -1000 || b > 1000)
+ __builtin_unreachable ();
+ int t = a - b;
+ return a - (t < 0 ? t : 0);
+}
+
+int
+clamp_lo_ranged (int x, int y)
+{
+ if (x < -1000 || x > 1000)
+ __builtin_unreachable ();
+ if (y < -1000 || y > 1000)
+ __builtin_unreachable ();
+ int s = x + y;
+ return (s < y ? s : y) - y;
+}
+
+int
+clamp_hi_ranged (int x, int y)
+{
+ if (x < -1000 || x > 1000)
+ __builtin_unreachable ();
+ if (y < -1000 || y > 1000)
+ __builtin_unreachable ();
+ int s = x + y;
+ return (s > y ? s : y) - y;
+}
+
+/* { dg-final { scan-tree-dump-not "<plus_expr," "optimized" } } */
+/* { dg-final { scan-tree-dump-not "<minus_expr," "optimized" } } */
+/* { dg-final { scan-tree-dump-times "<min_expr," 2 "optimized" } } */
+/* { dg-final { scan-tree-dump-times "<max_expr," 2 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-5.c
b/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-5.c
new file mode 100644
index 00000000000..931d5a0d741
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-5.c
@@ -0,0 +1,70 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -Wno-psabi -fdump-tree-forwprop1-raw" } */
+
+/* All six rules on a vector type. The selects against zero are spelled with
+ >, not <: a vector x < 0 is canonicalised to the sign mask x >> 31, and the
+ rule that turns x & (x >> 31) into MIN <x, 0> is scalar, so that spelling
+ forms no vector MIN_EXPR at all. The scan is on forwprop1, which runs
+ before the vector lowering that would split a MAX_EXPR the target cannot
+ do. */
+
+typedef int v4si __attribute__ ((vector_size (16)));
+
+/* Y + MIN (X - Y, 0) -> MIN (X, Y). */
+v4si
+add_min_vec (v4si a, v4si b)
+{
+ v4si t = a - b, z = { 0, 0, 0, 0 };
+ v4si m = t > z;
+ return b + ((z & m) | (t & ~m));
+}
+
+/* Y + MAX (X - Y, 0) -> MAX (X, Y). */
+v4si
+add_max_vec (v4si a, v4si b)
+{
+ v4si t = a - b, z = { 0, 0, 0, 0 };
+ v4si m = t > z;
+ return b + ((t & m) | (z & ~m));
+}
+
+/* X - MIN (X - Y, 0) -> MAX (X, Y). */
+v4si
+sub_min_vec (v4si a, v4si b)
+{
+ v4si t = a - b, z = { 0, 0, 0, 0 };
+ v4si m = t > z;
+ return a - ((z & m) | (t & ~m));
+}
+
+/* X - MAX (X - Y, 0) -> MIN (X, Y). */
+v4si
+sub_max_vec (v4si a, v4si b)
+{
+ v4si t = a - b, z = { 0, 0, 0, 0 };
+ v4si m = t > z;
+ return a - ((t & m) | (z & ~m));
+}
+
+/* MIN (X + Y, Y) - Y -> MIN (X, 0). */
+v4si
+clamp_lo_vec (v4si x, v4si y)
+{
+ v4si s = x + y;
+ v4si m = s < y;
+ return ((s & m) | (y & ~m)) - y;
+}
+
+/* MAX (X + Y, Y) - Y -> MAX (X, 0). */
+v4si
+clamp_hi_vec (v4si x, v4si y)
+{
+ v4si s = x + y;
+ v4si m = s > y;
+ return ((s & m) | (y & ~m)) - y;
+}
+
+/* { dg-final { scan-tree-dump-not "<plus_expr," "forwprop1" } } */
+/* { dg-final { scan-tree-dump-not "<minus_expr," "forwprop1" } } */
+/* { dg-final { scan-tree-dump-times "<min_expr," 3 "forwprop1" } } */
+/* { dg-final { scan-tree-dump-times "<max_expr," 3 "forwprop1" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-6.c
b/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-6.c
new file mode 100644
index 00000000000..4f096a00229
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-6.c
@@ -0,0 +1,53 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target vect_int } */
+/* { dg-options "-O3 -fdump-tree-optimized" } */
+/* { dg-additional-options "-msse2" { target ia32 } } */
+
+/* The two difference pairs reaching vector code through the loop vectorizer:
+ the fold is on the scalar loop body, so what is left to vectorize is a
+ vector MIN or MAX of the two loads, with no vector add or subtract. */
+
+void
+add_min (int *r, int *a, int *b, int n)
+{
+ for (int i = 0; i < n; i++)
+ {
+ int t = a[i] - b[i];
+ r[i] = b[i] + (t < 0 ? t : 0);
+ }
+}
+
+void
+add_max (int *r, int *a, int *b, int n)
+{
+ for (int i = 0; i < n; i++)
+ {
+ int t = a[i] - b[i];
+ r[i] = b[i] + (t > 0 ? t : 0);
+ }
+}
+
+void
+sub_min (int *r, int *a, int *b, int n)
+{
+ for (int i = 0; i < n; i++)
+ {
+ int t = a[i] - b[i];
+ r[i] = a[i] - (t < 0 ? t : 0);
+ }
+}
+
+void
+sub_max (int *r, int *a, int *b, int n)
+{
+ for (int i = 0; i < n; i++)
+ {
+ int t = a[i] - b[i];
+ r[i] = a[i] - (t > 0 ? t : 0);
+ }
+}
+
+/* { dg-final { scan-tree-dump-not "= vect_\[^;\]* - vect_" "optimized" } } */
+/* { dg-final { scan-tree-dump-not "= vect_\[^;\]* \\+ vect_" "optimized" } }
*/
+/* { dg-final { scan-tree-dump "MIN_EXPR <vect_\[^,\]*, vect_" "optimized" {
target { ! vect_no_int_min_max } } } } */
+/* { dg-final { scan-tree-dump "MAX_EXPR <vect_\[^,\]*, vect_" "optimized" {
target { ! vect_no_int_min_max } } } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-7.c
b/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-7.c
new file mode 100644
index 00000000000..125fcb3016c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/minmax-swar-7.c
@@ -0,0 +1,32 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target vect_int } */
+/* { dg-options "-O3 -fdump-tree-optimized" } */
+/* { dg-additional-options "-msse2" { target ia32 } } */
+
+/* MINMAX (X + Y, Y) - Y through the loop vectorizer: the vector add and the
+ vector subtract both go, leaving the clamp against zero. */
+
+void
+clamp_lo (int *r, int *x, int *y, int n)
+{
+ for (int i = 0; i < n; i++)
+ {
+ int s = x[i] + y[i];
+ r[i] = (s < y[i] ? s : y[i]) - y[i];
+ }
+}
+
+void
+clamp_hi (int *r, int *x, int *y, int n)
+{
+ for (int i = 0; i < n; i++)
+ {
+ int s = x[i] + y[i];
+ r[i] = (s > y[i] ? s : y[i]) - y[i];
+ }
+}
+
+/* { dg-final { scan-tree-dump-not "= vect_\[^;\]* - vect_" "optimized" } } */
+/* { dg-final { scan-tree-dump-not "= vect_\[^;\]* \\+ vect_" "optimized" } }
*/
+/* { dg-final { scan-tree-dump "MIN_EXPR <vect_\[^,\]*, \{ 0" "optimized" {
target { ! vect_no_int_min_max } } } } */
+/* { dg-final { scan-tree-dump "MAX_EXPR <vect_\[^,\]*, \{ 0" "optimized" {
target { ! vect_no_int_min_max } } } } */
--
2.55.0