This patch updates num_sign_bit_copies1 to correctly handle CLZ, CTZ,
and POPCOUNT, and nonzero_bits1 to provide a more precise mask for
these opcodes.

With this change, the redundant sext.w is eliminated, but an extra
andi instruction remained:

  ctzw    a0,a0
  andn    a0,a0,a1
  andi    a0,a0,63
  ret

To resolve this, this patch adds define_insn_and_split patterns in
bitmanip.md that allow the combine to merge these operations:

  ctzw    a0,a0
  andn    a0,a0,a1
  ret

Separate patterns are included for both the AND and ANDN cases to
ensure both are handled.
Additionally, this patch includes a new test case gcc.target/riscv/pr123905.c
which covers several scenarios: AND, ANDN, and combinations like clz & ctz
 and clz & ctz & popcount

2026-07-06  Milan Tripkovic  <[email protected]>

gcc/ChangeLog:

      * config/riscv/bitmanip.md (*<bitmanip_optab>_andn_andi_combine):
      (*<bitmanip_optab>_and_andi_combine): new patterns
      * rtlanal.cc (nonzero_bits1): fixed mask
      (num_sign_bit_copies1): Add CLZ/CTZ/POPCOUNT handling.

gcc/testsuite/ChangeLog:

      * gcc.target/riscv/pr123905.c: New test.





CONFIDENTIALITY: The contents of this e-mail are confidential and intended only 
for the above addressee(s). If you are not the intended recipient, or the 
person responsible for delivering it to the intended recipient, copying or 
delivering it to anyone else or using it in any unauthorized manner is 
prohibited and may be unlawful. If you receive this e-mail by mistake, please 
notify the sender and the systems administrator at [email protected] 
immediately.
---
 gcc/config/riscv/bitmanip.md              | 41 +++++++++++++++++
 gcc/rtlanal.cc                            | 56 ++++++++++++-----------
 gcc/testsuite/gcc.target/riscv/pr123905.c | 55 ++++++++++++++++++++++
 3 files changed, 126 insertions(+), 26 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/riscv/pr123905.c

diff --git a/gcc/config/riscv/bitmanip.md b/gcc/config/riscv/bitmanip.md
index be893f12d9..4fa6354240 100644
--- a/gcc/config/riscv/bitmanip.md
+++ b/gcc/config/riscv/bitmanip.md
@@ -1465,6 +1465,47 @@
                                      (const_int 1)
                                      (zero_extend:X (match_dup 2))))])
 
+;; The "& 63"/"& 127" mask left over from combining with andn is
+;; always redundant: clz/ctz/popcount is already bounded, so those
+;; bits are known zero.  Fold the extension directly into the andn
+(define_insn_and_split "*<bitmanip_optab>_andn_andi_combine"
+  [(set (match_operand:DI 0 "register_operand" "=r")
+       (and:DI
+         (and:DI
+            (not:DI (match_operand:DI 1 "register_operand" "r"))
+    (subreg:DI
+       (clz_ctz_pcnt:SI (match_operand:SI 2 "register_operand" "r")) 0))
+     (match_operand:DI 3 "const_int_operand" "n")))]
+  "TARGET_64BIT && TARGET_ZBB
+   && (INTVAL (operands[3]) == 63 || INTVAL (operands[3]) == 127)"
+  "#"
+  "&& 1"
+  [
+    (set (match_dup 0)
+        (sign_extend:DI (clz_ctz_pcnt:SI (match_dup 2))))
+    (set (match_dup 0)
+        (and:DI (not:DI (match_dup 1))
+                (match_dup 0)))])
+
+(define_insn_and_split "*<bitmanip_optab>_and_andi_combine"
+  [(set (match_operand:DI 0 "register_operand" "=r")
+       (and:DI
+         (and:DI
+            (subreg:DI
+               (clz_ctz_pcnt:SI (match_operand:SI 2 "register_operand" "r")) 0)
+         (match_operand:DI 1 "register_operand" "r"))
+       (match_operand:DI 3 "const_int_operand" "n")))]
+  "TARGET_64BIT && TARGET_ZBB
+   && (INTVAL (operands[3]) == 63 || INTVAL (operands[3]) == 127)"
+  "#"
+  "&& 1"
+  [
+   (set (match_dup 0)
+       (sign_extend:DI (clz_ctz_pcnt:SI (match_dup 2))))
+   (set (match_dup 0)
+       (and:DI (match_dup 0)
+               (match_dup 1)))])
+
 ;; If we have (and (not X) (not Y)), and we can implement one of those NOT
 ;; expressions as a single insn, then do so as that will allow using andn.
 ;; In this case we exploit ~(-x) == x - 1.  Two versions as we can reverse
diff --git a/gcc/rtlanal.cc b/gcc/rtlanal.cc
index 5274a5c59c..9cd298b169 100644
--- a/gcc/rtlanal.cc
+++ b/gcc/rtlanal.cc
@@ -5222,34 +5222,27 @@ nonzero_bits1 (const_rtx x, scalar_int_mode mode, 
const_rtx known_x,
 
     case FFS:
     case POPCOUNT:
-      /* This is at most the number of bits in the mode.  */
-      nonzero = (HOST_WIDE_INT_UC (2) << (floor_log2 (op_mode_width))) - 1;
-      break;
-
     case CLZ:
-      /* If CLZ has a known value at zero, then the nonzero bits are
-        that value, plus the number of bits in the mode minus one.
-        If we have a different operand mode, don't try to get nonzero
-        bits as currently nonzero is not a poly_int.  */
-      if (op_mode == mode
-         && CLZ_DEFINED_VALUE_AT_ZERO (mode, nonzero))
-       nonzero
-         |= (HOST_WIDE_INT_1U << (floor_log2 (mode_width))) - 1;
-      else
-       nonzero = -1;
-      break;
-
     case CTZ:
-      /* If CTZ has a known value at zero, then the nonzero bits are
-        that value, plus the number of bits in the mode minus one.
-        See above for op_mode != mode.  */
-      if (op_mode == mode
-         && CLZ_DEFINED_VALUE_AT_ZERO (mode, nonzero))
-       nonzero
-         |= (HOST_WIDE_INT_1U << (floor_log2 (mode_width))) - 1;
-      else
-       nonzero = -1;
-      break;
+  {
+    /* Same reasoning as num_sign_bit_copies1 below: the result
+          is at most floor_log2 (op_mode_width) + 1 bits wide, so
+          this is the mask of possibly-nonzero bits.  */
+    unsigned HOST_WIDE_INT mask
+      = (HOST_WIDE_INT_1U << (floor_log2 (op_mode_width) + 1)) - 1;
+       /* If CLZ/CTZ has a known value at zero, fold that in too.  */
+    if (op_mode == mode)
+      {
+         HOST_WIDE_INT val_at_zero;
+         if (code == CLZ && CLZ_DEFINED_VALUE_AT_ZERO (op_mode, val_at_zero))
+           mask |= (unsigned HOST_WIDE_INT) val_at_zero;
+    else if (code == CTZ && CTZ_DEFINED_VALUE_AT_ZERO (op_mode, val_at_zero))
+           mask |= (unsigned HOST_WIDE_INT) val_at_zero;
+      }
+
+    nonzero = mask;
+    break;
+  }
 
     case CLRSB:
       /* This is at most the number of bits in the mode minus 1.  */
@@ -5424,6 +5417,17 @@ num_sign_bit_copies1 (const_rtx x, scalar_int_mode mode, 
const_rtx known_x,
      the code in the switch below.  */
   switch (code)
     {
+    /* Same reasoning as nonzero_bits1: the result is at most
+          floor_log2 (bitwidth) + 1 bits wide, so the remaining
+          upper bits are all zero, i.e.  sign bit copies.  */
+    case CLZ:
+    case CTZ:
+    case POPCOUNT:
+  {
+    unsigned int bitwidth = GET_MODE_PRECISION (mode);
+    int used_bits = floor_log2 (bitwidth) + 1;
+    return bitwidth - used_bits;
+  }
     case REG:
 
 #if defined(POINTERS_EXTEND_UNSIGNED)
diff --git a/gcc/testsuite/gcc.target/riscv/pr123905.c 
b/gcc/testsuite/gcc.target/riscv/pr123905.c
new file mode 100644
index 0000000000..769bd0e8af
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/pr123905.c
@@ -0,0 +1,55 @@
+/* PR target/123905 */
+/* { dg-do compile } */
+/* { dg-options "-O2 -march=rv64gcb_zbb -mabi=lp64d" } */
+
+int
+foo1 (unsigned a, unsigned b)
+{
+  return __builtin_clz (a) & ~b;
+}
+
+int
+foo2 (unsigned a, unsigned b)
+{
+  return __builtin_ctz (a) & ~b;
+}
+
+int
+foo3 (unsigned a, unsigned b)
+{
+  return __builtin_popcount (a) & ~b;
+}
+
+
+int
+test_and (unsigned a, unsigned b)
+{
+  return __builtin_clz (a) & b;
+}
+
+int
+test_and2 (unsigned a, unsigned b)
+{
+  return __builtin_ctz (a) & b;
+}
+
+int
+test_and3 (unsigned a, unsigned b)
+{
+  return __builtin_popcount (a) & b;
+}
+
+int
+test_and_and (unsigned a, unsigned c)
+{
+  return __builtin_clz (a) & __builtin_ctz (c);
+}
+
+int
+test_and_and_and (unsigned a, unsigned b, unsigned c)
+{
+  return __builtin_clz (a) & __builtin_ctz (b) & __builtin_popcount (c);
+}
+
+/* { dg-final { scan-assembler-not "sext\\.w" } } */
+/* { dg-final { scan-assembler-not "andi\\s" } } */
-- 
2.34.1

Reply via email to