This patch fixes PR123905, where the compiler generates
redundant sign extensions and bit masks on RISC-V when
using __builtin_clz, __builtin_ctz, and __builtin_popcount.
To solve this, the patch introduces the following changes
based on Jeff's suggestion:
ext-dce.cc: Updated ext_dce_try_optimize_extension to prevent
the conversion of a SIGN_EXTEND to a subreg if the source's sign bit
is already known to be zero.
rtlanal.cc: Improve nonzero_bits1 and num_sign_bit_copies1 so that
they recognize that the results of CLZ, CTZ, and POPCOUNT are
bounded by floor_log2 (bitwidth) + 1 bits, meaning that all higher
bits are known to be zero.
ASM Before:
clzw a0,a0
andn t0,a0,a1
sext.w a0,t0
ret
ASM After:
clzw a0,a0
andn a0,a0,a1
ret
The redundant sext.w is eliminated because the compiler now knows that
the result already has a zero sign bit.
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-08-06 Milan Tripkovic <[email protected]>
gcc/ChangeLog:
* ext-dce.cc (ext_dce_try_optimize_extension): prevent conversion.
* 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 case.
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/ext-dce.cc | 38 ++++++++++++++++
gcc/rtlanal.cc | 55 ++++++++++++-----------
gcc/testsuite/gcc.target/riscv/pr123905.c | 55 +++++++++++++++++++++++
3 files changed, 123 insertions(+), 25 deletions(-)
create mode 100644 gcc/testsuite/gcc.target/riscv/pr123905.c
diff --git a/gcc/ext-dce.cc b/gcc/ext-dce.cc
index 9e4b0a429b..e89b6e1778 100644
--- a/gcc/ext-dce.cc
+++ b/gcc/ext-dce.cc
@@ -538,6 +538,44 @@ ext_dce_try_optimize_extension (rtx_insn *insn, rtx set)
if (!(REG_P (inner) || (SUBREG_P (inner) && REG_P (SUBREG_REG (inner)))))
return;
+/* Keep SIGN_EXTEND when DEF_SRC sign bit is known zero. */
+ if (GET_CODE (src) == SIGN_EXTEND)
+ {
+ rtx def_reg = inner;
+ scalar_int_mode inner_mode;
+
+ if (SUBREG_P (def_reg))
+ def_reg = SUBREG_REG (def_reg);
+
+ if (REG_P (def_reg)
+ && is_a <scalar_int_mode> (GET_MODE (inner), &inner_mode))
+ {
+ unsigned int regno = REGNO (def_reg);
+
+ if (regno >= FIRST_PSEUDO_REGISTER && DF_REG_DEF_COUNT (regno) == 1)
+ {
+ df_ref def = DF_REG_DEF_CHAIN (regno);
+ if (def)
+ {
+ rtx_insn *def_insn = DF_REF_INSN (def);
+ rtx def_set = def_insn ? single_set (def_insn) : NULL_RTX;
+
+ if (def_set)
+ {
+ rtx def_src = SET_SRC (def_set);
+ unsigned HOST_WIDE_INT nz
+ = nonzero_bits (def_src, inner_mode);
+ unsigned HOST_WIDE_INT sign_mask
+ = HOST_WIDE_INT_1U << (GET_MODE_PRECISION (inner_mode)-1);
+
+ if ((nz & sign_mask) == 0)
+ return;
+ }
+ }
+ }
+ }
+ }
+
rtx new_pattern;
if (dump_file)
{
diff --git a/gcc/rtlanal.cc b/gcc/rtlanal.cc
index 5274a5c59c..bf6d19fa1d 100644
--- a/gcc/rtlanal.cc
+++ b/gcc/rtlanal.cc
@@ -5222,34 +5222,28 @@ 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;
+ {
+ /* 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 +5418,17 @@ num_sign_bit_copies1 (const_rtx x, scalar_int_mode mode,
const_rtx known_x,
the code in the switch below. */
switch (code)
{
+ case CLZ:
+ case CTZ:
+ case POPCOUNT:
+ {
+ /* 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. */
+ 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