[PATCH v3] RISC-V: PR rtl-optimization/123905 - Failure to optimize away sign extension on RISC-V

Milan Tripkovic <[email protected]>
Newsgroups gmane.comp.gcc.patches
Message-ID <AM6PR08MB428053CC2718B6AA7B5D1ADBA9DB2@AM6PR08MB4280.eurprd08.prod.outlook.com>
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: Extracted 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 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

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-13  Milan Tripkovic  <[email protected]>

gcc/ChangeLog:

      * ext-dce.cc (is_trivially_redundant_extension): New helper function.
      (ext_dce_try_optimize_extension): Keep trivially redundant SIGN_EXTENDs.
      * rtlanal.cc (nonzero_bits1): Handle POPCOUNT/CLZ/CTZ.
      (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.
pr123905patchv3.txt (text/plain, 5.9 KB)
---
 gcc/ext-dce.cc                            | 45 ++++++++++++++++++
 gcc/rtlanal.cc                            | 58 +++++++++++++----------
 gcc/testsuite/gcc.target/riscv/pr123905.c | 54 +++++++++++++++++++++
 3 files changed, 131 insertions(+), 26 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..a7c23f7c3c 100644
--- a/gcc/ext-dce.cc
+++ b/gcc/ext-dce.cc
@@ -476,6 +476,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))
+    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
@@ -538,6 +575,14 @@ ext_dce_try_optimize_extension (rtx_insn *insn, rtx set)
   if (!(REG_P (inner) || (SUBREG_P (inner) && REG_P (SUBREG_REG (inner)))))
     return;
 
+  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 5274a5c59c..2adc5a174a 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;
-      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 +5418,18 @@ 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..014f4d352e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/pr123905.c
@@ -0,0 +1,54 @@
+/* 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.