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>
