https://gcc.gnu.org/g:7f02676a414e4a96c498c0b3e58487aca3c45185
commit r17-4483-g7f02676a414e4a96c498c0b3e58487aca3c45185 Author: Kyrylo Tkachov <[email protected]> Date: Mon Sep 14 09:25:13 2026 +0200 match-sat-alu.pd: Recognize signed constant-clamp truncation A signed value clamped to the bounds of a narrower signed type is signed saturating truncation. Recognize both nesting orders of the MIN and MAX operations. Require exact bounds at the input precision, signed input and output types, a narrowing conversion, and one use of the outer clamp. With AArch64 -O2 -march=armv8-a -fopenmp-simd: before: clamp_i16_4: ldr q0, [x1] movi v30.4s, 0x7f, msl 8 mvni v31.4s, 0x7f, msl 8 smin v30.4s, v0.4s, v30.4s smax v30.4s, v30.4s, v31.4s xtn v30.4h, v30.4s str d30, [x0] ret after: clamp_i16_4: ldr q31, [x1] sqxtn v31.4h, v31.4s str d31, [x0] ret Runtime tests cover both clamp orders, two widths, and LTO. Target tests cover valid forms and inexact bounds. Bootstrapped and tested on aarch64-none-linux-gnu. gcc/ChangeLog: * match-sat-alu.pd (signed_integer_sat_trunc): Add the constant-clamp form. gcc/testsuite/ChangeLog: * gcc.dg/vect/vect-sat-trunc-clamp-2.c: New test. * gcc.target/aarch64/vect-sat-trunc-clamp-s-1.c: Likewise. * gcc.target/aarch64/vect-sat-trunc-clamp-s-negative-1.c: Likewise. Signed-off-by: Kyrylo Tkachov <[email protected]> Diff: --- gcc/match-sat-alu.pd | 21 +++- gcc/testsuite/gcc.dg/vect/vect-sat-trunc-clamp-2.c | 126 +++++++++++++++++++++ .../gcc.target/aarch64/vect-sat-trunc-clamp-s-1.c | 7 ++ .../aarch64/vect-sat-trunc-clamp-s-negative-1.c | 30 +++++ 4 files changed, 183 insertions(+), 1 deletion(-) diff --git a/gcc/match-sat-alu.pd b/gcc/match-sat-alu.pd index c4b24dfab11a..d8b4d9bb6b83 100644 --- a/gcc/match-sat-alu.pd +++ b/gcc/match-sat-alu.pd @@ -631,7 +631,26 @@ along with GCC; see the file COPYING3. If not see && wi::eq_p (int_cst_2, limit_2)) || (wi::eq_p (int_cst_1, offset) && wi::eq_p (int_cst_2, limit_2)) || (wi::eq_p (int_cst_1, otype_max_in_range) - && wi::eq_p (int_cst_2, limit_1)))))))) + && wi::eq_p (int_cst_2, limit_1))))))) + (match (signed_integer_sat_trunc @0) + /* SAT_S_TRUNC = (NT)MAX (MIN (X, NT_MAX), NT_MIN). */ + (convert (integer_constant_clamp@3 @0 @1 @2)) + (if (!TYPE_UNSIGNED (TREE_TYPE (@0)) + && single_use (@3)) + (with + { + unsigned itype_precision = TYPE_PRECISION (TREE_TYPE (@0)); + unsigned otype_precision = TYPE_PRECISION (type); + wide_int otype_min = wi::mask (otype_precision - 1, true, + itype_precision); + wide_int otype_max = wi::mask (otype_precision - 1, false, + itype_precision); + wide_int lo_cst = wi::to_wide (@1, itype_precision); + wide_int hi_cst = wi::to_wide (@2, itype_precision); + } + (if (otype_precision < itype_precision + && wi::eq_p (lo_cst, otype_min) + && wi::eq_p (hi_cst, otype_max))))))) /* An arithmetic right shift preserves the sign of its signed input. The saturation value can therefore use the input sign while the range check diff --git a/gcc/testsuite/gcc.dg/vect/vect-sat-trunc-clamp-2.c b/gcc/testsuite/gcc.dg/vect/vect-sat-trunc-clamp-2.c new file mode 100644 index 000000000000..193979587b87 --- /dev/null +++ b/gcc/testsuite/gcc.dg/vect/vect-sat-trunc-clamp-2.c @@ -0,0 +1,126 @@ +/* { dg-do run } */ +/* { dg-require-effective-target vect_int } */ +/* { dg-additional-options "-O3 -fvect-cost-model=unlimited" } */ + +#include "tree-vect.h" + +typedef __UINT32_TYPE__ u32; +typedef __UINT64_TYPE__ u64; +typedef __INT16_TYPE__ i16; +typedef __INT32_TYPE__ i32; +typedef __INT64_TYPE__ i64; + +#define N 259 + +static i32 in32[N]; +static i64 in64[N]; +static i16 out16[N]; +static i32 out32[N]; + +__attribute__((noipa)) static void +clamp_s16_lo (i16 *__restrict out, const i32 *__restrict in, int n) +{ + for (int i = 0; i < n; ++i) + { + i32 x = in[i]; + out[i] = x < -32768 ? -32768 : (x > 32767 ? 32767 : x); + } +} + +__attribute__((noipa)) static void +clamp_s16_hi (i16 *__restrict out, const i32 *__restrict in, int n) +{ + for (int i = 0; i < n; ++i) + { + i32 x = in[i]; + out[i] = x > 32767 ? 32767 : (x < -32768 ? -32768 : x); + } +} + +__attribute__((noipa)) static void +clamp_s32_lo (i32 *__restrict out, const i64 *__restrict in, int n) +{ + for (int i = 0; i < n; ++i) + { + i64 x = in[i]; + out[i] = (x < -2147483647LL - 1 + ? -2147483647LL - 1 : (x > 2147483647LL + ? 2147483647LL : x)); + } +} + +__attribute__((noipa)) static void +clamp_s32_hi (i32 *__restrict out, const i64 *__restrict in, int n) +{ + for (int i = 0; i < n; ++i) + { + i64 x = in[i]; + out[i] = (x > 2147483647LL + ? 2147483647LL : (x < -2147483647LL - 1 + ? -2147483647LL - 1 : x)); + } +} + +static i16 +ref_s16 (i32 x) +{ + return x < -32768 ? -32768 : (x > 32767 ? 32767 : x); +} + +static i32 +ref_s32 (i64 x) +{ + return (x < -2147483647LL - 1 + ? -2147483647LL - 1 : (x > 2147483647LL ? 2147483647LL : x)); +} + +static void +check_s16 (void) +{ +#pragma GCC novector + for (int i = 0; i < N; ++i) + if (out16[i] != ref_s16 (in32[i])) + __builtin_abort (); +} + +static void +check_s32 (void) +{ +#pragma GCC novector + for (int i = 0; i < N; ++i) + if (out32[i] != ref_s32 (in64[i])) + __builtin_abort (); +} + +int +main (void) +{ + check_vect (); + + for (int i = 0; i < N; ++i) + { + in32[i] = (i32) ((u32) i * 2654435761U + 1013904223U); + in64[i] = (i64) ((u64) i * 11400714819323198485ULL + + 13787848793156543929ULL); + } + + in32[0] = -32769; + in32[1] = -32768; + in32[2] = 32767; + in32[3] = 32768; + in64[0] = -2147483647LL - 2; + in64[1] = -2147483647LL - 1; + in64[2] = 2147483647LL; + in64[3] = 2147483648LL; + + clamp_s16_lo (out16, in32, N); + check_s16 (); + clamp_s16_hi (out16, in32, N); + check_s16 (); + clamp_s32_lo (out32, in64, N); + check_s32 (); + clamp_s32_hi (out32, in64, N); + check_s32 (); + + return 0; +} diff --git a/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-clamp-s-1.c b/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-clamp-s-1.c new file mode 100644 index 000000000000..3a603ca18ef8 --- /dev/null +++ b/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-clamp-s-1.c @@ -0,0 +1,7 @@ +/* { dg-do compile } */ +/* { dg-options "-O3 -march=armv8-a" } */ +/* { dg-additional-options "-mmax-vectorization --param=vect-epilogues-nomask=0 -fdump-tree-vect-details" } */ + +#include "../../gcc.dg/vect/vect-sat-trunc-clamp-2.c" + +/* { dg-final { scan-tree-dump-times "sat_trunc pattern recognized" 4 "vect" } } */ diff --git a/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-clamp-s-negative-1.c b/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-clamp-s-negative-1.c new file mode 100644 index 000000000000..b0f86653554d --- /dev/null +++ b/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-clamp-s-negative-1.c @@ -0,0 +1,30 @@ +/* { dg-do compile } */ +/* { dg-options "-O3 -march=armv8-a" } */ +/* { dg-additional-options "-mmax-vectorization --param=vect-epilogues-nomask=0 -fdump-tree-vect-details" } */ + +typedef __INT16_TYPE__ i16; +typedef __INT32_TYPE__ i32; + +__attribute__((noipa)) +void +bad_low (i16 *__restrict out, const i32 *__restrict in, int n) +{ + for (int i = 0; i < n; ++i) + { + i32 x = in[i]; + out[i] = x < -32767 ? -32767 : (x > 32767 ? 32767 : x); + } +} + +__attribute__((noipa)) +void +bad_high (i16 *__restrict out, const i32 *__restrict in, int n) +{ + for (int i = 0; i < n; ++i) + { + i32 x = in[i]; + out[i] = x > 32766 ? 32766 : (x < -32768 ? -32768 : x); + } +} + +/* { dg-final { scan-tree-dump-not "sat_trunc pattern recognized" "vect" } } */
