https://gcc.gnu.org/g:1dbe8802bdd361e2274d1c0bec5e75e09ad0bc64

commit r17-5261-g1dbe8802bdd361e2274d1c0bec5e75e09ad0bc64
Author: Kyrylo Tkachov <[email protected]>
Date:   Thu Oct 15 20:09:21 2026 +0200

    match-sat-alu.pd: Fix a constant check in signed SAT_ADD [PR127357]
    
    The form
    
      T SUM = (T) ((UT) X + (UT) IMM);
      ((X ^ SUM) & (X ^ C3)) < 0 ? (-(T) (X < 0) ^ MAX) : SUM
    
    is a signed saturating add only when (X ^ C3) < 0 tests that X has the
    sign of IMM, that is when C3 and IMM have opposite signs.  The source
    form has C3 == ~IMM.  The pattern, added in r15-4994, checks
    (IMM & C3) == 0, which also accepts IMM = 1, C3 = 2:
    
      int
      s_sat (int x)
      {
        int sum = (int) ((unsigned) x + 1u);
        if (((x ^ sum) & (x ^ 2)) < 0)
          return -(int) (x < 0) ^ INT_MAX;
        return sum;
      }
    
      _4 = x_10(D) ^ 2;
      _5 = _3 & _4;
      if (_5 < 0)
      ...
      # _9 = PHI <_12(3), sum_11(2)>
    
    widening_mul replaces the PHI with _9 = .SAT_ADD (x_10(D), 1), so
    s_sat (INT_MAX) returns INT_MAX instead of INT_MIN.  The vectorizer uses
    the same matcher, which is how the PR127357 testcase fails at -O3.
    s_sat in PR127916 is the same bug.
    
    Compare the sign bits of the two constants instead.  This also accepts
    valid pairs that the old check rejected, such as IMM = 1, C3 = -3.
    
    Bootstrapped and tested as part of the series on
    aarch64-unknown-linux-gnu and x86_64-pc-linux-gnu.
    
            PR tree-optimization/127357
            PR tree-optimization/127916
    
    gcc/ChangeLog:
    
            * match-sat-alu.pd (signed_integer_sat_add): Require opposite signs
            for the two constants of the single AND IMM form.
    
    gcc/testsuite/ChangeLog:
    
            * gcc.dg/torture/pr127357.c: New test.
    
    Signed-off-by: Kyrylo Tkachov <[email protected]>

Diff:
---
 gcc/match-sat-alu.pd                    |  4 ++-
 gcc/testsuite/gcc.dg/torture/pr127357.c | 62 +++++++++++++++++++++++++++++++++
 2 files changed, 65 insertions(+), 1 deletion(-)

diff --git a/gcc/match-sat-alu.pd b/gcc/match-sat-alu.pd
index 5f3389a4ab61..236d6fb2e197 100644
--- a/gcc/match-sat-alu.pd
+++ b/gcc/match-sat-alu.pd
@@ -480,7 +480,9 @@ along with GCC; see the file COPYING3.  If not see
                        (bit_xor:c @0 INTEGER_CST@3)) integer_zerop)
         (signed_integer_sat_val @0)
         @2)
-  (if (wi::bit_and (wi::to_wide (@1), wi::to_wide (@3)) == 0)))
+  /* (X ^ C3) < 0 has to test that X has the sign of IMM, so C3 and IMM
+     need opposite signs.  The source form above has C3 == ~IMM.  */
+  (if (tree_int_cst_sign_bit (@1) != tree_int_cst_sign_bit (@3))))
 
 (match (signed_integer_sat_add @0 @1)
   /* T SUM = (T)((UT)X + (UT)-1);
diff --git a/gcc/testsuite/gcc.dg/torture/pr127357.c 
b/gcc/testsuite/gcc.dg/torture/pr127357.c
new file mode 100644
index 000000000000..0505b7b392c1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/pr127357.c
@@ -0,0 +1,62 @@
+/* PR tree-optimization/127357 */
+/* PR tree-optimization/127916 */
+/* { dg-do run } */
+
+/* ((X ^ SUM) & (X ^ 2)) < 0 tests X < 0 && SUM >= 0, which is not the
+   overflow test of X + 1.  */
+
+__attribute__((noipa)) int
+s_sat (int x)
+{
+  int sum = (int) ((unsigned) x + 1u);
+  if (((x ^ sum) & (x ^ 2)) < 0)
+    return -(int) (x < 0) ^ __INT_MAX__;
+  return sum;
+}
+
+static signed char
+idiom (signed char x)
+{
+  signed char sum = (signed char) ((unsigned char) x + 1);
+  signed char c = (signed char) ((x ^ sum) & (x ^ 2));
+  signed char satval = (signed char) (-(signed char) (x < 0) ^ 127);
+  return c < 0 ? satval : sum;
+}
+
+#define N 256
+signed char in[N], out[N];
+
+__attribute__((noipa)) void
+loop (void)
+{
+  for (int i = 0; i < N; i++)
+    out[i] = idiom (in[i]);
+}
+
+/* (X ^ -3) < 0 tests X >= 0, so this is a saturating addition.  */
+
+__attribute__((noipa)) int
+sat (int x)
+{
+  int sum = (int) ((unsigned) x + 1u);
+  if (((x ^ sum) & (x ^ -3)) < 0)
+    return -(int) (x < 0) ^ __INT_MAX__;
+  return sum;
+}
+
+int
+main (void)
+{
+  if (s_sat (__INT_MAX__) != -__INT_MAX__ - 1
+      || s_sat (-1) != -__INT_MAX__ - 1 || s_sat (1) != 2)
+    __builtin_abort ();
+  for (int i = 0; i < N; i++)
+    in[i] = i - 128;
+  loop ();
+  for (int i = 0; i < N; i++)
+    if (out[i] != (in[i] == -1 ? -128 : (signed char) (in[i] + 1)))
+      __builtin_abort ();
+  if (sat (__INT_MAX__) != __INT_MAX__ || sat (-1) != 0 || sat (5) != 6)
+    __builtin_abort ();
+  return 0;
+}

Reply via email to