https://gcc.gnu.org/g:5827af4ea1ea11036b336e1029b4a25ca670b594

commit r17-5472-g5827af4ea1ea11036b336e1029b4a25ca670b594
Author: Milan Tripkovic <[email protected]>
Date:   Wed Oct 21 16:35:53 2026 -0600

    PR rtl-optimization/123905 - Failure to optimize away sign extension on 
RISC-V
    
    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 suggestions:
    
    ext-dce.cc: Extract the redundant sign extension check into a new helper
    function is_trivially_redundant_extension. This uses num_sign_bit_copies
    evaluated in the outer mode to check if the extension is already redundant,
    which allows combine to eliminate it instead of ext-dce leaving SUBREGs
    that are harder to optimize.
    
    rtlanal.cc: Improve nonzero_bits1 so that CLZ and CTZ are handled when
    the operand mode differs from MODE, as long as it is a scalar integer
    mode, since the result depends on the operand.  If the value at
    zero is not defined, the fallback is the same as before.
    
    ASM Before:
    clzw a0,a0
    andn t0,a0,a1
    sext.w a0,t0
    ret
    
    ASM After:
    clzw  a0,a0
    andn  a0,a0,a1
    ret
    
    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.
    
    Changes since v4:
    
    -Eliminated the redundant num_sign_bit_copies1 handling.
    -Removed the POPCOUNT change from nonzero_bits1.
    -nonzero_bits1 keeps the old fallback for CLZ/CTZ, but not for
     mismatched modes, since the result depends on the operand.
    
    Bootstrapped and regression tested on x86_64.
    Regression tested on riscv64.
    
            PR rtl-optimization/123905
    
    gcc/ChangeLog:
    
            * ext-dce.cc (is_trivially_redundant_extension): New helper 
function.
            (ext_dce_try_optimize_extension): Keep trivially redundant 
extensions.
            * rtlanal.cc (nonzero_bits1): Improve CLZ and CTZ handling.
    
    gcc/testsuite/ChangeLog:
    
            * gcc.target/riscv/pr123905.c: New test case.

Diff:
---
 gcc/ext-dce.cc                            | 49 +++++++++++++++++++++++++++
 gcc/rtlanal.cc                            | 25 +++++++-------
 gcc/testsuite/gcc.target/riscv/pr123905.c | 55 +++++++++++++++++++++++++++++++
 3 files changed, 117 insertions(+), 12 deletions(-)

diff --git a/gcc/ext-dce.cc b/gcc/ext-dce.cc
index 1a3e624e7653..e38ecc5e98cc 100644
--- a/gcc/ext-dce.cc
+++ b/gcc/ext-dce.cc
@@ -477,6 +477,43 @@ ext_dce_try_optimize_rshift (rtx_insn *insn, rtx set, rtx 
new_src, rtx_insn *new
     }
 }
 
+/* Return true if the extension of INNER to OUTER_MODE is already
+   guaranteed by its definition, making the SIGN_EXTEND redundant.  */
+static bool
+is_trivially_redundant_extension (rtx inner, scalar_int_mode inner_mode,
+                                 scalar_int_mode outer_mode)
+{
+  rtx def_reg = inner;
+
+  if (SUBREG_P (def_reg) && subreg_lowpart_p (def_reg))
+    def_reg = SUBREG_REG (def_reg);
+
+  if (!REG_P (def_reg))
+    return false;
+
+  unsigned int regno = REGNO (def_reg);
+  if (HARD_REGISTER_NUM_P (regno) || DF_REG_DEF_COUNT (regno) != 1)
+    return false;
+
+  df_ref def = DF_REG_DEF_CHAIN (regno);
+  if (!def)
+    return false;
+
+  rtx_insn *def_insn = DF_REF_INSN (def);
+  rtx def_set = def_insn ? single_set (def_insn) : NULL_RTX;
+  if (!def_set)
+    return false;
+
+  rtx def_src = SET_SRC (def_set);
+
+  unsigned int diff = GET_MODE_PRECISION (outer_mode)
+                     - GET_MODE_PRECISION (inner_mode);
+
+  if (num_sign_bit_copies (def_src, outer_mode) > diff)
+    return true;
+
+  return false;
+}
 
 /* INSN has a sign/zero extended source inside SET that we will
    try to turn into a SUBREG.  If NEW_SRC is non-null, use that
@@ -539,6 +576,18 @@ ext_dce_try_optimize_extension (rtx_insn *insn, rtx set)
   if (!(REG_P (inner) || (SUBREG_P (inner) && REG_P (SUBREG_REG (inner)))))
     return;
 
+  /* If the extension is trivially redundant, then we leave it in the IL
+     as we know combine can easily remove it.  By doing so those sign
+     bits keep a known state rather than unknown state which helps combine
+     make more improvements.  */
+  scalar_int_mode inner_mode, outer_mode;
+  if (GET_CODE (src) == SIGN_EXTEND
+      && is_a <scalar_int_mode> (GET_MODE (inner), &inner_mode)
+      && is_a <scalar_int_mode> (GET_MODE (src), &outer_mode))
+    {
+      if (is_trivially_redundant_extension (inner, inner_mode, outer_mode))
+       return;
+    }
   rtx new_pattern;
   if (dump_file)
     {
diff --git a/gcc/rtlanal.cc b/gcc/rtlanal.cc
index fa4d59d0e6f0..98e0481cf79a 100644
--- a/gcc/rtlanal.cc
+++ b/gcc/rtlanal.cc
@@ -4800,7 +4800,7 @@ nonzero_bits1 (const_rtx x, scalar_int_mode mode, 
const_rtx known_x,
   enum rtx_code code = GET_CODE (x);
   machine_mode inner_mode;
   unsigned int inner_width;
-  scalar_int_mode xmode;
+  scalar_int_mode xmode, op_imode;
 
   unsigned int mode_width = GET_MODE_PRECISION (mode);
 
@@ -5230,25 +5230,26 @@ nonzero_bits1 (const_rtx x, scalar_int_mode mode, 
const_rtx known_x,
 
     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))
+        that value, plus the number of bits in the operand minus one.
+        The operand mode may differ from MODE, but if it is not a scalar
+        integer mode, don't try to get nonzero bits as currently nonzero
+        is not a poly_int.  */
+      if (is_a <scalar_int_mode> (GET_MODE (XEXP (x, 0)), &op_imode)
+         && CLZ_DEFINED_VALUE_AT_ZERO (op_imode, nonzero))
        nonzero
-         |= (HOST_WIDE_INT_1U << (floor_log2 (mode_width))) - 1;
+         |= (HOST_WIDE_INT_1U << (floor_log2 (op_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))
+        that value, plus the number of bits in the operand minus one.
+        See above for the operand mode.  */
+      if (is_a <scalar_int_mode> (GET_MODE (XEXP (x, 0)), &op_imode)
+         && CTZ_DEFINED_VALUE_AT_ZERO (op_imode, nonzero))
        nonzero
-         |= (HOST_WIDE_INT_1U << (floor_log2 (mode_width))) - 1;
+         |= (HOST_WIDE_INT_1U << (floor_log2 (op_mode_width))) - 1;
       else
        nonzero = -1;
       break;
diff --git a/gcc/testsuite/gcc.target/riscv/pr123905.c 
b/gcc/testsuite/gcc.target/riscv/pr123905.c
new file mode 100644
index 000000000000..1a6fd2fa6ec1
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/pr123905.c
@@ -0,0 +1,55 @@
+/* PR target/123905 */
+/* { dg-do compile } */
+/* { dg-options "-O2 -march=rv64gcbv -mabi=lp64d" } */
+/* { dg-options "-O2 -march=rv32gcbv -mabi=ilp32" } */
+
+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" } } */

Reply via email to