On 9/24/26 2:26 AM, Naveen wrote:
The generic conditional-move expansion currently expands both operands
before looking for a target-specific conditional negate or complement.
This misses idioms such as:
X ? -X : X
X ? ~X : X
and can allow recursive conditional-move expansion when the target-specific
expansion fails.
Recognize conditional negate and complement expressions including reversed
operands and SSA no-op conversions before the existing conditional-move
expansion. Keep the recursion guard active while trying the target
expansion and while falling back to the normal operand expansion.
Add the RISC-V neg<mode>cc pattern for TARGET_ZICOND_LIKE targets. The
pattern expands conditional negation as:
CMP = X ? 1 : 0
MASK = -CMP
DEST = X ^ MASK
DEST += CMP
Avoid this expansion when the operand is a materialized constant since
the existing constant sequence is shorter.
Regression tested on riscv64-unknown-elf. No new failures.
gcc/ChangeLog:
PR target/126245
* expr.cc (strip_ssa_nop_conversions): New.
(get_unary_op_operand): New.
(expand_cond_expr_using_cmove): Recognize conditional negate and
complement expressions and use target-specific expansion while
* riscv.md (neg<mode>cc): New.
gcc/testsuite/ChangeLog:
PR target/126245
* gcc.target/riscv/pr126245.c: New test.
co-authored-by: Andrew Pinski <[email protected]>
Signed-off-by: Naveen <[email protected]>
This seems more complex than I anticipated. So to be clear, not
rejecting, just exploring why that is.
---
gcc/config/riscv/riscv.md | 32 +++++
gcc/expr.cc | 146 ++++++++++++++++++++++
gcc/testsuite/gcc.target/riscv/pr126245.c | 24 ++++
3 files changed, 202 insertions(+)
create mode 100644 gcc/testsuite/gcc.target/riscv/pr126245.c
diff --git a/gcc/config/riscv/riscv.md b/gcc/config/riscv/riscv.md
index 8420b3d29e7..ae57b77cbd0 100644
--- a/gcc/config/riscv/riscv.md
+++ b/gcc/config/riscv/riscv.md
@@ -3504,6 +3504,38 @@
FAIL;
})
+(define_expand "neg<mode>cc"
+ [(set (match_operand:GPR 0 "register_operand")
+ (if_then_else:GPR (match_operand 1 "comparison_operator")
+ (neg:GPR (match_operand:GPR 2 "register_operand"))
+ (match_operand:GPR 3 "register_operand")))]
+ "TARGET_ZICOND_LIKE"
+{
+ rtx cmp = operands[1];
+ rtx cmp0 = XEXP (cmp, 0);
+ rtx cmp1 = XEXP (cmp, 1);
+
+ if (!INTEGRAL_MODE_P (GET_MODE (cmp0))
+ || !rtx_equal_p (operands[2], operands[3]))
+ FAIL;
+
+ rtx_insn *last = get_last_insn_anywhere ();
+ rtx set = last ? single_set (last) : NULL_RTX;
+ if (set
+ && rtx_equal_p (SET_DEST (set), operands[2])
+ && CONST_INT_P (SET_SRC (set)))
+ FAIL;
+
+ rtx cmp_result = gen_reg_rtx (<MODE>mode);
+ riscv_expand_int_scc (cmp_result, GET_CODE (cmp), cmp0, cmp1);
+
+ rtx mask = gen_reg_rtx (<MODE>mode);
+ riscv_emit_unary (NEG, mask, cmp_result);
+ riscv_emit_binary (XOR, operands[0], operands[2], mask);
+ riscv_emit_binary (PLUS, operands[0], operands[0], cmp_result);
+ DONE;
+})
I would probably suggest this as an independent patch as this is useful
on its own without the expr.cc changes (it'll get used via pre-existing
paths in ifcvt.cc).
Drop the TARGET_ZICOND_LIKE test. This is independent of the zicond
style extensions.
Not sure why you're trying to find a prior insn then verify that it's
the same as operands[2] or why you want to verify the source of that set
is a constant integer. It seems this sequence works irrespective of
these conditions.
I would suggest generating another pseudo for the destination of the XOR
so that we're not writing operands[0] twice. There's nothing
functionally wrong with doing that, but there's minor downstream
optimizations that work better if pseudos are only set once.
+/* Strip no-op conversions represented by SSA assignments from EXP. */
What's the motivation here for trying to handle conversions? If we're
really going to try and strip them, we have the STRIP_NOPs family of
macros for that.
@@ -9701,6 +9736,117 @@ expand_cond_expr_using_cmove (tree treeop0
ATTRIBUTE_UNUSED,
expanding_cond_expr_using_cmove = true;
start_sequence ();
+
+ /* Try to expand a conditional negate or complement directly. Expanding
+ the negation first would lose this opportunity and leave the target with
+ a conditional move between X and -X. */
Right. Though selecting between a conditional move vs this custom
sequence may be something we need to cost. A target with true
conditional moves would generate neg+cmov. This sequence is going to be
neg+xor+add. So I think we need to generate both forms and cost the
generated sequence if we tackle this in expr.cc.
+ tree conditional_neg_operand = NULL_TREE;
+ rtx_code conditional_neg_code = UNKNOWN;
+ bool reverse_conditional_neg = false;
+
+ tree other_tree = strip_ssa_nop_conversions (treeop2);
+ tree neg_operand = get_unary_op_operand (treeop1, NEGATE_EXPR);
+ if (neg_operand)
+ conditional_neg_code = NEG;
+ else
+ {
+ neg_operand = get_unary_op_operand (treeop1, BIT_NOT_EXPR);
+ if (neg_operand)
+ conditional_neg_code = NOT;
+ }
+
+ if (neg_operand
+ && operand_equal_p (strip_ssa_nop_conversions (neg_operand),
+ other_tree, 0))
+ conditional_neg_operand = treeop2;
+ else
+ {
+ tree other_neg_operand
+ = get_unary_op_operand (treeop2, NEGATE_EXPR);
+ if (other_neg_operand)
+ conditional_neg_code = NEG;
+ else
+ {
+ other_neg_operand = get_unary_op_operand (treeop2, BIT_NOT_EXPR);
+ if (other_neg_operand)
+ conditional_neg_code = NOT;
+ }
+
+ if (other_neg_operand
+ && operand_equal_p (strip_ssa_nop_conversions (other_neg_operand),
+ strip_ssa_nop_conversions (treeop1), 0))
+ {
+ conditional_neg_operand = treeop1;
+ reverse_conditional_neg = true;
+ }
+ }
So would come kind of canonicalization simplify the hunk above? You're
basically looking for the NEG/NOT in both arms which is great, but it
seems like we ought to be able to make this somewhat cleaner.
+
+ if (conditional_neg_operand != NULL_TREE
+ && !TREE_SIDE_EFFECTS (conditional_neg_operand))
+ {
+ rtx_insn *last = get_last_insn ();
+
+ rtx value = expand_normal (conditional_neg_operand);
+
+ if (TREE_CODE (treeop0) == SSA_NAME
+ && (srcstmt = get_def_for_expr_class (treeop0, tcc_comparison))
+ && !VECTOR_TYPE_P (TREE_TYPE (gimple_assign_rhs1 (srcstmt))))
+ {
+ type = TREE_TYPE (gimple_assign_rhs1 (srcstmt));
+ enum tree_code cmpcode = gimple_assign_rhs_code (srcstmt);
+ op00 = expand_normal (gimple_assign_rhs1 (srcstmt));
+ op01 = expand_normal (gimple_assign_rhs2 (srcstmt));
+ comparison_mode = TYPE_MODE (type);
+ unsignedp = TYPE_UNSIGNED (type);
+ comparison_code = convert_tree_comp_to_rtx (cmpcode, unsignedp);
+ }
+ else if (COMPARISON_CLASS_P (treeop0)
+ && !VECTOR_TYPE_P (TREE_TYPE (TREE_OPERAND (treeop0, 0))))
+ {
+ type = TREE_TYPE (TREE_OPERAND (treeop0, 0));
+ enum tree_code cmpcode = TREE_CODE (treeop0);
+ op00 = expand_normal (TREE_OPERAND (treeop0, 0));
+ op01 = expand_normal (TREE_OPERAND (treeop0, 1));
+ unsignedp = TYPE_UNSIGNED (type);
+ comparison_mode = TYPE_MODE (type);
+ comparison_code = convert_tree_comp_to_rtx (cmpcode, unsignedp);
And this feels similar. The code is almost exactly the same in this two
paths above.
So concretely, let's tackle the RISC-V target bits separately since that
pattern can go in independently of the expr.cc changes.
For the expr.cc changes the big issue is needing to cost both styles of
sequence and pick whichever is lower, try to use STRIP_NOPs rather than
rolling our own and avoid duplicate blocks of code if possible with some
refactoring/canonicalization.
Thanks for tackling this!
jeff