[PATCH] match.pd: drop a clamp whose bounds cross

<[email protected]> Tue, 4 Aug 2026 11:54:04 +0200
Newsgroups gmane.comp.gcc.patches
Message-ID <[email protected]>
From: Kyrylo Tkachov <[email protected]>

MIN (MAX (X, Y + CST), Y) is Y for a positive CST: the inner select is at
least Y + CST, which is strictly above Y where the type cannot wrap, so the
outer select always takes Y.  The dual holds for MAX over MIN with a
negative offset.  Both leave the whole nest dead.

  int f (int x, int y)
  {
    int a = x > y + 7 ? x : y + 7;
    return a < y ? a : y;
  }

aarch64 -O2 before:

	add	w2, w1, 7
	cmp	w2, w0
	csel	w0, w2, w0, ge
	cmp	w0, w1
	csel	w0, w0, w1, le

after:

	mov	w0, w1

The shape arises after inlining, when a clamp helper is instantiated with a
lower bound that the caller has already pushed above the upper one.  The
existing min/max-with-offset rules only cover the case where the two selects
name the same operand, so the nest survived.

Bootstrapped and tested on aarch64-none-linux-gnu.
Ok for trunk?
Thanks,
Kyrill

gcc/ChangeLog:

	* match.pd (MIN (MAX (X, Y + CST), Y)): New simplification.
	(MAX (MIN (X, Y + CST), Y)): Likewise.

gcc/testsuite/ChangeLog:

	* gcc.dg/tree-ssa/minmax-offset-absorb-1.c: New test.

Signed-off-by: Kyrylo Tkachov <[email protected]>
---
 gcc/match.pd                                  | 15 ++++++++
 .../gcc.dg/tree-ssa/minmax-offset-absorb-1.c  | 36 +++++++++++++++++++
 2 files changed, 51 insertions(+)
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/minmax-offset-absorb-1.c

diff --git a/gcc/match.pd b/gcc/match.pd
index 30a77da929a..71de58f2fd8 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -4680,6 +4680,21 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
     @0
     @2)))
 
+/* MIN (MAX (X, Y + CST), Y) -> Y for a positive CST, and the dual
+   MAX (MIN (X, Y + CST), Y) -> Y for a negative one.  The inner select is
+   at least Y + CST, which the absence of wrapping puts strictly beyond Y,
+   so the outer select always takes Y and the whole nest is dead.  */
+(simplify
+ (min:c (max:c @0 (plus @1 INTEGER_CST@2)) @1)
+ (if (TYPE_OVERFLOW_UNDEFINED (type) && !TYPE_OVERFLOW_SANITIZED (type)
+      && tree_int_cst_sgn (@2) > 0)
+  @1))
+(simplify
+ (max:c (min:c @0 (plus @1 INTEGER_CST@2)) @1)
+ (if (TYPE_OVERFLOW_UNDEFINED (type) && !TYPE_OVERFLOW_SANITIZED (type)
+      && tree_int_cst_sgn (@2) < 0)
+  @1))
+
 /* min (a, b) op max (a, b) -> a op b */
 (for op (plus mult bit_and bit_xor bit_ior eq ne min max)
  (simplify
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/minmax-offset-absorb-1.c b/gcc/testsuite/gcc.dg/tree-ssa/minmax-offset-absorb-1.c
new file mode 100644
index 00000000000..ae41d6d801e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/minmax-offset-absorb-1.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+/* MIN (MAX (X, Y + CST), Y) is Y for a positive CST, and the dual
+   MAX (MIN (X, Y + CST), Y) is Y for a negative one.  */
+
+int f1 (int x, int y)
+{
+  int a = x > y + 7 ? x : y + 7;
+  return a < y ? a : y;
+}
+
+long f2 (long x, long y)
+{
+  long a = x < y - 9 ? x : y - 9;
+  return a > y ? a : y;
+}
+
+/* Must not fold: with a wrapping type Y + CST can land below Y.  */
+unsigned keep1 (unsigned x, unsigned y)
+{
+  unsigned a = x > y + 7 ? x : y + 7;
+  return a < y ? a : y;
+}
+
+/* Must not fold: the offset has the wrong sign for the outer select.  */
+int keep2 (int x, int y)
+{
+  int a = x > y - 7 ? x : y - 7;
+  return a < y ? a : y;
+}
+
+/* f1 and f2 collapse to a bare return of Y.  */
+/* { dg-final { scan-tree-dump-times "return y_" 2 "optimized" } } */
+/* keep1 holds on to its clamp.  */
+/* { dg-final { scan-tree-dump-times "MAX_EXPR" 1 "optimized" } } */
-- 
2.50.1 (Apple Git-155)