]> git.ipfire.org Git - thirdparty/gcc.git/blobdiff - gcc/config/sh/sh_treg_combine.cc
Update copyright years.
[thirdparty/gcc.git] / gcc / config / sh / sh_treg_combine.cc
index fc4a1c03bce076b5cd2994ff0df1efb29846525e..ed9dcb0b52fae37f342138240cd49b0ed7e8ef02 100644 (file)
@@ -1,6 +1,6 @@
 /* An SH specific RTL pass that tries to combine comparisons and redundant
    condition code register stores across multiple basic blocks.
-   Copyright (C) 2013 Free Software Foundation, Inc.
+   Copyright (C) 2013-2020 Free Software Foundation, Inc.
 
 This file is part of GCC.
 
@@ -18,25 +18,26 @@ You should have received a copy of the GNU General Public License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
+#define IN_TARGET_CODE 1
+
 #include "config.h"
+#define INCLUDE_ALGORITHM
+#define INCLUDE_LIST
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
-#include "machmode.h"
-#include "basic-block.h"
-#include "df.h"
+#include "backend.h"
+#include "target.h"
 #include "rtl.h"
-#include "insn-config.h"
-#include "insn-codes.h"
+#include "tree.h"
+#include "memmodel.h"
+#include "optabs.h"
 #include "emit-rtl.h"
 #include "recog.h"
+#include "cfgrtl.h"
 #include "tree-pass.h"
-#include "target.h"
 #include "expr.h"
 
-#include <algorithm>
-#include <list>
-#include <vector>
-
 /*
 This pass tries to optimize for example this:
        mov.l   @(4,r4),r1
@@ -78,14 +79,17 @@ Example 1)
 
 In [bb 4] elimination of the comparison would require inversion of the branch
 condition and compensation of other BBs.
-Instead an inverting reg-move can be used:
+Instead the comparison in [bb 3] can be replaced with the comparison in [bb 5]
+by using a reg-reg move.  In [bb 4] a logical not is used to compensate the
+inverted condition.
 
 [bb 3]
 (set (reg:SI 167) (reg:SI 173))
 -> bb 5
 
 [BB 4]
-(set (reg:SI 167) (not:SI (reg:SI 177)))
+(set (reg:SI 147 t) (eq:SI (reg:SI 177) (const_int 0)))
+(set (reg:SI 167) (reg:SI 147 t))
 -> bb 5
 
 [bb 5]
@@ -214,9 +218,9 @@ In order to handle cases such as above the RTL pass does the following:
       and replace the comparisons in the BBs with reg-reg copies to get the
       operands in place (create new pseudo regs).
 
-    - If the cstores differ, try to apply the special case
-        (eq (reg) (const_int 0)) -> inverted = (not (reg)).
-      for the subordinate cstore types and eliminate the dominating ones.
+    - If the cstores differ and the comparison is a test against zero,
+      use reg-reg copies for the dominating cstores and logical not cstores
+      for the subordinate cstores.
 
 - If the comparison types in the BBs are not the same, or the first approach
   doesn't work out for some reason, try to eliminate the comparison before the
@@ -250,8 +254,8 @@ In order to handle cases such as above the RTL pass does the following:
 
 struct set_of_reg
 {
-  // The insn where the search stopped or NULL_RTX.
-  rtx insn;
+  // The insn where the search stopped or NULL.
+  rtx_insn *insn;
 
   // The set rtx of the specified reg if found, NULL_RTX otherwise.
   // Notice that the set rtx can also be in a parallel.
@@ -281,15 +285,15 @@ struct set_of_reg
 // Given a reg rtx and a start insn find the insn (in the same basic block)
 // that sets the reg.
 static set_of_reg
-find_set_of_reg_bb (rtx reg, rtx insn)
+find_set_of_reg_bb (rtx reg, rtx_insn *insn)
 {
   set_of_reg result = { insn, NULL_RTX };
 
-  if (!REG_P (reg) || insn == NULL_RTX)
+  if (!REG_P (reg) || insn == NULL)
     return result;
 
-  for (result.insn = insn; result.insn != NULL_RTX;
-       result.insn = prev_nonnote_insn_bb (result.insn))
+  for (result.insn = insn; result.insn != NULL;
+       result.insn = prev_nonnote_nondebug_insn_bb (result.insn))
     {
       if (BARRIER_P (result.insn))
        return result;
@@ -338,7 +342,7 @@ is_adjacent_bb (basic_block a, basic_block b)
 
 // Internal function of trace_reg_uses.
 static void
-trace_reg_uses_1 (rtx reg, rtx start_insn, basic_block bb, int& count,
+trace_reg_uses_1 (rtx reg, rtx_insn *start_insn, basic_block bb, int& count,
                  std::vector<basic_block>& visited_bb, rtx abort_at_insn)
 {
   if (bb == NULL)
@@ -360,7 +364,7 @@ trace_reg_uses_1 (rtx reg, rtx start_insn, basic_block bb, int& count,
   if (end_insn == NULL_RTX)
     log_return_void ("[bb %d] end_insn is null\n", bb->index);
 
-  for (rtx i = NEXT_INSN (start_insn); i != end_insn; i = NEXT_INSN (i))
+  for (rtx_insn *i = NEXT_INSN (start_insn); i != end_insn; i = NEXT_INSN (i))
     {
       if (INSN_P (i))
        {
@@ -396,7 +400,7 @@ trace_reg_uses_1 (rtx reg, rtx start_insn, basic_block bb, int& count,
 // that insn.  If the insn 'abort_at_insn' uses the specified reg, it is also
 // counted.
 static int
-trace_reg_uses (rtx reg, rtx start_insn, rtx abort_at_insn)
+trace_reg_uses (rtx reg, rtx_insn *start_insn, rtx abort_at_insn)
 {
   log_msg ("\ntrace_reg_uses\nreg = ");
   log_rtx (reg);
@@ -412,6 +416,16 @@ trace_reg_uses (rtx reg, rtx start_insn, rtx abort_at_insn)
   return count;
 }
 
+static bool
+is_conditional_insn (rtx_insn* i)
+{
+  if (! (INSN_P (i) && NONDEBUG_INSN_P (i)))
+    return false;
+
+  rtx p = PATTERN (i);
+  return GET_CODE (p) == SET && GET_CODE (XEXP (p, 1)) == IF_THEN_ELSE;
+}
+
 // FIXME: Remove dependency on SH predicate function somehow.
 extern int t_reg_operand (rtx, machine_mode);
 extern int negt_reg_operand (rtx, machine_mode);
@@ -424,8 +438,8 @@ class sh_treg_combine : public rtl_opt_pass
 public:
   sh_treg_combine (gcc::context* ctx, bool split_insns, const char* name);
   virtual ~sh_treg_combine (void);
-  virtual bool gate (void);
-  virtual unsigned int execute (void);
+  virtual bool gate (function *);
+  virtual unsigned int execute (function *);
 
 private:
   // Type of ccreg store that is supported.
@@ -463,7 +477,8 @@ private:
   // A ccreg trace for a conditional branch.
   struct cbranch_trace
   {
-    rtx cbranch_insn;
+    rtx_insn *cbranch_insn;
+    rtx* condition_rtx_in_insn;
     branch_condition_type_t cbranch_type;
 
     // The comparison against zero right before the conditional branch.
@@ -473,11 +488,16 @@ private:
     // the BB of the cbranch itself and might be empty.
     std::list<bb_entry> bb_entries;
 
-    cbranch_trace (rtx insn)
+    cbranch_trace (rtx_insn *insn)
     : cbranch_insn (insn),
+      condition_rtx_in_insn (NULL),
       cbranch_type (unknown_branch_condition),
       setcc ()
     {
+      if (is_conditional_insn (cbranch_insn))
+       condition_rtx_in_insn = &XEXP (XEXP (PATTERN (cbranch_insn), 1), 0);
+      else if (rtx x = pc_set (cbranch_insn))
+       condition_rtx_in_insn = &XEXP (XEXP (x, 1), 0);
     }
 
     basic_block bb (void) const { return BLOCK_FOR_INSN (cbranch_insn); }
@@ -485,8 +505,16 @@ private:
     rtx
     branch_condition_rtx (void) const
     {
-      rtx x = pc_set (cbranch_insn);
-      return x == NULL_RTX ? NULL_RTX : XEXP (XEXP (x, 1), 0);
+      return condition_rtx_in_insn != NULL ? *condition_rtx_in_insn : NULL;
+    }
+    rtx&
+    branch_condition_rtx_ref (void) const
+    {
+      // Before anything gets to invoke this function, there are other checks
+      // in place to make sure that we have a known branch condition and thus
+      // the ref to the rtx in the insn.
+      gcc_assert (condition_rtx_in_insn != NULL);
+      return *condition_rtx_in_insn;
     }
 
     bool
@@ -537,7 +565,8 @@ private:
     set_not_found,
     other_set_found
   };
-  record_return_t record_set_of_reg (rtx reg, rtx start_insn, bb_entry& e);
+  record_return_t record_set_of_reg (rtx reg, rtx_insn *start_insn,
+                                     bb_entry& e);
 
   // Tells whether the cbranch insn of the specified bb_entry can be removed
   // safely without triggering any side effects.
@@ -558,11 +587,12 @@ private:
   bool can_extend_ccreg_usage (const bb_entry& e,
                               const cbranch_trace& trace) const;
 
-  // Create an insn rtx that is a negating reg move (not operation).
+  // Create an insn rtx that performs a logical not (test != 0) on the src_reg
+  // and stores the result in dst_reg.
   rtx make_not_reg_insn (rtx dst_reg, rtx src_reg) const;
 
   // Create an insn rtx that inverts the ccreg.
-  rtx make_inv_ccreg_insn (void) const;
+  rtx_insn *make_inv_ccreg_insn (void) const;
 
   // Adds the specified insn to the set of modified or newly added insns that
   // might need splitting at the end of the pass.
@@ -584,7 +614,7 @@ private:
 
   // Given a branch insn, try to optimize its branch condition.
   // If any insns are modified or added they are added to 'm_touched_insns'.
-  void try_optimize_cbranch (rtx i);
+  void try_optimize_cbranch (rtx_insn *i);
 };
 
 
@@ -593,15 +623,12 @@ const pass_data sh_treg_combine::default_pass_data =
   RTL_PASS,            // type
   "",                  // name (overwritten by the constructor)
   OPTGROUP_NONE,       // optinfo_flags
-  true,                        // has_gate
-  true,                        // has_execute
   TV_OPTIMIZE,         // tv_id
   0,                   // properties_required
   0,                   // properties_provided
   0,                   // properties_destroyed
   0,                   // todo_flags_start
   TODO_df_finish | TODO_df_verify      // todo_flags_finish
-  | TODO_verify_rtl_sharing
 };
 
 sh_treg_combine::sh_treg_combine (gcc::context* ctx, bool split_insns,
@@ -673,7 +700,7 @@ sh_treg_combine::is_inverted_ccreg (const_rtx x) const
 }
 
 sh_treg_combine::record_return_t
-sh_treg_combine::record_set_of_reg (rtx reg, rtx start_insn,
+sh_treg_combine::record_set_of_reg (rtx reg, rtx_insn *start_insn,
                                    bb_entry& new_entry)
 {
   log_msg ("\n[bb %d]\n", new_entry.bb->index);
@@ -683,7 +710,7 @@ sh_treg_combine::record_set_of_reg (rtx reg, rtx start_insn,
 
   new_entry.cstore_type = cstore_unknown;
 
-  for (rtx i = start_insn; i != NULL_RTX; )
+  for (rtx_insn *i = start_insn; i != NULL; )
     {
       new_entry.cstore = find_set_of_reg_bb (reg, i);
 
@@ -723,9 +750,8 @@ sh_treg_combine::record_set_of_reg (rtx reg, rtx start_insn,
       // Now see how the ccreg was set.
       // For now it must be in the same BB.
       log_msg ("tracing ccreg\n");
-      new_entry.setcc =
-         find_set_of_reg_bb (m_ccreg,
-                             prev_nonnote_insn_bb (new_entry.cstore.insn));
+      new_entry.setcc = find_set_of_reg_bb
+       (m_ccreg, prev_nonnote_nondebug_insn_bb (new_entry.cstore.insn));
 
       // If cstore was found but setcc was not found continue anyway, as
       // for some of the optimization types the setcc is irrelevant.
@@ -794,7 +820,7 @@ sh_treg_combine::can_remove_cstore (const bb_entry& e,
   // must not be a usage of the copied regs between the reg-reg copies.
   // Otherwise we assume that the result of the cstore is used in some
   // other way.
-  rtx prev_insn = e.cstore.insn;
+  rtx_insn *prev_insn = e.cstore.insn;
   for (std::vector<set_of_reg>::const_reverse_iterator i =
           e.cstore_reg_reg_copies.rbegin ();
        i != e.cstore_reg_reg_copies.rend (); ++i)
@@ -892,22 +918,42 @@ sh_treg_combine::can_remove_comparison (const bb_entry& e,
 rtx
 sh_treg_combine::make_not_reg_insn (rtx dst_reg, rtx src_reg) const
 {
-  // This will to go through expanders and may output multiple insns
-  // for multi-word regs.
+  // On SH we can do only SImode and DImode comparisons.
+  if (! (GET_MODE (src_reg) == SImode || GET_MODE (src_reg) == DImode))
+    return NULL;
+
+  // On SH we can store the ccreg into an SImode or DImode reg only.
+  if (! (GET_MODE (dst_reg) == SImode || GET_MODE (dst_reg) == DImode))
+    return NULL;
+
   start_sequence ();
-  expand_simple_unop (GET_MODE (dst_reg), NOT, src_reg, dst_reg, 0);
+
+  emit_insn (gen_rtx_SET (m_ccreg, gen_rtx_fmt_ee (EQ, SImode,
+                                                  src_reg, const0_rtx)));
+
+  if (GET_MODE (dst_reg) == SImode)
+    emit_move_insn (dst_reg, m_ccreg);
+  else if (GET_MODE (dst_reg) == DImode)
+    {
+      emit_move_insn (gen_lowpart (SImode, dst_reg), m_ccreg);
+      emit_move_insn (gen_highpart (SImode, dst_reg), const0_rtx);
+    }
+  else
+    gcc_unreachable ();
+
   rtx i = get_insns ();
   end_sequence ();
+
   return i;
 }
 
-rtx
+rtx_insn *
 sh_treg_combine::make_inv_ccreg_insn (void) const
 {
   start_sequence ();
-  rtx i = emit_insn (gen_rtx_SET (VOIDmode, m_ccreg,
-                                 gen_rtx_fmt_ee (XOR, GET_MODE (m_ccreg),
-                                                 m_ccreg, const1_rtx)));
+  rtx_insn *i = emit_insn (gen_rtx_SET (m_ccreg,
+                                        gen_rtx_fmt_ee (XOR, GET_MODE (m_ccreg),
+                                                        m_ccreg, const1_rtx)));
   end_sequence ();
   return i;
 }
@@ -994,8 +1040,18 @@ sh_treg_combine::try_invert_branch_condition (cbranch_trace& trace)
 {
   log_msg ("inverting branch condition\n");
 
-  if (!invert_jump_1 (trace.cbranch_insn, JUMP_LABEL (trace.cbranch_insn)))
-    log_return (false, "invert_jump_1 failed\n");
+  rtx& comp = trace.branch_condition_rtx_ref ();
+
+  rtx_code rev_cmp_code = reversed_comparison_code (comp, trace.cbranch_insn);
+
+  if (rev_cmp_code == UNKNOWN)
+    log_return (false, "reversed_comparison_code = UNKNOWN\n");
+
+  validate_change (trace.cbranch_insn, &comp,
+                  gen_rtx_fmt_ee (rev_cmp_code,
+                                  GET_MODE (comp), XEXP (comp, 0),
+                                  XEXP (comp, 1)),
+                  1);
 
   if (verify_changes (num_validated_changes ()))
     confirm_change_group ();
@@ -1080,7 +1136,12 @@ sh_treg_combine::try_combine_comparisons (cbranch_trace& trace,
   // There is one special case though, where an integer comparison
   //     (eq (reg) (const_int 0))
   // can be inverted with a sequence
-  //     (eq (not (reg)) (const_int 0))
+  //     (set (t) (eq (reg) (const_int 0))
+  //     (set (reg) (t))
+  //     (eq (reg) (const_int 0))
+  //
+  // FIXME: On SH2A it might be better to use the nott insn in this case,
+  // i.e. do the try_eliminate_cstores approach instead.
   if (inv_cstore_count != 0 && cstore_count != 0)
     {
       if (make_not_reg_insn (comp_op0, comp_op0) == NULL_RTX)
@@ -1224,7 +1285,7 @@ sh_treg_combine::try_eliminate_cstores (cbranch_trace& trace,
   // invert the ccreg as a replacement for one of them.
   if (cstore_count != 0 && inv_cstore_count != 0)
     {
-      rtx i = make_inv_ccreg_insn ();
+      rtx_insn *i = make_inv_ccreg_insn ();
       if (recog_memoized (i) < 0)
        {
          log_msg ("failed to match ccreg inversion insn:\n");
@@ -1265,7 +1326,7 @@ sh_treg_combine::try_eliminate_cstores (cbranch_trace& trace,
 }
 
 void
-sh_treg_combine::try_optimize_cbranch (rtx insn)
+sh_treg_combine::try_optimize_cbranch (rtx_insn *insn)
 {
   cbranch_trace trace (insn);
 
@@ -1293,11 +1354,20 @@ sh_treg_combine::try_optimize_cbranch (rtx insn)
   //   (set (reg ccreg) (eq (reg) (const_int 0)))
   // The testing insn could also be outside of the current basic block, but
   // for now we limit the search to the current basic block.
-  trace.setcc = find_set_of_reg_bb (m_ccreg, prev_nonnote_insn_bb (insn));
+  trace.setcc = find_set_of_reg_bb
+    (m_ccreg, prev_nonnote_nondebug_insn_bb (insn));
 
-  if (!is_cmp_eq_zero (trace.setcc.set_src ()))
+  if (trace.setcc.set_src () == NULL_RTX)
     log_return_void ("could not find set of ccreg in current BB\n");
 
+  if (!is_cmp_eq_zero (trace.setcc.set_src ())
+      && !is_inverted_ccreg (trace.setcc.set_src ()))
+    {
+      log_msg ("unsupported set of ccreg in current BB: ");
+      log_rtx (trace.setcc.set_src ());
+      log_return_void ("\n");
+    }
+
   rtx trace_reg = XEXP (trace.setcc.set_src (), 0);
 
   log_msg ("set of ccreg:\n");
@@ -1314,6 +1384,19 @@ sh_treg_combine::try_optimize_cbranch (rtx insn)
       log_return_void ("\nbecause it's volatile\n");
     }
 
+  // If the ccreg is inverted before cbranch try inverting the branch
+  // condition.
+  if (is_inverted_ccreg (trace.setcc.set_src ()))
+    {
+      if (!trace.can_invert_condition ())
+       log_return_void ("branch condition can't be inverted - aborting\n");
+
+      if (try_invert_branch_condition (trace))
+       delete_insn (trace.setcc.insn);
+
+      return;
+    }
+
   // Now that we have an insn which tests some reg and sets the condition
   // reg before the conditional branch, try to figure out how that tested
   // reg was formed, i.e. find all the insns that set the tested reg in
@@ -1331,9 +1414,9 @@ sh_treg_combine::try_optimize_cbranch (rtx insn)
   // If we find it here there's no point in checking other BBs.
   trace.bb_entries.push_front (bb_entry (trace.bb ()));
 
-  record_return_t res =
-      record_set_of_reg (trace_reg, prev_nonnote_insn_bb (trace.setcc.insn),
-                        trace.bb_entries.front ());
+  record_return_t res = record_set_of_reg
+    (trace_reg, prev_nonnote_nondebug_insn_bb (trace.setcc.insn),
+     trace.bb_entries.front ());
 
   if (res == other_set_found)
     log_return_void ("other set found - aborting trace\n");
@@ -1436,13 +1519,13 @@ sh_treg_combine::try_optimize_cbranch (rtx insn)
 }
 
 bool
-sh_treg_combine::gate (void)
+sh_treg_combine::gate (function *)
 {
   return optimize > 0;
 }
 
 unsigned int
-sh_treg_combine::execute (void)
+sh_treg_combine::execute (function *fun)
 {
   unsigned int ccr0 = INVALID_REGNUM;
   unsigned int ccr1 = INVALID_REGNUM;
@@ -1466,14 +1549,26 @@ sh_treg_combine::execute (void)
   log_rtx (m_ccreg);
   log_msg ("  STORE_FLAG_VALUE = %d\n", STORE_FLAG_VALUE);
 
-  // Look for basic blocks that end with a conditional branch and try to
-  // optimize them.
+  // Look for basic blocks that end with a conditional branch or for
+  // conditional insns and try to optimize them.
   basic_block bb;
-  FOR_EACH_BB_FN (bb, cfun)
+  FOR_EACH_BB_FN (bb, fun)
     {
-      rtx i = BB_END (bb);
+      rtx_insn* i = BB_END (bb);
+      if (i == NULL || i == PREV_INSN (BB_HEAD (bb)))
+       continue;
+
+      // A conditional branch is always the last insn of a basic block.
       if (any_condjump_p (i) && onlyjump_p (i))
-       try_optimize_cbranch (i);
+       {
+         try_optimize_cbranch (i);
+         i = PREV_INSN (i);
+       }
+
+      // Check all insns in block for conditional insns.
+      for (; i != NULL && i != PREV_INSN (BB_HEAD (bb)); i = PREV_INSN (i))
+       if (is_conditional_insn (i))
+         try_optimize_cbranch (i);
     }
 
   log_msg ("\n\n");
@@ -1491,7 +1586,7 @@ sh_treg_combine::execute (void)
        log_msg ("trying to split insn:\n");
        log_insn (*i);
        log_msg ("\n");
-       try_split (PATTERN (*i), *i, 0);
+       try_split (PATTERN (*i), safe_as_a <rtx_insn *> (*i), 0);
       }
 
   m_touched_insns.clear ();