diff mbox series

[09/13] match-sat-alu.pd: Recognize signed saturating truncation after a shift

Message ID 20260902145254.77832-11-ktkachov@nvidia.com
State New
Headers show
Series Saturating arithmetic matching improvements | expand

Commit Message

Kyrylo Tkachov Sept. 2, 2026, 2:52 p.m. UTC
From: Kyrylo Tkachov <ktkachov@nvidia.com>

An arithmetic right shift preserves its input sign.  The range test and
conversion can therefore use the shifted value while the saturation value
uses the original input:

  shifted = x >> 8;
  range = (unsigned int) shifted + 32768;
  out = range > 65535
        ? (short int) (x >> 31) ^ 32767 : (short int) shifted;

This is equivalent to .SAT_TRUNC (shifted).  Recognize a precision-minus-one
sign shift and allow the saturation value to use the original input sign.

Require the data shift to be a constant smaller than the input precision.
Require the range type to be at least as wide as the shifted value and the
output type, so the conversion and clamp constants do not lose bits.

With AArch64 -O2 -march=armv8-a -fopenmp-simd:

before:

shifted_clip4:
	ldp	d0, d30, [x1]
	movi	v27.2s, 0x80, lsl 8
	movi	v25.2s, 0xff, msl 8
	mvni	v31.4h, 0x80, lsl 8
	sshr	v29.2s, v0.2s, 8
	sshr	v28.2s, v30.2s, 8
	cmlt	v0.2s, v0.2s, #0
	cmlt	v30.2s, v30.2s, #0
	add	v26.2s, v29.2s, v27.2s
	add	v27.2s, v28.2s, v27.2s
	uzp1	v30.4h, v0.4h, v30.4h
	uzp1	v28.4h, v29.4h, v28.4h
	cmhi	v26.2s, v26.2s, v25.2s
	cmhi	v27.2s, v27.2s, v25.2s
	eor	v31.8b, v30.8b, v31.8b
	uzp1	v27.4h, v26.4h, v27.4h
	bit	v28.8b, v31.8b, v27.8b
	str	d28, [x0]
	ret

after:

shifted_clip4:
	ldr	q31, [x1]
	sqshrn	v31.4h, v31.4s, 8
	str	d31, [x0]
	ret

The vector sequence becomes one SQSHRN.  Runtime tests cover valid and
wrapping forms.  Target tests cover valid forms, rejected forms, and the
related signed saturating addition and subtraction forms.

Bootstrapped and tested on aarch64-none-linux-gnu.
Tested on x86_64-pc-linux-gnu.

Ok for trunk?

gcc/ChangeLog:

	* match-sat-alu.pd (signed_integer_sat_val): Recognize an arithmetic
	sign shift.
	(signed_integer_sat_trunc): Use signed_integer_sat_val in the existing
	form.  Recognize a shifted value with an equivalent pre-shift sign
	source.

gcc/testsuite/ChangeLog:

	* gcc.dg/vect/vect-sat-trunc-shift-sign-source-1.c: New test.
	* gcc.target/aarch64/sat_s_val_ashr-1.c: Likewise.
	* gcc.target/aarch64/vect-sat-trunc-shift-sign-source-1.c: Likewise.
	* gcc.target/aarch64/vect-sat-trunc-shift-sign-source-negative-1.c:
	Likewise.

Signed-off-by: Kyrylo Tkachov <ktkachov@nvidia.com>
---
 gcc/match-sat-alu.pd                          |  69 +++++-
 .../vect/vect-sat-trunc-shift-sign-source-1.c | 221 ++++++++++++++++++
 .../gcc.target/aarch64/sat_s_val_ashr-1.c     |  30 +++
 .../vect-sat-trunc-shift-sign-source-1.c      |   7 +
 ...t-sat-trunc-shift-sign-source-negative-1.c | 122 ++++++++++
 5 files changed, 438 insertions(+), 11 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/vect/vect-sat-trunc-shift-sign-source-1.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/sat_s_val_ashr-1.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-shift-sign-source-1.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-shift-sign-source-negative-1.c

Comments

Andrea Pinski Sept. 3, 2026, 6:29 a.m. UTC | #1
On Wed, Sep 2, 2026 at 8:02 AM <ktkachov@nvidia.com> wrote:
>
> From: Kyrylo Tkachov <ktkachov@nvidia.com>
>
> An arithmetic right shift preserves its input sign.  The range test and
> conversion can therefore use the shifted value while the saturation value
> uses the original input:
>
>   shifted = x >> 8;
>   range = (unsigned int) shifted + 32768;
>   out = range > 65535
>         ? (short int) (x >> 31) ^ 32767 : (short int) shifted;
>
> This is equivalent to .SAT_TRUNC (shifted).  Recognize a precision-minus-one
> sign shift and allow the saturation value to use the original input sign.
>
> Require the data shift to be a constant smaller than the input precision.
> Require the range type to be at least as wide as the shifted value and the
> output type, so the conversion and clamp constants do not lose bits.
>
> With AArch64 -O2 -march=armv8-a -fopenmp-simd:
>
> before:
>
> shifted_clip4:
>         ldp     d0, d30, [x1]
>         movi    v27.2s, 0x80, lsl 8
>         movi    v25.2s, 0xff, msl 8
>         mvni    v31.4h, 0x80, lsl 8
>         sshr    v29.2s, v0.2s, 8
>         sshr    v28.2s, v30.2s, 8
>         cmlt    v0.2s, v0.2s, #0
>         cmlt    v30.2s, v30.2s, #0
>         add     v26.2s, v29.2s, v27.2s
>         add     v27.2s, v28.2s, v27.2s
>         uzp1    v30.4h, v0.4h, v30.4h
>         uzp1    v28.4h, v29.4h, v28.4h
>         cmhi    v26.2s, v26.2s, v25.2s
>         cmhi    v27.2s, v27.2s, v25.2s
>         eor     v31.8b, v30.8b, v31.8b
>         uzp1    v27.4h, v26.4h, v27.4h
>         bit     v28.8b, v31.8b, v27.8b
>         str     d28, [x0]
>         ret
>
> after:
>
> shifted_clip4:
>         ldr     q31, [x1]
>         sqshrn  v31.4h, v31.4s, 8
>         str     d31, [x0]
>         ret
>
> The vector sequence becomes one SQSHRN.  Runtime tests cover valid and
> wrapping forms.  Target tests cover valid forms, rejected forms, and the
> related signed saturating addition and subtraction forms.
>
> Bootstrapped and tested on aarch64-none-linux-gnu.
> Tested on x86_64-pc-linux-gnu.
>
> Ok for trunk?
>
> gcc/ChangeLog:
>
>         * match-sat-alu.pd (signed_integer_sat_val): Recognize an arithmetic
>         sign shift.
>         (signed_integer_sat_trunc): Use signed_integer_sat_val in the existing
>         form.  Recognize a shifted value with an equivalent pre-shift sign
>         source.
>
> gcc/testsuite/ChangeLog:
>
>         * gcc.dg/vect/vect-sat-trunc-shift-sign-source-1.c: New test.
>         * gcc.target/aarch64/sat_s_val_ashr-1.c: Likewise.
>         * gcc.target/aarch64/vect-sat-trunc-shift-sign-source-1.c: Likewise.
>         * gcc.target/aarch64/vect-sat-trunc-shift-sign-source-negative-1.c:
>         Likewise.
>
> Signed-off-by: Kyrylo Tkachov <ktkachov@nvidia.com>
> ---
>  gcc/match-sat-alu.pd                          |  69 +++++-
>  .../vect/vect-sat-trunc-shift-sign-source-1.c | 221 ++++++++++++++++++
>  .../gcc.target/aarch64/sat_s_val_ashr-1.c     |  30 +++
>  .../vect-sat-trunc-shift-sign-source-1.c      |   7 +
>  ...t-sat-trunc-shift-sign-source-negative-1.c | 122 ++++++++++
>  5 files changed, 438 insertions(+), 11 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/vect/vect-sat-trunc-shift-sign-source-1.c
>  create mode 100644 gcc/testsuite/gcc.target/aarch64/sat_s_val_ashr-1.c
>  create mode 100644 gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-shift-sign-source-1.c
>  create mode 100644 gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-shift-sign-source-negative-1.c
>
> diff --git a/gcc/match-sat-alu.pd b/gcc/match-sat-alu.pd
> index 477ae366661..b141c87be03 100644
> --- a/gcc/match-sat-alu.pd
> +++ b/gcc/match-sat-alu.pd
> @@ -383,7 +383,15 @@ along with GCC; see the file COPYING3.  If not see
>   (match (signed_integer_sat_val @0)
>    (bit_xor:c (nop_convert? (negate
>                             (nop_convert? (convert (lt @0 integer_zerop)))))
> -            max_value)))
> +            max_value))
> + /* SAT_VAL = ((T)(X >> (PRECISION (X) - 1)) ^ MAX)  */
> + (match (signed_integer_sat_val @0)
> +  (bit_xor:c (convert? (rshift @0 INTEGER_CST@1)) max_value)
> +  (if (!TYPE_UNSIGNED (TREE_TYPE (@0))
> +       && tree_fits_uhwi_p (@1)
> +       && (tree_to_uhwi (@1)
> +           == (unsigned HOST_WIDE_INT)
> +              TYPE_PRECISION (TREE_TYPE (@0)) - 1)))))

Just:
wi::to_wide (@1) ==  (TYPE_PRECISION (TREE_TYPE (@0)) - 1)

Should be enough.
Should there be a check on precision of the conversion here; is
extending ok and what about truncating?  What about the signedness of
`type`?

>
>  /* Saturation add for signed integer.  */
>  (if (INTEGRAL_TYPE_P (type) && !TYPE_UNSIGNED (type))
> @@ -603,9 +611,7 @@ along with GCC; see the file COPYING3.  If not see
>   (match (signed_integer_sat_trunc @0)
>    /* SAT_S_TRUNC(X) = (unsigned)X + NT_MAX + 1  > Unsigned_MAX ? (NT)X  */
>    (cond^ (gt (plus:c (convert@4 @0) INTEGER_CST@1) INTEGER_CST@2)
> -        (bit_xor:c (nop_convert?
> -                    (negate (nop_convert? (convert (lt @0 integer_zerop)))))
> -                   INTEGER_CST@3)
> +        (signed_integer_sat_val @0)
>          (convert @0))
>    /* The comparison type has to be unsigned and at least as wide as X.  A
>       narrower type would examine only the low bits of X.  The result
> @@ -623,17 +629,58 @@ along with GCC; see the file COPYING3.  If not see
>       wide_int limit_0 = wi::mask (otype_prec, false, range_prec); // Aka 255
>       wide_int limit_1 = limit_0 - 2; // Aka 253
>       wide_int limit_2 = limit_0 - 1; // Aka 254
> -     wide_int otype_max = wi::mask (otype_prec - 1, false, otype_prec);
> -     wide_int itype_max = wi::mask (otype_prec - 1, false, range_prec);
> +     wide_int otype_max_in_range
> +       = wi::mask (otype_prec - 1, false, range_prec);
>       wide_int int_cst_1 = wi::to_wide (@1);
>       wide_int int_cst_2 = wi::to_wide (@2);
> -     wide_int int_cst_3 = wi::to_wide (@3);
>      }
> -    (if (((wi::eq_p (int_cst_1, offset) && wi::eq_p (int_cst_2, limit_0))
> -        || (wi::eq_p (int_cst_1, itype_max) && wi::eq_p (int_cst_2, limit_2))
> +    (if ((wi::eq_p (int_cst_1, offset)
> +         && wi::eq_p (int_cst_2, limit_0))
> +        || (wi::eq_p (int_cst_1, otype_max_in_range)
> +            && 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, itype_max) && wi::eq_p (int_cst_2, limit_1)))
> -        && wi::eq_p (int_cst_3, otype_max)))))))
> +        || (wi::eq_p (int_cst_1, otype_max_in_range)
> +            && wi::eq_p (int_cst_2, limit_1))))))))
> +
> +/* An arithmetic right shift preserves the sign of its signed input.  The
> +   saturation value can therefore use the input sign while the range check
> +   and truncated result use the shifted value.  */
> +(if (INTEGRAL_TYPE_P (type) && !TYPE_UNSIGNED (type))
> + (match (signed_integer_sat_trunc @0)
> +  (cond^ (gt (plus:c (convert@4
> +                      (rshift@0 @5 INTEGER_CST@6)) INTEGER_CST@1)
> +            INTEGER_CST@2)
> +        (signed_integer_sat_val @5)
> +        (convert @0))
> +  (if (!TYPE_UNSIGNED (TREE_TYPE (@0))
> +       && TYPE_UNSIGNED (TREE_TYPE (@4))
> +       && tree_fits_uhwi_p (@6)
> +       && TYPE_PRECISION (type) < TYPE_PRECISION (TREE_TYPE (@0))
> +       && tree_to_uhwi (@6) < TYPE_PRECISION (TREE_TYPE (@0))
> +       && TYPE_PRECISION (TREE_TYPE (@4)) >= TYPE_PRECISION (type)
> +       && (TYPE_PRECISION (TREE_TYPE (@4))
> +           >= TYPE_PRECISION (TREE_TYPE (@0)) - tree_to_uhwi (@6)))
> +   (with
> +    {
> +     unsigned otype_prec = TYPE_PRECISION (type);
> +     unsigned range_prec = TYPE_PRECISION (TREE_TYPE (@4));
> +     wide_int offset = wi::set_bit_in_zero (otype_prec - 1, range_prec);
> +     wide_int limit_0 = wi::mask (otype_prec, false, range_prec); // Aka 255
> +     wide_int limit_1 = limit_0 - 2; // Aka 253
> +     wide_int limit_2 = limit_0 - 1; // Aka 254
> +     wide_int otype_max_in_range
> +       = wi::mask (otype_prec - 1, false, range_prec);
> +     wide_int int_cst_1 = wi::to_wide (@1);
> +     wide_int int_cst_2 = wi::to_wide (@2);
> +    }
> +    (if ((wi::eq_p (int_cst_1, offset)
> +             && wi::eq_p (int_cst_2, limit_0))
> +            || (wi::eq_p (int_cst_1, otype_max_in_range)
> +                && 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))))))))
>
>  /* Saturation mult for unsigned integer.  */
>  (if (INTEGRAL_TYPE_P (type) && TYPE_UNSIGNED (type))
> diff --git a/gcc/testsuite/gcc.dg/vect/vect-sat-trunc-shift-sign-source-1.c b/gcc/testsuite/gcc.dg/vect/vect-sat-trunc-shift-sign-source-1.c
> new file mode 100644
> index 00000000000..f68e503b6c0
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/vect/vect-sat-trunc-shift-sign-source-1.c
> @@ -0,0 +1,221 @@
> +/* { dg-do run { target bitint } } */
> +/* { dg-additional-options "-O3 -fvect-cost-model=unlimited" } */
> +
> +typedef __INT8_TYPE__ int8_t;
> +typedef __INT16_TYPE__ int16_t;
> +typedef __INT32_TYPE__ int32_t;
> +typedef __INT64_TYPE__ int64_t;
> +typedef __UINT32_TYPE__ uint32_t;
> +typedef __UINT64_TYPE__ uint64_t;
> +typedef signed _BitInt(16) int16b_t;
> +typedef unsigned _BitInt(12) uint12b_t;
> +typedef unsigned _BitInt(16) uint16b_t;
> +typedef unsigned _BitInt(17) uint17b_t;
> +typedef unsigned _BitInt(24) uint24b_t;
> +
> +#define N 259
> +
> +#define SHIFT_SAT_VALUE(OUT, X, SHIFT, MAX) \
> +  ((OUT) ((OUT) ((X) >> (SHIFT)) ^ (OUT) (MAX)))
> +#define COMPARE_SAT_VALUE(OUT, X, SHIFT, MAX) \
> +  ((OUT) (-((OUT) ((X) < 0)) ^ (OUT) (MAX)))
> +
> +#define DEF_CLIP(NAME, SAT, OUT, IN, UIN, SHIFT, SIGN_SHIFT, OFFSET, LIMIT, \
> +                MAX)                                                        \
> +  __attribute__((noipa))                                                   \
> +  static void                                                              \
> +  NAME (OUT *__restrict out, const IN *__restrict in, int n)               \
> +  {                                                                        \
> +    for (int i = 0; i < n; ++i)                                           \
> +      {                                                                    \
> +       IN source = in[i];                                                  \
> +       IN shifted = source >> SHIFT;                                       \
> +       UIN range = (UIN) shifted + (UIN) OFFSET;                            \
> +       out[i] = (range > (UIN) LIMIT                                       \
> +                 ? SAT (OUT, source, SIGN_SHIFT, MAX) : (OUT) shifted);      \
> +      }                                                                    \
> +  }
> +
> +#define DEF_REF(NAME, OUT, IN, SHIFT, MIN, MAX)                           \
> +  __attribute__((noipa, optimize ("O0")))                                \
> +  static void                                                             \
> +  NAME (OUT *out, const IN *in, int n)                                    \
> +  {                                                                       \
> +    for (int i = 0; i < n; ++i)                                          \
> +      {                                                                   \
> +       IN shifted = in[i] >> SHIFT;                                       \
> +       if (shifted < (IN) MIN)                                            \
> +         out[i] = (OUT) MIN;                                              \
> +       else if (shifted > (IN) MAX)                                      \
> +         out[i] = (OUT) MAX;                                              \
> +       else                                                               \
> +         out[i] = (OUT) shifted;                                          \
> +      }                                                                   \
> +  }
> +
> +DEF_CLIP (clip_s16_s8, SHIFT_SAT_VALUE, int8_t, int16b_t, uint16b_t,
> +         3, 15, 128, 255, 127)
> +DEF_CLIP (clip_s32_s16, COMPARE_SAT_VALUE, int16_t, int32_t, uint32_t,
> +         8, 31, 32768U, 65535U, 32767)
> +DEF_CLIP (clip_s64_s32, SHIFT_SAT_VALUE, int32_t, int64_t, uint64_t,
> +         13, 63, 2147483648ULL, 4294967295ULL, 2147483647)
> +DEF_CLIP (clip_s16_s8_wide_unshifted, SHIFT_SAT_VALUE, int8_t, int16b_t,
> +         uint32_t, 0, 15, 128, 255, 127)
> +DEF_CLIP (clip_s16_s8_wide_shifted, SHIFT_SAT_VALUE, int8_t, int16b_t,
> +         uint32_t, 3, 15, 128, 255, 127)
> +DEF_CLIP (clip_s32_s16_reduced_range, SHIFT_SAT_VALUE, int16_t, int32_t,
> +         uint24b_t, 8, 31, 32768, 65535, 32767)
> +
> +DEF_REF (ref_s16_s8, int8_t, int16b_t, 3, -128, 127)
> +DEF_REF (ref_s32_s16, int16_t, int32_t, 8, -32768, 32767)
> +DEF_REF (ref_s64_s32, int32_t, int64_t, 13,
> +        -2147483647 - 1, 2147483647)
> +DEF_REF (ref_s16_s8_wide_unshifted, int8_t, int16b_t, 0, -128, 127)
> +DEF_REF (ref_s16_s8_wide_shifted, int8_t, int16b_t, 3, -128, 127)
> +DEF_REF (ref_s32_s16_reduced_range, int16_t, int32_t, 8, -32768, 32767)
> +
> +/* This range calculation can wrap a shifted value that still fits in the
> +   shifted input type.  */
> +
> +__attribute__((noipa))
> +static void
> +narrow_range (int16_t *__restrict out, const int32_t *__restrict in, int n)
> +{
> +  for (int i = 0; i < n; ++i)
> +    {
> +      int32_t source = in[i];
> +      int32_t shifted = source >> 1;
> +      uint17b_t range = (uint17b_t) shifted + (uint17b_t) 32768;
> +      out[i] = (range > (uint17b_t) 65535
> +               ? (int16_t) (source >> 31) ^ 32767 : (int16_t) shifted);
> +    }
> +}
> +
> +__attribute__((noipa, optimize ("O0")))
> +static void
> +ref_narrow_range (int16_t *out, const int32_t *in, int n)
> +{
> +  for (int i = 0; i < n; ++i)
> +    {
> +      int32_t source = in[i];
> +      int32_t shifted = source >> 1;
> +      uint17b_t range = (uint17b_t) shifted + (uint17b_t) 32768;
> +      out[i] = (range > (uint17b_t) 65535
> +               ? (int16_t) (source >> 31) ^ 32767 : (int16_t) shifted);
> +    }
> +}
> +
> +/* These wrapped constants do not describe a saturating truncation.  */
> +
> +__attribute__((noipa))
> +static void
> +wrapped_output_range (int16_t *__restrict out, const int32_t *__restrict in,
> +                     int n)
> +{
> +  for (int i = 0; i < n; ++i)
> +    {
> +      int32_t source = in[i];
> +      int32_t shifted = source >> 20;
> +      uint12b_t range = (uint12b_t) shifted + (uint12b_t) 32767;
> +      out[i] = (range > (uint12b_t) 65534
> +               ? (int16_t) (source >> 31) ^ 32767 : (int16_t) shifted);
> +    }
> +}
> +
> +__attribute__((noipa, optimize ("O0")))
> +static void
> +ref_wrapped_output_range (int16_t *out, const int32_t *in, int n)
> +{
> +  for (int i = 0; i < n; ++i)
> +    {
> +      int32_t source = in[i];
> +      int32_t shifted = source >> 20;
> +      uint12b_t range = (uint12b_t) shifted + (uint12b_t) 32767;
> +      out[i] = (range > (uint12b_t) 65534
> +               ? (int16_t) (source >> 31) ^ 32767 : (int16_t) shifted);
> +    }
> +}
> +
> +typedef void (*clip_fn) (int16_t *, const int32_t *, int);
> +
> +static void
> +check_invalid (clip_fn fn, clip_fn ref_fn, int32_t special)
> +{
> +  int16_t out[N];
> +  int16_t ref[N];
> +  int32_t in[N];
> +
> +  for (int i = 0; i < N; ++i)
> +    in[i] = (int32_t) ((uint32_t) i * 2654435761U + 1013904223U);
> +  in[0] = special;
> +
> +  for (int n = 0; n <= N; ++n)
> +    {
> +      for (int i = 0; i < N; ++i)
> +       out[i] = ref[i] = 23;
> +      fn (out, in, n);
> +      ref_fn (ref, in, n);
> +      for (int i = 0; i < N; ++i)
> +       if (out[i] != ref[i])
> +         __builtin_abort ();
> +    }
> +}
> +
> +#define CHECK(NAME, REF, OUT, IN, INIT, SHIFT, MIN, MAX)                  \
> +  do                                                                     \
> +    {                                                                    \
> +      OUT out[N];                                                        \
> +      OUT ref[N];                                                        \
> +      IN in[N];                                                          \
> +      IN scale = (IN) 1 << (SHIFT);                                      \
> +      for (int i = 0; i < N; ++i)                                      \
> +       in[i] = (IN) (INIT);                                               \
> +      in[0] = ((IN) (MIN) - 1) * scale;                                 \
> +      in[1] = (IN) (MIN) * scale;                                       \
> +      in[2] = (IN) -1 * scale;                                          \
> +      in[3] = 0;                                                        \
> +      in[4] = (IN) (MAX) * scale;                                       \
> +      in[5] = ((IN) (MAX) + 1) * scale;                                 \
> +      for (int n = 0; n <= N; ++n)                                     \
> +       {                                                                 \
> +         for (int i = 0; i < N; ++i)                                    \
> +           out[i] = ref[i] = (OUT) 23;                                   \
> +         NAME (out, in, n);                                              \
> +         REF (ref, in, n);                                               \
> +         for (int i = 0; i < N; ++i)                                    \
> +           if (out[i] != ref[i])                                         \
> +             __builtin_abort ();                                         \
> +       }                                                                 \
> +    }                                                                    \
> +  while (0)
> +
> +int
> +main (void)
> +{
> +  CHECK (clip_s16_s8, ref_s16_s8, int8_t, int16b_t,
> +        (uint16b_t) i * (uint16b_t) 40503 + (uint16b_t) 97,
> +        3, -128, 127);
> +  CHECK (clip_s32_s16, ref_s32_s16, int16_t, int32_t,
> +        (uint32_t) i * 2654435761U + 1013904223U,
> +        8, -32768, 32767);
> +  CHECK (clip_s64_s32, ref_s64_s32, int32_t, int64_t,
> +        (uint64_t) i * 11400714819323198485ULL
> +        + 13787848793156543929ULL,
> +        13, -2147483647 - 1, 2147483647);
> +  CHECK (clip_s16_s8_wide_unshifted, ref_s16_s8_wide_unshifted,
> +        int8_t, int16b_t,
> +        (uint16b_t) i * (uint16b_t) 40503 + (uint16b_t) 97,
> +        0, -128, 127);
> +  CHECK (clip_s16_s8_wide_shifted, ref_s16_s8_wide_shifted,
> +        int8_t, int16b_t,
> +        (uint16b_t) i * (uint16b_t) 40503 + (uint16b_t) 97,
> +        3, -128, 127);
> +  CHECK (clip_s32_s16_reduced_range, ref_s32_s16_reduced_range,
> +        int16_t, int32_t,
> +        (uint32_t) i * 2654435761U + 1013904223U,
> +        8, -32768, 32767);
> +  check_invalid (narrow_range, ref_narrow_range, 196608);
> +  check_invalid (wrapped_output_range, ref_wrapped_output_range, 0);
> +  return 0;
> +}
> +
> diff --git a/gcc/testsuite/gcc.target/aarch64/sat_s_val_ashr-1.c b/gcc/testsuite/gcc.target/aarch64/sat_s_val_ashr-1.c
> new file mode 100644
> index 00000000000..62ce0378d0d
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/sat_s_val_ashr-1.c
> @@ -0,0 +1,30 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-tree-optimized" } */
> +
> +#include <stdint.h>
> +
> +/* Write the saturation value of a signed overflow as
> +   (X >> (PREC - 1)) ^ MAX.  */
> +
> +#define DEF(N, T, MX, SH)                                      \
> +  T f1_##N (T x, T y)                                          \
> +  {                                                            \
> +    T s = (T) ((uint64_t) x + (uint64_t) y);                   \
> +    return ((x ^ s) & ~(x ^ y)) < 0 ? ((x >> SH) ^ MX) : s;    \
> +  }                                                            \
> +  T f2_##N (T x, T y)                                          \
> +  {                                                            \
> +    T r;                                                       \
> +    return __builtin_add_overflow (x, y, &r) ? ((x >> SH) ^ MX) : r; \
> +  }                                                            \
> +  T f3_##N (T x, T y)                                          \
> +  {                                                            \
> +    T r;                                                       \
> +    return __builtin_sub_overflow (x, y, &r) ? ((x >> SH) ^ MX) : r; \
> +  }
> +
> +DEF (32, int32_t, INT32_MAX, 31)
> +DEF (64, int64_t, INT64_MAX, 63)
> +
> +/* { dg-final { scan-tree-dump-times "\\.SAT_ADD " 4 "optimized" } } */
> +/* { dg-final { scan-tree-dump-times "\\.SAT_SUB " 2 "optimized" } } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-shift-sign-source-1.c b/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-shift-sign-source-1.c
> new file mode 100644
> index 00000000000..b3256db7b6e
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-shift-sign-source-1.c
> @@ -0,0 +1,7 @@
> +/* { dg-do compile { target bitint } } */
> +/* { 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-shift-sign-source-1.c"
> +
> +/* { dg-final { scan-tree-dump-times "sat_trunc pattern recognized" 6 "vect" } } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-shift-sign-source-negative-1.c b/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-shift-sign-source-negative-1.c
> new file mode 100644
> index 00000000000..25925c96759
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-shift-sign-source-negative-1.c
> @@ -0,0 +1,122 @@
> +/* { dg-do compile { target bitint } } */
> +/* { dg-options "-O3 -march=armv8-a" } */
> +/* { dg-additional-options "-mmax-vectorization --param=vect-epilogues-nomask=0 -fdump-tree-vect-details" } */
> +
> +typedef __INT16_TYPE__ int16_t;
> +typedef __INT32_TYPE__ int32_t;
> +typedef __UINT16_TYPE__ uint16_t;
> +typedef __UINT32_TYPE__ uint32_t;
> +typedef unsigned _BitInt(12) uint12b_t;
> +typedef unsigned _BitInt(17) uint17b_t;
> +
> +__attribute__((noipa))
> +void
> +different_source (int16_t *__restrict out, const int32_t *__restrict in,
> +                 const int32_t *__restrict signs, int n)
> +{
> +  for (int i = 0; i < n; ++i)
> +    {
> +      int32_t source = in[i];
> +      int32_t shifted = source >> 8;
> +      uint32_t range = (uint32_t) shifted + 32768U;
> +      int32_t sign_source = signs[i];
> +      out[i] = (range > 65535U
> +               ? (int16_t) (sign_source >> 31) ^ 32767
> +               : (int16_t) shifted);
> +    }
> +}
> +
> +__attribute__((noipa))
> +void
> +logical_shift (int16_t *__restrict out, const uint32_t *__restrict in, int n)
> +{
> +  for (int i = 0; i < n; ++i)
> +    {
> +      uint32_t source = in[i];
> +      uint32_t shifted = source >> 8;
> +      uint32_t range = shifted + 32768U;
> +      out[i] = (range > 65535U
> +               ? (int16_t) ((int32_t) source >> 31) ^ 32767
> +               : (int16_t) shifted);
> +    }
> +}
> +
> +__attribute__((noipa))
> +void
> +variable_count (int16_t *__restrict out, const int32_t *__restrict in,
> +               const uint16_t *__restrict counts, int n)
> +{
> +  for (int i = 0; i < n; ++i)
> +    {
> +      int32_t source = in[i];
> +      int32_t shifted = source >> (counts[i] & 31);
> +      uint32_t range = (uint32_t) shifted + 32768U;
> +      out[i] = (range > 65535U
> +               ? (int16_t) (source >> 31) ^ 32767
> +               : (int16_t) shifted);
> +    }
> +}
> +
> +__attribute__((noipa))
> +void
> +wrong_sign_count (int16_t *__restrict out, const int32_t *__restrict in,
> +                 int n)
> +{
> +  for (int i = 0; i < n; ++i)
> +    {
> +      int32_t source = in[i];
> +      int32_t shifted = source >> 8;
> +      uint32_t range = (uint32_t) shifted + 32768U;
> +      out[i] = (range > 65535U
> +               ? (int16_t) (source >> 30) ^ 32767
> +               : (int16_t) shifted);
> +    }
> +}
> +
> +__attribute__((noipa))
> +void
> +wrong_range (int16_t *__restrict out, const int32_t *__restrict in, int n)
> +{
> +  for (int i = 0; i < n; ++i)
> +    {
> +      int32_t source = in[i];
> +      int32_t shifted = source >> 8;
> +      uint32_t range = (uint32_t) shifted + 32767U;
> +      out[i] = (range > 65535U
> +               ? (int16_t) (source >> 31) ^ 32767
> +               : (int16_t) shifted);
> +    }
> +}
> +
> +__attribute__((noipa))
> +void
> +narrow_range (int16_t *__restrict out, const int32_t *__restrict in, int n)
> +{
> +  for (int i = 0; i < n; ++i)
> +    {
> +      int32_t source = in[i];
> +      int32_t shifted = source >> 1;
> +      uint17b_t range = (uint17b_t) shifted + (uint17b_t) 32768;
> +      out[i] = (range > (uint17b_t) 65535
> +               ? (int16_t) (source >> 31) ^ 32767
> +               : (int16_t) shifted);
> +    }
> +}
> +
> +__attribute__((noipa))
> +void
> +wrapped_output_range (int16_t *__restrict out,
> +                     const int32_t *__restrict in, int n)
> +{
> +  for (int i = 0; i < n; ++i)
> +    {
> +      int32_t source = in[i];
> +      int32_t shifted = source >> 20;
> +      uint12b_t range = (uint12b_t) shifted + (uint12b_t) 32767;
> +      out[i] = (range > (uint12b_t) 65534
> +               ? (int16_t) (source >> 31) ^ 32767
> +               : (int16_t) shifted);
> +    }
> +}
> +
> +/* { dg-final { scan-tree-dump-not "sat_trunc pattern recognized" "vect" } } */
> --
> 2.50.1 (Apple Git-155)
>
diff mbox series

Patch

diff --git a/gcc/match-sat-alu.pd b/gcc/match-sat-alu.pd
index 477ae366661..b141c87be03 100644
--- a/gcc/match-sat-alu.pd
+++ b/gcc/match-sat-alu.pd
@@ -383,7 +383,15 @@  along with GCC; see the file COPYING3.  If not see
  (match (signed_integer_sat_val @0)
   (bit_xor:c (nop_convert? (negate
 			    (nop_convert? (convert (lt @0 integer_zerop)))))
-	     max_value)))
+	     max_value))
+ /* SAT_VAL = ((T)(X >> (PRECISION (X) - 1)) ^ MAX)  */
+ (match (signed_integer_sat_val @0)
+  (bit_xor:c (convert? (rshift @0 INTEGER_CST@1)) max_value)
+  (if (!TYPE_UNSIGNED (TREE_TYPE (@0))
+	&& tree_fits_uhwi_p (@1)
+	&& (tree_to_uhwi (@1)
+	    == (unsigned HOST_WIDE_INT)
+	       TYPE_PRECISION (TREE_TYPE (@0)) - 1)))))
 
 /* Saturation add for signed integer.  */
 (if (INTEGRAL_TYPE_P (type) && !TYPE_UNSIGNED (type))
@@ -603,9 +611,7 @@  along with GCC; see the file COPYING3.  If not see
  (match (signed_integer_sat_trunc @0)
   /* SAT_S_TRUNC(X) = (unsigned)X + NT_MAX + 1  > Unsigned_MAX ? (NT)X  */
   (cond^ (gt (plus:c (convert@4 @0) INTEGER_CST@1) INTEGER_CST@2)
-	 (bit_xor:c (nop_convert?
-		     (negate (nop_convert? (convert (lt @0 integer_zerop)))))
-		    INTEGER_CST@3)
+	 (signed_integer_sat_val @0)
 	 (convert @0))
   /* The comparison type has to be unsigned and at least as wide as X.  A
      narrower type would examine only the low bits of X.  The result
@@ -623,17 +629,58 @@  along with GCC; see the file COPYING3.  If not see
      wide_int limit_0 = wi::mask (otype_prec, false, range_prec); // Aka 255
      wide_int limit_1 = limit_0 - 2; // Aka 253
      wide_int limit_2 = limit_0 - 1; // Aka 254
-     wide_int otype_max = wi::mask (otype_prec - 1, false, otype_prec);
-     wide_int itype_max = wi::mask (otype_prec - 1, false, range_prec);
+     wide_int otype_max_in_range
+	= wi::mask (otype_prec - 1, false, range_prec);
      wide_int int_cst_1 = wi::to_wide (@1);
      wide_int int_cst_2 = wi::to_wide (@2);
-     wide_int int_cst_3 = wi::to_wide (@3);
     }
-    (if (((wi::eq_p (int_cst_1, offset) && wi::eq_p (int_cst_2, limit_0))
-	 || (wi::eq_p (int_cst_1, itype_max) && wi::eq_p (int_cst_2, limit_2))
+    (if ((wi::eq_p (int_cst_1, offset)
+	  && wi::eq_p (int_cst_2, limit_0))
+	 || (wi::eq_p (int_cst_1, otype_max_in_range)
+	     && 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, itype_max) && wi::eq_p (int_cst_2, limit_1)))
-	 && wi::eq_p (int_cst_3, otype_max)))))))
+	 || (wi::eq_p (int_cst_1, otype_max_in_range)
+	     && wi::eq_p (int_cst_2, limit_1))))))))
+
+/* An arithmetic right shift preserves the sign of its signed input.  The
+   saturation value can therefore use the input sign while the range check
+   and truncated result use the shifted value.  */
+(if (INTEGRAL_TYPE_P (type) && !TYPE_UNSIGNED (type))
+ (match (signed_integer_sat_trunc @0)
+  (cond^ (gt (plus:c (convert@4
+		       (rshift@0 @5 INTEGER_CST@6)) INTEGER_CST@1)
+	     INTEGER_CST@2)
+	 (signed_integer_sat_val @5)
+	 (convert @0))
+  (if (!TYPE_UNSIGNED (TREE_TYPE (@0))
+	&& TYPE_UNSIGNED (TREE_TYPE (@4))
+	&& tree_fits_uhwi_p (@6)
+	&& TYPE_PRECISION (type) < TYPE_PRECISION (TREE_TYPE (@0))
+	&& tree_to_uhwi (@6) < TYPE_PRECISION (TREE_TYPE (@0))
+	&& TYPE_PRECISION (TREE_TYPE (@4)) >= TYPE_PRECISION (type)
+	&& (TYPE_PRECISION (TREE_TYPE (@4))
+	    >= TYPE_PRECISION (TREE_TYPE (@0)) - tree_to_uhwi (@6)))
+   (with
+    {
+     unsigned otype_prec = TYPE_PRECISION (type);
+     unsigned range_prec = TYPE_PRECISION (TREE_TYPE (@4));
+     wide_int offset = wi::set_bit_in_zero (otype_prec - 1, range_prec);
+     wide_int limit_0 = wi::mask (otype_prec, false, range_prec); // Aka 255
+     wide_int limit_1 = limit_0 - 2; // Aka 253
+     wide_int limit_2 = limit_0 - 1; // Aka 254
+     wide_int otype_max_in_range
+	= wi::mask (otype_prec - 1, false, range_prec);
+     wide_int int_cst_1 = wi::to_wide (@1);
+     wide_int int_cst_2 = wi::to_wide (@2);
+    }
+    (if ((wi::eq_p (int_cst_1, offset)
+	      && wi::eq_p (int_cst_2, limit_0))
+	     || (wi::eq_p (int_cst_1, otype_max_in_range)
+		 && 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))))))))
 
 /* Saturation mult for unsigned integer.  */
 (if (INTEGRAL_TYPE_P (type) && TYPE_UNSIGNED (type))
diff --git a/gcc/testsuite/gcc.dg/vect/vect-sat-trunc-shift-sign-source-1.c b/gcc/testsuite/gcc.dg/vect/vect-sat-trunc-shift-sign-source-1.c
new file mode 100644
index 00000000000..f68e503b6c0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/vect/vect-sat-trunc-shift-sign-source-1.c
@@ -0,0 +1,221 @@ 
+/* { dg-do run { target bitint } } */
+/* { dg-additional-options "-O3 -fvect-cost-model=unlimited" } */
+
+typedef __INT8_TYPE__ int8_t;
+typedef __INT16_TYPE__ int16_t;
+typedef __INT32_TYPE__ int32_t;
+typedef __INT64_TYPE__ int64_t;
+typedef __UINT32_TYPE__ uint32_t;
+typedef __UINT64_TYPE__ uint64_t;
+typedef signed _BitInt(16) int16b_t;
+typedef unsigned _BitInt(12) uint12b_t;
+typedef unsigned _BitInt(16) uint16b_t;
+typedef unsigned _BitInt(17) uint17b_t;
+typedef unsigned _BitInt(24) uint24b_t;
+
+#define N 259
+
+#define SHIFT_SAT_VALUE(OUT, X, SHIFT, MAX) \
+  ((OUT) ((OUT) ((X) >> (SHIFT)) ^ (OUT) (MAX)))
+#define COMPARE_SAT_VALUE(OUT, X, SHIFT, MAX) \
+  ((OUT) (-((OUT) ((X) < 0)) ^ (OUT) (MAX)))
+
+#define DEF_CLIP(NAME, SAT, OUT, IN, UIN, SHIFT, SIGN_SHIFT, OFFSET, LIMIT, \
+		 MAX)                                                        \
+  __attribute__((noipa))                                                   \
+  static void                                                              \
+  NAME (OUT *__restrict out, const IN *__restrict in, int n)               \
+  {                                                                        \
+    for (int i = 0; i < n; ++i)                                           \
+      {                                                                    \
+	IN source = in[i];                                                  \
+	IN shifted = source >> SHIFT;                                       \
+	UIN range = (UIN) shifted + (UIN) OFFSET;                            \
+	out[i] = (range > (UIN) LIMIT                                       \
+		  ? SAT (OUT, source, SIGN_SHIFT, MAX) : (OUT) shifted);      \
+      }                                                                    \
+  }
+
+#define DEF_REF(NAME, OUT, IN, SHIFT, MIN, MAX)                           \
+  __attribute__((noipa, optimize ("O0")))                                \
+  static void                                                             \
+  NAME (OUT *out, const IN *in, int n)                                    \
+  {                                                                       \
+    for (int i = 0; i < n; ++i)                                          \
+      {                                                                   \
+	IN shifted = in[i] >> SHIFT;                                       \
+	if (shifted < (IN) MIN)                                            \
+	  out[i] = (OUT) MIN;                                              \
+	else if (shifted > (IN) MAX)                                      \
+	  out[i] = (OUT) MAX;                                              \
+	else                                                               \
+	  out[i] = (OUT) shifted;                                          \
+      }                                                                   \
+  }
+
+DEF_CLIP (clip_s16_s8, SHIFT_SAT_VALUE, int8_t, int16b_t, uint16b_t,
+	  3, 15, 128, 255, 127)
+DEF_CLIP (clip_s32_s16, COMPARE_SAT_VALUE, int16_t, int32_t, uint32_t,
+	  8, 31, 32768U, 65535U, 32767)
+DEF_CLIP (clip_s64_s32, SHIFT_SAT_VALUE, int32_t, int64_t, uint64_t,
+	  13, 63, 2147483648ULL, 4294967295ULL, 2147483647)
+DEF_CLIP (clip_s16_s8_wide_unshifted, SHIFT_SAT_VALUE, int8_t, int16b_t,
+	  uint32_t, 0, 15, 128, 255, 127)
+DEF_CLIP (clip_s16_s8_wide_shifted, SHIFT_SAT_VALUE, int8_t, int16b_t,
+	  uint32_t, 3, 15, 128, 255, 127)
+DEF_CLIP (clip_s32_s16_reduced_range, SHIFT_SAT_VALUE, int16_t, int32_t,
+	  uint24b_t, 8, 31, 32768, 65535, 32767)
+
+DEF_REF (ref_s16_s8, int8_t, int16b_t, 3, -128, 127)
+DEF_REF (ref_s32_s16, int16_t, int32_t, 8, -32768, 32767)
+DEF_REF (ref_s64_s32, int32_t, int64_t, 13,
+	 -2147483647 - 1, 2147483647)
+DEF_REF (ref_s16_s8_wide_unshifted, int8_t, int16b_t, 0, -128, 127)
+DEF_REF (ref_s16_s8_wide_shifted, int8_t, int16b_t, 3, -128, 127)
+DEF_REF (ref_s32_s16_reduced_range, int16_t, int32_t, 8, -32768, 32767)
+
+/* This range calculation can wrap a shifted value that still fits in the
+   shifted input type.  */
+
+__attribute__((noipa))
+static void
+narrow_range (int16_t *__restrict out, const int32_t *__restrict in, int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      int32_t source = in[i];
+      int32_t shifted = source >> 1;
+      uint17b_t range = (uint17b_t) shifted + (uint17b_t) 32768;
+      out[i] = (range > (uint17b_t) 65535
+		? (int16_t) (source >> 31) ^ 32767 : (int16_t) shifted);
+    }
+}
+
+__attribute__((noipa, optimize ("O0")))
+static void
+ref_narrow_range (int16_t *out, const int32_t *in, int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      int32_t source = in[i];
+      int32_t shifted = source >> 1;
+      uint17b_t range = (uint17b_t) shifted + (uint17b_t) 32768;
+      out[i] = (range > (uint17b_t) 65535
+		? (int16_t) (source >> 31) ^ 32767 : (int16_t) shifted);
+    }
+}
+
+/* These wrapped constants do not describe a saturating truncation.  */
+
+__attribute__((noipa))
+static void
+wrapped_output_range (int16_t *__restrict out, const int32_t *__restrict in,
+		      int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      int32_t source = in[i];
+      int32_t shifted = source >> 20;
+      uint12b_t range = (uint12b_t) shifted + (uint12b_t) 32767;
+      out[i] = (range > (uint12b_t) 65534
+		? (int16_t) (source >> 31) ^ 32767 : (int16_t) shifted);
+    }
+}
+
+__attribute__((noipa, optimize ("O0")))
+static void
+ref_wrapped_output_range (int16_t *out, const int32_t *in, int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      int32_t source = in[i];
+      int32_t shifted = source >> 20;
+      uint12b_t range = (uint12b_t) shifted + (uint12b_t) 32767;
+      out[i] = (range > (uint12b_t) 65534
+		? (int16_t) (source >> 31) ^ 32767 : (int16_t) shifted);
+    }
+}
+
+typedef void (*clip_fn) (int16_t *, const int32_t *, int);
+
+static void
+check_invalid (clip_fn fn, clip_fn ref_fn, int32_t special)
+{
+  int16_t out[N];
+  int16_t ref[N];
+  int32_t in[N];
+
+  for (int i = 0; i < N; ++i)
+    in[i] = (int32_t) ((uint32_t) i * 2654435761U + 1013904223U);
+  in[0] = special;
+
+  for (int n = 0; n <= N; ++n)
+    {
+      for (int i = 0; i < N; ++i)
+	out[i] = ref[i] = 23;
+      fn (out, in, n);
+      ref_fn (ref, in, n);
+      for (int i = 0; i < N; ++i)
+	if (out[i] != ref[i])
+	  __builtin_abort ();
+    }
+}
+
+#define CHECK(NAME, REF, OUT, IN, INIT, SHIFT, MIN, MAX)                  \
+  do                                                                     \
+    {                                                                    \
+      OUT out[N];                                                        \
+      OUT ref[N];                                                        \
+      IN in[N];                                                          \
+      IN scale = (IN) 1 << (SHIFT);                                      \
+      for (int i = 0; i < N; ++i)                                      \
+	in[i] = (IN) (INIT);                                               \
+      in[0] = ((IN) (MIN) - 1) * scale;                                 \
+      in[1] = (IN) (MIN) * scale;                                       \
+      in[2] = (IN) -1 * scale;                                          \
+      in[3] = 0;                                                        \
+      in[4] = (IN) (MAX) * scale;                                       \
+      in[5] = ((IN) (MAX) + 1) * scale;                                 \
+      for (int n = 0; n <= N; ++n)                                     \
+	{                                                                 \
+	  for (int i = 0; i < N; ++i)                                    \
+	    out[i] = ref[i] = (OUT) 23;                                   \
+	  NAME (out, in, n);                                              \
+	  REF (ref, in, n);                                               \
+	  for (int i = 0; i < N; ++i)                                    \
+	    if (out[i] != ref[i])                                         \
+	      __builtin_abort ();                                         \
+	}                                                                 \
+    }                                                                    \
+  while (0)
+
+int
+main (void)
+{
+  CHECK (clip_s16_s8, ref_s16_s8, int8_t, int16b_t,
+	 (uint16b_t) i * (uint16b_t) 40503 + (uint16b_t) 97,
+	 3, -128, 127);
+  CHECK (clip_s32_s16, ref_s32_s16, int16_t, int32_t,
+	 (uint32_t) i * 2654435761U + 1013904223U,
+	 8, -32768, 32767);
+  CHECK (clip_s64_s32, ref_s64_s32, int32_t, int64_t,
+	 (uint64_t) i * 11400714819323198485ULL
+	 + 13787848793156543929ULL,
+	 13, -2147483647 - 1, 2147483647);
+  CHECK (clip_s16_s8_wide_unshifted, ref_s16_s8_wide_unshifted,
+	 int8_t, int16b_t,
+	 (uint16b_t) i * (uint16b_t) 40503 + (uint16b_t) 97,
+	 0, -128, 127);
+  CHECK (clip_s16_s8_wide_shifted, ref_s16_s8_wide_shifted,
+	 int8_t, int16b_t,
+	 (uint16b_t) i * (uint16b_t) 40503 + (uint16b_t) 97,
+	 3, -128, 127);
+  CHECK (clip_s32_s16_reduced_range, ref_s32_s16_reduced_range,
+	 int16_t, int32_t,
+	 (uint32_t) i * 2654435761U + 1013904223U,
+	 8, -32768, 32767);
+  check_invalid (narrow_range, ref_narrow_range, 196608);
+  check_invalid (wrapped_output_range, ref_wrapped_output_range, 0);
+  return 0;
+}
+
diff --git a/gcc/testsuite/gcc.target/aarch64/sat_s_val_ashr-1.c b/gcc/testsuite/gcc.target/aarch64/sat_s_val_ashr-1.c
new file mode 100644
index 00000000000..62ce0378d0d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sat_s_val_ashr-1.c
@@ -0,0 +1,30 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+#include <stdint.h>
+
+/* Write the saturation value of a signed overflow as
+   (X >> (PREC - 1)) ^ MAX.  */
+
+#define DEF(N, T, MX, SH)					\
+  T f1_##N (T x, T y)						\
+  {								\
+    T s = (T) ((uint64_t) x + (uint64_t) y);			\
+    return ((x ^ s) & ~(x ^ y)) < 0 ? ((x >> SH) ^ MX) : s;	\
+  }								\
+  T f2_##N (T x, T y)						\
+  {								\
+    T r;							\
+    return __builtin_add_overflow (x, y, &r) ? ((x >> SH) ^ MX) : r; \
+  }								\
+  T f3_##N (T x, T y)						\
+  {								\
+    T r;							\
+    return __builtin_sub_overflow (x, y, &r) ? ((x >> SH) ^ MX) : r; \
+  }
+
+DEF (32, int32_t, INT32_MAX, 31)
+DEF (64, int64_t, INT64_MAX, 63)
+
+/* { dg-final { scan-tree-dump-times "\\.SAT_ADD " 4 "optimized" } } */
+/* { dg-final { scan-tree-dump-times "\\.SAT_SUB " 2 "optimized" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-shift-sign-source-1.c b/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-shift-sign-source-1.c
new file mode 100644
index 00000000000..b3256db7b6e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-shift-sign-source-1.c
@@ -0,0 +1,7 @@ 
+/* { dg-do compile { target bitint } } */
+/* { 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-shift-sign-source-1.c"
+
+/* { dg-final { scan-tree-dump-times "sat_trunc pattern recognized" 6 "vect" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-shift-sign-source-negative-1.c b/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-shift-sign-source-negative-1.c
new file mode 100644
index 00000000000..25925c96759
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-shift-sign-source-negative-1.c
@@ -0,0 +1,122 @@ 
+/* { dg-do compile { target bitint } } */
+/* { dg-options "-O3 -march=armv8-a" } */
+/* { dg-additional-options "-mmax-vectorization --param=vect-epilogues-nomask=0 -fdump-tree-vect-details" } */
+
+typedef __INT16_TYPE__ int16_t;
+typedef __INT32_TYPE__ int32_t;
+typedef __UINT16_TYPE__ uint16_t;
+typedef __UINT32_TYPE__ uint32_t;
+typedef unsigned _BitInt(12) uint12b_t;
+typedef unsigned _BitInt(17) uint17b_t;
+
+__attribute__((noipa))
+void
+different_source (int16_t *__restrict out, const int32_t *__restrict in,
+		  const int32_t *__restrict signs, int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      int32_t source = in[i];
+      int32_t shifted = source >> 8;
+      uint32_t range = (uint32_t) shifted + 32768U;
+      int32_t sign_source = signs[i];
+      out[i] = (range > 65535U
+		? (int16_t) (sign_source >> 31) ^ 32767
+		: (int16_t) shifted);
+    }
+}
+
+__attribute__((noipa))
+void
+logical_shift (int16_t *__restrict out, const uint32_t *__restrict in, int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      uint32_t source = in[i];
+      uint32_t shifted = source >> 8;
+      uint32_t range = shifted + 32768U;
+      out[i] = (range > 65535U
+		? (int16_t) ((int32_t) source >> 31) ^ 32767
+		: (int16_t) shifted);
+    }
+}
+
+__attribute__((noipa))
+void
+variable_count (int16_t *__restrict out, const int32_t *__restrict in,
+		const uint16_t *__restrict counts, int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      int32_t source = in[i];
+      int32_t shifted = source >> (counts[i] & 31);
+      uint32_t range = (uint32_t) shifted + 32768U;
+      out[i] = (range > 65535U
+		? (int16_t) (source >> 31) ^ 32767
+		: (int16_t) shifted);
+    }
+}
+
+__attribute__((noipa))
+void
+wrong_sign_count (int16_t *__restrict out, const int32_t *__restrict in,
+		  int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      int32_t source = in[i];
+      int32_t shifted = source >> 8;
+      uint32_t range = (uint32_t) shifted + 32768U;
+      out[i] = (range > 65535U
+		? (int16_t) (source >> 30) ^ 32767
+		: (int16_t) shifted);
+    }
+}
+
+__attribute__((noipa))
+void
+wrong_range (int16_t *__restrict out, const int32_t *__restrict in, int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      int32_t source = in[i];
+      int32_t shifted = source >> 8;
+      uint32_t range = (uint32_t) shifted + 32767U;
+      out[i] = (range > 65535U
+		? (int16_t) (source >> 31) ^ 32767
+		: (int16_t) shifted);
+    }
+}
+
+__attribute__((noipa))
+void
+narrow_range (int16_t *__restrict out, const int32_t *__restrict in, int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      int32_t source = in[i];
+      int32_t shifted = source >> 1;
+      uint17b_t range = (uint17b_t) shifted + (uint17b_t) 32768;
+      out[i] = (range > (uint17b_t) 65535
+		? (int16_t) (source >> 31) ^ 32767
+		: (int16_t) shifted);
+    }
+}
+
+__attribute__((noipa))
+void
+wrapped_output_range (int16_t *__restrict out,
+		      const int32_t *__restrict in, int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      int32_t source = in[i];
+      int32_t shifted = source >> 20;
+      uint12b_t range = (uint12b_t) shifted + (uint12b_t) 32767;
+      out[i] = (range > (uint12b_t) 65534
+		? (int16_t) (source >> 31) ^ 32767
+		: (int16_t) shifted);
+    }
+}
+
+/* { dg-final { scan-tree-dump-not "sat_trunc pattern recognized" "vect" } } */