[gcc r14-12789] match.pd: Fix up ((C << A) & D) != 0 simplification [PR126476]

Jakub Jelinek via Gcc-cvs <[email protected]> Sat, 1 Aug 2026 10:29:31 +0000 (GMT)
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:b56074b3831ccfde9b87cb4ac322a30c38d7d63d

commit r14-12789-gb56074b3831ccfde9b87cb4ac322a30c38d7d63d
Author: Jakub Jelinek <[email protected]>
Date:   Fri Jul 31 08:53:52 2026 +0200

    match.pd: Fix up ((C << A) & D) != 0 simplification [PR126476]
    
    This simplification for power of two @1 and @2 folds to false (resp.
    to true for the == version) if @1 is larger than @2 (in unsigned
    comparison), because @1 & @2 is known to be zero (i.e. for shift count 0)
    and for shift count larger than that it will be zero too, either because
    @1 << @0 is even larger, or if @0 is too large @1 << @0 overflows to zero.
    This is the case of e.g. ((4 << x) & 2) != 0, which is always false.
    Now, this PR is about a different problem, if @1 is smaller than @2, say
    ((1 << x) & 256) != 0, but x has a very narrow type, say unsigned _BitInt(3),
    then the largest possible value of x is 7 and ((1 << 7) & 256) is
    still 0, 1 << 7 is 128 and so still smaller than 256.
    So, if c1 - c2 doesn't fit into the shift count type
    (resp. for the other case c2 - c1), it will be also always false (resp.
    true).
    Trying to improve it and using range of x (aka @0) is not needed,
    this simplification folds it into @0 != (c1 - c2) and so will be folded
    later.  Just the case where c1 - c2 overflows is problematic because
    we've lost the details (unless we'd promote both operands or something).
    Another possible way to do this would be build_int_cst and check for
    the overflow flags, but I think this is shorter.
    
    2026-07-31  Jakub Jelinek  <[email protected]>
    
            PR tree-optimization/126476
            * match.pd (((C << A) & D) != 0 -> A == 0,
            ((C << A) & D) == 0 -> A != 0): Fold to false/true if
            c1 - c2 resp. c2 - c1 doesn't fit into TREE_TYPE (@0).
    
            * gcc.dg/torture/bitint-103.c: New test.
    
    Reviewed-by: Richard Biener <[email protected]>
    (cherry picked from commit 5c62a2771f2c7d5301ee6456f1817098c2fea113)

Diff:
---
 gcc/match.pd                              | 11 +++++++++--
 gcc/testsuite/gcc.dg/torture/bitint-103.c | 22 ++++++++++++++++++++++
 2 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/gcc/match.pd b/gcc/match.pd
index 72004f144d84..ad9ca4ce1ec2 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -4437,7 +4437,12 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
   (cmp (bit_and (lshift integer_pow2p@1 @0) integer_pow2p@2) integer_zerop)
    (with { int c1 = wi::clz (wi::to_wide (@1));
 	   int c2 = wi::clz (wi::to_wide (@2)); }
-    (if (c1 < c2)
+    (if (c1 < c2
+	 /* If c1 - c2 isn't representable in TREE_TYPE (@0), it is also
+	    never true, because for any valid x C << x will be smaller
+	    than D.  See PR126476.  */
+	 || !wi::fits_to_tree_p (wi::shwi (c1 - c2, HOST_BITS_PER_INT),
+				 TREE_TYPE (@0)))
      { constant_boolean_node (cmp == NE_EXPR ? false : true, type); }
      (icmp @0 { build_int_cst (TREE_TYPE (@0), c1 - c2); }))))
  (simplify
@@ -4445,7 +4450,9 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
    (if (tree_int_cst_sgn (@1) > 0)
     (with { int c1 = wi::clz (wi::to_wide (@1));
 	    int c2 = wi::clz (wi::to_wide (@2)); }
-     (if (c1 > c2)
+     (if (c1 > c2
+	  || !wi::fits_to_tree_p (wi::shwi (c2 - c1, HOST_BITS_PER_INT),
+				  TREE_TYPE (@0)))
       { constant_boolean_node (cmp == NE_EXPR ? false : true, type); }
       (icmp @0 { build_int_cst (TREE_TYPE (@0), c2 - c1); })))))
  /* `(1 >> X) != 0` -> `X == 0` */
diff --git a/gcc/testsuite/gcc.dg/torture/bitint-103.c b/gcc/testsuite/gcc.dg/torture/bitint-103.c
new file mode 100644
index 000000000000..54c0fc0b1d0f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/bitint-103.c
@@ -0,0 +1,22 @@
+/* PR tree-optimization/126476 */
+/* { dg-do run { target bitint } } */
+
+[[gnu::noipa]] int
+foo (unsigned _BitInt(4) n)
+{
+  return ((1ULL << n) & (1ULL << 20)) != 0;
+}
+
+[[gnu::noipa]] int
+bar (unsigned _BitInt(4) n)
+{
+  return (((1ULL << 40) >> n) & (1ULL << 20)) != 0;
+}
+
+int
+main ()
+{
+  for (unsigned i = 0; i < 16; i++)
+    if (foo (i) != 0 || bar (i) != 0)
+        __builtin_abort ();
+}