aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gcc/ChangeLog12
-rw-r--r--gcc/config.gcc2
-rw-r--r--gcc/config/sh/sh.c11
-rw-r--r--gcc/config/sh/sh_optimize_sett_clrt.cc453
-rw-r--r--gcc/config/sh/t-sh4
-rw-r--r--gcc/testsuite/ChangeLog5
-rw-r--r--gcc/testsuite/gcc.target/sh/pr53976-1.c41
7 files changed, 525 insertions, 3 deletions
diff --git a/gcc/ChangeLog b/gcc/ChangeLog
index 98ac033..ac4cf0b 100644
--- a/gcc/ChangeLog
+++ b/gcc/ChangeLog
@@ -1,3 +1,12 @@
+2013-11-21 Oleg Endo <olegendo@gcc.gnu.org>
+
+ PR target/53976
+ * config/sh/sh_optimize_sett_clrt.cc: New SH specific RTL pass.
+ * config/sh/sh.c (register_sh_passes): Add sh_optimize_sett_clrt pass.
+ * config/sh/sh/t-sh (sh_optimize_sett_clrt pass.o): New entry.
+ * config.gcc (sh[123456789lbe]*-*-* | sh-*-*): Add
+ sh_optimize_sett_clrt pass.o to extra_objs.
+
2013-11-20 David Malcolm <dmalcolm@redhat.com>
* cfg.c (dump_edge_info): Remove redundant comment.
@@ -9301,7 +9310,8 @@
PR target/51244
* config/sh/sh_treg_combine.cc: New SH specific RTL pass.
- * config.gcc (SH extra_objs): Add sh_ifcvt.o.
+ * config.gcc (sh[123456789lbe]*-*-* | sh-*-*): Add sh_ifcvt.o to
+ extra_objs.
* config/sh/t-sh (sh_treg_combine.o): New entry.
* config/sh/sh.c (sh_fixed_condition_code_regs): New function that
implements the target hook TARGET_FIXED_CONDITION_CODE_REGS.
diff --git a/gcc/config.gcc b/gcc/config.gcc
index 2907018..1f20f18 100644
--- a/gcc/config.gcc
+++ b/gcc/config.gcc
@@ -469,7 +469,7 @@ sh[123456789lbe]*-*-* | sh-*-*)
cpu_type=sh
need_64bit_hwint=yes
extra_options="${extra_options} fused-madd.opt"
- extra_objs="${extra_objs} sh_treg_combine.o sh-mem.o"
+ extra_objs="${extra_objs} sh_treg_combine.o sh-mem.o sh_optimize_sett_clrt.o"
;;
v850*-*-*)
cpu_type=v850
diff --git a/gcc/config/sh/sh.c b/gcc/config/sh/sh.c
index d5f7f15..6981ab6 100644
--- a/gcc/config/sh/sh.c
+++ b/gcc/config/sh/sh.c
@@ -720,7 +720,9 @@ got_mode_name:;
/* Register SH specific RTL passes. */
extern opt_pass* make_pass_sh_treg_combine (gcc::context* ctx, bool split_insns,
- const char* name);
+ const char* name);
+extern opt_pass* make_pass_sh_optimize_sett_clrt (gcc::context* ctx,
+ const char* name);
static void
register_sh_passes (void)
{
@@ -744,6 +746,13 @@ register_sh_passes (void)
reordering as this sometimes creates new opportunities. */
register_pass (make_pass_sh_treg_combine (g, true, "sh_treg_combine3"),
PASS_POS_INSERT_AFTER, "split4", 1);
+
+ /* Optimize sett and clrt insns, by e.g. removing them if the T bit value
+ is known after a conditional branch.
+ This must be done after basic blocks and branch conditions have
+ stabilized and won't be changed by further passes. */
+ register_pass (make_pass_sh_optimize_sett_clrt (g, "sh_optimize_sett_clrt"),
+ PASS_POS_INSERT_BEFORE, "sched2", 1);
}
/* Implement TARGET_OPTION_OVERRIDE macro. Validate and override
diff --git a/gcc/config/sh/sh_optimize_sett_clrt.cc b/gcc/config/sh/sh_optimize_sett_clrt.cc
new file mode 100644
index 0000000..abb7567
--- /dev/null
+++ b/gcc/config/sh/sh_optimize_sett_clrt.cc
@@ -0,0 +1,453 @@
+/* An SH specific RTL pass that tries to optimize clrt and sett insns.
+ Copyright (C) 2013 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+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/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "machmode.h"
+#include "basic-block.h"
+#include "df.h"
+#include "rtl.h"
+#include "insn-config.h"
+#include "tree-pass.h"
+#include "target.h"
+
+#include <vector>
+
+/*
+This pass tries to eliminate unnecessary sett or clrt instructions in cases
+where the ccreg value is already known to be the same as the constant set
+would set it to. This is done as follows:
+
+Check every BB's insn and see if it's a sett or clrt.
+Once a sett or clrt insn is hit, walk insns and predecessor basic blocks
+backwards from that insn and determine all possible ccreg values from all
+basic block paths.
+Insns that set the ccreg value in some way (simple set, clobber etc) are
+recorded. Conditional branches where one edge leads to the sett / clrt insn
+are also recorded, since for each edge in the conditional branch the ccreg
+value is known constant.
+After collecting all possible ccreg values at the sett / clrt insn, check that
+all the values are the same. If that value is the same as the sett / clrt
+insn would set the ccreg to, the sett / clrt insn can be eliminated.
+*/
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Helper functions
+
+#define log_msg(...)\
+ do { if (dump_file != NULL) fprintf (dump_file, __VA_ARGS__); } while (0)
+
+#define log_insn(i)\
+ do { if (dump_file != NULL) print_rtl_single (dump_file, \
+ (const_rtx)i); } while (0)
+
+#define log_rtx(r)\
+ do { if (dump_file != NULL) print_rtl (dump_file, (const_rtx)r); } while (0)
+
+#define log_return(retval, ...)\
+ do { if (dump_file != NULL) fprintf (dump_file, __VA_ARGS__); \
+ return retval; } while (0)
+
+#define log_return_void(...)\
+ do { if (dump_file != NULL) fprintf (dump_file, __VA_ARGS__); \
+ return; } while (0)
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// RTL pass class
+
+class sh_optimize_sett_clrt : public rtl_opt_pass
+{
+public:
+ sh_optimize_sett_clrt (gcc::context* ctx, const char* name);
+ virtual ~sh_optimize_sett_clrt (void);
+ virtual bool gate (void);
+ virtual unsigned int execute (void);
+
+private:
+ static const pass_data default_pass_data;
+
+ struct ccreg_value
+ {
+ // The insn at which the ccreg value was determined.
+ rtx insn;
+
+ // The basic block where the insn was discovered.
+ // Notice that the CFG might be invalid at late RTL stages and
+ // BLOCK_FOR_INSN might return null. Thus the basic block are recorded
+ // here while traversing them.
+ basic_block bb;
+
+ // The value of ccreg. If NULL_RTX, the value is not known, e.g. if the
+ // ccreg is clobbered.
+ rtx value;
+ };
+
+ // Update the mode of the captured m_ccreg with the specified mode.
+ void update_ccreg_mode (machine_mode m);
+
+ // Given an insn pattern, check if it sets the ccreg to a constant value
+ // of either zero or STORE_FLAG_VALUE. If so, return the value rtx,
+ // NULL_RTX otherwise.
+ rtx const_setcc_value (rtx pat) const;
+
+ // Given a start insn and its basic block, recursively determine all
+ // possible ccreg values in all basic block paths that can lead to the
+ // start insn.
+ void find_last_ccreg_values (rtx start_insn, basic_block bb,
+ std::vector<ccreg_value>& values_out,
+ basic_block prev_visited_bb = NULL) const;
+
+ // Given a cbranch insn, its basic block and another basic block, determine
+ // the value to which the ccreg will be set after jumping/falling through to
+ // the specified target basic block.
+ bool sh_cbranch_ccreg_value (rtx cbranch_insn,
+ basic_block cbranch_insn_bb,
+ basic_block branch_target_bb) const;
+
+ // Check whether all of the ccreg values are the same.
+ static bool all_ccreg_values_equal (const std::vector<ccreg_value>& values);
+
+ // Remove REG_DEAD and REG_UNUSED notes from insns of the specified
+ // ccreg_value entries.
+ void remove_ccreg_dead_unused_notes (std::vector<ccreg_value>& values) const;
+
+ // rtx of the ccreg that is obtained from the target.
+ rtx m_ccreg;
+};
+
+const pass_data sh_optimize_sett_clrt::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
+ 0 // todo_flags_finish
+};
+
+sh_optimize_sett_clrt::sh_optimize_sett_clrt (gcc::context* ctx,
+ const char* name)
+: rtl_opt_pass (default_pass_data, ctx),
+ m_ccreg (NULL_RTX)
+{
+ // Overwrite default name in pass_data base class.
+ this->name = name;
+}
+
+sh_optimize_sett_clrt::~sh_optimize_sett_clrt (void)
+{
+}
+
+bool
+sh_optimize_sett_clrt::gate (void)
+{
+ return optimize > 0;
+}
+
+unsigned int
+sh_optimize_sett_clrt::execute (void)
+{
+ unsigned int ccr0 = INVALID_REGNUM;
+ unsigned int ccr1 = INVALID_REGNUM;
+
+ if (targetm.fixed_condition_code_regs (&ccr0, &ccr1)
+ && ccr0 != INVALID_REGNUM)
+ {
+ // Initially create a reg rtx with VOIDmode.
+ // When the constant setcc is discovered, the mode is changed
+ // to the mode that is actually used by the target.
+ m_ccreg = gen_rtx_REG (VOIDmode, ccr0);
+ }
+
+ if (m_ccreg == NULL_RTX)
+ log_return (0, "no ccreg.\n\n");
+
+ if (STORE_FLAG_VALUE != 1)
+ log_return (0, "unsupported STORE_FLAG_VALUE %d", STORE_FLAG_VALUE);
+
+ log_msg ("ccreg: ");
+ log_rtx (m_ccreg);
+ log_msg (" STORE_FLAG_VALUE = %d\n", STORE_FLAG_VALUE);
+
+ if (!df_regs_ever_live_p (ccr0))
+ log_return (0, "ccreg never live\n\n");
+
+ // Output vector for find_known_ccreg_values.
+ std::vector<ccreg_value> ccreg_values;
+ ccreg_values.reserve (32);
+
+ // Look for insns that set the ccreg to a constant value and see if it can
+ // be optimized.
+ basic_block bb;
+ FOR_EACH_BB_REVERSE (bb)
+ for (rtx next_i, i = NEXT_INSN (BB_HEAD (bb));
+ i != NULL_RTX && i != BB_END (bb); i = next_i)
+ {
+ next_i = NEXT_INSN (i);
+
+ if (!INSN_P (i) || !NONDEBUG_INSN_P (i))
+ continue;
+
+ rtx setcc_val = const_setcc_value (PATTERN (i));
+ if (setcc_val != NULL_RTX)
+ {
+ update_ccreg_mode (GET_MODE (XEXP (PATTERN (i), 0)));
+
+ log_msg ("\n\nfound const setcc insn in [bb %d]: \n", bb->index);
+ log_insn (i);
+ log_msg ("\n");
+
+ ccreg_values.clear ();
+ find_last_ccreg_values (PREV_INSN (i), bb, ccreg_values);
+
+ log_msg ("number of ccreg values collected: %u\n",
+ (unsigned int)ccreg_values.size ());
+
+ // If all the collected values are equal and are equal to the
+ // constant value of the setcc insn, the setcc insn can be
+ // removed.
+ if (all_ccreg_values_equal (ccreg_values)
+ && rtx_equal_p (ccreg_values.front ().value, setcc_val))
+ {
+ log_msg ("all values are ");
+ log_rtx (setcc_val);
+ log_msg ("\n");
+
+ delete_insn (i);
+ remove_ccreg_dead_unused_notes (ccreg_values);
+ }
+ }
+ }
+
+ log_return (0, "\n\n");
+}
+
+void
+sh_optimize_sett_clrt::update_ccreg_mode (machine_mode m)
+{
+ if (GET_MODE (m_ccreg) == m)
+ return;
+
+ PUT_MODE (m_ccreg, m);
+ log_msg ("updated ccreg mode: ");
+ log_rtx (m_ccreg);
+ log_msg ("\n\n");
+}
+
+rtx
+sh_optimize_sett_clrt::const_setcc_value (rtx pat) const
+{
+ if (GET_CODE (pat) == SET
+ && REG_P (XEXP (pat, 0)) && REGNO (XEXP (pat, 0)) == REGNO (m_ccreg)
+ && CONST_INT_P (XEXP (pat, 1))
+ && (INTVAL (XEXP (pat, 1)) == 0
+ || INTVAL (XEXP (pat, 1)) == STORE_FLAG_VALUE))
+ return XEXP (pat, 1);
+ else
+ return NULL_RTX;
+}
+
+bool
+sh_optimize_sett_clrt
+::sh_cbranch_ccreg_value (rtx cbranch_insn, basic_block cbranch_insn_bb,
+ basic_block branch_target_bb) const
+{
+ rtx pc_set_rtx = pc_set (cbranch_insn);
+ gcc_assert (pc_set_rtx != NULL_RTX);
+ gcc_assert (branch_target_bb != NULL);
+
+ rtx cond = XEXP (XEXP (pc_set_rtx, 1), 0);
+ bool branch_if;
+
+ if (GET_CODE (cond) == NE
+ && REG_P (XEXP (cond, 0)) && REGNO (XEXP (cond, 0)) == REGNO (m_ccreg)
+ && XEXP (cond, 1) == const0_rtx)
+ branch_if = true;
+
+ else if (GET_CODE (cond) == EQ
+ && REG_P (XEXP (cond, 0)) && REGNO (XEXP (cond, 0)) == REGNO (m_ccreg)
+ && XEXP (cond, 1) == const0_rtx)
+ branch_if = false;
+
+ else
+ gcc_unreachable ();
+
+ if (branch_target_bb == BRANCH_EDGE (cbranch_insn_bb)->dest)
+ return branch_if;
+ else if (branch_target_bb == FALLTHRU_EDGE (cbranch_insn_bb)->dest)
+ return !branch_if;
+ else
+ gcc_unreachable ();
+}
+
+void
+sh_optimize_sett_clrt
+::find_last_ccreg_values (rtx start_insn, basic_block bb,
+ std::vector<ccreg_value>& values_out,
+ basic_block prev_visited_bb) const
+{
+ if (start_insn == NULL_RTX)
+ return;
+
+ log_msg ("looking for ccreg values in [bb %d]\n", bb->index);
+
+ for (rtx i = start_insn; i != NULL_RTX && i != PREV_INSN (BB_HEAD (bb));
+ i = PREV_INSN (i))
+ {
+ if (!INSN_P (i))
+ continue;
+
+ if (reg_set_p (m_ccreg, i))
+ {
+ const_rtx set_rtx = set_of (m_ccreg, i);
+
+ ccreg_value v;
+ v.insn = i;
+ v.bb = bb;
+ v.value = set_rtx != NULL_RTX && GET_CODE (set_rtx) == SET
+ ? XEXP (set_rtx, 1)
+ : NULL_RTX;
+
+ log_msg ("found setcc in [bb %d] in insn:\n", bb->index);
+ log_insn (i);
+ log_msg ("\nccreg value: ");
+ log_rtx (v.value);
+ log_msg ("\n");
+
+ values_out.push_back (v);
+ return;
+ }
+
+ if (any_condjump_p (i) && onlyjump_p (i) && prev_visited_bb != NULL)
+ {
+ // For a conditional branch the ccreg value will be a known constant
+ // of either 0 or STORE_FLAG_VALUE after branching/falling through
+ // to one of the two successor BBs. Record the value for the BB
+ // where we came from.
+ log_msg ("found cbranch in [bb %d]:\n", bb->index);
+ log_insn (i);
+
+ ccreg_value v;
+ v.insn = i;
+ v.bb = bb;
+ v.value = GEN_INT (sh_cbranch_ccreg_value (i, bb, prev_visited_bb));
+
+ log_msg (" branches to [bb %d] with ccreg value ",
+ prev_visited_bb->index);
+ log_rtx (v.value);
+ log_msg ("\n");
+
+ values_out.push_back (v);
+ return;
+ }
+ }
+
+ // If here, we've walked up all the insns of the current basic block
+ // and none of them seems to modify the ccreg.
+ // In this case, check the predecessor basic blocks.
+ unsigned int pred_bb_count = 0;
+
+ // If the current basic block is the same as the previous one, it's a loop.
+ // Don't try to recurse again, as this will result in an infinite loop.
+ if (bb != prev_visited_bb)
+ for (edge_iterator ei = ei_start (bb->preds); !ei_end_p (ei); ei_next (&ei))
+ {
+ basic_block pred_bb = ei_edge (ei)->src;
+ if (pred_bb->index == ENTRY_BLOCK)
+ continue;
+
+ pred_bb_count += 1;
+ find_last_ccreg_values (BB_END (pred_bb), pred_bb, values_out, bb);
+ }
+
+ log_msg ("[bb %d] pred_bb_count = %u\n", bb->index, pred_bb_count);
+
+ // If here, we've walked up all the predecessor basic blocks without finding
+ // anything setcc related. Add an entry for the last insn of the current
+ // basic block with the ccreg value being set to unknown (NULL_RTX).
+ if (pred_bb_count == 0)
+ {
+ log_msg ("unknown ccreg value for [bb %d]\n", bb->index);
+
+ ccreg_value v;
+ v.insn = BB_END (bb);
+ v.bb = bb;
+ v.value = NULL_RTX;
+
+ values_out.push_back (v);
+ }
+}
+
+bool
+sh_optimize_sett_clrt
+::all_ccreg_values_equal (const std::vector<ccreg_value>& values)
+{
+ if (values.empty ())
+ return false;
+
+ rtx last_value = values.front ().value;
+
+ // If the ccreg is modified in the insn but the exact value is not known
+ // the value rtx might be null.
+ if (last_value == NULL_RTX)
+ return false;
+
+ for (std::vector<ccreg_value>::const_iterator i = values.begin ();
+ i != values.end (); ++i)
+ if (i->value == NULL_RTX || !rtx_equal_p (last_value, i->value))
+ return false;
+
+ return true;
+}
+
+void
+sh_optimize_sett_clrt
+::remove_ccreg_dead_unused_notes (std::vector<ccreg_value>& values) const
+{
+ for (std::vector<ccreg_value>::iterator i = values.begin ();
+ i != values.end (); ++i)
+ {
+ if (i->insn == NULL_RTX)
+ continue;
+
+ rtx n = find_regno_note (i->insn, REG_DEAD, REGNO (m_ccreg));
+ if (n != NULL_RTX)
+ remove_note (i->insn, n);
+
+ n = find_regno_note (i->insn, REG_UNUSED, REGNO (m_ccreg));
+ if (n != NULL_RTX)
+ remove_note (i->insn, n);
+ }
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// This allows instantiating the pass somewhere else without having to pull
+// in a header file.
+opt_pass*
+make_pass_sh_optimize_sett_clrt (gcc::context* ctx, const char* name)
+{
+ return new sh_optimize_sett_clrt (ctx, name);
+}
diff --git a/gcc/config/sh/t-sh b/gcc/config/sh/t-sh
index 710bc87..405d314 100644
--- a/gcc/config/sh/t-sh
+++ b/gcc/config/sh/t-sh
@@ -29,6 +29,10 @@ sh_treg_combine.o: $(srcdir)/config/sh/sh_treg_combine.cc \
$(CONFIG_H) $(SYSTEM_H) $(TREE_H) $(TM_H) $(TM_P_H) coretypes.h
$(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) $(INCLUDES) $<
+sh_optimize_sett_clrt.o: $(srcdir)/config/sh/sh_optimize_sett_clrt.cc \
+ $(CONFIG_H) $(SYSTEM_H) $(TREE_H) $(TM_H) $(TM_P_H) coretypes.h
+ $(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) $(INCLUDES) $<
+
DEFAULT_ENDIAN = $(word 1,$(TM_ENDIAN_CONFIG))
OTHER_ENDIAN = $(word 2,$(TM_ENDIAN_CONFIG))
diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog
index 694fb83..f7986f2 100644
--- a/gcc/testsuite/ChangeLog
+++ b/gcc/testsuite/ChangeLog
@@ -1,3 +1,8 @@
+2013-11-21 Oleg Endo <olegendo@gcc.gnu.org>
+
+ PR target/53976
+ * gcc.target/sh/pr53976-1.c: New.
+
2013-11-20 Francois-Xavier Coudert <fxcoudert@gcc.gnu.org>
PR libfortran/49024
diff --git a/gcc/testsuite/gcc.target/sh/pr53976-1.c b/gcc/testsuite/gcc.target/sh/pr53976-1.c
new file mode 100644
index 0000000..4893b06
--- /dev/null
+++ b/gcc/testsuite/gcc.target/sh/pr53976-1.c
@@ -0,0 +1,41 @@
+/* Check that the SH specific sh_optimize_sett_clrt RTL optimization pass
+ works as expected. */
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-skip-if "" { "sh*-*-*" } { "-m5*" } { "" } } */
+/* { dg-final { scan-assembler-times "clrt" 2 } } */
+/* { dg-final { scan-assembler-times "sett" 1 } } */
+
+long long
+test_00 (long long a, long long b, long long c, int d)
+{
+ /* One of the blocks should have a clrt and the other one should not. */
+ if (d > 5)
+ return a + b;
+ else
+ return a + c;
+}
+
+long long
+test_01 (long long a, long long b)
+{
+ /* Must see a clrt because T bit is undefined at function entry. */
+ return a + b;
+}
+
+int
+test_02 (const char* a)
+{
+ /* Must not see a sett after the inlined strlen. */
+ return __builtin_strlen (a);
+}
+
+int
+test_03 (int a, int b, int c, int d)
+{
+ /* One of the blocks should have a sett and the other one should not. */
+ if (d > 4)
+ return a + b + 1;
+ else
+ return a + c + 1;
+}