On Tue, Sep 1, 2026 at 7:53 AM <[email protected]> wrote: > > From: Kyrylo Tkachov <[email protected]> > > 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 before gimplification, while its overflow > semantics are still present: > > (NT) MIN ((UT) MAX (x, 0), NT_MAX)
What's the reason to do this only for GENERIC? Once you split the expression and use some temporaries the canonicalization will no longer work on GENERIC but would work fine during early GIMPLE optimization, before if-conversion wrecks it. > Require an unsigned comparison, a signed right shift, undefined and > unsanitized negation overflow, exact constants, no side effects, and > enabled optimization. This keeps the original form with -O0, -fwrapv, > -ftrapv, sanitization, volatile operands, and explicit unsigned negation. > > The tests cover these boundaries and restore the RISC-V saturation > patterns. They also enable three optimized dumps that the existing > tests scan. Update pr103771.c because the same transformation now > happens before phiopt. > > Bootstrapped and tested on aarch64-none-linux-gnu. > Ok for trunk? > Thanks, > Kyrill > > gcc/ChangeLog: > > PR tree-optimization/127166 > * match.pd: Canonicalize conditional signed narrow clips before > gimplification. > > 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-2.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]> > --- > gcc/match.pd | 40 +++++++++++++++ > gcc/testsuite/gcc.dg/tree-ssa/pr103771.c | 5 +- > gcc/testsuite/gcc.dg/tree-ssa/pr127166-2.c | 13 +++++ > gcc/testsuite/gcc.dg/tree-ssa/pr127166-3.c | 15 ++++++ > gcc/testsuite/gcc.dg/tree-ssa/pr127166.c | 30 +++++++++++ > 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 +- > 10 files changed, 158 insertions(+), 9 deletions(-) > create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/pr127166-2.c > create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/pr127166-3.c > create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/pr127166.c > create mode 100644 gcc/testsuite/gcc.dg/vect/pr127166.c > > diff --git a/gcc/match.pd b/gcc/match.pd > index 5777c833896..263bb93159b 100644 > --- a/gcc/match.pd > +++ b/gcc/match.pd > @@ -13372,6 +13372,46 @@ and, > (BUILT_IN_CONSTANT_P (nop_convert@1 @0)) > (BUILT_IN_CONSTANT_P @0)) > > +#if GENERIC > +/* 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 (optimize > + && !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))))))) > +#endif > + > #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 8061e2df79e..b3dde923281 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-2.c > b/gcc/testsuite/gcc.dg/tree-ssa/pr127166-2.c > new file mode 100644 > index 00000000000..9f48c5b79aa > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tree-ssa/pr127166-2.c > @@ -0,0 +1,13 @@ > +/* PR tree-optimization/127166 */ > +/* { dg-do compile } */ > +/* { dg-require-effective-target int32 } */ > +/* { dg-options "-O0 -fdump-tree-gimple" } */ > + > +unsigned short > +f (int x) > +{ > + return ((unsigned int) x > 65535u ? (-x) >> 31 : x); > +} > + > +/* Keep the source control flow when optimization is disabled. */ > +/* { dg-final { scan-tree-dump-not "MAX_EXPR|MIN_EXPR" "gimple" } } */ > 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 00000000000..266b0aac75a > --- /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 00000000000..9649fc2d565 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tree-ssa/pr127166.c > @@ -0,0 +1,30 @@ > +/* PR tree-optimization/127166 */ > +/* { dg-do compile } */ > +/* { dg-require-effective-target int32 } */ > +/* { dg-options "-O2 -fdump-tree-gimple" } */ > + > +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); > +} > + > +volatile int v; > + > +unsigned short > +volatile_clip (void) > +{ > + return ((unsigned int) v > 65535u ? (-v) >> 31 : v); > +} > + > +/* Only the side-effect-free conditional signed negation can become a narrow > + clip. */ > +/* { dg-final { scan-tree-dump-times "MAX_EXPR" 1 "gimple" } } */ > +/* { dg-final { scan-tree-dump-times "MIN_EXPR" 1 "gimple" } } */ > diff --git a/gcc/testsuite/gcc.dg/vect/pr127166.c > b/gcc/testsuite/gcc.dg/vect/pr127166.c > new file mode 100644 > index 00000000000..5bc59ad3769 > --- /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 54f25e0ceaa..f5a9acec858 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 9a880e0f67b..2208e954242 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 d6b2ddd3f8d..5941b3b7543 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 4657e52f883..57cca4504ba 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> > > -- > 2.50.1 (Apple Git-155) >
