On 7/6/2026 8:42 AM, Milan Tripkovic wrote:
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.
So the rtlanal.cc changes look like they're going in the right
direction. There may be formatting goofs in there we need to address,
but basically look OK.
As you noted, we then get the annoying masking. But rather than fixing
with a RISC-V pattern, it seems like more work in nonzero_bits or
perhaps simplify-rtx.cc would be the right way to fix the extraneous ANDI.
(set (reg:DI 146)
(and:DI (and:DI (not:DI (reg:DI 148 [ b ]))
(subreg:DI (clz:SI (subreg/s/u:SI (reg:DI 147 [ a ]) 0)) 0))
(const_int 63 [0x3f])))
So we essentially have A & B & C. We ought to be able to look at A & B,
A & C, B & C and see if we can make a term go away. That would be
simplify_associative_operation in simplify-rtx.cc It seems to try and
do that kind of optimization, so something is not working right.
We do pass these down to simplify_binary_operation_1:
(gdb) p debug_rtx (op0)
(subreg:DI (clz:SI (subreg/s/u:SI (reg:DI 147 [ a ]) 0)) 0)
$31 = void
(gdb) p debug_rtx (op1)
(const_int 63 [0x3f])
$32 = void
(gdb) p nonzero_bits (op0, E_DImode)
$33 = 18446744069414584383
(gdb) p/x nonzero_bits (op0, E_DImode)
$34 = 0xffffffff0000003f
If we strip the subreg and ask what the nonzero bits are for the clz
expression we get:
(gdb) p/x nonzero_bits (op0.u.fld[0].rt_rtx, E_SImode)
$35 = 0x3f
So I think the question here is what should nonzero_bits be for the
outer expression. For a pardoxical subreg like we have here:
The high-order bits of rvalues are defined in the following circumstances:
@table @asis
@item @code{subreg}s of @code{mem}
When @var{m2} is smaller than a word, the macro @code{LOAD_EXTEND_OP},
can control how the high-order bits are defined.
@item @code{subreg} of @code{reg}s
The upper bits are defined when @code{SUBREG_PROMOTED_VAR_P} is true.
@code{SUBREG_PROMOTED_UNSIGNED_P} describes what the upper bits hold.
Such subregs usually represent local variables, register variables
and parameter pseudo variables that have been promoted to a wider mode.
This doesn't fall under either case and thus those upper bits are
undefined, ie, potentially on given RTL semantics for paradoxicals. Not
good.
As we enter ext-dce we have this:
(insn 7 4 8 2 (set (reg:SI 140)
(clz:SI (subreg/s/u:SI (reg/v:DI 138 [ a ]) 0))) "j.c":3:10
540 {*clzsi2}
(expr_list:REG_DEAD (reg/v:DI 138 [ a ])
(nil)))
(insn 8 7 10 2 (set (reg:DI 134 [ _1 ])
(sign_extend:DI (reg:SI 140))) "j.c":3:10 125
{*extendsidi2_internal}
(expr_list:REG_DEAD (reg:SI 140)
(nil)))
(insn 10 8 12 2 (set (reg:DI 143)
(not:DI (reg/v:DI 139 [ b ]))) "j.c":3:29 discrim 1 113
{one_cmpldi2}
(expr_list:REG_DEAD (reg/v:DI 139 [ b ])
(nil)))
(insn 12 10 13 2 (set (reg:DI 145)
(and:DI (reg:DI 134 [ _1 ])
(reg:DI 143))) "j.c":3:27 discrim 1 104 {*anddi3}
(expr_list:REG_DEAD (reg:DI 143)
(expr_list:REG_DEAD (reg:DI 134 [ _1 ])
(nil))))
(insn 13 12 18 2 (set (reg:DI 146)
(sign_extend:DI (subreg:SI (reg:DI 145) 0))) "j.c":3:27
discrim 2 125 {*extendsidi2_internal}
(expr_list:REG_DEAD (reg:DI 145)
(nil)))
ext-dce realizes the upper 32 bits of (reg 134) are never read, so it
turns the sign extension into the subreg expression resulting in:
(insn 7 4 8 2 (set (reg:SI 140)
(clz:SI (subreg/s/u:SI (reg/v:DI 138 [ a ]) 0))) "j.c":3:10
540 {*clzsi2}
(expr_list:REG_DEAD (reg/v:DI 138 [ a ])
(nil)))
(insn 8 7 10 2 (set (reg:DI 134 [ _1 ])
(subreg:DI (reg:SI 140) 0)) "j.c":3:10 275 {*movdi_64bit}
(expr_list:REG_DEAD (reg:SI 140)
(nil)))
(insn 10 8 12 2 (set (reg:DI 143)
(not:DI (reg/v:DI 139 [ b ]))) "j.c":3:29 discrim 1 113
{one_cmpldi2}
(expr_list:REG_DEAD (reg/v:DI 139 [ b ])
(nil)))
(insn 12 10 13 2 (set (reg:DI 145)
(and:DI (reg:DI 134 [ _1 ])
(reg:DI 143))) "j.c":3:27 discrim 1 104 {*anddi3}
(expr_list:REG_DEAD (reg:DI 143)
(expr_list:REG_DEAD (reg:DI 134 [ _1 ])
(nil))))
(insn 13 12 18 2 (set (reg:DI 146)
(sign_extend:DI (subreg:SI (reg:DI 145) 0))) "j.c":3:27
discrim 2 125 {*extendsidi2_internal}
(expr_list:REG_DEAD (reg:DI 145)
(nil)))
One possibility might be to inhibit the extension->subreg transformation
when the value in question is already properly extended. We can see how
that behaves by using your rtlanal.cc patch and the "-fno-ext-dce"
option. That results in the code we want for all 3 testcases.
I haven't tried to analyze if there'd be regressions from avoiding the
extension removal for that case but it seems at least worth some
exploration to see if we reasonably can avoid the transformation. If we
can, then we need to look at what the consequences might be.
Jeff