https://gcc.gnu.org/g:60379a459b0eab9fee4ad4681cb3a1e7f3092307

commit r17-3052-g60379a459b0eab9fee4ad4681cb3a1e7f3092307
Author: Kyrylo Tkachov <[email protected]>
Date:   Fri Jul 31 05:13:13 2026 +0200

    match.pd: drop a clamp whose bounds cross
    
    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.
    
    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.
            * gcc.dg/tree-ssa/minmax-offset-absorb-2.c: Likewise.
    
    Signed-off-by: Kyrylo Tkachov <[email protected]>

Diff:
---
 gcc/match.pd                                       | 14 ++++++++++++++
 .../gcc.dg/tree-ssa/minmax-offset-absorb-1.c       | 22 ++++++++++++++++++++++
 .../gcc.dg/tree-ssa/minmax-offset-absorb-2.c       | 20 ++++++++++++++++++++
 3 files changed, 56 insertions(+)

diff --git a/gcc/match.pd b/gcc/match.pd
index 7e644bc84d84..94801f19fc61 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -4637,6 +4637,20 @@ 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.  */
+(for outer (min max)
+     inner (max min)
+ (simplify
+  (outer:c (inner:c @0 (plus @1 INTEGER_CST@2)) @1)
+  (if (TYPE_OVERFLOW_UNDEFINED (type)
+       && !TYPE_OVERFLOW_SANITIZED (type)
+       && ((outer == MIN_EXPR && tree_int_cst_sgn (@2) > 0)
+          || (outer == MAX_EXPR && 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 000000000000..d53d61248c07
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/minmax-offset-absorb-1.c
@@ -0,0 +1,22 @@
+/* { 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;
+}
+
+/* f1 and f2 collapse to a bare return of Y.  */
+/* { dg-final { scan-tree-dump-times "return y_" 2 "optimized" } } */
+/* { dg-final { scan-tree-dump-not "MAX_EXPR" "optimized" } } */
+/* { dg-final { scan-tree-dump-not "MIN_EXPR" "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/minmax-offset-absorb-2.c 
b/gcc/testsuite/gcc.dg/tree-ssa/minmax-offset-absorb-2.c
new file mode 100644
index 000000000000..4773c59d9835
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/minmax-offset-absorb-2.c
@@ -0,0 +1,20 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+/* With a wrapping type Y + CST can land below Y.  */
+unsigned int keep1 (unsigned int x, unsigned int y)
+{
+  unsigned int a = x > y + 7 ? x : y + 7;
+  return a < y ? a : y;
+}
+
+/* This clamp has the opposite offset sign.  Use >= so phiopt forms both
+   the inner MAX_EXPR and the outer MIN_EXPR.  */
+int keep2 (int x, int y)
+{
+  int a = x >= y - 7 ? x : y - 7;
+  return a < y ? a : y;
+}
+
+/* { dg-final { scan-tree-dump-times "MAX_EXPR" 2 "optimized" } } */
+/* { dg-final { scan-tree-dump-times "MIN_EXPR" 2 "optimized" } } */

Reply via email to