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" } } */
