diff mbox

[RFC,4/2,v3] Refactor noce_try_cmove_arith

Message ID 1469008295-28884-4-git-send-email-james.greenhalgh@arm.com
State New
Headers show

Commit Message

James Greenhalgh July 20, 2016, 9:51 a.m. UTC
Hi,

This patch pulls some duplicate logic out from noce_try_cmove_arith.
We do this in order to make reasoning about the code easier.

Some of the natural simplification that comes from this process improves
the generation of temporaries in the code, which is good as it reduces
the size and speed costs of the generated sequence.  We want to do this
as the more useless register moves we can remove early, the more accurate
our profitability analysis will be.

Bootstrapped on x86_64 and aarch64 with no issues.

OK?

Thanks,
James

---
2016-07-20  James Greenhalgh  <james.greenhalgh@arm.com>

	* ifcvt.c (noce_arith_helper): New.
	(noce_try_cmove_arith): Refactor.
diff mbox

Patch

diff --git a/gcc/ifcvt.c b/gcc/ifcvt.c
index 4e3d8f3..f2e7ac6 100644
--- a/gcc/ifcvt.c
+++ b/gcc/ifcvt.c
@@ -2068,23 +2068,127 @@  noce_emit_bb (rtx last_insn, basic_block bb, bool simple)
   return true;
 }
 
-/* Try more complex cases involving conditional_move.  */
+/* Helper for noce_try_cmove_arith.  This gets called twice, once for the
+   then branch, once for the else branch X_BB gives the basic block for the
+   branch we are currently interested in.  X is the destination for this
+   branch.  If X is complex, we need to move it in to a register first, by
+   possibly copying from INSN_X such that we preserve clobbers etc from the
+   original instruction.  EMIT_X is the target register for this branch
+   result.  ORIG_OTHER_DEST gives the original destination from the
+   opposite branch.  OTHER_BB_EXISTS_P is true if there was an opposite
+   branch for us to consider.  */
+
+bool
+noce_arith_helper (rtx *x, rtx *emit_x, rtx_insn *insn_x,
+		   basic_block x_bb, rtx orig_other_dest,
+		   bool other_bb_exists_p)
+{
+  rtx set_tmp = NULL_RTX;
+
+  machine_mode x_mode = GET_MODE (*x);
+
+  /* Two cases to catch here.  Either X is not yet a general operand, in
+     which case we need to move it to an appropriate register.  Or, the other
+     block is empty, in which case ORIG_OTHER_DEST came from the test block.
+     The non-empty complex block that we will emit might clobber the register
+     used by ORIG_OTHER_DEST, so move it to a pseudo first.  */
+  if (! general_operand (*x, x_mode)
+      || !other_bb_exists_p)
+    {
+      rtx reg = gen_reg_rtx (x_mode);
+      if (insn_x)
+	{
+	  rtx_insn *copy_of_x = as_a <rtx_insn *> (copy_rtx (insn_x));
+	  rtx set = single_set (copy_of_x);
+	  SET_DEST (set) = reg;
+	  set_tmp = PATTERN (copy_of_x);
+	}
+      else
+	{
+	  set_tmp = gen_rtx_SET (reg, *x);
+	}
+      *x = reg;
+    }
+
+  /* Check that our new insn isn't going to clobber ORIG_OTHER_DEST.  */
+  bool modified_in_x = (set_tmp != NULL_RTX)
+			&& modified_in_p (orig_other_dest, set_tmp);
+
+  /* If we have a X_BB to check, go through it and make sure the insns we'd
+     duplicate don't write ORIG_OTHER_DEST.  */
+  if (x_bb)
+    {
+      rtx_insn *tmp_insn = NULL;
+      FOR_BB_INSNS (x_bb, tmp_insn)
+	/* Don't check inside the destination insn, we will have changed
+	   it to use a register that doesn't conflict.  */
+	if (!(insn_x && tmp_insn == insn_x)
+	    && modified_in_p (orig_other_dest, tmp_insn))
+	  {
+	    modified_in_x = true;
+	    break;
+	  }
+    }
+
+  /* Store the SET back in EMIT_X.  */
+  *emit_x = set_tmp;
+  return modified_in_x;
+}
+
+/* Try more complex cases involving conditional_move.
+
+   We have:
+
+      if (test)
+	x = a + b;
+      else
+	x = c - d;
+
+    Make it:
+
+      t1 = a + b;
+      t2 = c - d;
+      x = (test) ? t1 : t2;
+
+   Alternatively, we have:
+
+      if (test)
+	x = *y;
+      else
+	x = *z;
+
+   Make it:
+
+     p1 = (test) ? y : z;
+     x = *p1;
+*/
 
 static int
 noce_try_cmove_arith (struct noce_if_info *if_info)
 {
+  /* SET_SRC from the two branches.  */
   rtx a = if_info->a;
   rtx b = if_info->b;
+  /* SET_DEST of both branches.  */
   rtx x = if_info->x;
-  rtx orig_a, orig_b;
-  rtx_insn *insn_a, *insn_b;
+  /* Full insns from the two branches.  */
+  rtx_insn *insn_a = if_info->insn_a;
+  rtx_insn *insn_b = if_info->insn_b;
+  /* Whether the branches are single set.  */
   bool a_simple = if_info->then_simple;
   bool b_simple = if_info->else_simple;
+  /* Our two basic blocks.  */
   basic_block then_bb = if_info->then_bb;
   basic_block else_bb = if_info->else_bb;
+  /* Whether we're handling the transformation of a load.  */
+  bool is_mem = false;
+  /* Copies of A and B before we modified them.  */
+  rtx orig_a = a, orig_b = b;
+  /* A new target to be used by the conditional select.  */
   rtx target;
-  int is_mem = 0;
-  enum rtx_code code;
+  /* The RTX code for the condition in the test block.  */
+  enum rtx_code code = GET_CODE (if_info->cond);
+  /* Our generated sequence.  */
   rtx_insn *ifcvt_seq;
 
   /* A conditional move from two memory sources is equivalent to a
@@ -2094,33 +2198,19 @@  noce_try_cmove_arith (struct noce_if_info *if_info)
   if (cse_not_expected
       && MEM_P (a) && MEM_P (b)
       && MEM_ADDR_SPACE (a) == MEM_ADDR_SPACE (b))
-    {
-      machine_mode address_mode = get_address_mode (a);
-
-      a = XEXP (a, 0);
-      b = XEXP (b, 0);
-      x = gen_reg_rtx (address_mode);
-      is_mem = 1;
-    }
-
+    is_mem = true;
   /* ??? We could handle this if we knew that a load from A or B could
      not trap or fault.  This is also true if we've already loaded
      from the address along the path from ENTRY.  */
   else if (may_trap_or_fault_p (a) || may_trap_or_fault_p (b))
     return FALSE;
 
-  /* if (test) x = a + b; else x = c - d;
-     => y = a + b;
-        x = c - d;
-	if (test)
-	  x = y;
-  */
 
   code = GET_CODE (if_info->cond);
   insn_a = if_info->insn_a;
   insn_b = if_info->insn_b;
 
-  machine_mode x_mode = GET_MODE (x);
+  machine_mode x_mode = is_mem ? get_address_mode (a) : GET_MODE (x);
 
   if (!can_conditionally_move_p (x_mode))
     return FALSE;
@@ -2151,117 +2241,27 @@  noce_try_cmove_arith (struct noce_if_info *if_info)
 
   start_sequence ();
 
-  /* If one of the blocks is empty then the corresponding B or A value
-     came from the test block.  The non-empty complex block that we will
-     emit might clobber the register used by B or A, so move it to a pseudo
-     first.  */
-
-  rtx tmp_a = NULL_RTX;
-  rtx tmp_b = NULL_RTX;
-
-  if (b_simple || !else_bb)
-    tmp_b = gen_reg_rtx (x_mode);
-
-  if (a_simple || !then_bb)
-    tmp_a = gen_reg_rtx (x_mode);
-
   orig_a = a;
   orig_b = b;
 
-  rtx emit_a = NULL_RTX;
-  rtx emit_b = NULL_RTX;
-  rtx_insn *tmp_insn = NULL;
-  bool modified_in_a = false;
-  bool  modified_in_b = false;
-  /* If either operand is complex, load it into a register first.
-     The best way to do this is to copy the original insn.  In this
-     way we preserve any clobbers etc that the insn may have had.
-     This is of course not possible in the IS_MEM case.  */
-
-  if (! general_operand (a, GET_MODE (a)) || tmp_a)
-    {
-
-      if (is_mem)
-	{
-	  rtx reg = gen_reg_rtx (GET_MODE (a));
-	  emit_a = gen_rtx_SET (reg, a);
-	}
-      else
-	{
-	  if (insn_a)
-	    {
-	      a = tmp_a ? tmp_a : gen_reg_rtx (GET_MODE (a));
-
-	      rtx_insn *copy_of_a = as_a <rtx_insn *> (copy_rtx (insn_a));
-	      rtx set = single_set (copy_of_a);
-	      SET_DEST (set) = a;
-
-	      emit_a = PATTERN (copy_of_a);
-	    }
-	  else
-	    {
-	      rtx tmp_reg = tmp_a ? tmp_a : gen_reg_rtx (GET_MODE (a));
-	      emit_a = gen_rtx_SET (tmp_reg, a);
-	      a = tmp_reg;
-	    }
-	}
-    }
-
-  if (! general_operand (b, GET_MODE (b)) || tmp_b)
+  /* Get the addresses if this is a MEM.  */
+  if (is_mem)
     {
-      if (is_mem)
-	{
-          rtx reg = gen_reg_rtx (GET_MODE (b));
-	  emit_b = gen_rtx_SET (reg, b);
-	}
-      else
-	{
-	  if (insn_b)
-	    {
-	      b = tmp_b ? tmp_b : gen_reg_rtx (GET_MODE (b));
-	      rtx_insn *copy_of_b = as_a <rtx_insn *> (copy_rtx (insn_b));
-	      rtx set = single_set (copy_of_b);
-
-	      SET_DEST (set) = b;
-	      emit_b = PATTERN (copy_of_b);
-	    }
-	  else
-	    {
-	      rtx tmp_reg = tmp_b ? tmp_b : gen_reg_rtx (GET_MODE (b));
-	      emit_b = gen_rtx_SET (tmp_reg, b);
-	      b = tmp_reg;
-	  }
-	}
+      a = XEXP (a, 0);
+      b = XEXP (b, 0);
     }
 
-  modified_in_a = emit_a != NULL_RTX && modified_in_p (orig_b, emit_a);
-  if (tmp_b && then_bb)
-    {
-      FOR_BB_INSNS (then_bb, tmp_insn)
-	/* Don't check inside insn_a.  We will have changed it to emit_a
-	   with a destination that doesn't conflict.  */
-	if (!(insn_a && tmp_insn == insn_a)
-	    && modified_in_p (orig_b, tmp_insn))
-	  {
-	    modified_in_a = true;
-	    break;
-	  }
-
-    }
+  rtx emit_a = NULL_RTX;
+  rtx emit_b = NULL_RTX;
 
-  modified_in_b = emit_b != NULL_RTX && modified_in_p (orig_a, emit_b);
-  if (tmp_a && else_bb)
-    {
-      FOR_BB_INSNS (else_bb, tmp_insn)
-      /* Don't check inside insn_b.  We will have changed it to emit_b
-	 with a destination that doesn't conflict.  */
-      if (!(insn_b && tmp_insn == insn_b)
-	  && modified_in_p (orig_a, tmp_insn))
-	{
-	  modified_in_b = true;
-	  break;
-	}
-    }
+  /* Sort out temporary registers and figure out whether either branch
+     would clobber the other.  */
+  bool modified_in_a
+    = noce_arith_helper (&a, &emit_a, insn_a, then_bb,
+			 orig_b, (else_bb != NULL));
+  bool modified_in_b
+    = noce_arith_helper (&b, &emit_b, insn_b, else_bb,
+			 orig_a, (then_bb != NULL));
 
   /* If insn to set up A clobbers any registers B depends on, try to
      swap insn that sets up A with the one that sets up B.  If even
@@ -2285,6 +2285,12 @@  noce_try_cmove_arith (struct noce_if_info *if_info)
   else
     goto end_seq_and_fail;
 
+  /* Emit the conditional move.  If our operands were MEMs, we
+     need to generate a temporary to hold the result of conditionally
+     selecting between the two possible addresses.  We'll fix this up
+     immediately afterwards.  */
+  if (is_mem)
+    x = gen_reg_rtx (x_mode);
   target = noce_emit_cmove (if_info, x, code, XEXP (if_info->cond, 0),
 			    XEXP (if_info->cond, 1), a, b);