https://gcc.gnu.org/g:c07fc7b5c69884adbfea21c3db08a9583f664377

commit r17-3040-gc07fc7b5c69884adbfea21c3db08a9583f664377
Author: Kyrylo Tkachov <[email protected]>
Date:   Thu Aug 6 16:29:25 2026 +0200

    match: fold two idioms built from the negation of a value
    
    X | -X has every bit from the lowest set bit of X upwards, so adding X to
    it clears that bit, and masking with it isolates the padding needed to
    round X up:
    
      X + (X | -X) -> X & (X - 1)
      X + ((-X) & (C - 1)) -> (X + C - 1) & -C   for a power of two C
    
    The second is the alignment round up written with the padding computed
    first, which is how allocators tend to spell it.
    
    Neither needs a wrapping type.  X - 1 overflows only for the most
    negative value, where the source already does, and rounding X up is
    representable exactly when X + C - 1 is, because the largest multiple of
    C below the maximum leaves room for C - 1.  Restrict both rules to integral
    types.  The bitwise operations also accept fixed-point types, whose
    saturating arithmetic does not have these integer semantics.
    
      int f (int x) { return x + ((-x) & 15); }
    
    aarch64 -O2:
    
      before                          after
        neg   w1, w0                    add   w0, w0, 15
        and   w1, w1, 15                and   w0, w0, -16
        add   w0, w1, w0
    
    The vector spelling folds too, a uniform vector constant is matched with
    uniform_integer_cst_p.
    
    Keep trapping and sanitized negations.  Also keep the source addition
    of X + (X | -X) under overflow sanitization because the replacement
    would remove one recoverable diagnostic.  Use :s on the consumed bitwise
    expression.  This prevents new work when it remains live, but allows the
    fold when the replacement arithmetic is already available.
    
    Reuse the matched uniform constant and test its wide value directly.
    This avoids rebuilding the same constant only to inspect it.
    
    Bootstrapped and tested on aarch64-none-linux-gnu.
    
    gcc/ChangeLog:
    
            * match.pd (X + (X | -X)): New simplification.
            (X + ((-X) & (C - 1))): Likewise.
    
    gcc/testsuite/ChangeLog:
    
            * gcc.dg/tree-ssa/signbit-1.c: New test.
            * gcc.dg/tree-ssa/alignup-2.c: New test.
            * gcc.dg/tree-ssa/vector-alignup-1.c: New test.
            * gcc.dg/tree-ssa/alignup-overflow-1.c: New test.
            * gcc.dg/tree-ssa/alignup-overflow-2.c: New test.
            * gcc.dg/tree-ssa/signbit-4.c: New test.
    
    Signed-off-by: Kyrylo Tkachov <[email protected]>

Diff:
---
 gcc/match.pd                                       | 23 +++++++++++++++++
 gcc/testsuite/gcc.dg/tree-ssa/alignup-2.c          | 29 ++++++++++++++++++++++
 gcc/testsuite/gcc.dg/tree-ssa/alignup-overflow-1.c | 10 ++++++++
 gcc/testsuite/gcc.dg/tree-ssa/alignup-overflow-2.c | 10 ++++++++
 gcc/testsuite/gcc.dg/tree-ssa/signbit-1.c          | 26 +++++++++++++++++++
 gcc/testsuite/gcc.dg/tree-ssa/signbit-4.c          | 11 ++++++++
 gcc/testsuite/gcc.dg/tree-ssa/vector-alignup-1.c   | 15 +++++++++++
 7 files changed, 124 insertions(+)

diff --git a/gcc/match.pd b/gcc/match.pd
index 0bf2414b3f0c..df6a179a848a 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -1985,6 +1985,29 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
        && !TYPE_SATURATING (type))
    (res @0 @1))))
 
+/* X + (X | -X) -> X & (X - 1).  X | -X has every bit from the lowest set
+   bit of X upwards, so adding it clears that bit.  */
+(simplify
+ (plus:c @0 (bit_ior:cs @0 (negate @0)))
+ (if (ANY_INTEGRAL_TYPE_P (type)
+      && !TYPE_OVERFLOW_SANITIZED (type))
+  (bit_and @0 (plus @0 { build_minus_one_cst (type); }))))
+
+/* X + ((-X) & (C - 1)) -> (X + C - 1) & -C for a power of two C, the
+   round up to a multiple of C written with the padding computed first.  */
+(simplify
+ (plus:c @0 (bit_and:cs (negate @0) uniform_integer_cst_p@1))
+ (with { tree cst = uniform_integer_cst_p (@1);
+        tree etype = TREE_TYPE (cst);
+        wide_int c = wi::to_wide (cst); }
+  (if (ANY_INTEGRAL_TYPE_P (type)
+       && !TYPE_OVERFLOW_TRAPS (type)
+       && !TYPE_OVERFLOW_SANITIZED (type)
+       && wi::popcount (c + 1) == 1)
+   (bit_and (plus @0 @1)
+           { build_uniform_cst
+               (type, wide_int_to_tree (etype, wi::bit_not (c))); }))))
+
 /* (x | y) - y -> (x & ~y) */
 (simplify
  (minus (bit_ior:cs @0 @1) @1)
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/alignup-2.c 
b/gcc/testsuite/gcc.dg/tree-ssa/alignup-2.c
new file mode 100644
index 000000000000..e71af6dcddf6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/alignup-2.c
@@ -0,0 +1,29 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target int32plus } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+/* Rounding up by adding the padding is the same as rounding up with a
+   mask.  */
+
+unsigned int f1 (unsigned int x) { return x + ((-x) & 15u); }
+unsigned int f2 (unsigned int x) { return ((-x) & 4095u) + x; }
+unsigned long f3 (unsigned long x) { return x + ((-x) & 63ul); }
+
+/* The identity needs no wrapping type, a signed operand works too.  */
+int f5 (int x) { return x + ((-x) & 15); }
+
+unsigned int f6 (unsigned int x, unsigned int *p)
+{
+  unsigned int pad = (-x) & 15u;
+  *p = pad;
+  return x + pad;
+}
+
+/* Not a power of two, leave it alone.  */
+unsigned int f4 (unsigned int x) { return x + ((-x) & 14u); }
+
+/* { dg-final { scan-tree-dump-times " & 14;" 1 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " & 4294967280" 1 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " & 4294963200" 1 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " & -16" 1 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " & 15" 1 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/alignup-overflow-1.c 
b/gcc/testsuite/gcc.dg/tree-ssa/alignup-overflow-1.c
new file mode 100644
index 000000000000..f4995d08cc00
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/alignup-overflow-1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -ftrapv -fdump-tree-optimized" } */
+
+int
+f (int x)
+{
+  return x + ((-x) & 15);
+}
+
+/* { dg-final { scan-tree-dump " -x" "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/alignup-overflow-2.c 
b/gcc/testsuite/gcc.dg/tree-ssa/alignup-overflow-2.c
new file mode 100644
index 000000000000..75e29d601896
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/alignup-overflow-2.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fsanitize=signed-integer-overflow -fdump-tree-optimized" 
} */
+
+int
+f (int x)
+{
+  return x + ((-x) & 15);
+}
+
+/* { dg-final { scan-tree-dump "UBSAN_CHECK_SUB" "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/signbit-1.c 
b/gcc/testsuite/gcc.dg/tree-ssa/signbit-1.c
new file mode 100644
index 000000000000..d7bcd91173b1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/signbit-1.c
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+/* X | -X has the sign bit set exactly when X is non-zero.  */
+
+int f1 (int x)
+{ return (x | -x) >> (__SIZEOF_INT__ * __CHAR_BIT__ - 1); }
+unsigned int f2 (unsigned int x)
+{ return (x | -x) >> (__SIZEOF_INT__ * __CHAR_BIT__ - 1); }
+long f3 (long x) { return (x | -x) >> (__SIZEOF_LONG__ * __CHAR_BIT__ - 1); }
+
+/* X + (X | -X) clears the lowest set bit of X.  The identity holds for a
+   signed operand too, X - 1 overflows only where the source does.  */
+unsigned int f4 (unsigned int x) { return x + (x | -x); }
+int f5 (int x) { return x + (x | -x); }
+
+unsigned int f6 (unsigned int x, unsigned int *p)
+{
+  unsigned int y = x | -x;
+  *p = y;
+  return x + y;
+}
+
+/* { dg-final { scan-tree-dump-times " \\| " 1 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " != 0" 3 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " & " 2 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/signbit-4.c 
b/gcc/testsuite/gcc.dg/tree-ssa/signbit-4.c
new file mode 100644
index 000000000000..8ed81a169d3a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/signbit-4.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fsanitize=signed-integer-overflow -fdump-tree-optimized" 
} */
+
+int
+f (int x)
+{
+  return x + (x | -x);
+}
+
+/* { dg-final { scan-tree-dump "\\.UBSAN_CHECK_SUB" "optimized" } } */
+/* { dg-final { scan-tree-dump "\\.UBSAN_CHECK_ADD" "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vector-alignup-1.c 
b/gcc/testsuite/gcc.dg/tree-ssa/vector-alignup-1.c
new file mode 100644
index 000000000000..fec5c5c6b930
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/vector-alignup-1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target vect_int } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+/* Rounding up by adding the padding, spelled with vectors.  */
+
+typedef unsigned int v4ui __attribute__((vector_size (16)));
+typedef int v4si __attribute__((vector_size (16)));
+
+v4ui f1 (v4ui x) { return x + ((-x) & 15); }
+v4si f2 (v4si x) { return x + ((-x) & 63); }
+
+/* { dg-final { scan-tree-dump-not "= -" "optimized" } } */
+/* { dg-final { scan-tree-dump-times " \\+ " 2 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " & " 2 "optimized" } } */

Reply via email to