]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
tree-optimization/126415 - perform inverse converted +- lookup in VN
authorPhilipp Tomsich <philipp.tomsich@vrull.eu>
Tue, 28 Jul 2026 13:28:33 +0000 (15:28 +0200)
committerPhilipp Tomsich <philipp.tomsich@vrull.eu>
Wed, 29 Jul 2026 12:00:28 +0000 (14:00 +0200)
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.

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
gcc/testsuite/gcc.dg/pr124545.c
gcc/testsuite/gcc.dg/torture/pr126415.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/tree-ssa/ssa-fre-113.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/tree-ssa/ssa-fre-114.c [new file with mode: 0644]
gcc/tree-ssa-sccvn.cc

index f6ecee41509e360e430da5e0339f998c069495ba..6f2d96c034b4603aaff82455ef66e38876c94176 100644 (file)
@@ -4087,38 +4087,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)
index a21346b179c7b2b807224a2654703c1bf9a15b40..954aeff434a4db785e0d5b2264095efe921448c5 100644 (file)
@@ -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 (file)
index 0000000..7120704
--- /dev/null
@@ -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 (file)
index 0000000..f04e3f3
--- /dev/null
@@ -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 (file)
index 0000000..554f09d
--- /dev/null
@@ -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" } } */
index a95350abccbf4725c8798b3d34e08e0a0e6b5527..840bcf70e0a6f9bb3d995d6c3b7e3e2d78e7202d 100644 (file)
@@ -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