https://gcc.gnu.org/g:da3b0463f66312c9e93a027bae73bf17c24a8833

commit r17-3845-gda3b0463f66312c9e93a027bae73bf17c24a8833
Author: Kyrylo Tkachov <[email protected]>
Date:   Tue Sep 1 00:07:44 2026 +0200

    tree-optimization: Canonicalize conditional signed narrow clips [PR127166]
    
    The PR120378 RISC-V tests use this source form:
    
      (NT) ((UT) x > NT_MAX ? (-x) >> (PREC - 1) : x)
    
    For every defined execution, it is an unsigned saturating truncation of
    MAX (x, 0).  The signed negation is undefined at the signed minimum under
    the default overflow rules.
    
    Loop if-conversion changes the selected negation to unsigned arithmetic.
    This loses the distinction between the signed source and the explicit
    unsigned-negation form from PR126981, which must not become a saturating
    truncation.
    
    Canonicalize the signed form while its overflow semantics are still
    available before loop if-conversion:
    
      (NT) MIN ((UT) MAX (x, 0), NT_MAX)
    
    Require an unsigned comparison, a signed right shift, undefined and
    unsanitized negation overflow, exact constants, and no side effects.  This
    keeps the original form with -fwrapv, -ftrapv, sanitization, volatile
    operands, and explicit unsigned negation.
    
    With this patch the tests restore the RISC-V saturation patterns.
    Update pr103771.c because the direct form is
    canonicalized before phiopt.
    
    Bootstrapped and tested on aarch64-none-linux-gnu.
    
    gcc/ChangeLog:
    
            PR tree-optimization/127166
            * match.pd: Canonicalize conditional signed narrow clips before loop
            if-conversion.
    
    gcc/testsuite/ChangeLog:
    
            PR tree-optimization/127166
            * gcc.dg/tree-ssa/pr103771.c: Check the earlier canonicalization.
            * gcc.dg/tree-ssa/pr127166.c: New test.
            * gcc.dg/tree-ssa/pr127166-3.c: New test.
            * gcc.dg/vect/pr127166.c: New test.
            * gcc.target/riscv/rvv/autovec/pr120378-1.c: Update expected dump
            counts.
            * gcc.target/riscv/rvv/autovec/pr120378-2.c: Request an optimized 
dump.
            * gcc.target/riscv/rvv/autovec/pr120378-3.c: Likewise.  Update 
expected
            dump counts.
            * gcc.target/riscv/rvv/autovec/pr120378-4.c: Request an optimized 
dump.
    
    Signed-off-by: Kyrylo Tkachov <[email protected]>

Diff:
---
 gcc/match.pd                                       | 37 +++++++++++++++
 gcc/testsuite/gcc.dg/tree-ssa/pr103771.c           |  5 +-
 gcc/testsuite/gcc.dg/tree-ssa/pr127166-3.c         | 15 ++++++
 gcc/testsuite/gcc.dg/tree-ssa/pr127166.c           | 53 ++++++++++++++++++++++
 gcc/testsuite/gcc.dg/vect/pr127166.c               | 50 ++++++++++++++++++++
 .../gcc.target/riscv/rvv/autovec/pr120378-1.c      |  4 +-
 .../gcc.target/riscv/rvv/autovec/pr120378-2.c      |  2 +-
 .../gcc.target/riscv/rvv/autovec/pr120378-3.c      |  6 +--
 .../gcc.target/riscv/rvv/autovec/pr120378-4.c      |  2 +-
 9 files changed, 165 insertions(+), 9 deletions(-)

diff --git a/gcc/match.pd b/gcc/match.pd
index 5777c8338966..54cad1cd2bc4 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -13372,6 +13372,43 @@ and,
  (BUILT_IN_CONSTANT_P (nop_convert@1 @0))
  (BUILT_IN_CONSTANT_P @0))
 
+/* Canonicalize a conditional signed narrow clip while the signed negation
+   has its original overflow semantics:
+
+     (NT) ((UT) X > NT_MAX ? (-X) >> (PREC - 1) : X)
+       -> (NT) MIN ((UT) MAX (X, 0), NT_MAX).  */
+(if (INTEGRAL_TYPE_P (type) && TYPE_UNSIGNED (type))
+ (simplify
+  (cond (gt (nop_convert@4 @0) INTEGER_CST@1)
+       (convert (rshift@5
+                 (nop_convert? (negate@3 (nop_convert? @0)))
+                 INTEGER_CST@2))
+       (convert @0))
+  (if (!TREE_SIDE_EFFECTS (@0)
+       && !TYPE_UNSIGNED (TREE_TYPE (@0))
+       && TYPE_UNSIGNED (TREE_TYPE (@4))
+       && !TYPE_UNSIGNED (TREE_TYPE (@5))
+       && TYPE_OVERFLOW_UNDEFINED (TREE_TYPE (@3))
+       && !TYPE_OVERFLOW_SANITIZED (TREE_TYPE (@3)))
+   (with
+    {
+     tree utype = TREE_TYPE (@4);
+     unsigned itype_precision = TYPE_PRECISION (TREE_TYPE (@0));
+     unsigned otype_precision = TYPE_PRECISION (type);
+     wide_int trunc_max = wi::mask (otype_precision, false, itype_precision);
+     wide_int int_cst_1 = wi::to_wide (@1, itype_precision);
+     wide_int int_cst_2 = wi::to_wide (@2, itype_precision);
+     wide_int shift_amount = wi::uhwi (itype_precision - 1,
+                                 itype_precision);
+    }
+    (if (otype_precision < itype_precision
+        && wi::eq_p (trunc_max, int_cst_1)
+        && wi::eq_p (int_cst_2, shift_amount))
+     (convert (min:utype
+               (convert:utype
+                (max @0 { build_zero_cst (TREE_TYPE (@0)); }))
+               @1)))))))
+
 #if GIMPLE
 
 /* Include the saturation alu match patterns.  */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr103771.c 
b/gcc/testsuite/gcc.dg/tree-ssa/pr103771.c
index 8061e2df79ea..b3dde9232811 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/pr103771.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/pr103771.c
@@ -1,6 +1,7 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fdump-tree-phiopt1-details" } */
-/* { dg-final { scan-tree-dump-times "changed to factor operation out from 
COND_EXPR." 1 "phiopt1" } } */
+/* { dg-options "-O3 -fdump-tree-gimple" } */
+/* { dg-final { scan-tree-dump-times "MAX_EXPR" 1 "gimple" } } */
+/* { dg-final { scan-tree-dump-times "MIN_EXPR" 1 "gimple" } } */
 
 typedef unsigned char uint8_t;
 
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr127166-3.c 
b/gcc/testsuite/gcc.dg/tree-ssa/pr127166-3.c
new file mode 100644
index 000000000000..266b0aac75a5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/pr127166-3.c
@@ -0,0 +1,15 @@
+/* PR tree-optimization/127166 */
+/* { dg-do compile } */
+/* { dg-require-effective-target int32 } */
+/* { dg-options "-O2 -fsanitize=signed-integer-overflow" } */
+/* { dg-additional-options "-fdump-tree-optimized" } */
+
+unsigned short
+f (int x)
+{
+  return ((unsigned int) x > 65535u ? (-x) >> 31 : x);
+}
+
+/* Keep the conditional signed negation so that it is checked at runtime.  */
+/* { dg-final { scan-tree-dump-not "MAX_EXPR|MIN_EXPR" "optimized" } } */
+/* { dg-final { scan-tree-dump {\.UBSAN_CHECK_SUB} "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr127166.c 
b/gcc/testsuite/gcc.dg/tree-ssa/pr127166.c
new file mode 100644
index 000000000000..9bf89246dba9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/pr127166.c
@@ -0,0 +1,53 @@
+/* PR tree-optimization/127166 */
+/* { dg-do compile } */
+/* { dg-require-effective-target int32 } */
+/* { dg-options "-O2 -fdump-tree-gimple -fdump-tree-phiopt2" } */
+
+unsigned short
+signed_clip (int x)
+{
+  return ((unsigned int) x > 65535u ? (-x) >> 31 : x);
+}
+
+unsigned short
+unsigned_clip (int x)
+{
+  return ((unsigned int) x > 65535u
+         ? (int) (-(unsigned int) x) >> 31 : x);
+}
+
+unsigned short
+signed_split_clip (int x)
+{
+  int neg = -x;
+  int sign = neg >> 31;
+  unsigned short high = sign;
+
+  return ((unsigned int) x > 65535u ? high : x);
+}
+
+unsigned short
+unsigned_split_clip (int x)
+{
+  unsigned int ux = x;
+  unsigned int neg = -ux;
+  int sign = (int) neg >> 31;
+  unsigned short high = sign;
+
+  return (ux > 65535u ? high : x);
+}
+
+volatile int v;
+
+unsigned short
+volatile_clip (void)
+{
+  return ((unsigned int) v > 65535u ? (-v) >> 31 : v);
+}
+
+/* The direct signed form is canonicalized in GENERIC.  The split signed form
+   is canonicalized in GIMPLE before loop if-conversion.  */
+/* { dg-final { scan-tree-dump-times "MAX_EXPR" 1 "gimple" } } */
+/* { dg-final { scan-tree-dump-times "MIN_EXPR" 1 "gimple" } } */
+/* { dg-final { scan-tree-dump-times "MAX_EXPR" 2 "phiopt2" } } */
+/* { dg-final { scan-tree-dump-times "MIN_EXPR" 2 "phiopt2" } } */
diff --git a/gcc/testsuite/gcc.dg/vect/pr127166.c 
b/gcc/testsuite/gcc.dg/vect/pr127166.c
new file mode 100644
index 000000000000..5bc59ad37693
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/vect/pr127166.c
@@ -0,0 +1,50 @@
+/* PR tree-optimization/127166 */
+/* { dg-additional-options "-fwrapv" } */
+/* { dg-require-effective-target int32 } */
+/* { dg-require-effective-target vect_int } */
+
+#include "tree-vect.h"
+
+#define N 64
+
+__attribute__ ((noipa)) void
+clip (unsigned short *__restrict out, const int *__restrict in, int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      int x = in[i];
+      out[i] = ((unsigned int) x > 65535u ? (-x) >> 31 : x);
+    }
+}
+
+int
+main (void)
+{
+  int in[N];
+  unsigned short out[N];
+
+  check_vect ();
+
+  for (int i = 0; i < N; ++i)
+    in[i] = (i & 3) == 0 ? (-__INT_MAX__ - 1) : i * 12345 - 30000;
+
+  clip (out, in, N);
+
+#pragma GCC novector
+  for (int i = 0; i < N; ++i)
+    {
+      int x = in[i];
+      unsigned short ref;
+
+      if (x == (-__INT_MAX__ - 1) || x > 65535)
+       ref = 65535;
+      else if (x < 0)
+       ref = 0;
+      else
+       ref = x;
+      if (out[i] != ref)
+       abort ();
+    }
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr120378-1.c 
b/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr120378-1.c
index 54f25e0ceaa9..f5a9acec858f 100644
--- a/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr120378-1.c
+++ b/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr120378-1.c
@@ -16,6 +16,6 @@ clip_loop (uint8_t *res, int *x, int w)
     res[i] = clip_uint8 (x[i]);
 }
 
-/* { dg-final { scan-tree-dump-times ".SAT_TRUNC " 1 "optimized" } } */
-/* { dg-final { scan-tree-dump-times "MAX_EXPR " 1 "optimized" } } */
+/* { dg-final { scan-tree-dump-times ".SAT_TRUNC " 2 "optimized" } } */
+/* { dg-final { scan-tree-dump-times "MAX_EXPR " 2 "optimized" } } */
 /* { dg-final { scan-assembler-times {vnclipu\.wi} 2 } } */
diff --git a/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr120378-2.c 
b/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr120378-2.c
index 9a880e0f67b2..2208e954242e 100644
--- a/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr120378-2.c
+++ b/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr120378-2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-march=rv64gcv -mabi=lp64d -O3" } */
+/* { dg-options "-march=rv64gcv -mabi=lp64d -O3 -fdump-tree-optimized" } */
 
 #include <stdint.h>
 
diff --git a/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr120378-3.c 
b/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr120378-3.c
index d6b2ddd3f8d0..5941b3b75436 100644
--- a/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr120378-3.c
+++ b/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr120378-3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-march=rv64gcv -mabi=lp64d -O3" } */
+/* { dg-options "-march=rv64gcv -mabi=lp64d -O3 -fdump-tree-optimized" } */
 
 #include <stdint.h>
 
@@ -16,6 +16,6 @@ clip_loop (uint8_t *res, int64_t *x, int w)
     res[i] = clip_uint8 (x[i]);
 }
 
-/* { dg-final { scan-tree-dump-times ".SAT_TRUNC " 1 "optimized" } } */
-/* { dg-final { scan-tree-dump-times "MAX_EXPR " 1 "optimized" } } */
+/* { dg-final { scan-tree-dump-times ".SAT_TRUNC " 2 "optimized" } } */
+/* { dg-final { scan-tree-dump-times "MAX_EXPR " 2 "optimized" } } */
 /* { dg-final { scan-assembler-times {vnclipu\.wi} 3 } } */
diff --git a/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr120378-4.c 
b/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr120378-4.c
index 4657e52f8839..57cca4504ba2 100644
--- a/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr120378-4.c
+++ b/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr120378-4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-march=rv64gcv -mabi=lp64d -O3" } */
+/* { dg-options "-march=rv64gcv -mabi=lp64d -O3 -fdump-tree-optimized" } */
 
 #include <stdint.h>

Reply via email to