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" } } */
