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

Kyrylo Tkachov <[email protected]> Thu, 6 Aug 2026 14:47:11 +0000
Newsgroups gmane.comp.gcc.patches
Message-ID <[email protected]>

> On 6 Aug 2026, at 06:33, Jeffrey Law <[email protected]> wrote:
> 
> 
> 
> On 8/4/2026 3:54 AM, [email protected] wrote:
>> 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.
> Generally OK.  Just one testcase question.
> 
>> +
>> +/* 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;
>> +}
> So very happy to see both positive and negative tests.  This one probably isn't testing what you think since it appears the MAX_EXPR is never discovered.  We discover the MIN_EXPR, but the MAX is still in if-then form.  So not really sure the scans are testing this in the way you want.  Not sure if you want to adjust that or not.  For reference the IL looks like this:
> 
>   _1 = y_3(D) + -6;
>   if (_1 > x_4(D))
>     goto <bb 4>; [50.00%]
>   else
>     goto <bb 3>; [50.00%]
> ;;    succ:       4 [50.0% (guessed)]  count:536870912 (estimated locally, freq 0.5000) (TRUE_VALUE,EXECUTABLE)
> ;;                3 [50.0% (guessed)]  count:536870912 (estimated locally, freq 0.5000) (FALSE_VALUE,EXECUTABLE)
> 
> ;;   basic block 3, loop depth 0, count 536870912 (estimated locally, freq 0.5000), maybe hot
> ;;    prev block 2, next block 4, flags: (NEW, REACHABLE, VISITED)
> ;;    pred:       2 [50.0% (guessed)]  count:536870912 (estimated locally, freq 0.5000) (FALSE_VALUE,EXECUTABLE)
>   _8 = MIN_EXPR <y_3(D), x_4(D)>;
>   goto <bb 5>; [100.00%]
> ;;    succ:       5 [always]  count:536870912 (estimated locally, freq 0.5000) (FALLTHRU,EXECUTABLE)
> 
> ;;   basic block 4, loop depth 0, count 536870912 (estimated locally, freq 0.5000), maybe hot
> ;;    prev block 3, next block 5, flags: (NEW, REACHABLE, VISITED)
> ;;    pred:       2 [50.0% (guessed)]  count:536870912 (estimated locally, freq 0.5000) (TRUE_VALUE,EXECUTABLE)
>   a_5 = y_3(D) + -7;
> ;;    succ:       5 [always]  count:536870912 (estimated locally, freq 0.5000) (FALLTHRU,EXECUTABLE)
> 
> ;;   basic block 5, loop depth 0, count 1073741824 (estimated locally, freq 1.0000), maybe hot
> ;;    prev block 4, next block 1, flags: (NEW, REACHABLE, VISITED)
> ;;    pred:       4 [always]  count:536870912 (estimated locally, freq 0.5000) (FALLTHRU,EXECUTABLE)
> ;;                3 [always]  count:536870912 (estimated locally, freq 0.5000) (FALLTHRU,EXECUTABLE)
>   # prephitmp_9 = PHI <a_5(4), _8(3)>
>   return prephitmp_9;
> 
> 

Thanks for catching this.
Here’s an updated set of tests with the patch.
Kyrill

> 
> Jeff
0001-match.pd-drop-a-clamp-whose-bounds-cross.patch (application/octet-stream, 4.4 KB)
From c46cb00865f2a227ca4d1694acd8962112c19a96 Mon Sep 17 00:00:00 2001
From: Kyrylo Tkachov <[email protected]>
Date: Fri, 31 Jul 2026 05:13:13 +0200
Subject: [PATCH] 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]>
---
 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(+)
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/minmax-offset-absorb-1.c
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/minmax-offset-absorb-2.c

diff --git a/gcc/match.pd b/gcc/match.pd
index 07cb2f10a6b..30fa1b13a75 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -4674,6 +4674,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 00000000000..d53d61248c0
--- /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 00000000000..4773c59d983
--- /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" } } */
-- 
2.50.1 (Apple Git-155)