/* Post reload partially redundant load elimination
- Copyright (C) 2004, 2005
+ Copyright (C) 2004, 2005, 2006, 2007, 2008
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 2, or (at your option) any later
+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
for more details.
You should have received a copy of the GNU General Public License
-along with GCC; see the file COPYING. If not, write to the Free
-Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA. */
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
#include "config.h"
#include "system.h"
#include "hashtab.h"
#include "params.h"
#include "target.h"
+#include "timevar.h"
+#include "tree-pass.h"
+#include "dbgcnt.h"
/* The following code implements gcse after reload, the purpose of this
pass is to cleanup redundant loads generated by reload and other
/* Support for hash table construction and transformations. */
static bool oprs_unchanged_p (rtx, rtx, bool);
-static void record_last_reg_set_info (rtx, int);
+static void record_last_reg_set_info (rtx, rtx);
+static void record_last_reg_set_info_regno (rtx, int);
static void record_last_mem_set_info (rtx);
-static void record_last_set_info (rtx, rtx, void *);
+static void record_last_set_info (rtx, const_rtx, void *);
static void record_opr_changes (rtx);
-static void find_mem_conflicts (rtx, rtx, void *);
+static void find_mem_conflicts (rtx, const_rtx, void *);
static int load_killed_in_block_p (int, rtx, bool);
static void reset_opr_set_tables (void);
static bool reg_killed_on_edge (rtx, edge);
static bool reg_used_on_edge (rtx, edge);
-static rtx reg_set_between_after_reload_p (rtx, rtx, rtx);
-static rtx reg_used_between_after_reload_p (rtx, rtx, rtx);
static rtx get_avail_load_store_reg (rtx);
static bool bb_has_well_behaved_predecessors (basic_block);
rtx insn;
/* Find the largest UID and create a mapping from UIDs to CUIDs. */
- uid_cuid = xcalloc (get_max_uid () + 1, sizeof (int));
- i = 0;
+ uid_cuid = XCNEWVEC (int, get_max_uid () + 1);
+ i = 1;
FOR_EACH_BB (bb)
FOR_BB_INSNS (bb, insn)
{
static hashval_t
hash_expr_for_htab (const void *expp)
{
- struct expr *exp = (struct expr *) expp;
+ const struct expr *const exp = (const struct expr *) expp;
return exp->hash;
}
static int
expr_equiv_p (const void *exp1p, const void *exp2p)
{
- struct expr *exp1 = (struct expr *) exp1p;
- struct expr *exp2 = (struct expr *) exp2p;
+ const struct expr *const exp1 = (const struct expr *) exp1p;
+ const struct expr *const exp2 = (const struct expr *) exp2p;
int equiv_p = exp_equiv_p (exp1->expr, exp2->expr, 0, true);
gcc_assert (!equiv_p || exp1->hash == exp2->hash);
fprintf (file, "\n");
}
\f
+/* Return true if register X is recorded as being set by an instruction
+ whose CUID is greater than the one given. */
+
+static bool
+reg_changed_after_insn_p (rtx x, int cuid)
+{
+ unsigned int regno, end_regno;
+
+ regno = REGNO (x);
+ end_regno = END_HARD_REGNO (x);
+ do
+ if (reg_avail_info[regno] > cuid)
+ return true;
+ while (++regno < end_regno);
+ return false;
+}
/* Return nonzero if the operands of expression X are unchanged
1) from the start of INSN's basic block up to but not including INSN
/* We are called after register allocation. */
gcc_assert (REGNO (x) < FIRST_PSEUDO_REGISTER);
if (after_insn)
- /* If the last CUID setting the insn is less than the CUID of
- INSN, then reg X is not changed in or after INSN. */
- return reg_avail_info[REGNO (x)] < INSN_CUID (insn);
+ return !reg_changed_after_insn_p (x, INSN_CUID (insn) - 1);
else
- /* Reg X is not set before INSN in the current basic block if
- we have not yet recorded the CUID of an insn that touches
- the reg. */
- return reg_avail_info[REGNO (x)] == 0;
+ return !reg_changed_after_insn_p (x, 0);
case MEM:
if (load_killed_in_block_p (INSN_CUID (insn), x, after_insn))
case CONST:
case CONST_INT:
case CONST_DOUBLE:
+ case CONST_FIXED:
case CONST_VECTOR:
case SYMBOL_REF:
case LABEL_REF:
to a nonzero value. */
static void
-find_mem_conflicts (rtx dest, rtx setter ATTRIBUTE_UNUSED,
+find_mem_conflicts (rtx dest, const_rtx setter ATTRIBUTE_UNUSED,
void *data)
{
rtx mem_op = (rtx) data;
/* Record register first/last/block set information for REGNO in INSN. */
static inline void
-record_last_reg_set_info (rtx insn, int regno)
+record_last_reg_set_info (rtx insn, rtx reg)
+{
+ unsigned int regno, end_regno;
+
+ regno = REGNO (reg);
+ end_regno = END_HARD_REGNO (reg);
+ do
+ reg_avail_info[regno] = INSN_CUID (insn);
+ while (++regno < end_regno);
+}
+
+static inline void
+record_last_reg_set_info_regno (rtx insn, int regno)
{
reg_avail_info[regno] = INSN_CUID (insn);
}
the SET is taking place. */
static void
-record_last_set_info (rtx dest, rtx setter ATTRIBUTE_UNUSED, void *data)
+record_last_set_info (rtx dest, const_rtx setter ATTRIBUTE_UNUSED, void *data)
{
rtx last_set_insn = (rtx) data;
dest = SUBREG_REG (dest);
if (REG_P (dest))
- record_last_reg_set_info (last_set_insn, REGNO (dest));
- else if (MEM_P (dest)
- /* Ignore pushes, they clobber nothing. */
- && ! push_operand (dest, GET_MODE (dest)))
- record_last_mem_set_info (last_set_insn);
+ record_last_reg_set_info (last_set_insn, dest);
+ else if (MEM_P (dest))
+ {
+ /* Ignore pushes, they don't clobber memory. They may still
+ clobber the stack pointer though. Some targets do argument
+ pushes without adding REG_INC notes. See e.g. PR25196,
+ where a pushsi2 on i386 doesn't have REG_INC notes. Note
+ such changes here too. */
+ if (! push_operand (dest, GET_MODE (dest)))
+ record_last_mem_set_info (last_set_insn);
+ else
+ record_last_reg_set_info_regno (last_set_insn, STACK_POINTER_REGNUM);
+ }
}
/* Also record autoincremented REGs for this insn as changed. */
for (note = REG_NOTES (insn); note; note = XEXP (note, 1))
if (REG_NOTE_KIND (note) == REG_INC)
- record_last_reg_set_info (insn, REGNO (XEXP (note, 0)));
+ record_last_reg_set_info (insn, XEXP (note, 0));
/* Finally, if this is a call, record all call clobbers. */
if (CALL_P (insn))
{
unsigned int regno;
+ rtx link, x;
for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
if (TEST_HARD_REG_BIT (regs_invalidated_by_call, regno))
- record_last_reg_set_info (insn, regno);
-
- if (! CONST_OR_PURE_CALL_P (insn))
+ record_last_reg_set_info_regno (insn, regno);
+
+ for (link = CALL_INSN_FUNCTION_USAGE (insn); link; link = XEXP (link, 1))
+ if (GET_CODE (XEXP (link, 0)) == CLOBBER)
+ {
+ x = XEXP (XEXP (link, 0), 0);
+ if (REG_P (x))
+ {
+ gcc_assert (HARD_REGISTER_P (x));
+ record_last_reg_set_info (insn, x);
+ }
+ }
+
+ if (! RTL_CONST_OR_PURE_CALL_P (insn))
record_last_mem_set_info (insn);
}
}
can_copy_p (GET_MODE (dest))
/* Is SET_SRC something we want to gcse? */
&& general_operand (src, GET_MODE (src))
+#ifdef STACK_REGS
+ /* Never consider insns touching the register stack. It may
+ create situations that reg-stack cannot handle (e.g. a stack
+ register live across an abnormal edge). */
+ && (REGNO (dest) < FIRST_STACK_REG || REGNO (dest) > LAST_STACK_REG)
+#endif
/* An expression is not available if its operands are
subsequently modified, including this insn. */
&& oprs_unchanged_p (src, insn, true))
can_copy_p (GET_MODE (src))
/* Is SET_DEST something we want to gcse? */
&& general_operand (dest, GET_MODE (dest))
+#ifdef STACK_REGS
+ /* As above for STACK_REGS. */
+ && (REGNO (src) < FIRST_STACK_REG || REGNO (src) > LAST_STACK_REG)
+#endif
&& ! (flag_float_store && FLOAT_MODE_P (GET_MODE (dest)))
/* Check if the memory expression is killed after insn. */
&& ! load_killed_in_block_p (INSN_CUID (insn) + 1, dest, true)
return false;
}
\f
-
-/* Return the insn that sets register REG or clobbers it in between
- FROM_INSN and TO_INSN (exclusive of those two).
- Just like reg_set_between but for hard registers and not pseudos. */
-
-static rtx
-reg_set_between_after_reload_p (rtx reg, rtx from_insn, rtx to_insn)
-{
- rtx insn;
-
- /* We are called after register allocation. */
- gcc_assert (REG_P (reg) && REGNO (reg) < FIRST_PSEUDO_REGISTER);
-
- if (from_insn == to_insn)
- return NULL_RTX;
-
- for (insn = NEXT_INSN (from_insn);
- insn != to_insn;
- insn = NEXT_INSN (insn))
- if (INSN_P (insn))
- {
- if (set_of (reg, insn) != NULL_RTX)
- return insn;
- if ((CALL_P (insn)
- && call_used_regs[REGNO (reg)])
- || find_reg_fusage (insn, CLOBBER, reg))
- return insn;
-
- if (FIND_REG_INC_NOTE (insn, reg))
- return insn;
- }
-
- return NULL_RTX;
-}
-
-/* Return the insn that uses register REG in between FROM_INSN and TO_INSN
- (exclusive of those two). Similar to reg_used_between but for hard
- registers and not pseudos. */
-
-static rtx
-reg_used_between_after_reload_p (rtx reg, rtx from_insn, rtx to_insn)
-{
- rtx insn;
-
- /* We are called after register allocation. */
- gcc_assert (REG_P (reg) && REGNO (reg) < FIRST_PSEUDO_REGISTER);
-
- if (from_insn == to_insn)
- return NULL_RTX;
-
- for (insn = NEXT_INSN (from_insn);
- insn != to_insn;
- insn = NEXT_INSN (insn))
- if (INSN_P (insn))
- {
- if (reg_overlap_mentioned_p (reg, PATTERN (insn))
- || (CALL_P (insn)
- && call_used_regs[REGNO (reg)])
- || find_reg_fusage (insn, USE, reg)
- || find_reg_fusage (insn, CLOBBER, reg))
- return insn;
-
- if (FIND_REG_INC_NOTE (insn, reg))
- return insn;
- }
-
- return NULL_RTX;
-}
-
-/* Return true if REG is used, set, or killed between the beginning of
- basic block BB and UP_TO_INSN. Caches the result in reg_avail_info. */
-
-static bool
-reg_set_or_used_since_bb_start (rtx reg, basic_block bb, rtx up_to_insn)
-{
- rtx insn, start = PREV_INSN (BB_HEAD (bb));
-
- if (reg_avail_info[REGNO (reg)] != 0)
- return true;
-
- insn = reg_used_between_after_reload_p (reg, start, up_to_insn);
- if (! insn)
- insn = reg_set_between_after_reload_p (reg, start, up_to_insn);
-
- if (insn)
- reg_avail_info[REGNO (reg)] = INSN_CUID (insn);
-
- return insn != NULL_RTX;
-}
-
/* Return the loaded/stored register of a load/store instruction. */
static rtx
/* Check that the loaded register is not used, set, or killed from the
beginning of the block. */
- if (reg_set_or_used_since_bb_start (dest, bb, insn))
+ if (reg_changed_after_insn_p (dest, 0)
+ || reg_used_between_p (dest, PREV_INSN (BB_HEAD (bb)), insn))
return;
/* Check potential for replacing load with copy for predecessors. */
avail_insn = NULL;
continue;
}
- if (! reg_set_between_after_reload_p (avail_reg, avail_insn,
- next_pred_bb_end))
+ if (!reg_set_between_p (avail_reg, avail_insn, next_pred_bb_end))
/* AVAIL_INSN remains non-null. */
break;
else
else /* Its a dead move no need to generate. */
continue;
occr = (struct unoccr *) obstack_alloc (&unoccr_obstack,
- sizeof (struct occr));
+ sizeof (struct unoccr));
occr->insn = avail_insn;
occr->pred = pred;
occr->next = avail_occrs;
}
else
{
- /* Adding a load on a critical edge will cuase a split. */
+ /* Adding a load on a critical edge will cause a split. */
if (EDGE_CRITICAL_P (pred))
critical_edge_split = true;
not_ok_count += pred->count;
if (/* No load can be replaced by copy. */
npred_ok == 0
/* Prevent exploding the code. */
- || (optimize_size && npred_ok > 1)
+ || (optimize_bb_for_size_p (bb) && npred_ok > 1)
/* If we don't have profile information we cannot tell if splitting
a critical edge is profitable or not so don't do it. */
|| ((! profile_info || ! flag_branch_probabilities
continue;
/* Do not try anything on cold basic blocks. */
- if (probably_cold_bb_p (bb))
+ if (optimize_bb_for_size_p (bb))
continue;
/* Reset the table of things changed since the start of the current
for (occr = expr->avail_occr; occr != NULL; occr = occr->next)
{
- if (occr->deleted_p)
+ if (occr->deleted_p && dbg_cnt (gcse2_delete))
{
delete_insn (occr->insn);
stats.insns_deleted++;
/* Main entry point of the GCSE after reload - clean some redundant loads
due to spilling. */
-void
+static void
gcse_after_reload_main (rtx f ATTRIBUTE_UNUSED)
{
memset (&stats, 0, sizeof (stats));
- /* Allocate ememory for this pass.
+ /* Allocate memory for this pass.
Also computes and initializes the insns' CUIDs. */
alloc_mem ();
free_mem ();
}
+\f
+static bool
+gate_handle_gcse2 (void)
+{
+ return (optimize > 0 && flag_gcse_after_reload
+ && optimize_function_for_speed_p (cfun));
+}
+
+
+static unsigned int
+rest_of_handle_gcse2 (void)
+{
+ gcse_after_reload_main (get_insns ());
+ rebuild_jump_labels (get_insns ());
+ return 0;
+}
+
+struct rtl_opt_pass pass_gcse2 =
+{
+ {
+ RTL_PASS,
+ "gcse2", /* name */
+ gate_handle_gcse2, /* gate */
+ rest_of_handle_gcse2, /* execute */
+ NULL, /* sub */
+ NULL, /* next */
+ 0, /* static_pass_number */
+ TV_GCSE_AFTER_RELOAD, /* tv_id */
+ 0, /* properties_required */
+ 0, /* properties_provided */
+ 0, /* properties_destroyed */
+ 0, /* todo_flags_start */
+ TODO_dump_func | TODO_verify_rtl_sharing
+ | TODO_verify_flow | TODO_ggc_collect /* todo_flags_finish */
+ }
+};
+