On Tue, 28 Jul 2026, Philipp Tomsich wrote:

> The match.pd rewrite (T)a +- X -> (T)(a +- X') from
> r17-2078-g8395fa7c79eecf creates the narrow operation, assuming the
> absence of overflow for an operation the program does not execute;
> this results in wrong code (PR126415).  Revert it and implement the
> equivalence in visit_nary_op instead.
> 
> visit_nary_op value-numbers (T)(a +- b) <- (T)a +- (T)b.  Add the
> inverse, (T)a +- X <- (T)(a +- X'), looking up the narrow a +- X'
> and converting the result.  This makes the equivalence independent
> of the order the two forms appear in the IL.
> 
> The transform is valid for sign changes and for widening conversions
> from a type with undefined overflow when the narrow operation
> dominates the statement being visited.  The narrow operation is only
> looked up, never created.  X may be an integer constant that narrows
> and extends back unchanged, or a conversion from the same narrow
> type.
> 
> The sign-change case makes the fold apply to ilp32 targets as well;
> remove the ilp32 xfail from the pr124545.c scan (PR116845).
> 
> Bootstrapped and regression-tested on x86_64-pc-linux-gnu

OK.

Thanks,
Richard.

> 
>       PR tree-optimization/126415
>       PR tree-optimization/124545
> 
> gcc/ChangeLog:
> 
>       * match.pd ((T)A +- CST -> (T)(A +- CST')): Revert.
>       * tree-ssa-sccvn.cc (ssa_integral_conversion_op): New function.
>       (vn_nary_result_avail_or_insertable_p): New function, split out
>       from ...
>       (visit_nary_op): ... here.  Handle ((T)p) +- X by looking up
>       (p +- X') and converting the result, for X an integer constant
>       that narrows and extends back unchanged or a conversion from
>       the same narrow type.
> 
> gcc/testsuite/ChangeLog:
> 
>       * gcc.dg/torture/pr126415.c: New testcase.
>       * gcc.dg/tree-ssa/ssa-fre-113.c: New testcase.
>       * gcc.dg/tree-ssa/ssa-fre-114.c: New testcase.
>       * gcc.dg/pr124545.c: Remove the ilp32 xfail.
> ---
>  gcc/match.pd                                |  32 ------
>  gcc/testsuite/gcc.dg/pr124545.c             |   3 +-
>  gcc/testsuite/gcc.dg/torture/pr126415.c     |  61 +++++++++++
>  gcc/testsuite/gcc.dg/tree-ssa/ssa-fre-113.c |  14 +++
>  gcc/testsuite/gcc.dg/tree-ssa/ssa-fre-114.c |  22 ++++
>  gcc/tree-ssa-sccvn.cc                       | 107 ++++++++++++++++++--
>  6 files changed, 196 insertions(+), 43 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/torture/pr126415.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/ssa-fre-113.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/ssa-fre-114.c
> 
> diff --git a/gcc/match.pd b/gcc/match.pd
> index 62d080931253..8e57c6be855e 100644
> --- a/gcc/match.pd
> +++ b/gcc/match.pd
> @@ -4099,38 +4099,6 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
>         (plus (convert @0) (op @2 (convert @1))))))
>  #endif
>  
> -/* Inverse of the above: (T)(A) +- CST -> (T)(A +- CST') when T is a
> -   widening conversion from a type with undefined overflow and the outer
> -   type wraps.  This allows VN to discover that (T)A + (T)C == (T)(A + C)
> -   regardless of which form appears first in program order.  PR124545.
> -   The rewrite is unsound for unsigned inner types: the narrow op wraps
> -   mod 2^prec (defined) while the widened op does not, changing the
> -   observed value.  Cover the unsigned case separately once ranger can
> -   prove no wrap.  */
> -#if GIMPLE
> -  (for op (plus minus)
> -   (simplify
> -    (op (convert @0) INTEGER_CST@1)
> -     (if (TREE_CODE (TREE_TYPE (@0)) == INTEGER_TYPE
> -       && TREE_CODE (type) == INTEGER_TYPE
> -       && TYPE_PRECISION (type) > TYPE_PRECISION (TREE_TYPE (@0))
> -       && TYPE_OVERFLOW_UNDEFINED (TREE_TYPE (@0))
> -       && !TYPE_OVERFLOW_SANITIZED (TREE_TYPE (@0))
> -       && TYPE_OVERFLOW_WRAPS (type)
> -       /* CST must be the sign-extension of its low inner-precision bits,
> -          otherwise narrowing changes the value.  Use min_precision (..,
> -          SIGNED) rather than int_fits_type_p so that small negative offsets
> -          encoded as large unsigned constants (e.g. -1 as sizetype) still
> -          qualify.  */
> -       && wi::min_precision (wi::to_wide (@1), SIGNED)
> -          <= TYPE_PRECISION (TREE_TYPE (@0)))
> -       (with {
> -       wide_int c1 = wi::to_wide (@1);
> -       tree inner_cst = wide_int_to_tree (TREE_TYPE (@0),
> -                          wi::sext (c1, TYPE_PRECISION (TREE_TYPE (@0)))); }
> -     (convert (op! @0 { inner_cst; }))))))
> -#endif
> -
>  /* (T)(A) +- (T)(B) -> (T)(A +- B) only when (A +- B) could be simplified
>     to a simple value.  */
>    (for op (plus minus)
> diff --git a/gcc/testsuite/gcc.dg/pr124545.c b/gcc/testsuite/gcc.dg/pr124545.c
> index a21346b179c7..954aeff434a4 100644
> --- a/gcc/testsuite/gcc.dg/pr124545.c
> +++ b/gcc/testsuite/gcc.dg/pr124545.c
> @@ -25,5 +25,4 @@ int func4(int *a, int j) {
>  }
>  
>  /* All four functions should fold to return 1 after FRE.  */
> -/* The pattern is not applied on ilp32 targets (PR116845).  */
> -/* { dg-final { scan-tree-dump-times "return 1;" 4 "fre1" { xfail { ilp32 } 
> } } } */
> +/* { dg-final { scan-tree-dump-times "return 1;" 4 "fre1" } } */
> diff --git a/gcc/testsuite/gcc.dg/torture/pr126415.c 
> b/gcc/testsuite/gcc.dg/torture/pr126415.c
> new file mode 100644
> index 000000000000..7120704cb60a
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/pr126415.c
> @@ -0,0 +1,61 @@
> +/* PR tree-optimization/126415 */
> +/* Wrong code from the inverse widening rewrite (T)A +- CST -> (T)(A +- 
> CST'):
> +   the introduced narrow signed operation may overflow even though the
> +   original widened operation is fully defined.  The narrow op then collapses
> +   through defined-wrap identities (mod 2^narrow-prec), changing the value.  
> */
> +/* { dg-do run } */
> +
> +int printf(const char *, ...);
> +
> +long d(short p1, unsigned e, char f) {
> +  if (246 >= 149u - f)
> +    return 0;
> +  return f;
> +}
> +int g(char p1) { return d(0, 0, p1); }
> +int fn3(char p1) {
> +  long i = g(p1 + 159);
> +  return i;
> +}
> +
> +/* Minimal variants: signed char/short/int inner, PLUS and MINUS, and a
> +   negative CST encoded as a large unsigned constant.  Use signed char
> +   explicitly: the checks encode sign-extension results and plain char
> +   is unsigned on some targets.  */
> +volatile signed char vc1 = -84;
> +volatile signed char vc2 = 50;
> +volatile short vs = -21000;
> +volatile int vi = -2000000000;
> +volatile signed char vc3 = 100;
> +
> +int main() {
> +  if (fn3(-84) != 0)
> +    __builtin_abort ();
> +
> +  signed char p1 = vc1;
> +  signed char f1 = (signed char)((unsigned char)p1 + 159);
> +  if ((unsigned)f1 + 97 != 172u)
> +    __builtin_abort ();
> +
> +  signed char p2 = vc2;
> +  signed char f2 = (signed char)((unsigned char)p2 + 97);
> +  if ((unsigned)f2 - 97 != 4294967090u)
> +    __builtin_abort ();
> +
> +  short p3 = vs;
> +  short f3 = (short)((unsigned short)p3 + 40000);
> +  if ((unsigned)f3 + 25536 != 44536u)
> +    __builtin_abort ();
> +
> +  int p4 = vi;
> +  int f4 = (int)((unsigned)p4 + 3000000000u);
> +  if ((unsigned long long)f4 + 1294967296ull != 2294967296ull)
> +    __builtin_abort ();
> +
> +  signed char p5 = vc3;
> +  signed char f5 = (signed char)((unsigned char)p5 + 97);
> +  if ((unsigned)f5 + 0xFFFFFF9Fu != 4294967140u)
> +    __builtin_abort ();
> +
> +  return 0;
> +}
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/ssa-fre-113.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/ssa-fre-113.c
> new file mode 100644
> index 000000000000..f04e3f3bf0c1
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/ssa-fre-113.c
> @@ -0,0 +1,14 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O -fdump-tree-fre1" } */
> +
> +int func1(int *a, int j) {
> +  int k = j - 1;
> +  return a[j - 1] == a[k];
> +}
> +
> +int func2(int *a, int j) {
> +  int k = j - 1;
> +  return a[k] == a[j-1];
> +}
> +
> +/* { dg-final { scan-tree-dump-times "return 1;" 2 "fre1" } } */
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/ssa-fre-114.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/ssa-fre-114.c
> new file mode 100644
> index 000000000000..554f09dae9d0
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/ssa-fre-114.c
> @@ -0,0 +1,22 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O -fdump-tree-fre1" } */
> +
> +/* Variable-addend variant of ssa-fre-113.c: the narrow operation is
> +   seen first, the widened form of it second.  Uses __PTRDIFF_TYPE__ so
> +   the test degenerates gracefully on ilp32 targets.  */
> +
> +int func1(int *a, int j, int i) {
> +  int k = j + i;
> +  int x = a[k];
> +  __PTRDIFF_TYPE__ idx = (__PTRDIFF_TYPE__)j + i;
> +  return x == a[idx];
> +}
> +
> +int func2(int *a, int j, int i) {
> +  int k = j - i;
> +  int x = a[k];
> +  __PTRDIFF_TYPE__ idx = (__PTRDIFF_TYPE__)j - (__PTRDIFF_TYPE__)i;
> +  return x == a[idx];
> +}
> +
> +/* { dg-final { scan-tree-dump-times "return 1;" 2 "fre1" } } */
> diff --git a/gcc/tree-ssa-sccvn.cc b/gcc/tree-ssa-sccvn.cc
> index a95350abccbf..840bcf70e0a6 100644
> --- a/gcc/tree-ssa-sccvn.cc
> +++ b/gcc/tree-ssa-sccvn.cc
> @@ -5642,6 +5642,37 @@ valueized_wider_op (tree wide_type, tree op, bool 
> allow_truncate)
>    return NULL_TREE;
>  }
>  
> +/* Return true if RESULT, the result of a value-number lookup, may be
> +   used at the statement being visited.  A result of wrapping type can
> +   be inserted for code hoisting without introducing undefined
> +   overflow; anything else has to be available.  See PR86554.  */
> +
> +static bool
> +vn_nary_result_avail_or_insertable_p (tree result)
> +{
> +  return (TYPE_OVERFLOW_WRAPS (TREE_TYPE (result))
> +       || (rpo_avail && vn_context_bb
> +           && rpo_avail->eliminate_avail (vn_context_bb, result)));
> +}
> +
> +/* If OP is an SSA name defined by a conversion from an integral type,
> +   return the valueized source of the conversion, otherwise return
> +   NULL_TREE.  */
> +
> +static tree
> +ssa_integral_conversion_op (tree op)
> +{
> +  if (TREE_CODE (op) != SSA_NAME)
> +    return NULL_TREE;
> +  gassign *def = dyn_cast <gassign *> (SSA_NAME_DEF_STMT (op));
> +  if (!def || !CONVERT_EXPR_CODE_P (gimple_assign_rhs_code (def)))
> +    return NULL_TREE;
> +  const tree src = gimple_assign_rhs1 (def);
> +  if (!INTEGRAL_TYPE_P (TREE_TYPE (src)))
> +    return NULL_TREE;
> +  return vn_valueize (src);
> +}
> +
>  /* Visit a nary operator RHS, value number it, and return true if the
>     value number of LHS has changed as a result.  */
>  
> @@ -5698,15 +5729,7 @@ visit_nary_op (tree lhs, gassign *stmt)
>                 ops[0] = vn_nary_op_lookup_pieces
>                     (2, gimple_assign_rhs_code (def), type, ops, NULL);
>                 /* We have wider operation available.  */
> -               if (ops[0]
> -                   /* If the leader is a wrapping operation we can
> -                      insert it for code hoisting w/o introducing
> -                      undefined overflow.  If it is not it has to
> -                      be available.  See PR86554.  */
> -                   && (TYPE_OVERFLOW_WRAPS (TREE_TYPE (ops[0]))
> -                       || (rpo_avail && vn_context_bb
> -                           && rpo_avail->eliminate_avail (vn_context_bb,
> -                                                          ops[0]))))
> +               if (ops[0] && vn_nary_result_avail_or_insertable_p (ops[0]))
>                   {
>                     unsigned lhs_prec = TYPE_PRECISION (type);
>                     unsigned rhs_prec = TYPE_PRECISION (TREE_TYPE (rhs1));
> @@ -5747,6 +5770,72 @@ visit_nary_op (tree lhs, gassign *stmt)
>           }
>       }
>        break;
> +    case PLUS_EXPR:
> +    case MINUS_EXPR:
> +      {
> +     /* Match (T)A +- B against an existing (T)(A +- B'), the inverse
> +        of the conversion case above, so the redundancy is detected
> +        regardless of the order the two forms appear in the IL.
> +        See PR124545.  The narrow operation is only ever looked up,
> +        never created: assuming no overflow is only valid for
> +        operations the program actually executes, so the narrow
> +        leader has to be available.  Creating the narrow operation
> +        instead is wrong-code, see PR126415.  */
> +     const tree narrow1 = ssa_integral_conversion_op (vn_valueize (rhs1));
> +     if (!INTEGRAL_TYPE_P (type) || !narrow1)
> +       break;
> +     const tree ntype = TREE_TYPE (narrow1);
> +     /* A sign-change keeps the value bit-identical; a widening is
> +        only handled when the narrow operation cannot wrap.  */
> +     const bool sign_change_p
> +       = TYPE_PRECISION (ntype) == TYPE_PRECISION (type);
> +     const bool nowrap_widening_p
> +       = (TYPE_PRECISION (ntype) < TYPE_PRECISION (type)
> +          && TYPE_OVERFLOW_UNDEFINED (ntype));
> +     if (!sign_change_p && !nowrap_widening_p)
> +       break;
> +     /* Determine the narrow variant of the second operand: a
> +        constant that narrows and extends back unchanged, or a
> +        conversion from the same narrow type.  */
> +     const tree rhs2 = gimple_assign_rhs2 (stmt);
> +     tree narrow2 = NULL_TREE;
> +     if (TREE_CODE (rhs2) == INTEGER_CST)
> +       {
> +         const widest_int cst = wi::to_widest (rhs2);
> +         const widest_int narrowed
> +           = wi::ext (cst, TYPE_PRECISION (ntype), TYPE_SIGN (ntype));
> +         const widest_int extended
> +           = wi::ext (narrowed, TYPE_PRECISION (type), TYPE_SIGN (type));
> +         if (cst == extended)
> +           narrow2 = fold_convert (ntype, rhs2);
> +       }
> +     else if (TREE_CODE (rhs2) == SSA_NAME)
> +       {
> +         const tree op = ssa_integral_conversion_op (vn_valueize (rhs2));
> +         if (op && types_compatible_p (TREE_TYPE (op), ntype))
> +           narrow2 = op;
> +       }
> +     if (!narrow2)
> +       break;
> +     tree ops[3] = { narrow1, narrow2 };
> +     const tree narrow_val
> +       = vn_nary_op_lookup_pieces (2, code, ntype, ops, NULL);
> +     /* We have a narrower or sign-changed operation available.  */
> +     if (narrow_val && vn_nary_result_avail_or_insertable_p (narrow_val))
> +       {
> +         gimple_match_op match_op (gimple_match_cond::UNCOND,
> +                                   NOP_EXPR, type, narrow_val);
> +         result = vn_nary_build_or_lookup (&match_op);
> +         if (result)
> +           {
> +             const bool changed = set_ssa_val_to (lhs, result);
> +             if (TREE_CODE (result) == SSA_NAME)
> +               vn_nary_op_insert_stmt (stmt, result);
> +             return changed;
> +           }
> +       }
> +      }
> +      break;
>      case BIT_AND_EXPR:
>        if (INTEGRAL_TYPE_P (type)
>         && TREE_CODE (rhs1) == SSA_NAME
> 

-- 
Richard Biener <[email protected]>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Jochen Jaser, Andrew McDonald, Abhinav Puri; (HRB 36809, AG Nuernberg)

Reply via email to