> On 1 Sep 2026, at 09:05, Richard Biener <[email protected]> wrote:
> 
> 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.

I guess I was trying to be conservative for the test cases in particular, but 
doing it unconditionally works as well. I’ve sent out an updated version.
Thanks,
Kyrill


> 
>> 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)


Reply via email to