On 5/27/2026 3:57 AM, Dusan Stojkovic wrote:
Handle more shapes in ifcvt that occur in code on rv64 + zicond:

For example:

   int foo (int cond, int val) {
     if (cond) val *= 64;
     return val;
   }

the desired 3-insn output:

   li      a5,6
   czero.eqz a0,a5,a0
   sllw    a0,a1,a0

in place of the previous 4-insn slli/czero.eqz/czero.nez/add sequence
that noce_try_cmove_arith was producing.

This patch improves cases for:
* int_ShiftLeft_const
* int_Mul_pow2_const
* int_Add\Sub_const

As well as improving tests from PR123322 with
(sign_extend:DI (subreg:SI (reg:DI 138) 0)))
cases.

The peel of the outer SIGN_EXTEND/ZERO_EXTEND is gated on the SUBREG
inside the binop being SUBREG_PROMOTED_VAR_P with a promotion sign
matching the outer extension code. This is the property that
makes (EXT (SUBREG b)) bit-equal to b.
W
The existing profitability gate (targetm.noce_conversion_profitable_p
at end_ifcvt_sequence) already covers this transform, so no separate
cost check is added.

AND is excluded from the peel path because its identity op is -1,
not 0, and noce_try_cond_arith already handles it through a separate
code path.

XOR is a different case which succeeds through noce_convert_multiple_sets
And thus still produces suboptimal code.
2026-05-27  Dusan Stojkovic  <[email protected]>

      PR target/124956

gcc/ChangeLog:

      * ifcvt.cc (noce_peel_extend_around_binop): New helper.
      (noce_try_cond_arith): Peel an optional SIGN/ZERO_EXTEND
               wrapping of a supported binary op re-apply at success label.
               Accept CONST_INT as the second operand of the binary op.

gcc/testsuite/ChangeLog:

      * gcc.target/riscv/pr123322.c: Reduce the number of czeros expected.
      * gcc.target/riscv/pr124956.c: New test.
So at the heart this seems to rely on having the zero/sign extend wrapping a subreg with the appropriate promoted mode flags.  I would generally expect that such cases would be limited the zero/sign extension node could be eliminated in the IL with no change semantics.   So if this is working consistently, it likely points to a missed optimization elsewhere in the compiler.

What we did internally was recognize any wrapping sign/zero extend early, noting that we had done so and what kind of extension we saw.  We'd strip the extension, let the code run normally, then re-apply the extension at the end.

That in and of itself wasn't terrible and probably gets the vast majority of the benefit.  There's a bit of code that I noted as "odd" after the fact, but never chased down why it was added.  It's not obvious if it would still be needed or not.

Just playing around a bit I ended up with the attached patch.  I haven't tested it beyond verifying the testcase gives the desired code.

I wouldn't be surprised if handling constants in get_base is in and of itself a good thing.  So extracting those bits independently might be a good first step.

I haven't really thought about the implications of SUBREGs in the code, either narrowing or paradoxical.  It may be OK, it may not.

I really didn't like the extra call to get_base_reg_or_constant. But I didn't see a good solution.  In the immediately preceeding fragment I think we want the subreg stripped, but we need the subreg expression for all the subsequent code.  There may not be a better solution there.

The sequence we generate for the testcase looks like:

(insn 28 0 29 (set (reg:SI 142)
        (const_int 6 [0x6])) -1
     (nil))

(insn 29 28 30 (set (reg:SI 140)
        (if_then_else:SI (eq:DI (reg/v:DI 135 [ cond ])
                (const_int 0 [0]))
            (const_int 0 [0])
            (reg:SI 142))) -1
     (nil))

(insn 30 29 31 (set (reg:DI 144)
        (sign_extend:DI (ashift:SI (subreg:SI (reg/v:DI 136 [ val ]) 0)
                (subreg:QI (reg:SI 140) 0)))) -1
     (nil))

(insn 31 30 32 (set (reg:SI 143)
        (subreg/s/u:SI (reg:DI 144) 0)) -1
     (expr_list:REG_EQUAL (ashift:SI (subreg:SI (reg/v:DI 136 [ val ]) 0)
            (subreg:QI (reg:SI 140) 0))
        (nil)))

(insn 32 31 0 (set (reg/v:DI 136 [ val ])
        (sign_extend:DI (reg:SI 143))) -1
     (nil))

insns 31/32 are redundant.  They will eventually get eliminated, but they will be costed.  If there's a clean way to avoid those, it would be good.  Otherwise it may be better do go ahead and do the shift in DI, then extract the bits we want with

(sign_extend:DI (subreg:SI (shift output:DI)))

That's one less useless insn to cost.  But I haven't thought at all about the implications of generating code like that.

Anyway, it's another path to play with.

jeff


diff --git a/gcc/ifcvt.cc b/gcc/ifcvt.cc
index c151146c1b2..f4118d6cf2d 100644
--- a/gcc/ifcvt.cc
+++ b/gcc/ifcvt.cc
@@ -3120,12 +3120,16 @@ noce_cond_zero_binary_op_supported (rtx op)
     otherwise NULL_RTX for other RTX_CODE.  */
 
 static rtx
-get_base_reg (rtx exp)
+get_base_reg_or_constant (rtx exp, bool strip_subreg)
 {
   if (REG_P (exp))
     return exp;
-  else if (SUBREG_P (exp))
+  else if (SUBREG_P (exp) && strip_subreg)
     return SUBREG_REG (exp);
+  else if (SUBREG_P (exp))
+    return exp;
+  else if (CONST_INT_P (exp))
+    return exp;
 
   return NULL_RTX;
 }
@@ -3163,6 +3167,8 @@ noce_try_cond_arith (struct noce_if_info *if_info)
   rtx_insn *seq;
   rtx_code op;
   machine_mode mode = GET_MODE (if_info->x);
+  bool zero_extend = false;
+  bool extend = false;
 
   /* Scalar integral modes are only supported here.
      Could support scalar floating point but that
@@ -3177,6 +3183,15 @@ noce_try_cond_arith (struct noce_if_info *if_info)
   a = copy_rtx (if_info->a);
   b = copy_rtx (if_info->b);
 
+  /* Strip any extension of A.  We will re-apply it later.  */
+  if (GET_CODE (a) == SIGN_EXTEND || GET_CODE (a) == ZERO_EXTEND)
+    {
+      zero_extend = (op == ZERO_EXTEND);
+      a = XEXP (a, 0);
+      mode = GET_MODE (a);
+      extend = true;
+    }
+
   /* Canonicalize x = y : (y op z) to x = (y op z) : y.  */
   if (REG_P (a) && noce_cond_zero_binary_op_supported (b))
     {
@@ -3200,24 +3215,26 @@ noce_try_cond_arith (struct noce_if_info *if_info)
   op = GET_CODE (a);
 
   /* Canonicalize x = (z op y) : y to x = (y op z) : y */
-  a_op1 = get_base_reg (XEXP (a, 1));
+  a_op1 = get_base_reg_or_constant (XEXP (a, 1), !extend);
   if (a_op1 && rtx_equal_p (a_op1, b) && COMMUTATIVE_ARITH_P (a))
     {
       std::swap (XEXP (a, 0), XEXP (a, 1));
-      a_op1 = get_base_reg (XEXP (a, 1));
+      a_op1 = get_base_reg_or_constant (XEXP (a, 1), !extend);
     }
 
   if (a_op1 == NULL_RTX)
     goto fail;
 
   /* Ensure the cond is of form: x = (y op z) : y */
-  a_op0 = get_base_reg (XEXP (a, 0));
+  a_op0 = get_base_reg_or_constant (XEXP (a, 0), true);
   if (!(a_op0 && rtx_equal_p (a_op0, b)))
     goto fail;
 
+  a_op0 = get_base_reg_or_constant (XEXP (a, 0), extend);
+
   start_sequence ();
 
-  target = gen_reg_rtx (GET_MODE (XEXP (a, op != AND)));
+  target = gen_reg_rtx (GET_MODE (XEXP (a, !extend && op != AND)));
 
   /* AND requires !cond, instead we swap ops around.  */
   target = noce_emit_cmove (if_info, target, code,
@@ -3313,6 +3330,14 @@ noce_try_cond_arith (struct noce_if_info *if_info)
     goto end_seq_n_fail;
 
 success:
+  if (extend)
+    {
+      target = gen_rtx_fmt_e (zero_extend ? ZERO_EXTEND : SIGN_EXTEND,
+                             GET_MODE (if_info->x), target);
+      emit_move_insn (if_info->x, target);
+      target = if_info->x;
+    }
+
   if (target != if_info->x)
     noce_emit_move_insn (if_info->x, target);
 

Reply via email to