On Wed, 2 Sep 2026, [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 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. > Ok for trunk?
OK. Thanks, Richard. > Thanks, > Kyrill > > 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]> > --- > 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(-) > 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..54cad1cd2bc 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 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-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..9bf89246dba > --- /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 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> > > -- Richard Biener <[email protected]> SUSE Software Solutions Germany GmbH, Frankenstrasse 146, 90461 Nuernberg, Germany; GF: Jochen Jaser, Andrew McDonald, Abhinav Puri; (HRB 36809, AG Nuernberg)
