On Mon, Aug 24, 2026 at 9:02 AM <[email protected]> wrote:
>
> From: Kyrylo Tkachov <[email protected]>
>
> A VECTOR_CST can encode a stepped series without storing every lane.  The
> fold-const negate_expr_p checks only the encoded elements.  It can therefore
> approve a signed vector whose implicit final lane is INT_MIN.
>
>   typedef int v4si __attribute__ ((vector_size (16)));
>
>   const v4si c = { 2147483645, 2147483646, 2147483647,
>                    (-2147483647 - 1) };
>
>   v4si f (v4si x) { return (-x) - c; }
>   v4si g (v4si x) { return -(x + c); }
>   v4si h () { return -c; }
>
> The predicate lets f and g negate c.  It represents -INT_MIN as INT_MIN,
> which adds a false signed overflow for defined inputs.  fold_negate_expr_1
> can also negate the encoded elements of c directly.  This removes the
> required overflow diagnostic from h when overflow is instrumented.
>
> aarch64 -O2 -fsanitize=signed-integer-overflow
>         -fsanitize-trap=signed-integer-overflow before:
>
>   h:
>           adrp    x0, .LANCHOR0
>           ldr     q0, [x0, #:lo12:.LANCHOR0]
>           ret
>
> After:
>
>   h:
>           brk     #1000
>
> Reject a non-wrapping integral stepped vector in negate_expr_p.  Also keep a
> direct stepped integral negation when sanitizer instrumentation makes
> overflow observable.  Guard each TYPE_OVERFLOW_WRAPS query with its accepted
> integral type domain.  VECTOR_CST can also represent fixed-point vectors,
> which the wrapping predicate does not accept.  TYPE_OVERFLOW_SANITIZED
> accepts integral vectors.
>
> The two stepped checks have different conditions.  negate_expr_p is a proof
> for moving a negation.  Moving an implicit INT_MIN negation can introduce
> undefined overflow into f or g even when no overflow option is enabled, so
> that predicate rejects every non-wrapping integral stepped constant.
> fold_negate_expr_1 handles a direct source negation whose INT_MIN lane already
> overflows.  GCC normally folds that constant overflow, and -ftrapv does not
> trap vector constant negation.  Only sanitizer instrumentation makes the
> missing operation observable, so the direct-fold check is conditional on
> TYPE_OVERFLOW_SANITIZED.
>
> The sanitizer test checks that f and g do not report overflow for defined
> inputs.  It also checks that h reports its real INT_MIN negation.  The Arm
> test checks all six fixed-point vector modes in ordinary and saturating forms.
> Without the type-domain guards, a checking compiler fails.  A release compiler
> also changes the results of the six saturating cases under -fwrapv.
>
> Bootstrapped and tested on aarch64-none-linux-gnu.
> Tested on arm-linux-gnueabihf with QEMU.
> Tested on x86_64-pc-linux-gnu.
> Ok for trunk?
> Thanks,
> Kyrill
>
> gcc/ChangeLog:
>
>         * fold-const.cc (negate_expr_p): Reject non-wrapping stepped vector
>         constants.
>         (fold_negate_expr_1): Preserve sanitized stepped integral negations.
>         * match.pd (negate_expr_p): Guard the wrapping query for vector
>         constants.
>
> gcc/testsuite/ChangeLog:
>
>         * g++.dg/ubsan/fold-negate-vector-1.C: New test.
>         * gcc.target/arm/fixed-point-vector-negate-1.c: Likewise.
>
> Signed-off-by: Kyrylo Tkachov <[email protected]>
> ---
>  gcc/fold-const.cc                             | 12 ++-
>  gcc/match.pd                                  |  3 +-
>  .../g++.dg/ubsan/fold-negate-vector-1.C       | 46 +++++++++++
>  .../arm/fixed-point-vector-negate-1.c         | 76 +++++++++++++++++++
>  4 files changed, 134 insertions(+), 3 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/ubsan/fold-negate-vector-1.C
>  create mode 100644 gcc/testsuite/gcc.target/arm/fixed-point-vector-negate-1.c
>
> diff --git a/gcc/fold-const.cc b/gcc/fold-const.cc
> index 420e3185a2a..dc324c9cfdb 100644
> --- a/gcc/fold-const.cc
> +++ b/gcc/fold-const.cc
> @@ -406,10 +406,14 @@ negate_expr_p (tree t)
>
>      case VECTOR_CST:
>        {
> -       if (FLOAT_TYPE_P (TREE_TYPE (type)) || TYPE_OVERFLOW_WRAPS (type))
> +       if (FLOAT_TYPE_P (TREE_TYPE (type))
> +           || (ANY_INTEGRAL_TYPE_P (type) && TYPE_OVERFLOW_WRAPS (type)))
>           return true;
>
> -       /* Steps don't prevent negation.  */
> +       /* An implicit element of a stepped vector can be the minimum
> +          value.  */
> +       if (VECTOR_CST_STEPPED_P (t))

So if for example we encode { 1, +, 2 } then INT_MIN cannot be reached
without overflow.  Do we allow overflows in the stepped evaluation?  Likewise
if it is { 0, +, -3 } then INT_MIN is not reached, but -2147483647 and then
it would overflow.

So, can we improve this?

> +         return false;
>         unsigned int count = vector_cst_encoded_nelts (t);
>         for (unsigned int i = 0; i < count; ++i)
>           if (!negate_expr_p (VECTOR_CST_ENCODED_ELT (t, i)))
> @@ -566,6 +570,10 @@ fold_negate_expr_1 (location_t loc, tree t)
>
>      case VECTOR_CST:
>        {
> +       if (VECTOR_CST_STEPPED_P (t)
> +           && TYPE_OVERFLOW_SANITIZED (type))
> +         return NULL_TREE;
> +
>         tree_vector_builder elts;
>         elts.new_unary_operation (type, t, true);
>         unsigned int count = elts.encoded_nelts ();
> diff --git a/gcc/match.pd b/gcc/match.pd
> index bcfc7f0e8ce..a969aa20417 100644
> --- a/gcc/match.pd
> +++ b/gcc/match.pd
> @@ -2430,7 +2430,8 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
>     ways.  */
>  (match negate_expr_p
>   VECTOR_CST
> - (if (FLOAT_TYPE_P (TREE_TYPE (type)) || TYPE_OVERFLOW_WRAPS (type))))
> + (if (FLOAT_TYPE_P (TREE_TYPE (type))
> +      || (ANY_INTEGRAL_TYPE_P (type) && TYPE_OVERFLOW_WRAPS (type)))))
>  (match negate_expr_p
>   (minus @0 @1)
>   (if ((ANY_INTEGRAL_TYPE_P (type) && TYPE_OVERFLOW_WRAPS (type))
> diff --git a/gcc/testsuite/g++.dg/ubsan/fold-negate-vector-1.C 
> b/gcc/testsuite/g++.dg/ubsan/fold-negate-vector-1.C
> new file mode 100644
> index 00000000000..cda0fd9817f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ubsan/fold-negate-vector-1.C
> @@ -0,0 +1,46 @@
> +// { dg-do run { target int32 } }
> +// { dg-options "-O2 -Wno-psabi -fsanitize=signed-integer-overflow" }
> +
> +#define INT_MAX __INT_MAX__
> +#define INT_MIN (-INT_MAX - 1)
> +
> +typedef int v4si __attribute__ ((vector_size (16)));
> +
> +const v4si c = { INT_MAX - 2, INT_MAX - 1, INT_MAX, INT_MIN };
> +
> +v4si __attribute__ ((noipa))
> +f (v4si x)
> +{
> +  return (-x) - c;
> +}
> +
> +v4si __attribute__ ((noipa))
> +g (v4si x)
> +{
> +  return -(x + c);
> +}
> +
> +v4si __attribute__ ((noipa))
> +h ()
> +{
> +  return -c;
> +}
> +
> +int
> +main ()
> +{
> +  v4si x = { 0, 0, 0, 1 };
> +  v4si y = f (x);
> +  if (y[3] != INT_MAX)
> +    __builtin_abort ();
> +
> +  y = g (x);
> +  if (y[3] != INT_MAX)
> +    __builtin_abort ();
> +
> +  volatile v4si z = h ();
> +  if (z[3] != INT_MIN)
> +    __builtin_abort ();
> +}
> +
> +// { dg-output "negation of -2147483648 cannot be represented in type 'int'; 
> cast to an unsigned type to negate this value to itself" }
> diff --git a/gcc/testsuite/gcc.target/arm/fixed-point-vector-negate-1.c 
> b/gcc/testsuite/gcc.target/arm/fixed-point-vector-negate-1.c
> new file mode 100644
> index 00000000000..e34f9633929
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/arm/fixed-point-vector-negate-1.c
> @@ -0,0 +1,76 @@
> +/* { dg-do run { target fixed_point } } */
> +/* { dg-require-effective-target arm_arch_v6_arm_multilib } */
> +/* { dg-options "-O2 -std=gnu99 -fwrapv" } */
> +/* { dg-add-options arm_arch_v6_arm } */
> +
> +#include <stdfix.h>
> +
> +/* Check all Arm fixed-point vector modes and their saturating forms.  The
> +   saturating cases make an invalid X - C to X + (-C) rewrite observable.  */
> +
> +#define DEF2(NAME, TYPE, X, C, R)                                    \
> +  typedef TYPE NAME##_type __attribute__ ((vector_size (4)));        \
> +  static void __attribute__ ((noipa))                                \
> +  NAME (NAME##_type *out, const NAME##_type *in)                     \
> +  {                                                                 \
> +    *out = *in - (NAME##_type) { C, C };                             \
> +  }                                                                 \
> +  static void                                                       \
> +  check_##NAME (void)                                               \
> +  {                                                                 \
> +    NAME##_type in = { X, X };                                      \
> +    NAME##_type out;                                                \
> +    NAME (&out, &in);                                               \
> +    if (out[0] != R || out[1] != R)                                 \
> +      __builtin_abort ();                                           \
> +  }
> +
> +#define DEF4(NAME, TYPE, X, C, R)                                    \
> +  typedef TYPE NAME##_type __attribute__ ((vector_size (4)));        \
> +  static void __attribute__ ((noipa))                                \
> +  NAME (NAME##_type *out, const NAME##_type *in)                     \
> +  {                                                                 \
> +    *out = *in - (NAME##_type) { C, C, C, C };                       \
> +  }                                                                 \
> +  static void                                                       \
> +  check_##NAME (void)                                               \
> +  {                                                                 \
> +    NAME##_type in = { X, X, X, X };                                \
> +    NAME##_type out;                                                \
> +    NAME (&out, &in);                                               \
> +    if (out[0] != R || out[1] != R || out[2] != R || out[3] != R)   \
> +      __builtin_abort ();                                           \
> +  }
> +
> +DEF4 (v4qq, short _Fract, 0.5hr, 0.25hr, 0.25hr)
> +DEF4 (v4uqq, unsigned short _Fract, 0.5uhr, 0.25uhr, 0.25uhr)
> +DEF4 (sat_v4qq, _Sat short _Fract, SFRACT_MIN, SFRACT_MIN, 0.0hr)
> +DEF4 (sat_v4uqq, _Sat unsigned short _Fract, 0.5uhr, 0.5uhr, 0.0uhr)
> +
> +DEF2 (v2hq, _Fract, 0.5r, 0.25r, 0.25r)
> +DEF2 (v2uhq, unsigned _Fract, 0.5ur, 0.25ur, 0.25ur)
> +DEF2 (sat_v2hq, _Sat _Fract, FRACT_MIN, FRACT_MIN, 0.0r)
> +DEF2 (sat_v2uhq, _Sat unsigned _Fract, 0.5ur, 0.5ur, 0.0ur)
> +
> +DEF2 (v2ha, short _Accum, 0.5hk, 0.25hk, 0.25hk)
> +DEF2 (v2uha, unsigned short _Accum, 0.5uhk, 0.25uhk, 0.25uhk)
> +DEF2 (sat_v2ha, _Sat short _Accum, SACCUM_MIN, SACCUM_MIN, 0.0hk)
> +DEF2 (sat_v2uha, _Sat unsigned short _Accum, 0.5uhk, 0.5uhk, 0.0uhk)
> +
> +int
> +main (void)
> +{
> +  check_v4qq ();
> +  check_v4uqq ();
> +  check_sat_v4qq ();
> +  check_sat_v4uqq ();
> +  check_v2hq ();
> +  check_v2uhq ();
> +  check_sat_v2hq ();
> +  check_sat_v2uhq ();
> +  check_v2ha ();
> +  check_v2uha ();
> +  check_sat_v2ha ();
> +  check_sat_v2uha ();
> +  return 0;
> +}
> --
> 2.50.1 (Apple Git-155)
>

Reply via email to