[57/61] Implement synthesised conditional xor/or

Message ID 20250131171232.1018281-59-aleksandar.rakic@htecgroup.com
State New
Headers
Series Improve Mips target |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm fail Patch failed to apply
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 fail Patch failed to apply

Commit Message

Aleksandar Rakic Jan. 31, 2025, 5:13 p.m. UTC
  From: Mihailo Stojanovic <mihailo.stojanovic@rt-rk.com>

Create an additional case for if-conversion which expands the following
sequence: "if (test) x ^= C;" as

a = 0;
if (test) a = C;
x ^= a;

This reduces the number of necessary conditional moves on some targets
(most notably MIPS).

gcc/

        * config/mips/mips.cc (mips_rtx_costs): Increase the cost of
        conditional moves which allow both operands to be registers on
        mips64r6.
        * ifcvt.cc (noce_try_synthesized_xor_ok): New function.  Do not
        try the XOR/IOR conversion if the target has a conditional move
        which accepts two registers.
        (noce_try_synthesized_xor): New function.  Discover the sequence
        of instructions which fit the description and expand them
        accordingly.

gcc/testsuite/

        * gcc.target/mips/cond_xor.c: New test.
        * gcc.target/mips/cond_xor1.c: New test.
        * gcc.target/mips/cond_xor2.c: New test. Skip -Os.

Cherry-picked 5409eee7c24688cd73df92d83a6844a041545c2f,
31d6d46912ad3cbb56c6fc251418c2624b4bb07f and
ff607fa78b23b8e1d753a6e836419e3fe46e3045
from https://github.com/MIPS/gcc

Signed-off-by: Mihailo Stojanovic <mistojanovic@wavecomp.com>
Signed-off-by: Faraz Shahbazker <fshahbazker@wavecomp.com>
Signed-off-by: Chao-ying Fu <cfu@mips.com>
Signed-off-by: Aleksandar Rakic <aleksandar.rakic@htecgroup.com>
---
 gcc/config/mips/mips.cc                   |  18 ++-
 gcc/ifcvt.cc                              | 135 ++++++++++++++++++++++
 gcc/testsuite/gcc.target/mips/cond_xor.c  |  15 +++
 gcc/testsuite/gcc.target/mips/cond_xor1.c |  15 +++
 gcc/testsuite/gcc.target/mips/cond_xor2.c |  15 +++
 5 files changed, 194 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/mips/cond_xor.c
 create mode 100644 gcc/testsuite/gcc.target/mips/cond_xor1.c
 create mode 100644 gcc/testsuite/gcc.target/mips/cond_xor2.c
  

Patch

diff --git a/gcc/config/mips/mips.cc b/gcc/config/mips/mips.cc
index 19d428e6ed6..63b7bdd255c 100644
--- a/gcc/config/mips/mips.cc
+++ b/gcc/config/mips/mips.cc
@@ -5784,10 +5784,20 @@  mips_rtx_costs (rtx x, machine_mode mode, int outer_code,
       return false;
 
     case IF_THEN_ELSE:
-	  if (reg_or_0_operand (XEXP (x, 1), VOIDmode)
-	      || reg_or_0_operand (XEXP (x, 2), VOIDmode))
-	    *total = 0;
-	 return false;
+      if (reg_or_0_operand (XEXP (x, 1), VOIDmode)
+	  || reg_or_0_operand (XEXP (x, 2), VOIDmode))
+	*total = 0;
+      if (outer_code == SET)
+	{
+	  /* Conditional moves on r6 only allow one parameter to be a register
+	     (the other parameter is zero).  Increase the cost of conditional
+	     moves which allow both parameters to be registers.  */
+	  if (mips_isa_rev == 6
+	      && register_operand (XEXP (x, 1), VOIDmode)
+	      && register_operand (XEXP (x, 2), VOIDmode))
+	    *total = 1;
+	}
+      return false;
 
     default:
       return false;
diff --git a/gcc/ifcvt.cc b/gcc/ifcvt.cc
index 74f13a637b2..297ccd470dc 100644
--- a/gcc/ifcvt.cc
+++ b/gcc/ifcvt.cc
@@ -1962,6 +1962,137 @@  noce_try_cmove (struct noce_if_info *if_info)
   return false;
 }
 
+/* If the target has a conditional move which accepts two registers, do not
+   try synthesized conditional XOR/IOR, as it will not yield any benefits.  */
+
+static bool
+noce_try_synthesized_xor_ok (struct noce_if_info *if_info)
+{
+  rtx testreg = gen_rtx_REG (word_mode, LAST_VIRTUAL_REGISTER + 1);
+
+  rtx if_then_else = gen_rtx_IF_THEN_ELSE (word_mode,
+					    if_info->cond,
+					    const0_rtx, if_info->x);
+
+  rtx if_then_else_2 = gen_rtx_IF_THEN_ELSE (word_mode,
+					      if_info->cond,
+					      testreg, if_info->x);
+
+  return rtx_cost (if_then_else_2, word_mode, SET, 1, true)
+	  > rtx_cost (if_then_else, word_mode, SET, 1, true);
+}
+
+/* Expand "if (test) x ^= C;" as
+
+   a = 0;
+   if (test) a = C;
+   x ^= a;
+
+   This lowers the number of necessary conditional moves on some targets.
+
+   We allow for maximum of three instructions in the then block.
+   First one loads the constant into a register.  Second one is an actual
+   XOR/IOR instruction.  Third one is a zero or sign extend.  */
+
+static bool
+noce_try_synthesized_xor (struct noce_if_info *if_info)
+{
+  enum rtx_code code = GET_CODE (if_info->cond);
+
+  if (code != NE && code != EQ)
+    return FALSE;
+
+  /* Fail if there is an else block.  */
+  if (if_info->else_bb)
+    return FALSE;
+
+  /* We allow for the final instruction in the basic block to be sign or
+     zero extend.  */
+  rtx a = if_info->a;
+  rtx_insn *insn_a = if_info->insn_a;
+  if ((GET_CODE (a) == ZERO_EXTEND
+       || GET_CODE (a) == SIGN_EXTEND)
+      && single_set (prev_nonnote_nondebug_insn (insn_a)))
+    {
+      a = SET_SRC (single_set (prev_nonnote_nondebug_insn (insn_a)));
+      insn_a = prev_nonnote_nondebug_insn (insn_a);
+    }
+
+  /* Check that the operation is indeed XOR or IOR.  Also check that we don't
+     have any more instructions in the then block.  */
+  enum rtx_code opcode = GET_CODE (a);
+  if (opcode != XOR
+      && opcode != IOR)
+    return FALSE;
+
+  rtx xor_src = XEXP (a, 0);
+  rtx xor_const = XEXP (a, 1);
+  xor_src = GET_CODE (xor_src) == SUBREG ? SUBREG_REG (xor_src) : xor_src;
+
+  /* Check if the instruction prior to XOR or IOR loads the constant into
+     the register.  */
+  rtx_insn *prev = prev_nonnote_nondebug_insn (insn_a);
+  if (BLOCK_FOR_INSN (insn_a) == BLOCK_FOR_INSN (prev))
+    {
+      if (GET_CODE (xor_const) != REG)
+	return FALSE;
+
+      a = single_set (prev);
+      if (a != NULL_RTX
+	   && rtx_equal_p (SET_DEST (a), xor_const)
+	   && GET_CODE (SET_SRC (a)) == CONST_INT)
+	xor_const = SET_SRC (a);
+      else
+	return FALSE;
+
+      /* This must be the first instruction of the basic block.  */
+      if (BLOCK_FOR_INSN (prev)
+	   == BLOCK_FOR_INSN (prev_nonnote_nondebug_insn (prev)))
+	return FALSE;
+    }
+  else if (GET_CODE (xor_const) != CONST_INT)
+    return FALSE;
+
+  if (!rtx_equal_p (xor_src, if_info->x))
+    return FALSE;
+
+  start_sequence ();
+
+  machine_mode mode = GET_MODE (if_info->x);
+  rtx const_reg = gen_reg_rtx (mode);
+  rtx target = gen_reg_rtx (mode);
+
+  noce_emit_move_insn (const_reg, xor_const);
+  target = noce_emit_cmove (if_info, target, code,
+			     XEXP (if_info->cond, 0),
+			     XEXP (if_info->cond, 1),
+			     const_reg, const0_rtx);
+  if (!target)
+    {
+      end_sequence ();
+      return FALSE;
+    }
+
+  target = expand_simple_binop (GET_MODE (if_info->x), opcode,
+				 if_info->x, target, if_info->x,
+				 0, OPTAB_WIDEN);
+  if (!target)
+    {
+      end_sequence ();
+      return FALSE;
+    }
+
+  rtx_insn* seq = end_ifcvt_sequence (if_info);
+  if (!seq || !targetm.noce_conversion_profitable_p (seq, if_info))
+    return FALSE;
+
+  emit_insn_before_setloc (seq, if_info->jump,
+			    INSN_LOCATION (if_info->insn_a));
+  if_info->transform_name = "noce_try_synthesized_xor";
+
+  return TRUE;
+}
+
 /* Return true if X contains a conditional code mode rtx.  */
 
 static bool
@@ -4246,6 +4377,10 @@  noce_process_if_block (struct noce_if_info *if_info)
       if (HAVE_conditional_move
           && noce_try_cond_zero_arith (if_info))
 	goto success;
+      if (HAVE_conditional_move
+		&& noce_try_synthesized_xor_ok (if_info)
+		&& noce_try_synthesized_xor (if_info))
+  goto success;
       if (HAVE_conditional_move
 	  && noce_try_cmove_arith (if_info))
 	goto success;
diff --git a/gcc/testsuite/gcc.target/mips/cond_xor.c b/gcc/testsuite/gcc.target/mips/cond_xor.c
new file mode 100644
index 00000000000..c0635c9dc53
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/cond_xor.c
@@ -0,0 +1,15 @@ 
+/* { dg-do compile } */
+/* { dg-options "-mabi=64 -march=mips64r6" } */
+/* { dg-skip-if "code quality test" { *-*-* } { "-O0" } } */
+
+NOMIPS16 unsigned int
+foo (unsigned int x, unsigned int y)
+{
+  if (x == 1)
+    y ^= 0xabcd;
+
+  return y;
+}
+
+/* { dg-final { scan-assembler-times "seleqz" 1 } } */
+/* { dg-final { scan-assembler-not "selnez" } } */
diff --git a/gcc/testsuite/gcc.target/mips/cond_xor1.c b/gcc/testsuite/gcc.target/mips/cond_xor1.c
new file mode 100644
index 00000000000..6a09465cecd
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/cond_xor1.c
@@ -0,0 +1,15 @@ 
+/* { dg-do compile } */
+/* { dg-options "-mabi=64 -march=mips64r6" } */
+/* { dg-skip-if "code quality test" { *-*-* } { "-O0" } } */
+
+NOMIPS16 unsigned int
+foo (unsigned int x, unsigned int y, unsigned z)
+{
+  if (x == z)
+    y ^= 0xabcd;
+
+  return y;
+}
+
+/* { dg-final { scan-assembler-times "seleqz" 1 } } */
+/* { dg-final { scan-assembler-not "selnez" } } */
diff --git a/gcc/testsuite/gcc.target/mips/cond_xor2.c b/gcc/testsuite/gcc.target/mips/cond_xor2.c
new file mode 100644
index 00000000000..a64cc189116
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/cond_xor2.c
@@ -0,0 +1,15 @@ 
+/* { dg-do compile } */
+/* { dg-options "-mabi=64 -march=mips64r6" } */
+/* { dg-skip-if "code quality test" { *-*-* } { "-O0" "-Os" } } */
+
+NOMIPS16 unsigned int
+foo (unsigned int x, unsigned int y, unsigned int z)
+{
+  if (x == 1)
+    y ^= z;
+
+  return y;
+}
+
+/* { dg-final { scan-assembler-times "seleqz" 1 } } */
+/* { dg-final { scan-assembler-times "selnez" 1 } } */