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]>
---
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;
+})
+
(define_expand "add<mode>cc"
[(match_operand:GPR 0 "register_operand")
(match_operand 1 "comparison_operator")
diff --git a/gcc/expr.cc b/gcc/expr.cc
index a9157dc5d41..f865ff47c8a 100644
--- a/gcc/expr.cc
+++ b/gcc/expr.cc
@@ -9657,6 +9657,41 @@ expand_expr_real (tree exp, rtx target, machine_mode
tmode,
return ret;
}
+/* Strip no-op conversions represented by SSA assignments from EXP. */
+
+static tree
+strip_ssa_nop_conversions (tree exp)
+{
+ while (TREE_CODE (exp) == SSA_NAME)
+ {
+ gimple *stmt = get_gimple_for_ssa_name (exp);
+ if (!stmt
+ || !is_gimple_assign (stmt)
+ || !CONVERT_EXPR_CODE_P (gimple_assign_rhs_code (stmt))
+ || !tree_nop_conversion_p (TREE_TYPE (exp),
+ TREE_TYPE (gimple_assign_rhs1 (stmt))))
+ break;
+ exp = gimple_assign_rhs1 (stmt);
+ }
+
+ return tree_strip_nop_conversions (exp);
+}
+
+/* Return the operand of a unary operation CODE in EXP looking through
+ no-op conversions represented by SSA assignments. */
+
+static tree
+get_unary_op_operand (tree exp, enum tree_code code)
+{
+ exp = strip_ssa_nop_conversions (exp);
+
+ if (TREE_CODE (exp) == code)
+ return TREE_OPERAND (exp, 0);
+
+ gimple *stmt = get_def_for_expr (exp, code);
+ return stmt ? gimple_assign_rhs1 (stmt) : NULL_TREE;
+}
+
/* Try to expand the conditional expression which is represented by
TREEOP0 ? TREEOP1 : TREEOP2 using conditional moves. If it succeeds
return the rtl reg which represents the result. Otherwise return
@@ -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. */
+ 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;
+ }
+ }
+
+ 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);
+ }
+ else
+ {
+ op00 = expand_normal (treeop0);
+ op01 = const0_rtx;
+ comparison_code = NE;
+ comparison_mode = GET_MODE (op00);
+ if (comparison_mode == VOIDmode)
+ comparison_mode = TYPE_MODE (TREE_TYPE (treeop0));
+ }
+
+ if (reverse_conditional_neg)
+ comparison_code = reverse_condition (comparison_code);
+
+ if (comparison_code != UNKNOWN)
+ {
+ rtx cond = gen_rtx_fmt_ee (comparison_code, comparison_mode,
+ op00, op01);
+
+ if (GET_MODE (value) != mode)
+ value = gen_lowpart (mode, value);
+
+ insn = emit_conditional_neg_or_complement (temp,
+ conditional_neg_code,
+ mode, cond, value,
value);
+ if (insn)
+ {
+ rtx_insn *seq = end_sequence ();
+ emit_insn (seq);
+ expanding_cond_expr_using_cmove = false;
+ return convert_modes (orig_mode, mode, temp, 0);
+ }
+ }
+
+ delete_insns_since (last);
+ }
+
expand_operands (treeop1, treeop2,
mode == orig_mode ? temp : NULL_RTX, &op1, &op2,
EXPAND_NORMAL);
diff --git a/gcc/testsuite/gcc.target/riscv/pr126245.c
b/gcc/testsuite/gcc.target/riscv/pr126245.c
new file mode 100644
index 00000000000..cd0a4a4edd8
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/pr126245.c
@@ -0,0 +1,24 @@
+/* PR target/126245 */
+/* { dg-do compile } */
+/* { dg-options "-O2 -march=rv64g_zicond -mabi=lp64d" { target { rv64 } } } */
+/* { dg-options "-O2 -march=rv32g_zicond -mabi=ilp32d" { target { rv32 } } } */
+
+long
+f_cmp_lt (long x, long y)
+{
+ long cmp = x < y;
+ return (x ^ -cmp) + cmp;
+}
+
+/* { dg-final { scan-assembler-times {\mslt\M} 1 } } */
+/* { dg-final { scan-assembler-times {\mneg\M} 1 } } */
+/* { dg-final { scan-assembler-times {\mxor\M} 1 } } */
+/* { dg-final { scan-assembler {f_cmp_lt:(?:[^\n]*\n)*?\tadd\t} } } */
+
+long
+f_cmove_guard_reset (long cond, long a, long b)
+{
+ return cond ? a : b;
+}
+
+/* { dg-final { scan-assembler {\mczero\.(eqz|nez)\M} } } */
--
2.34.1