The generic conditional-move expansion currently expands both operands
before looking for a target-specific conditional negate or complement
operation.  This misses expressions such as:

  cond ? -value : value
  cond ? ~value : value

Recognize these forms including reversed operands and SSA expression
definitions before expanding the conditional-move operands.  Try the
target-specific operation while keeping the conditional-move recursion
guard active.  If both expansions succeed, select the cheaper sequence.

gcc/ChangeLog:
        PR target/126245
        * expr.cc (get_unary_op_operand): New.
        (get_conditional_neg_operand): New.
        (expand_conditional_comparison): New.
        (expand_cond_expr_using_cmove): Recognize conditional negate and
        complement expressions and try target-specific expansions.

gcc/testsuite/ChangeLog:
        PR target/126245
        * gcc.target/riscv/pr126245.c: New test.

Signed-off-by: Naveen <[email protected]>
---
 gcc/expr.cc                               | 231 +++++++++++++++++-----
 gcc/testsuite/gcc.target/riscv/pr126245.c |  24 +++
 2 files changed, 201 insertions(+), 54 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/riscv/pr126245.c

diff --git a/gcc/expr.cc b/gcc/expr.cc
index a9157dc5d41..b1b342b4106 100644
--- a/gcc/expr.cc
+++ b/gcc/expr.cc
@@ -9657,6 +9657,82 @@ expand_expr_real (tree exp, rtx target, machine_mode 
tmode,
   return ret;
 }
 
+/* Return the operand of a unary operation CODE in EXP.  */
+
+static tree
+get_unary_op_operand (tree exp, enum tree_code code)
+{
+  STRIP_NOPS (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;
+}
+
+/* Return the operand of a conditional negate or complement in EXP, and set
+   CODE to the corresponding RTL operation.  */
+
+static tree
+get_conditional_neg_operand (tree exp, rtx_code *code)
+{
+  tree operand = get_unary_op_operand (exp, NEGATE_EXPR);
+  if (operand)
+    {
+      *code = NEG;
+      return operand;
+    }
+
+  operand = get_unary_op_operand (exp, BIT_NOT_EXPR);
+  if (operand)
+    *code = NOT;
+  return operand;
+}
+
+/* Expand EXP into a comparison suitable for the conditional move expanders.
+   This is kept in one place since both candidate sequences need the same
+   condition.  */
+
+static void
+expand_conditional_comparison (tree exp, rtx_comparison *comparison,
+                              int *unsignedp)
+{
+  gimple *srcstmt = NULL;
+  if (TREE_CODE (exp) == SSA_NAME
+      && (srcstmt = get_def_for_expr_class (exp, tcc_comparison))
+      && !VECTOR_TYPE_P (TREE_TYPE (gimple_assign_rhs1 (srcstmt))))
+    {
+      tree type = TREE_TYPE (gimple_assign_rhs1 (srcstmt));
+      enum tree_code cmpcode = gimple_assign_rhs_code (srcstmt);
+      comparison->op0 = expand_normal (gimple_assign_rhs1 (srcstmt));
+      comparison->op1 = 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 (exp)
+          && !VECTOR_TYPE_P (TREE_TYPE (TREE_OPERAND (exp, 0))))
+    {
+      tree type = TREE_TYPE (TREE_OPERAND (exp, 0));
+      enum tree_code cmpcode = TREE_CODE (exp);
+      comparison->op0 = expand_normal (TREE_OPERAND (exp, 0));
+      comparison->op1 = expand_normal (TREE_OPERAND (exp, 1));
+      comparison->mode = TYPE_MODE (type);
+      *unsignedp = TYPE_UNSIGNED (type);
+      comparison->code = convert_tree_comp_to_rtx (cmpcode, *unsignedp);
+    }
+  else
+    {
+      comparison->op0 = expand_normal (exp);
+      comparison->op1 = const0_rtx;
+      comparison->code = NE;
+      comparison->mode = GET_MODE (comparison->op0);
+      if (comparison->mode == VOIDmode)
+       comparison->mode = TYPE_MODE (TREE_TYPE (exp));
+    }
+}
+
 /* 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
@@ -9667,11 +9743,7 @@ expand_cond_expr_using_cmove (tree treeop0 
ATTRIBUTE_UNUSED,
                              tree treeop1 ATTRIBUTE_UNUSED,
                              tree treeop2 ATTRIBUTE_UNUSED)
 {
-  rtx insn;
-  rtx op00, op01, op1, op2;
-  enum rtx_code comparison_code;
-  machine_mode comparison_mode;
-  gimple *srcstmt;
+  rtx op1, op2;
   rtx temp;
   tree type = TREE_TYPE (treeop1);
   int unsignedp = TYPE_UNSIGNED (type);
@@ -9699,45 +9771,84 @@ expand_cond_expr_using_cmove (tree treeop0 
ATTRIBUTE_UNUSED,
   else
     temp = assign_temp (type, 0, 1);
 
-  expanding_cond_expr_using_cmove = true;
-  start_sequence ();
-  expand_operands (treeop1, treeop2,
-                  mode == orig_mode ? temp : NULL_RTX, &op1, &op2,
-                  EXPAND_NORMAL);
+  /* Find a conditional negation or complement before expanding either arm.
+     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;
 
-  if (TREE_CODE (treeop0) == SSA_NAME
-      && (srcstmt = get_def_for_expr_class (treeop0, tcc_comparison))
-      && !VECTOR_TYPE_P (TREE_TYPE (gimple_assign_rhs1 (srcstmt))))
+  tree treeop1_nops = treeop1;
+  tree treeop2_nops = treeop2;
+  STRIP_NOPS (treeop1_nops);
+  STRIP_NOPS (treeop2_nops);
+
+  tree neg_operand = get_conditional_neg_operand (treeop1_nops,
+                                                &conditional_neg_code);
+  if (neg_operand)
     {
-      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);
+      STRIP_NOPS (neg_operand);
+      if (operand_equal_p (neg_operand, treeop2_nops, 0))
+       conditional_neg_operand = treeop2;
     }
-  else
+
+  if (!conditional_neg_operand)
     {
-      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));
+      rtx_code other_neg_code = UNKNOWN;
+      tree other_neg_operand
+       = get_conditional_neg_operand (treeop2_nops, &other_neg_code);
+      if (other_neg_operand)
+       {
+         STRIP_NOPS (other_neg_operand);
+         if (operand_equal_p (other_neg_operand, treeop1_nops, 0))
+           {
+             conditional_neg_code = other_neg_code;
+             conditional_neg_operand = treeop1;
+             reverse_conditional_neg = true;
+           }
+       }
     }
-  expanding_cond_expr_using_cmove = false;
+
+  expanding_cond_expr_using_cmove = true;
+
+  rtx_insn *neg_seq = NULL;
+  if (conditional_neg_operand
+      && !TREE_SIDE_EFFECTS (conditional_neg_operand))
+    {
+      start_sequence ();
+      rtx value = expand_normal (conditional_neg_operand);
+      rtx_comparison comparison;
+      int comparison_unsignedp = unsignedp;
+      expand_conditional_comparison (treeop0, &comparison,
+                                    &comparison_unsignedp);
+
+      if (reverse_conditional_neg)
+       comparison.code = reverse_condition (comparison.code);
+
+      rtx neg_insn = NULL_RTX;
+      if (comparison.code != UNKNOWN)
+       {
+         rtx cond = gen_rtx_fmt_ee (comparison.code, comparison.mode,
+                                             comparison.op0, comparison.op1);
+         if (GET_MODE (value) != mode)
+           value = gen_lowpart (mode, value);
+         neg_insn = emit_conditional_neg_or_complement
+           (temp, conditional_neg_code, mode, cond, value, value);
+       }
+
+      rtx_insn *seq = end_sequence ();
+      if (neg_insn)
+       neg_seq = seq;
+    }
+
+  start_sequence ();
+  expand_operands (treeop1, treeop2,
+                  mode == orig_mode ? temp : NULL_RTX, &op1, &op2,
+                  EXPAND_NORMAL);
+  rtx_comparison comparison;
+  int comparison_unsignedp = unsignedp;
+  expand_conditional_comparison (treeop0, &comparison,
+                                &comparison_unsignedp);
 
   if (GET_MODE (op1) != mode)
     op1 = gen_lowpart (mode, op1);
@@ -9746,25 +9857,37 @@ expand_cond_expr_using_cmove (tree treeop0 
ATTRIBUTE_UNUSED,
     op2 = gen_lowpart (mode, op2);
 
   /* Try to emit the conditional move.  */
-  insn = emit_conditional_move (temp,
-                               { comparison_code, op00, op01,
-                                 comparison_mode },
-                               op1, op2, mode,
-                               unsignedp);
+  rtx insn = emit_conditional_move (temp,
+                               comparison, op1, op2, mode,
+                               comparison_unsignedp);
+  rtx_insn *cmove_seq = end_sequence ();
+  if (!insn)
+    cmove_seq = NULL;
 
-  /* If we could do the conditional move, emit the sequence,
-     and return.  */
-  if (insn)
+  rtx_insn *seq = NULL;
+  if (neg_seq && cmove_seq)
     {
-      rtx_insn *seq = end_sequence ();
-      emit_insn (seq);
-      return convert_modes (orig_mode, mode, temp, 0);
+      bool speed_p = optimize_insn_for_speed_p ();
+      unsigned neg_cost = seq_cost (neg_seq, speed_p);
+      unsigned cmove_cost = seq_cost (cmove_seq, speed_p);
+      if (neg_cost == cmove_cost)
+       {
+         neg_cost = seq_cost (neg_seq, !speed_p);
+         cmove_cost = seq_cost (cmove_seq, !speed_p);
+       }
+      seq = neg_cost < cmove_cost ? neg_seq : cmove_seq;
     }
+  else if (neg_seq)
+    seq = neg_seq;
+  else if (cmove_seq)
+    seq = cmove_seq;
 
-  /* Otherwise discard the sequence and fall back to code with
-     branches.  */
-  end_sequence ();
-  return NULL_RTX;
+  expanding_cond_expr_using_cmove = false;
+  if (!seq)
+    return NULL_RTX;
+
+  emit_insn (seq);
+  return convert_modes (orig_mode, mode, temp, 0);
 }
 
 /* A helper function for expand_expr_real_2 to be used with a
diff --git a/gcc/testsuite/gcc.target/riscv/pr126245.c 
b/gcc/testsuite/gcc.target/riscv/pr126245.c
new file mode 100644
index 00000000000..d3ae337be58
--- /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 } } } */
+
+unsigned long
+f_cmp_lt (unsigned long x, unsigned long y)
+{
+  unsigned long cmp = x < y;
+  return (x ^ -cmp) + cmp;
+}
+
+/* { dg-final { scan-assembler-times {\msltu\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

Reply via email to