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)