| Message ID | 20260902145254.77832-11-ktkachov@nvidia.com |
|---|---|
| State | New |
| Headers | show |
| Series | Saturating arithmetic matching improvements | expand |
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 --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" } } */