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


Reply via email to