506868500a
gcc/ChangeLog: 2015-08-11 trevor Saunders <tbsaunde@tbsaunde.org> * bt-load.c, cgraph.h, dwarf2out.c, dwarf2out.h, final.c, function.c, graphite-scop-detection.c, haifa-sched.c, ipa-devirt.c, ipa-split.c, recog.c, ree.c, stmt.c, tree-data-ref.c, tree-ssa-dom.c, tree-ssa-loop-ivopts.c, varasm.c: Remove typedefs of structs. From-SVN: r227000
9179 lines
262 KiB
C
9179 lines
262 KiB
C
/* Instruction scheduling pass.
|
||
Copyright (C) 1992-2015 Free Software Foundation, Inc.
|
||
Contributed by Michael Tiemann (tiemann@cygnus.com) Enhanced by,
|
||
and currently maintained by, Jim Wilson (wilson@cygnus.com)
|
||
|
||
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/>. */
|
||
|
||
/* Instruction scheduling pass. This file, along with sched-deps.c,
|
||
contains the generic parts. The actual entry point for
|
||
the normal instruction scheduling pass is found in sched-rgn.c.
|
||
|
||
We compute insn priorities based on data dependencies. Flow
|
||
analysis only creates a fraction of the data-dependencies we must
|
||
observe: namely, only those dependencies which the combiner can be
|
||
expected to use. For this pass, we must therefore create the
|
||
remaining dependencies we need to observe: register dependencies,
|
||
memory dependencies, dependencies to keep function calls in order,
|
||
and the dependence between a conditional branch and the setting of
|
||
condition codes are all dealt with here.
|
||
|
||
The scheduler first traverses the data flow graph, starting with
|
||
the last instruction, and proceeding to the first, assigning values
|
||
to insn_priority as it goes. This sorts the instructions
|
||
topologically by data dependence.
|
||
|
||
Once priorities have been established, we order the insns using
|
||
list scheduling. This works as follows: starting with a list of
|
||
all the ready insns, and sorted according to priority number, we
|
||
schedule the insn from the end of the list by placing its
|
||
predecessors in the list according to their priority order. We
|
||
consider this insn scheduled by setting the pointer to the "end" of
|
||
the list to point to the previous insn. When an insn has no
|
||
predecessors, we either queue it until sufficient time has elapsed
|
||
or add it to the ready list. As the instructions are scheduled or
|
||
when stalls are introduced, the queue advances and dumps insns into
|
||
the ready list. When all insns down to the lowest priority have
|
||
been scheduled, the critical path of the basic block has been made
|
||
as short as possible. The remaining insns are then scheduled in
|
||
remaining slots.
|
||
|
||
The following list shows the order in which we want to break ties
|
||
among insns in the ready list:
|
||
|
||
1. choose insn with the longest path to end of bb, ties
|
||
broken by
|
||
2. choose insn with least contribution to register pressure,
|
||
ties broken by
|
||
3. prefer in-block upon interblock motion, ties broken by
|
||
4. prefer useful upon speculative motion, ties broken by
|
||
5. choose insn with largest control flow probability, ties
|
||
broken by
|
||
6. choose insn with the least dependences upon the previously
|
||
scheduled insn, or finally
|
||
7 choose the insn which has the most insns dependent on it.
|
||
8. choose insn with lowest UID.
|
||
|
||
Memory references complicate matters. Only if we can be certain
|
||
that memory references are not part of the data dependency graph
|
||
(via true, anti, or output dependence), can we move operations past
|
||
memory references. To first approximation, reads can be done
|
||
independently, while writes introduce dependencies. Better
|
||
approximations will yield fewer dependencies.
|
||
|
||
Before reload, an extended analysis of interblock data dependences
|
||
is required for interblock scheduling. This is performed in
|
||
compute_block_dependences ().
|
||
|
||
Dependencies set up by memory references are treated in exactly the
|
||
same way as other dependencies, by using insn backward dependences
|
||
INSN_BACK_DEPS. INSN_BACK_DEPS are translated into forward dependences
|
||
INSN_FORW_DEPS for the purpose of forward list scheduling.
|
||
|
||
Having optimized the critical path, we may have also unduly
|
||
extended the lifetimes of some registers. If an operation requires
|
||
that constants be loaded into registers, it is certainly desirable
|
||
to load those constants as early as necessary, but no earlier.
|
||
I.e., it will not do to load up a bunch of registers at the
|
||
beginning of a basic block only to use them at the end, if they
|
||
could be loaded later, since this may result in excessive register
|
||
utilization.
|
||
|
||
Note that since branches are never in basic blocks, but only end
|
||
basic blocks, this pass will not move branches. But that is ok,
|
||
since we can use GNU's delayed branch scheduling pass to take care
|
||
of this case.
|
||
|
||
Also note that no further optimizations based on algebraic
|
||
identities are performed, so this pass would be a good one to
|
||
perform instruction splitting, such as breaking up a multiply
|
||
instruction into shifts and adds where that is profitable.
|
||
|
||
Given the memory aliasing analysis that this pass should perform,
|
||
it should be possible to remove redundant stores to memory, and to
|
||
load values from registers instead of hitting memory.
|
||
|
||
Before reload, speculative insns are moved only if a 'proof' exists
|
||
that no exception will be caused by this, and if no live registers
|
||
exist that inhibit the motion (live registers constraints are not
|
||
represented by data dependence edges).
|
||
|
||
This pass must update information that subsequent passes expect to
|
||
be correct. Namely: reg_n_refs, reg_n_sets, reg_n_deaths,
|
||
reg_n_calls_crossed, and reg_live_length. Also, BB_HEAD, BB_END.
|
||
|
||
The information in the line number notes is carefully retained by
|
||
this pass. Notes that refer to the starting and ending of
|
||
exception regions are also carefully retained by this pass. All
|
||
other NOTE insns are grouped in their same relative order at the
|
||
beginning of basic blocks and regions that have been scheduled. */
|
||
|
||
#include "config.h"
|
||
#include "system.h"
|
||
#include "coretypes.h"
|
||
#include "backend.h"
|
||
#include "cfghooks.h"
|
||
#include "rtl.h"
|
||
#include "df.h"
|
||
#include "diagnostic-core.h"
|
||
#include "tm_p.h"
|
||
#include "regs.h"
|
||
#include "flags.h"
|
||
#include "insn-config.h"
|
||
#include "insn-attr.h"
|
||
#include "except.h"
|
||
#include "recog.h"
|
||
#include "cfgrtl.h"
|
||
#include "cfgbuild.h"
|
||
#include "sched-int.h"
|
||
#include "target.h"
|
||
#include "common/common-target.h"
|
||
#include "params.h"
|
||
#include "dbgcnt.h"
|
||
#include "cfgloop.h"
|
||
#include "ira.h"
|
||
#include "emit-rtl.h" /* FIXME: Can go away once crtl is moved to rtl.h. */
|
||
#include "dumpfile.h"
|
||
|
||
#ifdef INSN_SCHEDULING
|
||
|
||
/* True if we do register pressure relief through live-range
|
||
shrinkage. */
|
||
static bool live_range_shrinkage_p;
|
||
|
||
/* Switch on live range shrinkage. */
|
||
void
|
||
initialize_live_range_shrinkage (void)
|
||
{
|
||
live_range_shrinkage_p = true;
|
||
}
|
||
|
||
/* Switch off live range shrinkage. */
|
||
void
|
||
finish_live_range_shrinkage (void)
|
||
{
|
||
live_range_shrinkage_p = false;
|
||
}
|
||
|
||
/* issue_rate is the number of insns that can be scheduled in the same
|
||
machine cycle. It can be defined in the config/mach/mach.h file,
|
||
otherwise we set it to 1. */
|
||
|
||
int issue_rate;
|
||
|
||
/* This can be set to true by a backend if the scheduler should not
|
||
enable a DCE pass. */
|
||
bool sched_no_dce;
|
||
|
||
/* The current initiation interval used when modulo scheduling. */
|
||
static int modulo_ii;
|
||
|
||
/* The maximum number of stages we are prepared to handle. */
|
||
static int modulo_max_stages;
|
||
|
||
/* The number of insns that exist in each iteration of the loop. We use this
|
||
to detect when we've scheduled all insns from the first iteration. */
|
||
static int modulo_n_insns;
|
||
|
||
/* The current count of insns in the first iteration of the loop that have
|
||
already been scheduled. */
|
||
static int modulo_insns_scheduled;
|
||
|
||
/* The maximum uid of insns from the first iteration of the loop. */
|
||
static int modulo_iter0_max_uid;
|
||
|
||
/* The number of times we should attempt to backtrack when modulo scheduling.
|
||
Decreased each time we have to backtrack. */
|
||
static int modulo_backtracks_left;
|
||
|
||
/* The stage in which the last insn from the original loop was
|
||
scheduled. */
|
||
static int modulo_last_stage;
|
||
|
||
/* sched-verbose controls the amount of debugging output the
|
||
scheduler prints. It is controlled by -fsched-verbose=N:
|
||
N>0 and no -DSR : the output is directed to stderr.
|
||
N>=10 will direct the printouts to stderr (regardless of -dSR).
|
||
N=1: same as -dSR.
|
||
N=2: bb's probabilities, detailed ready list info, unit/insn info.
|
||
N=3: rtl at abort point, control-flow, regions info.
|
||
N=5: dependences info. */
|
||
|
||
int sched_verbose = 0;
|
||
|
||
/* Debugging file. All printouts are sent to dump, which is always set,
|
||
either to stderr, or to the dump listing file (-dRS). */
|
||
FILE *sched_dump = 0;
|
||
|
||
/* This is a placeholder for the scheduler parameters common
|
||
to all schedulers. */
|
||
struct common_sched_info_def *common_sched_info;
|
||
|
||
#define INSN_TICK(INSN) (HID (INSN)->tick)
|
||
#define INSN_EXACT_TICK(INSN) (HID (INSN)->exact_tick)
|
||
#define INSN_TICK_ESTIMATE(INSN) (HID (INSN)->tick_estimate)
|
||
#define INTER_TICK(INSN) (HID (INSN)->inter_tick)
|
||
#define FEEDS_BACKTRACK_INSN(INSN) (HID (INSN)->feeds_backtrack_insn)
|
||
#define SHADOW_P(INSN) (HID (INSN)->shadow_p)
|
||
#define MUST_RECOMPUTE_SPEC_P(INSN) (HID (INSN)->must_recompute_spec)
|
||
/* Cached cost of the instruction. Use insn_cost to get cost of the
|
||
insn. -1 here means that the field is not initialized. */
|
||
#define INSN_COST(INSN) (HID (INSN)->cost)
|
||
|
||
/* If INSN_TICK of an instruction is equal to INVALID_TICK,
|
||
then it should be recalculated from scratch. */
|
||
#define INVALID_TICK (-(max_insn_queue_index + 1))
|
||
/* The minimal value of the INSN_TICK of an instruction. */
|
||
#define MIN_TICK (-max_insn_queue_index)
|
||
|
||
/* Original order of insns in the ready list.
|
||
Used to keep order of normal insns while separating DEBUG_INSNs. */
|
||
#define INSN_RFS_DEBUG_ORIG_ORDER(INSN) (HID (INSN)->rfs_debug_orig_order)
|
||
|
||
/* The deciding reason for INSN's place in the ready list. */
|
||
#define INSN_LAST_RFS_WIN(INSN) (HID (INSN)->last_rfs_win)
|
||
|
||
/* List of important notes we must keep around. This is a pointer to the
|
||
last element in the list. */
|
||
rtx_insn *note_list;
|
||
|
||
static struct spec_info_def spec_info_var;
|
||
/* Description of the speculative part of the scheduling.
|
||
If NULL - no speculation. */
|
||
spec_info_t spec_info = NULL;
|
||
|
||
/* True, if recovery block was added during scheduling of current block.
|
||
Used to determine, if we need to fix INSN_TICKs. */
|
||
static bool haifa_recovery_bb_recently_added_p;
|
||
|
||
/* True, if recovery block was added during this scheduling pass.
|
||
Used to determine if we should have empty memory pools of dependencies
|
||
after finishing current region. */
|
||
bool haifa_recovery_bb_ever_added_p;
|
||
|
||
/* Counters of different types of speculative instructions. */
|
||
static int nr_begin_data, nr_be_in_data, nr_begin_control, nr_be_in_control;
|
||
|
||
/* Array used in {unlink, restore}_bb_notes. */
|
||
static rtx_insn **bb_header = 0;
|
||
|
||
/* Basic block after which recovery blocks will be created. */
|
||
static basic_block before_recovery;
|
||
|
||
/* Basic block just before the EXIT_BLOCK and after recovery, if we have
|
||
created it. */
|
||
basic_block after_recovery;
|
||
|
||
/* FALSE if we add bb to another region, so we don't need to initialize it. */
|
||
bool adding_bb_to_current_region_p = true;
|
||
|
||
/* Queues, etc. */
|
||
|
||
/* An instruction is ready to be scheduled when all insns preceding it
|
||
have already been scheduled. It is important to ensure that all
|
||
insns which use its result will not be executed until its result
|
||
has been computed. An insn is maintained in one of four structures:
|
||
|
||
(P) the "Pending" set of insns which cannot be scheduled until
|
||
their dependencies have been satisfied.
|
||
(Q) the "Queued" set of insns that can be scheduled when sufficient
|
||
time has passed.
|
||
(R) the "Ready" list of unscheduled, uncommitted insns.
|
||
(S) the "Scheduled" list of insns.
|
||
|
||
Initially, all insns are either "Pending" or "Ready" depending on
|
||
whether their dependencies are satisfied.
|
||
|
||
Insns move from the "Ready" list to the "Scheduled" list as they
|
||
are committed to the schedule. As this occurs, the insns in the
|
||
"Pending" list have their dependencies satisfied and move to either
|
||
the "Ready" list or the "Queued" set depending on whether
|
||
sufficient time has passed to make them ready. As time passes,
|
||
insns move from the "Queued" set to the "Ready" list.
|
||
|
||
The "Pending" list (P) are the insns in the INSN_FORW_DEPS of the
|
||
unscheduled insns, i.e., those that are ready, queued, and pending.
|
||
The "Queued" set (Q) is implemented by the variable `insn_queue'.
|
||
The "Ready" list (R) is implemented by the variables `ready' and
|
||
`n_ready'.
|
||
The "Scheduled" list (S) is the new insn chain built by this pass.
|
||
|
||
The transition (R->S) is implemented in the scheduling loop in
|
||
`schedule_block' when the best insn to schedule is chosen.
|
||
The transitions (P->R and P->Q) are implemented in `schedule_insn' as
|
||
insns move from the ready list to the scheduled list.
|
||
The transition (Q->R) is implemented in 'queue_to_insn' as time
|
||
passes or stalls are introduced. */
|
||
|
||
/* Implement a circular buffer to delay instructions until sufficient
|
||
time has passed. For the new pipeline description interface,
|
||
MAX_INSN_QUEUE_INDEX is a power of two minus one which is not less
|
||
than maximal time of instruction execution computed by genattr.c on
|
||
the base maximal time of functional unit reservations and getting a
|
||
result. This is the longest time an insn may be queued. */
|
||
|
||
static rtx_insn_list **insn_queue;
|
||
static int q_ptr = 0;
|
||
static int q_size = 0;
|
||
#define NEXT_Q(X) (((X)+1) & max_insn_queue_index)
|
||
#define NEXT_Q_AFTER(X, C) (((X)+C) & max_insn_queue_index)
|
||
|
||
#define QUEUE_SCHEDULED (-3)
|
||
#define QUEUE_NOWHERE (-2)
|
||
#define QUEUE_READY (-1)
|
||
/* QUEUE_SCHEDULED - INSN is scheduled.
|
||
QUEUE_NOWHERE - INSN isn't scheduled yet and is neither in
|
||
queue or ready list.
|
||
QUEUE_READY - INSN is in ready list.
|
||
N >= 0 - INSN queued for X [where NEXT_Q_AFTER (q_ptr, X) == N] cycles. */
|
||
|
||
#define QUEUE_INDEX(INSN) (HID (INSN)->queue_index)
|
||
|
||
/* The following variable value refers for all current and future
|
||
reservations of the processor units. */
|
||
state_t curr_state;
|
||
|
||
/* The following variable value is size of memory representing all
|
||
current and future reservations of the processor units. */
|
||
size_t dfa_state_size;
|
||
|
||
/* The following array is used to find the best insn from ready when
|
||
the automaton pipeline interface is used. */
|
||
signed char *ready_try = NULL;
|
||
|
||
/* The ready list. */
|
||
struct ready_list ready = {NULL, 0, 0, 0, 0};
|
||
|
||
/* The pointer to the ready list (to be removed). */
|
||
static struct ready_list *readyp = &ready;
|
||
|
||
/* Scheduling clock. */
|
||
static int clock_var;
|
||
|
||
/* Clock at which the previous instruction was issued. */
|
||
static int last_clock_var;
|
||
|
||
/* Set to true if, when queuing a shadow insn, we discover that it would be
|
||
scheduled too late. */
|
||
static bool must_backtrack;
|
||
|
||
/* The following variable value is number of essential insns issued on
|
||
the current cycle. An insn is essential one if it changes the
|
||
processors state. */
|
||
int cycle_issued_insns;
|
||
|
||
/* This records the actual schedule. It is built up during the main phase
|
||
of schedule_block, and afterwards used to reorder the insns in the RTL. */
|
||
static vec<rtx_insn *> scheduled_insns;
|
||
|
||
static int may_trap_exp (const_rtx, int);
|
||
|
||
/* Nonzero iff the address is comprised from at most 1 register. */
|
||
#define CONST_BASED_ADDRESS_P(x) \
|
||
(REG_P (x) \
|
||
|| ((GET_CODE (x) == PLUS || GET_CODE (x) == MINUS \
|
||
|| (GET_CODE (x) == LO_SUM)) \
|
||
&& (CONSTANT_P (XEXP (x, 0)) \
|
||
|| CONSTANT_P (XEXP (x, 1)))))
|
||
|
||
/* Returns a class that insn with GET_DEST(insn)=x may belong to,
|
||
as found by analyzing insn's expression. */
|
||
|
||
|
||
static int haifa_luid_for_non_insn (rtx x);
|
||
|
||
/* Haifa version of sched_info hooks common to all headers. */
|
||
const struct common_sched_info_def haifa_common_sched_info =
|
||
{
|
||
NULL, /* fix_recovery_cfg */
|
||
NULL, /* add_block */
|
||
NULL, /* estimate_number_of_insns */
|
||
haifa_luid_for_non_insn, /* luid_for_non_insn */
|
||
SCHED_PASS_UNKNOWN /* sched_pass_id */
|
||
};
|
||
|
||
/* Mapping from instruction UID to its Logical UID. */
|
||
vec<int> sched_luids = vNULL;
|
||
|
||
/* Next LUID to assign to an instruction. */
|
||
int sched_max_luid = 1;
|
||
|
||
/* Haifa Instruction Data. */
|
||
vec<haifa_insn_data_def> h_i_d = vNULL;
|
||
|
||
void (* sched_init_only_bb) (basic_block, basic_block);
|
||
|
||
/* Split block function. Different schedulers might use different functions
|
||
to handle their internal data consistent. */
|
||
basic_block (* sched_split_block) (basic_block, rtx);
|
||
|
||
/* Create empty basic block after the specified block. */
|
||
basic_block (* sched_create_empty_bb) (basic_block);
|
||
|
||
/* Return the number of cycles until INSN is expected to be ready.
|
||
Return zero if it already is. */
|
||
static int
|
||
insn_delay (rtx_insn *insn)
|
||
{
|
||
return MAX (INSN_TICK (insn) - clock_var, 0);
|
||
}
|
||
|
||
static int
|
||
may_trap_exp (const_rtx x, int is_store)
|
||
{
|
||
enum rtx_code code;
|
||
|
||
if (x == 0)
|
||
return TRAP_FREE;
|
||
code = GET_CODE (x);
|
||
if (is_store)
|
||
{
|
||
if (code == MEM && may_trap_p (x))
|
||
return TRAP_RISKY;
|
||
else
|
||
return TRAP_FREE;
|
||
}
|
||
if (code == MEM)
|
||
{
|
||
/* The insn uses memory: a volatile load. */
|
||
if (MEM_VOLATILE_P (x))
|
||
return IRISKY;
|
||
/* An exception-free load. */
|
||
if (!may_trap_p (x))
|
||
return IFREE;
|
||
/* A load with 1 base register, to be further checked. */
|
||
if (CONST_BASED_ADDRESS_P (XEXP (x, 0)))
|
||
return PFREE_CANDIDATE;
|
||
/* No info on the load, to be further checked. */
|
||
return PRISKY_CANDIDATE;
|
||
}
|
||
else
|
||
{
|
||
const char *fmt;
|
||
int i, insn_class = TRAP_FREE;
|
||
|
||
/* Neither store nor load, check if it may cause a trap. */
|
||
if (may_trap_p (x))
|
||
return TRAP_RISKY;
|
||
/* Recursive step: walk the insn... */
|
||
fmt = GET_RTX_FORMAT (code);
|
||
for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--)
|
||
{
|
||
if (fmt[i] == 'e')
|
||
{
|
||
int tmp_class = may_trap_exp (XEXP (x, i), is_store);
|
||
insn_class = WORST_CLASS (insn_class, tmp_class);
|
||
}
|
||
else if (fmt[i] == 'E')
|
||
{
|
||
int j;
|
||
for (j = 0; j < XVECLEN (x, i); j++)
|
||
{
|
||
int tmp_class = may_trap_exp (XVECEXP (x, i, j), is_store);
|
||
insn_class = WORST_CLASS (insn_class, tmp_class);
|
||
if (insn_class == TRAP_RISKY || insn_class == IRISKY)
|
||
break;
|
||
}
|
||
}
|
||
if (insn_class == TRAP_RISKY || insn_class == IRISKY)
|
||
break;
|
||
}
|
||
return insn_class;
|
||
}
|
||
}
|
||
|
||
/* Classifies rtx X of an insn for the purpose of verifying that X can be
|
||
executed speculatively (and consequently the insn can be moved
|
||
speculatively), by examining X, returning:
|
||
TRAP_RISKY: store, or risky non-load insn (e.g. division by variable).
|
||
TRAP_FREE: non-load insn.
|
||
IFREE: load from a globally safe location.
|
||
IRISKY: volatile load.
|
||
PFREE_CANDIDATE, PRISKY_CANDIDATE: load that need to be checked for
|
||
being either PFREE or PRISKY. */
|
||
|
||
static int
|
||
haifa_classify_rtx (const_rtx x)
|
||
{
|
||
int tmp_class = TRAP_FREE;
|
||
int insn_class = TRAP_FREE;
|
||
enum rtx_code code;
|
||
|
||
if (GET_CODE (x) == PARALLEL)
|
||
{
|
||
int i, len = XVECLEN (x, 0);
|
||
|
||
for (i = len - 1; i >= 0; i--)
|
||
{
|
||
tmp_class = haifa_classify_rtx (XVECEXP (x, 0, i));
|
||
insn_class = WORST_CLASS (insn_class, tmp_class);
|
||
if (insn_class == TRAP_RISKY || insn_class == IRISKY)
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
code = GET_CODE (x);
|
||
switch (code)
|
||
{
|
||
case CLOBBER:
|
||
/* Test if it is a 'store'. */
|
||
tmp_class = may_trap_exp (XEXP (x, 0), 1);
|
||
break;
|
||
case SET:
|
||
/* Test if it is a store. */
|
||
tmp_class = may_trap_exp (SET_DEST (x), 1);
|
||
if (tmp_class == TRAP_RISKY)
|
||
break;
|
||
/* Test if it is a load. */
|
||
tmp_class =
|
||
WORST_CLASS (tmp_class,
|
||
may_trap_exp (SET_SRC (x), 0));
|
||
break;
|
||
case COND_EXEC:
|
||
tmp_class = haifa_classify_rtx (COND_EXEC_CODE (x));
|
||
if (tmp_class == TRAP_RISKY)
|
||
break;
|
||
tmp_class = WORST_CLASS (tmp_class,
|
||
may_trap_exp (COND_EXEC_TEST (x), 0));
|
||
break;
|
||
case TRAP_IF:
|
||
tmp_class = TRAP_RISKY;
|
||
break;
|
||
default:;
|
||
}
|
||
insn_class = tmp_class;
|
||
}
|
||
|
||
return insn_class;
|
||
}
|
||
|
||
int
|
||
haifa_classify_insn (const_rtx insn)
|
||
{
|
||
return haifa_classify_rtx (PATTERN (insn));
|
||
}
|
||
|
||
/* After the scheduler initialization function has been called, this function
|
||
can be called to enable modulo scheduling. II is the initiation interval
|
||
we should use, it affects the delays for delay_pairs that were recorded as
|
||
separated by a given number of stages.
|
||
|
||
MAX_STAGES provides us with a limit
|
||
after which we give up scheduling; the caller must have unrolled at least
|
||
as many copies of the loop body and recorded delay_pairs for them.
|
||
|
||
INSNS is the number of real (non-debug) insns in one iteration of
|
||
the loop. MAX_UID can be used to test whether an insn belongs to
|
||
the first iteration of the loop; all of them have a uid lower than
|
||
MAX_UID. */
|
||
void
|
||
set_modulo_params (int ii, int max_stages, int insns, int max_uid)
|
||
{
|
||
modulo_ii = ii;
|
||
modulo_max_stages = max_stages;
|
||
modulo_n_insns = insns;
|
||
modulo_iter0_max_uid = max_uid;
|
||
modulo_backtracks_left = PARAM_VALUE (PARAM_MAX_MODULO_BACKTRACK_ATTEMPTS);
|
||
}
|
||
|
||
/* A structure to record a pair of insns where the first one is a real
|
||
insn that has delay slots, and the second is its delayed shadow.
|
||
I1 is scheduled normally and will emit an assembly instruction,
|
||
while I2 describes the side effect that takes place at the
|
||
transition between cycles CYCLES and (CYCLES + 1) after I1. */
|
||
struct delay_pair
|
||
{
|
||
struct delay_pair *next_same_i1;
|
||
rtx_insn *i1, *i2;
|
||
int cycles;
|
||
/* When doing modulo scheduling, we a delay_pair can also be used to
|
||
show that I1 and I2 are the same insn in a different stage. If that
|
||
is the case, STAGES will be nonzero. */
|
||
int stages;
|
||
};
|
||
|
||
/* Helpers for delay hashing. */
|
||
|
||
struct delay_i1_hasher : nofree_ptr_hash <delay_pair>
|
||
{
|
||
typedef void *compare_type;
|
||
static inline hashval_t hash (const delay_pair *);
|
||
static inline bool equal (const delay_pair *, const void *);
|
||
};
|
||
|
||
/* Returns a hash value for X, based on hashing just I1. */
|
||
|
||
inline hashval_t
|
||
delay_i1_hasher::hash (const delay_pair *x)
|
||
{
|
||
return htab_hash_pointer (x->i1);
|
||
}
|
||
|
||
/* Return true if I1 of pair X is the same as that of pair Y. */
|
||
|
||
inline bool
|
||
delay_i1_hasher::equal (const delay_pair *x, const void *y)
|
||
{
|
||
return x->i1 == y;
|
||
}
|
||
|
||
struct delay_i2_hasher : free_ptr_hash <delay_pair>
|
||
{
|
||
typedef void *compare_type;
|
||
static inline hashval_t hash (const delay_pair *);
|
||
static inline bool equal (const delay_pair *, const void *);
|
||
};
|
||
|
||
/* Returns a hash value for X, based on hashing just I2. */
|
||
|
||
inline hashval_t
|
||
delay_i2_hasher::hash (const delay_pair *x)
|
||
{
|
||
return htab_hash_pointer (x->i2);
|
||
}
|
||
|
||
/* Return true if I2 of pair X is the same as that of pair Y. */
|
||
|
||
inline bool
|
||
delay_i2_hasher::equal (const delay_pair *x, const void *y)
|
||
{
|
||
return x->i2 == y;
|
||
}
|
||
|
||
/* Two hash tables to record delay_pairs, one indexed by I1 and the other
|
||
indexed by I2. */
|
||
static hash_table<delay_i1_hasher> *delay_htab;
|
||
static hash_table<delay_i2_hasher> *delay_htab_i2;
|
||
|
||
/* Called through htab_traverse. Walk the hashtable using I2 as
|
||
index, and delete all elements involving an UID higher than
|
||
that pointed to by *DATA. */
|
||
int
|
||
haifa_htab_i2_traverse (delay_pair **slot, int *data)
|
||
{
|
||
int maxuid = *data;
|
||
struct delay_pair *p = *slot;
|
||
if (INSN_UID (p->i2) >= maxuid || INSN_UID (p->i1) >= maxuid)
|
||
{
|
||
delay_htab_i2->clear_slot (slot);
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
/* Called through htab_traverse. Walk the hashtable using I2 as
|
||
index, and delete all elements involving an UID higher than
|
||
that pointed to by *DATA. */
|
||
int
|
||
haifa_htab_i1_traverse (delay_pair **pslot, int *data)
|
||
{
|
||
int maxuid = *data;
|
||
struct delay_pair *p, *first, **pprev;
|
||
|
||
if (INSN_UID ((*pslot)->i1) >= maxuid)
|
||
{
|
||
delay_htab->clear_slot (pslot);
|
||
return 1;
|
||
}
|
||
pprev = &first;
|
||
for (p = *pslot; p; p = p->next_same_i1)
|
||
{
|
||
if (INSN_UID (p->i2) < maxuid)
|
||
{
|
||
*pprev = p;
|
||
pprev = &p->next_same_i1;
|
||
}
|
||
}
|
||
*pprev = NULL;
|
||
if (first == NULL)
|
||
delay_htab->clear_slot (pslot);
|
||
else
|
||
*pslot = first;
|
||
return 1;
|
||
}
|
||
|
||
/* Discard all delay pairs which involve an insn with an UID higher
|
||
than MAX_UID. */
|
||
void
|
||
discard_delay_pairs_above (int max_uid)
|
||
{
|
||
delay_htab->traverse <int *, haifa_htab_i1_traverse> (&max_uid);
|
||
delay_htab_i2->traverse <int *, haifa_htab_i2_traverse> (&max_uid);
|
||
}
|
||
|
||
/* This function can be called by a port just before it starts the final
|
||
scheduling pass. It records the fact that an instruction with delay
|
||
slots has been split into two insns, I1 and I2. The first one will be
|
||
scheduled normally and initiates the operation. The second one is a
|
||
shadow which must follow a specific number of cycles after I1; its only
|
||
purpose is to show the side effect that occurs at that cycle in the RTL.
|
||
If a JUMP_INSN or a CALL_INSN has been split, I1 should be a normal INSN,
|
||
while I2 retains the original insn type.
|
||
|
||
There are two ways in which the number of cycles can be specified,
|
||
involving the CYCLES and STAGES arguments to this function. If STAGES
|
||
is zero, we just use the value of CYCLES. Otherwise, STAGES is a factor
|
||
which is multiplied by MODULO_II to give the number of cycles. This is
|
||
only useful if the caller also calls set_modulo_params to enable modulo
|
||
scheduling. */
|
||
|
||
void
|
||
record_delay_slot_pair (rtx_insn *i1, rtx_insn *i2, int cycles, int stages)
|
||
{
|
||
struct delay_pair *p = XNEW (struct delay_pair);
|
||
struct delay_pair **slot;
|
||
|
||
p->i1 = i1;
|
||
p->i2 = i2;
|
||
p->cycles = cycles;
|
||
p->stages = stages;
|
||
|
||
if (!delay_htab)
|
||
{
|
||
delay_htab = new hash_table<delay_i1_hasher> (10);
|
||
delay_htab_i2 = new hash_table<delay_i2_hasher> (10);
|
||
}
|
||
slot = delay_htab->find_slot_with_hash (i1, htab_hash_pointer (i1), INSERT);
|
||
p->next_same_i1 = *slot;
|
||
*slot = p;
|
||
slot = delay_htab_i2->find_slot (p, INSERT);
|
||
*slot = p;
|
||
}
|
||
|
||
/* Examine the delay pair hashtable to see if INSN is a shadow for another,
|
||
and return the other insn if so. Return NULL otherwise. */
|
||
rtx_insn *
|
||
real_insn_for_shadow (rtx_insn *insn)
|
||
{
|
||
struct delay_pair *pair;
|
||
|
||
if (!delay_htab)
|
||
return NULL;
|
||
|
||
pair = delay_htab_i2->find_with_hash (insn, htab_hash_pointer (insn));
|
||
if (!pair || pair->stages > 0)
|
||
return NULL;
|
||
return pair->i1;
|
||
}
|
||
|
||
/* For a pair P of insns, return the fixed distance in cycles from the first
|
||
insn after which the second must be scheduled. */
|
||
static int
|
||
pair_delay (struct delay_pair *p)
|
||
{
|
||
if (p->stages == 0)
|
||
return p->cycles;
|
||
else
|
||
return p->stages * modulo_ii;
|
||
}
|
||
|
||
/* Given an insn INSN, add a dependence on its delayed shadow if it
|
||
has one. Also try to find situations where shadows depend on each other
|
||
and add dependencies to the real insns to limit the amount of backtracking
|
||
needed. */
|
||
void
|
||
add_delay_dependencies (rtx_insn *insn)
|
||
{
|
||
struct delay_pair *pair;
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
|
||
if (!delay_htab)
|
||
return;
|
||
|
||
pair = delay_htab_i2->find_with_hash (insn, htab_hash_pointer (insn));
|
||
if (!pair)
|
||
return;
|
||
add_dependence (insn, pair->i1, REG_DEP_ANTI);
|
||
if (pair->stages)
|
||
return;
|
||
|
||
FOR_EACH_DEP (pair->i2, SD_LIST_BACK, sd_it, dep)
|
||
{
|
||
rtx_insn *pro = DEP_PRO (dep);
|
||
struct delay_pair *other_pair
|
||
= delay_htab_i2->find_with_hash (pro, htab_hash_pointer (pro));
|
||
if (!other_pair || other_pair->stages)
|
||
continue;
|
||
if (pair_delay (other_pair) >= pair_delay (pair))
|
||
{
|
||
if (sched_verbose >= 4)
|
||
{
|
||
fprintf (sched_dump, ";;\tadding dependence %d <- %d\n",
|
||
INSN_UID (other_pair->i1),
|
||
INSN_UID (pair->i1));
|
||
fprintf (sched_dump, ";;\tpair1 %d <- %d, cost %d\n",
|
||
INSN_UID (pair->i1),
|
||
INSN_UID (pair->i2),
|
||
pair_delay (pair));
|
||
fprintf (sched_dump, ";;\tpair2 %d <- %d, cost %d\n",
|
||
INSN_UID (other_pair->i1),
|
||
INSN_UID (other_pair->i2),
|
||
pair_delay (other_pair));
|
||
}
|
||
add_dependence (pair->i1, other_pair->i1, REG_DEP_ANTI);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Forward declarations. */
|
||
|
||
static int priority (rtx_insn *);
|
||
static int autopref_rank_for_schedule (const rtx_insn *, const rtx_insn *);
|
||
static int rank_for_schedule (const void *, const void *);
|
||
static void swap_sort (rtx_insn **, int);
|
||
static void queue_insn (rtx_insn *, int, const char *);
|
||
static int schedule_insn (rtx_insn *);
|
||
static void adjust_priority (rtx_insn *);
|
||
static void advance_one_cycle (void);
|
||
static void extend_h_i_d (void);
|
||
|
||
|
||
/* Notes handling mechanism:
|
||
=========================
|
||
Generally, NOTES are saved before scheduling and restored after scheduling.
|
||
The scheduler distinguishes between two types of notes:
|
||
|
||
(1) LOOP_BEGIN, LOOP_END, SETJMP, EHREGION_BEG, EHREGION_END notes:
|
||
Before scheduling a region, a pointer to the note is added to the insn
|
||
that follows or precedes it. (This happens as part of the data dependence
|
||
computation). After scheduling an insn, the pointer contained in it is
|
||
used for regenerating the corresponding note (in reemit_notes).
|
||
|
||
(2) All other notes (e.g. INSN_DELETED): Before scheduling a block,
|
||
these notes are put in a list (in rm_other_notes() and
|
||
unlink_other_notes ()). After scheduling the block, these notes are
|
||
inserted at the beginning of the block (in schedule_block()). */
|
||
|
||
static void ready_add (struct ready_list *, rtx_insn *, bool);
|
||
static rtx_insn *ready_remove_first (struct ready_list *);
|
||
static rtx_insn *ready_remove_first_dispatch (struct ready_list *ready);
|
||
|
||
static void queue_to_ready (struct ready_list *);
|
||
static int early_queue_to_ready (state_t, struct ready_list *);
|
||
|
||
/* The following functions are used to implement multi-pass scheduling
|
||
on the first cycle. */
|
||
static rtx_insn *ready_remove (struct ready_list *, int);
|
||
static void ready_remove_insn (rtx_insn *);
|
||
|
||
static void fix_inter_tick (rtx_insn *, rtx_insn *);
|
||
static int fix_tick_ready (rtx_insn *);
|
||
static void change_queue_index (rtx_insn *, int);
|
||
|
||
/* The following functions are used to implement scheduling of data/control
|
||
speculative instructions. */
|
||
|
||
static void extend_h_i_d (void);
|
||
static void init_h_i_d (rtx_insn *);
|
||
static int haifa_speculate_insn (rtx_insn *, ds_t, rtx *);
|
||
static void generate_recovery_code (rtx_insn *);
|
||
static void process_insn_forw_deps_be_in_spec (rtx_insn *, rtx_insn *, ds_t);
|
||
static void begin_speculative_block (rtx_insn *);
|
||
static void add_to_speculative_block (rtx_insn *);
|
||
static void init_before_recovery (basic_block *);
|
||
static void create_check_block_twin (rtx_insn *, bool);
|
||
static void fix_recovery_deps (basic_block);
|
||
static bool haifa_change_pattern (rtx_insn *, rtx);
|
||
static void dump_new_block_header (int, basic_block, rtx_insn *, rtx_insn *);
|
||
static void restore_bb_notes (basic_block);
|
||
static void fix_jump_move (rtx_insn *);
|
||
static void move_block_after_check (rtx_insn *);
|
||
static void move_succs (vec<edge, va_gc> **, basic_block);
|
||
static void sched_remove_insn (rtx_insn *);
|
||
static void clear_priorities (rtx_insn *, rtx_vec_t *);
|
||
static void calc_priorities (rtx_vec_t);
|
||
static void add_jump_dependencies (rtx_insn *, rtx_insn *);
|
||
|
||
#endif /* INSN_SCHEDULING */
|
||
|
||
/* Point to state used for the current scheduling pass. */
|
||
struct haifa_sched_info *current_sched_info;
|
||
|
||
#ifndef INSN_SCHEDULING
|
||
void
|
||
schedule_insns (void)
|
||
{
|
||
}
|
||
#else
|
||
|
||
/* Do register pressure sensitive insn scheduling if the flag is set
|
||
up. */
|
||
enum sched_pressure_algorithm sched_pressure;
|
||
|
||
/* Map regno -> its pressure class. The map defined only when
|
||
SCHED_PRESSURE != SCHED_PRESSURE_NONE. */
|
||
enum reg_class *sched_regno_pressure_class;
|
||
|
||
/* The current register pressure. Only elements corresponding pressure
|
||
classes are defined. */
|
||
static int curr_reg_pressure[N_REG_CLASSES];
|
||
|
||
/* Saved value of the previous array. */
|
||
static int saved_reg_pressure[N_REG_CLASSES];
|
||
|
||
/* Register living at given scheduling point. */
|
||
static bitmap curr_reg_live;
|
||
|
||
/* Saved value of the previous array. */
|
||
static bitmap saved_reg_live;
|
||
|
||
/* Registers mentioned in the current region. */
|
||
static bitmap region_ref_regs;
|
||
|
||
/* Effective number of available registers of a given class (see comment
|
||
in sched_pressure_start_bb). */
|
||
static int sched_class_regs_num[N_REG_CLASSES];
|
||
/* Number of call_used_regs. This is a helper for calculating of
|
||
sched_class_regs_num. */
|
||
static int call_used_regs_num[N_REG_CLASSES];
|
||
|
||
/* Initiate register pressure relative info for scheduling the current
|
||
region. Currently it is only clearing register mentioned in the
|
||
current region. */
|
||
void
|
||
sched_init_region_reg_pressure_info (void)
|
||
{
|
||
bitmap_clear (region_ref_regs);
|
||
}
|
||
|
||
/* PRESSURE[CL] describes the pressure on register class CL. Update it
|
||
for the birth (if BIRTH_P) or death (if !BIRTH_P) of register REGNO.
|
||
LIVE tracks the set of live registers; if it is null, assume that
|
||
every birth or death is genuine. */
|
||
static inline void
|
||
mark_regno_birth_or_death (bitmap live, int *pressure, int regno, bool birth_p)
|
||
{
|
||
enum reg_class pressure_class;
|
||
|
||
pressure_class = sched_regno_pressure_class[regno];
|
||
if (regno >= FIRST_PSEUDO_REGISTER)
|
||
{
|
||
if (pressure_class != NO_REGS)
|
||
{
|
||
if (birth_p)
|
||
{
|
||
if (!live || bitmap_set_bit (live, regno))
|
||
pressure[pressure_class]
|
||
+= (ira_reg_class_max_nregs
|
||
[pressure_class][PSEUDO_REGNO_MODE (regno)]);
|
||
}
|
||
else
|
||
{
|
||
if (!live || bitmap_clear_bit (live, regno))
|
||
pressure[pressure_class]
|
||
-= (ira_reg_class_max_nregs
|
||
[pressure_class][PSEUDO_REGNO_MODE (regno)]);
|
||
}
|
||
}
|
||
}
|
||
else if (pressure_class != NO_REGS
|
||
&& ! TEST_HARD_REG_BIT (ira_no_alloc_regs, regno))
|
||
{
|
||
if (birth_p)
|
||
{
|
||
if (!live || bitmap_set_bit (live, regno))
|
||
pressure[pressure_class]++;
|
||
}
|
||
else
|
||
{
|
||
if (!live || bitmap_clear_bit (live, regno))
|
||
pressure[pressure_class]--;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Initiate current register pressure related info from living
|
||
registers given by LIVE. */
|
||
static void
|
||
initiate_reg_pressure_info (bitmap live)
|
||
{
|
||
int i;
|
||
unsigned int j;
|
||
bitmap_iterator bi;
|
||
|
||
for (i = 0; i < ira_pressure_classes_num; i++)
|
||
curr_reg_pressure[ira_pressure_classes[i]] = 0;
|
||
bitmap_clear (curr_reg_live);
|
||
EXECUTE_IF_SET_IN_BITMAP (live, 0, j, bi)
|
||
if (sched_pressure == SCHED_PRESSURE_MODEL
|
||
|| current_nr_blocks == 1
|
||
|| bitmap_bit_p (region_ref_regs, j))
|
||
mark_regno_birth_or_death (curr_reg_live, curr_reg_pressure, j, true);
|
||
}
|
||
|
||
/* Mark registers in X as mentioned in the current region. */
|
||
static void
|
||
setup_ref_regs (rtx x)
|
||
{
|
||
int i, j;
|
||
const RTX_CODE code = GET_CODE (x);
|
||
const char *fmt;
|
||
|
||
if (REG_P (x))
|
||
{
|
||
bitmap_set_range (region_ref_regs, REGNO (x), REG_NREGS (x));
|
||
return;
|
||
}
|
||
fmt = GET_RTX_FORMAT (code);
|
||
for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--)
|
||
if (fmt[i] == 'e')
|
||
setup_ref_regs (XEXP (x, i));
|
||
else if (fmt[i] == 'E')
|
||
{
|
||
for (j = 0; j < XVECLEN (x, i); j++)
|
||
setup_ref_regs (XVECEXP (x, i, j));
|
||
}
|
||
}
|
||
|
||
/* Initiate current register pressure related info at the start of
|
||
basic block BB. */
|
||
static void
|
||
initiate_bb_reg_pressure_info (basic_block bb)
|
||
{
|
||
unsigned int i ATTRIBUTE_UNUSED;
|
||
rtx_insn *insn;
|
||
|
||
if (current_nr_blocks > 1)
|
||
FOR_BB_INSNS (bb, insn)
|
||
if (NONDEBUG_INSN_P (insn))
|
||
setup_ref_regs (PATTERN (insn));
|
||
initiate_reg_pressure_info (df_get_live_in (bb));
|
||
if (bb_has_eh_pred (bb))
|
||
for (i = 0; ; ++i)
|
||
{
|
||
unsigned int regno = EH_RETURN_DATA_REGNO (i);
|
||
|
||
if (regno == INVALID_REGNUM)
|
||
break;
|
||
if (! bitmap_bit_p (df_get_live_in (bb), regno))
|
||
mark_regno_birth_or_death (curr_reg_live, curr_reg_pressure,
|
||
regno, true);
|
||
}
|
||
}
|
||
|
||
/* Save current register pressure related info. */
|
||
static void
|
||
save_reg_pressure (void)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < ira_pressure_classes_num; i++)
|
||
saved_reg_pressure[ira_pressure_classes[i]]
|
||
= curr_reg_pressure[ira_pressure_classes[i]];
|
||
bitmap_copy (saved_reg_live, curr_reg_live);
|
||
}
|
||
|
||
/* Restore saved register pressure related info. */
|
||
static void
|
||
restore_reg_pressure (void)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < ira_pressure_classes_num; i++)
|
||
curr_reg_pressure[ira_pressure_classes[i]]
|
||
= saved_reg_pressure[ira_pressure_classes[i]];
|
||
bitmap_copy (curr_reg_live, saved_reg_live);
|
||
}
|
||
|
||
/* Return TRUE if the register is dying after its USE. */
|
||
static bool
|
||
dying_use_p (struct reg_use_data *use)
|
||
{
|
||
struct reg_use_data *next;
|
||
|
||
for (next = use->next_regno_use; next != use; next = next->next_regno_use)
|
||
if (NONDEBUG_INSN_P (next->insn)
|
||
&& QUEUE_INDEX (next->insn) != QUEUE_SCHEDULED)
|
||
return false;
|
||
return true;
|
||
}
|
||
|
||
/* Print info about the current register pressure and its excess for
|
||
each pressure class. */
|
||
static void
|
||
print_curr_reg_pressure (void)
|
||
{
|
||
int i;
|
||
enum reg_class cl;
|
||
|
||
fprintf (sched_dump, ";;\t");
|
||
for (i = 0; i < ira_pressure_classes_num; i++)
|
||
{
|
||
cl = ira_pressure_classes[i];
|
||
gcc_assert (curr_reg_pressure[cl] >= 0);
|
||
fprintf (sched_dump, " %s:%d(%d)", reg_class_names[cl],
|
||
curr_reg_pressure[cl],
|
||
curr_reg_pressure[cl] - sched_class_regs_num[cl]);
|
||
}
|
||
fprintf (sched_dump, "\n");
|
||
}
|
||
|
||
/* Determine if INSN has a condition that is clobbered if a register
|
||
in SET_REGS is modified. */
|
||
static bool
|
||
cond_clobbered_p (rtx_insn *insn, HARD_REG_SET set_regs)
|
||
{
|
||
rtx pat = PATTERN (insn);
|
||
gcc_assert (GET_CODE (pat) == COND_EXEC);
|
||
if (TEST_HARD_REG_BIT (set_regs, REGNO (XEXP (COND_EXEC_TEST (pat), 0))))
|
||
{
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
haifa_change_pattern (insn, ORIG_PAT (insn));
|
||
FOR_EACH_DEP (insn, SD_LIST_BACK, sd_it, dep)
|
||
DEP_STATUS (dep) &= ~DEP_CANCELLED;
|
||
TODO_SPEC (insn) = HARD_DEP;
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump,
|
||
";;\t\tdequeue insn %s because of clobbered condition\n",
|
||
(*current_sched_info->print_insn) (insn, 0));
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/* This function should be called after modifying the pattern of INSN,
|
||
to update scheduler data structures as needed. */
|
||
static void
|
||
update_insn_after_change (rtx_insn *insn)
|
||
{
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
|
||
dfa_clear_single_insn_cache (insn);
|
||
|
||
sd_it = sd_iterator_start (insn,
|
||
SD_LIST_FORW | SD_LIST_BACK | SD_LIST_RES_BACK);
|
||
while (sd_iterator_cond (&sd_it, &dep))
|
||
{
|
||
DEP_COST (dep) = UNKNOWN_DEP_COST;
|
||
sd_iterator_next (&sd_it);
|
||
}
|
||
|
||
/* Invalidate INSN_COST, so it'll be recalculated. */
|
||
INSN_COST (insn) = -1;
|
||
/* Invalidate INSN_TICK, so it'll be recalculated. */
|
||
INSN_TICK (insn) = INVALID_TICK;
|
||
|
||
/* Invalidate autoprefetch data entry. */
|
||
INSN_AUTOPREF_MULTIPASS_DATA (insn)[0].status
|
||
= AUTOPREF_MULTIPASS_DATA_UNINITIALIZED;
|
||
INSN_AUTOPREF_MULTIPASS_DATA (insn)[1].status
|
||
= AUTOPREF_MULTIPASS_DATA_UNINITIALIZED;
|
||
}
|
||
|
||
|
||
/* Two VECs, one to hold dependencies for which pattern replacements
|
||
need to be applied or restored at the start of the next cycle, and
|
||
another to hold an integer that is either one, to apply the
|
||
corresponding replacement, or zero to restore it. */
|
||
static vec<dep_t> next_cycle_replace_deps;
|
||
static vec<int> next_cycle_apply;
|
||
|
||
static void apply_replacement (dep_t, bool);
|
||
static void restore_pattern (dep_t, bool);
|
||
|
||
/* Look at the remaining dependencies for insn NEXT, and compute and return
|
||
the TODO_SPEC value we should use for it. This is called after one of
|
||
NEXT's dependencies has been resolved.
|
||
We also perform pattern replacements for predication, and for broken
|
||
replacement dependencies. The latter is only done if FOR_BACKTRACK is
|
||
false. */
|
||
|
||
static ds_t
|
||
recompute_todo_spec (rtx_insn *next, bool for_backtrack)
|
||
{
|
||
ds_t new_ds;
|
||
sd_iterator_def sd_it;
|
||
dep_t dep, modify_dep = NULL;
|
||
int n_spec = 0;
|
||
int n_control = 0;
|
||
int n_replace = 0;
|
||
bool first_p = true;
|
||
|
||
if (sd_lists_empty_p (next, SD_LIST_BACK))
|
||
/* NEXT has all its dependencies resolved. */
|
||
return 0;
|
||
|
||
if (!sd_lists_empty_p (next, SD_LIST_HARD_BACK))
|
||
return HARD_DEP;
|
||
|
||
/* If NEXT is intended to sit adjacent to this instruction, we don't
|
||
want to try to break any dependencies. Treat it as a HARD_DEP. */
|
||
if (SCHED_GROUP_P (next))
|
||
return HARD_DEP;
|
||
|
||
/* Now we've got NEXT with speculative deps only.
|
||
1. Look at the deps to see what we have to do.
|
||
2. Check if we can do 'todo'. */
|
||
new_ds = 0;
|
||
|
||
FOR_EACH_DEP (next, SD_LIST_BACK, sd_it, dep)
|
||
{
|
||
rtx_insn *pro = DEP_PRO (dep);
|
||
ds_t ds = DEP_STATUS (dep) & SPECULATIVE;
|
||
|
||
if (DEBUG_INSN_P (pro) && !DEBUG_INSN_P (next))
|
||
continue;
|
||
|
||
if (ds)
|
||
{
|
||
n_spec++;
|
||
if (first_p)
|
||
{
|
||
first_p = false;
|
||
|
||
new_ds = ds;
|
||
}
|
||
else
|
||
new_ds = ds_merge (new_ds, ds);
|
||
}
|
||
else if (DEP_TYPE (dep) == REG_DEP_CONTROL)
|
||
{
|
||
if (QUEUE_INDEX (pro) != QUEUE_SCHEDULED)
|
||
{
|
||
n_control++;
|
||
modify_dep = dep;
|
||
}
|
||
DEP_STATUS (dep) &= ~DEP_CANCELLED;
|
||
}
|
||
else if (DEP_REPLACE (dep) != NULL)
|
||
{
|
||
if (QUEUE_INDEX (pro) != QUEUE_SCHEDULED)
|
||
{
|
||
n_replace++;
|
||
modify_dep = dep;
|
||
}
|
||
DEP_STATUS (dep) &= ~DEP_CANCELLED;
|
||
}
|
||
}
|
||
|
||
if (n_replace > 0 && n_control == 0 && n_spec == 0)
|
||
{
|
||
if (!dbg_cnt (sched_breakdep))
|
||
return HARD_DEP;
|
||
FOR_EACH_DEP (next, SD_LIST_BACK, sd_it, dep)
|
||
{
|
||
struct dep_replacement *desc = DEP_REPLACE (dep);
|
||
if (desc != NULL)
|
||
{
|
||
if (desc->insn == next && !for_backtrack)
|
||
{
|
||
gcc_assert (n_replace == 1);
|
||
apply_replacement (dep, true);
|
||
}
|
||
DEP_STATUS (dep) |= DEP_CANCELLED;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
else if (n_control == 1 && n_replace == 0 && n_spec == 0)
|
||
{
|
||
rtx_insn *pro, *other;
|
||
rtx new_pat;
|
||
rtx cond = NULL_RTX;
|
||
bool success;
|
||
rtx_insn *prev = NULL;
|
||
int i;
|
||
unsigned regno;
|
||
|
||
if ((current_sched_info->flags & DO_PREDICATION) == 0
|
||
|| (ORIG_PAT (next) != NULL_RTX
|
||
&& PREDICATED_PAT (next) == NULL_RTX))
|
||
return HARD_DEP;
|
||
|
||
pro = DEP_PRO (modify_dep);
|
||
other = real_insn_for_shadow (pro);
|
||
if (other != NULL_RTX)
|
||
pro = other;
|
||
|
||
cond = sched_get_reverse_condition_uncached (pro);
|
||
regno = REGNO (XEXP (cond, 0));
|
||
|
||
/* Find the last scheduled insn that modifies the condition register.
|
||
We can stop looking once we find the insn we depend on through the
|
||
REG_DEP_CONTROL; if the condition register isn't modified after it,
|
||
we know that it still has the right value. */
|
||
if (QUEUE_INDEX (pro) == QUEUE_SCHEDULED)
|
||
FOR_EACH_VEC_ELT_REVERSE (scheduled_insns, i, prev)
|
||
{
|
||
HARD_REG_SET t;
|
||
|
||
find_all_hard_reg_sets (prev, &t, true);
|
||
if (TEST_HARD_REG_BIT (t, regno))
|
||
return HARD_DEP;
|
||
if (prev == pro)
|
||
break;
|
||
}
|
||
if (ORIG_PAT (next) == NULL_RTX)
|
||
{
|
||
ORIG_PAT (next) = PATTERN (next);
|
||
|
||
new_pat = gen_rtx_COND_EXEC (VOIDmode, cond, PATTERN (next));
|
||
success = haifa_change_pattern (next, new_pat);
|
||
if (!success)
|
||
return HARD_DEP;
|
||
PREDICATED_PAT (next) = new_pat;
|
||
}
|
||
else if (PATTERN (next) != PREDICATED_PAT (next))
|
||
{
|
||
bool success = haifa_change_pattern (next,
|
||
PREDICATED_PAT (next));
|
||
gcc_assert (success);
|
||
}
|
||
DEP_STATUS (modify_dep) |= DEP_CANCELLED;
|
||
return DEP_CONTROL;
|
||
}
|
||
|
||
if (PREDICATED_PAT (next) != NULL_RTX)
|
||
{
|
||
int tick = INSN_TICK (next);
|
||
bool success = haifa_change_pattern (next,
|
||
ORIG_PAT (next));
|
||
INSN_TICK (next) = tick;
|
||
gcc_assert (success);
|
||
}
|
||
|
||
/* We can't handle the case where there are both speculative and control
|
||
dependencies, so we return HARD_DEP in such a case. Also fail if
|
||
we have speculative dependencies with not enough points, or more than
|
||
one control dependency. */
|
||
if ((n_spec > 0 && (n_control > 0 || n_replace > 0))
|
||
|| (n_spec > 0
|
||
/* Too few points? */
|
||
&& ds_weak (new_ds) < spec_info->data_weakness_cutoff)
|
||
|| n_control > 0
|
||
|| n_replace > 0)
|
||
return HARD_DEP;
|
||
|
||
return new_ds;
|
||
}
|
||
|
||
/* Pointer to the last instruction scheduled. */
|
||
static rtx_insn *last_scheduled_insn;
|
||
|
||
/* Pointer to the last nondebug instruction scheduled within the
|
||
block, or the prev_head of the scheduling block. Used by
|
||
rank_for_schedule, so that insns independent of the last scheduled
|
||
insn will be preferred over dependent instructions. */
|
||
static rtx_insn *last_nondebug_scheduled_insn;
|
||
|
||
/* Pointer that iterates through the list of unscheduled insns if we
|
||
have a dbg_cnt enabled. It always points at an insn prior to the
|
||
first unscheduled one. */
|
||
static rtx_insn *nonscheduled_insns_begin;
|
||
|
||
/* Compute cost of executing INSN.
|
||
This is the number of cycles between instruction issue and
|
||
instruction results. */
|
||
int
|
||
insn_cost (rtx_insn *insn)
|
||
{
|
||
int cost;
|
||
|
||
if (sched_fusion)
|
||
return 0;
|
||
|
||
if (sel_sched_p ())
|
||
{
|
||
if (recog_memoized (insn) < 0)
|
||
return 0;
|
||
|
||
cost = insn_default_latency (insn);
|
||
if (cost < 0)
|
||
cost = 0;
|
||
|
||
return cost;
|
||
}
|
||
|
||
cost = INSN_COST (insn);
|
||
|
||
if (cost < 0)
|
||
{
|
||
/* A USE insn, or something else we don't need to
|
||
understand. We can't pass these directly to
|
||
result_ready_cost or insn_default_latency because it will
|
||
trigger a fatal error for unrecognizable insns. */
|
||
if (recog_memoized (insn) < 0)
|
||
{
|
||
INSN_COST (insn) = 0;
|
||
return 0;
|
||
}
|
||
else
|
||
{
|
||
cost = insn_default_latency (insn);
|
||
if (cost < 0)
|
||
cost = 0;
|
||
|
||
INSN_COST (insn) = cost;
|
||
}
|
||
}
|
||
|
||
return cost;
|
||
}
|
||
|
||
/* Compute cost of dependence LINK.
|
||
This is the number of cycles between instruction issue and
|
||
instruction results.
|
||
??? We also use this function to call recog_memoized on all insns. */
|
||
int
|
||
dep_cost_1 (dep_t link, dw_t dw)
|
||
{
|
||
rtx_insn *insn = DEP_PRO (link);
|
||
rtx_insn *used = DEP_CON (link);
|
||
int cost;
|
||
|
||
if (DEP_COST (link) != UNKNOWN_DEP_COST)
|
||
return DEP_COST (link);
|
||
|
||
if (delay_htab)
|
||
{
|
||
struct delay_pair *delay_entry;
|
||
delay_entry
|
||
= delay_htab_i2->find_with_hash (used, htab_hash_pointer (used));
|
||
if (delay_entry)
|
||
{
|
||
if (delay_entry->i1 == insn)
|
||
{
|
||
DEP_COST (link) = pair_delay (delay_entry);
|
||
return DEP_COST (link);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* A USE insn should never require the value used to be computed.
|
||
This allows the computation of a function's result and parameter
|
||
values to overlap the return and call. We don't care about the
|
||
dependence cost when only decreasing register pressure. */
|
||
if (recog_memoized (used) < 0)
|
||
{
|
||
cost = 0;
|
||
recog_memoized (insn);
|
||
}
|
||
else
|
||
{
|
||
enum reg_note dep_type = DEP_TYPE (link);
|
||
|
||
cost = insn_cost (insn);
|
||
|
||
if (INSN_CODE (insn) >= 0)
|
||
{
|
||
if (dep_type == REG_DEP_ANTI)
|
||
cost = 0;
|
||
else if (dep_type == REG_DEP_OUTPUT)
|
||
{
|
||
cost = (insn_default_latency (insn)
|
||
- insn_default_latency (used));
|
||
if (cost <= 0)
|
||
cost = 1;
|
||
}
|
||
else if (bypass_p (insn))
|
||
cost = insn_latency (insn, used);
|
||
}
|
||
|
||
|
||
if (targetm.sched.adjust_cost_2)
|
||
cost = targetm.sched.adjust_cost_2 (used, (int) dep_type, insn, cost,
|
||
dw);
|
||
else if (targetm.sched.adjust_cost != NULL)
|
||
{
|
||
/* This variable is used for backward compatibility with the
|
||
targets. */
|
||
rtx_insn_list *dep_cost_rtx_link =
|
||
alloc_INSN_LIST (NULL_RTX, NULL);
|
||
|
||
/* Make it self-cycled, so that if some tries to walk over this
|
||
incomplete list he/she will be caught in an endless loop. */
|
||
XEXP (dep_cost_rtx_link, 1) = dep_cost_rtx_link;
|
||
|
||
/* Targets use only REG_NOTE_KIND of the link. */
|
||
PUT_REG_NOTE_KIND (dep_cost_rtx_link, DEP_TYPE (link));
|
||
|
||
cost = targetm.sched.adjust_cost (used, dep_cost_rtx_link,
|
||
insn, cost);
|
||
|
||
free_INSN_LIST_node (dep_cost_rtx_link);
|
||
}
|
||
|
||
if (cost < 0)
|
||
cost = 0;
|
||
}
|
||
|
||
DEP_COST (link) = cost;
|
||
return cost;
|
||
}
|
||
|
||
/* Compute cost of dependence LINK.
|
||
This is the number of cycles between instruction issue and
|
||
instruction results. */
|
||
int
|
||
dep_cost (dep_t link)
|
||
{
|
||
return dep_cost_1 (link, 0);
|
||
}
|
||
|
||
/* Use this sel-sched.c friendly function in reorder2 instead of increasing
|
||
INSN_PRIORITY explicitly. */
|
||
void
|
||
increase_insn_priority (rtx_insn *insn, int amount)
|
||
{
|
||
if (!sel_sched_p ())
|
||
{
|
||
/* We're dealing with haifa-sched.c INSN_PRIORITY. */
|
||
if (INSN_PRIORITY_KNOWN (insn))
|
||
INSN_PRIORITY (insn) += amount;
|
||
}
|
||
else
|
||
{
|
||
/* In sel-sched.c INSN_PRIORITY is not kept up to date.
|
||
Use EXPR_PRIORITY instead. */
|
||
sel_add_to_insn_priority (insn, amount);
|
||
}
|
||
}
|
||
|
||
/* Return 'true' if DEP should be included in priority calculations. */
|
||
static bool
|
||
contributes_to_priority_p (dep_t dep)
|
||
{
|
||
if (DEBUG_INSN_P (DEP_CON (dep))
|
||
|| DEBUG_INSN_P (DEP_PRO (dep)))
|
||
return false;
|
||
|
||
/* Critical path is meaningful in block boundaries only. */
|
||
if (!current_sched_info->contributes_to_priority (DEP_CON (dep),
|
||
DEP_PRO (dep)))
|
||
return false;
|
||
|
||
if (DEP_REPLACE (dep) != NULL)
|
||
return false;
|
||
|
||
/* If flag COUNT_SPEC_IN_CRITICAL_PATH is set,
|
||
then speculative instructions will less likely be
|
||
scheduled. That is because the priority of
|
||
their producers will increase, and, thus, the
|
||
producers will more likely be scheduled, thus,
|
||
resolving the dependence. */
|
||
if (sched_deps_info->generate_spec_deps
|
||
&& !(spec_info->flags & COUNT_SPEC_IN_CRITICAL_PATH)
|
||
&& (DEP_STATUS (dep) & SPECULATIVE))
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
/* Compute the number of nondebug deps in list LIST for INSN. */
|
||
|
||
static int
|
||
dep_list_size (rtx_insn *insn, sd_list_types_def list)
|
||
{
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
int dbgcount = 0, nodbgcount = 0;
|
||
|
||
if (!MAY_HAVE_DEBUG_INSNS)
|
||
return sd_lists_size (insn, list);
|
||
|
||
FOR_EACH_DEP (insn, list, sd_it, dep)
|
||
{
|
||
if (DEBUG_INSN_P (DEP_CON (dep)))
|
||
dbgcount++;
|
||
else if (!DEBUG_INSN_P (DEP_PRO (dep)))
|
||
nodbgcount++;
|
||
}
|
||
|
||
gcc_assert (dbgcount + nodbgcount == sd_lists_size (insn, list));
|
||
|
||
return nodbgcount;
|
||
}
|
||
|
||
bool sched_fusion;
|
||
|
||
/* Compute the priority number for INSN. */
|
||
static int
|
||
priority (rtx_insn *insn)
|
||
{
|
||
if (! INSN_P (insn))
|
||
return 0;
|
||
|
||
/* We should not be interested in priority of an already scheduled insn. */
|
||
gcc_assert (QUEUE_INDEX (insn) != QUEUE_SCHEDULED);
|
||
|
||
if (!INSN_PRIORITY_KNOWN (insn))
|
||
{
|
||
int this_priority = -1;
|
||
|
||
if (sched_fusion)
|
||
{
|
||
int this_fusion_priority;
|
||
|
||
targetm.sched.fusion_priority (insn, FUSION_MAX_PRIORITY,
|
||
&this_fusion_priority, &this_priority);
|
||
INSN_FUSION_PRIORITY (insn) = this_fusion_priority;
|
||
}
|
||
else if (dep_list_size (insn, SD_LIST_FORW) == 0)
|
||
/* ??? We should set INSN_PRIORITY to insn_cost when and insn has
|
||
some forward deps but all of them are ignored by
|
||
contributes_to_priority hook. At the moment we set priority of
|
||
such insn to 0. */
|
||
this_priority = insn_cost (insn);
|
||
else
|
||
{
|
||
rtx_insn *prev_first, *twin;
|
||
basic_block rec;
|
||
|
||
/* For recovery check instructions we calculate priority slightly
|
||
different than that of normal instructions. Instead of walking
|
||
through INSN_FORW_DEPS (check) list, we walk through
|
||
INSN_FORW_DEPS list of each instruction in the corresponding
|
||
recovery block. */
|
||
|
||
/* Selective scheduling does not define RECOVERY_BLOCK macro. */
|
||
rec = sel_sched_p () ? NULL : RECOVERY_BLOCK (insn);
|
||
if (!rec || rec == EXIT_BLOCK_PTR_FOR_FN (cfun))
|
||
{
|
||
prev_first = PREV_INSN (insn);
|
||
twin = insn;
|
||
}
|
||
else
|
||
{
|
||
prev_first = NEXT_INSN (BB_HEAD (rec));
|
||
twin = PREV_INSN (BB_END (rec));
|
||
}
|
||
|
||
do
|
||
{
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
|
||
FOR_EACH_DEP (twin, SD_LIST_FORW, sd_it, dep)
|
||
{
|
||
rtx_insn *next;
|
||
int next_priority;
|
||
|
||
next = DEP_CON (dep);
|
||
|
||
if (BLOCK_FOR_INSN (next) != rec)
|
||
{
|
||
int cost;
|
||
|
||
if (!contributes_to_priority_p (dep))
|
||
continue;
|
||
|
||
if (twin == insn)
|
||
cost = dep_cost (dep);
|
||
else
|
||
{
|
||
struct _dep _dep1, *dep1 = &_dep1;
|
||
|
||
init_dep (dep1, insn, next, REG_DEP_ANTI);
|
||
|
||
cost = dep_cost (dep1);
|
||
}
|
||
|
||
next_priority = cost + priority (next);
|
||
|
||
if (next_priority > this_priority)
|
||
this_priority = next_priority;
|
||
}
|
||
}
|
||
|
||
twin = PREV_INSN (twin);
|
||
}
|
||
while (twin != prev_first);
|
||
}
|
||
|
||
if (this_priority < 0)
|
||
{
|
||
gcc_assert (this_priority == -1);
|
||
|
||
this_priority = insn_cost (insn);
|
||
}
|
||
|
||
INSN_PRIORITY (insn) = this_priority;
|
||
INSN_PRIORITY_STATUS (insn) = 1;
|
||
}
|
||
|
||
return INSN_PRIORITY (insn);
|
||
}
|
||
|
||
/* Macros and functions for keeping the priority queue sorted, and
|
||
dealing with queuing and dequeuing of instructions. */
|
||
|
||
/* For each pressure class CL, set DEATH[CL] to the number of registers
|
||
in that class that die in INSN. */
|
||
|
||
static void
|
||
calculate_reg_deaths (rtx_insn *insn, int *death)
|
||
{
|
||
int i;
|
||
struct reg_use_data *use;
|
||
|
||
for (i = 0; i < ira_pressure_classes_num; i++)
|
||
death[ira_pressure_classes[i]] = 0;
|
||
for (use = INSN_REG_USE_LIST (insn); use != NULL; use = use->next_insn_use)
|
||
if (dying_use_p (use))
|
||
mark_regno_birth_or_death (0, death, use->regno, true);
|
||
}
|
||
|
||
/* Setup info about the current register pressure impact of scheduling
|
||
INSN at the current scheduling point. */
|
||
static void
|
||
setup_insn_reg_pressure_info (rtx_insn *insn)
|
||
{
|
||
int i, change, before, after, hard_regno;
|
||
int excess_cost_change;
|
||
machine_mode mode;
|
||
enum reg_class cl;
|
||
struct reg_pressure_data *pressure_info;
|
||
int *max_reg_pressure;
|
||
static int death[N_REG_CLASSES];
|
||
|
||
gcc_checking_assert (!DEBUG_INSN_P (insn));
|
||
|
||
excess_cost_change = 0;
|
||
calculate_reg_deaths (insn, death);
|
||
pressure_info = INSN_REG_PRESSURE (insn);
|
||
max_reg_pressure = INSN_MAX_REG_PRESSURE (insn);
|
||
gcc_assert (pressure_info != NULL && max_reg_pressure != NULL);
|
||
for (i = 0; i < ira_pressure_classes_num; i++)
|
||
{
|
||
cl = ira_pressure_classes[i];
|
||
gcc_assert (curr_reg_pressure[cl] >= 0);
|
||
change = (int) pressure_info[i].set_increase - death[cl];
|
||
before = MAX (0, max_reg_pressure[i] - sched_class_regs_num[cl]);
|
||
after = MAX (0, max_reg_pressure[i] + change
|
||
- sched_class_regs_num[cl]);
|
||
hard_regno = ira_class_hard_regs[cl][0];
|
||
gcc_assert (hard_regno >= 0);
|
||
mode = reg_raw_mode[hard_regno];
|
||
excess_cost_change += ((after - before)
|
||
* (ira_memory_move_cost[mode][cl][0]
|
||
+ ira_memory_move_cost[mode][cl][1]));
|
||
}
|
||
INSN_REG_PRESSURE_EXCESS_COST_CHANGE (insn) = excess_cost_change;
|
||
}
|
||
|
||
/* This is the first page of code related to SCHED_PRESSURE_MODEL.
|
||
It tries to make the scheduler take register pressure into account
|
||
without introducing too many unnecessary stalls. It hooks into the
|
||
main scheduling algorithm at several points:
|
||
|
||
- Before scheduling starts, model_start_schedule constructs a
|
||
"model schedule" for the current block. This model schedule is
|
||
chosen solely to keep register pressure down. It does not take the
|
||
target's pipeline or the original instruction order into account,
|
||
except as a tie-breaker. It also doesn't work to a particular
|
||
pressure limit.
|
||
|
||
This model schedule gives us an idea of what pressure can be
|
||
achieved for the block and gives us an example of a schedule that
|
||
keeps to that pressure. It also makes the final schedule less
|
||
dependent on the original instruction order. This is important
|
||
because the original order can either be "wide" (many values live
|
||
at once, such as in user-scheduled code) or "narrow" (few values
|
||
live at once, such as after loop unrolling, where several
|
||
iterations are executed sequentially).
|
||
|
||
We do not apply this model schedule to the rtx stream. We simply
|
||
record it in model_schedule. We also compute the maximum pressure,
|
||
MP, that was seen during this schedule.
|
||
|
||
- Instructions are added to the ready queue even if they require
|
||
a stall. The length of the stall is instead computed as:
|
||
|
||
MAX (INSN_TICK (INSN) - clock_var, 0)
|
||
|
||
(= insn_delay). This allows rank_for_schedule to choose between
|
||
introducing a deliberate stall or increasing pressure.
|
||
|
||
- Before sorting the ready queue, model_set_excess_costs assigns
|
||
a pressure-based cost to each ready instruction in the queue.
|
||
This is the instruction's INSN_REG_PRESSURE_EXCESS_COST_CHANGE
|
||
(ECC for short) and is effectively measured in cycles.
|
||
|
||
- rank_for_schedule ranks instructions based on:
|
||
|
||
ECC (insn) + insn_delay (insn)
|
||
|
||
then as:
|
||
|
||
insn_delay (insn)
|
||
|
||
So, for example, an instruction X1 with an ECC of 1 that can issue
|
||
now will win over an instruction X0 with an ECC of zero that would
|
||
introduce a stall of one cycle. However, an instruction X2 with an
|
||
ECC of 2 that can issue now will lose to both X0 and X1.
|
||
|
||
- When an instruction is scheduled, model_recompute updates the model
|
||
schedule with the new pressures (some of which might now exceed the
|
||
original maximum pressure MP). model_update_limit_points then searches
|
||
for the new point of maximum pressure, if not already known. */
|
||
|
||
/* Used to separate high-verbosity debug information for SCHED_PRESSURE_MODEL
|
||
from surrounding debug information. */
|
||
#define MODEL_BAR \
|
||
";;\t\t+------------------------------------------------------\n"
|
||
|
||
/* Information about the pressure on a particular register class at a
|
||
particular point of the model schedule. */
|
||
struct model_pressure_data {
|
||
/* The pressure at this point of the model schedule, or -1 if the
|
||
point is associated with an instruction that has already been
|
||
scheduled. */
|
||
int ref_pressure;
|
||
|
||
/* The maximum pressure during or after this point of the model schedule. */
|
||
int max_pressure;
|
||
};
|
||
|
||
/* Per-instruction information that is used while building the model
|
||
schedule. Here, "schedule" refers to the model schedule rather
|
||
than the main schedule. */
|
||
struct model_insn_info {
|
||
/* The instruction itself. */
|
||
rtx_insn *insn;
|
||
|
||
/* If this instruction is in model_worklist, these fields link to the
|
||
previous (higher-priority) and next (lower-priority) instructions
|
||
in the list. */
|
||
struct model_insn_info *prev;
|
||
struct model_insn_info *next;
|
||
|
||
/* While constructing the schedule, QUEUE_INDEX describes whether an
|
||
instruction has already been added to the schedule (QUEUE_SCHEDULED),
|
||
is in model_worklist (QUEUE_READY), or neither (QUEUE_NOWHERE).
|
||
old_queue records the value that QUEUE_INDEX had before scheduling
|
||
started, so that we can restore it once the schedule is complete. */
|
||
int old_queue;
|
||
|
||
/* The relative importance of an unscheduled instruction. Higher
|
||
values indicate greater importance. */
|
||
unsigned int model_priority;
|
||
|
||
/* The length of the longest path of satisfied true dependencies
|
||
that leads to this instruction. */
|
||
unsigned int depth;
|
||
|
||
/* The length of the longest path of dependencies of any kind
|
||
that leads from this instruction. */
|
||
unsigned int alap;
|
||
|
||
/* The number of predecessor nodes that must still be scheduled. */
|
||
int unscheduled_preds;
|
||
};
|
||
|
||
/* Information about the pressure limit for a particular register class.
|
||
This structure is used when applying a model schedule to the main
|
||
schedule. */
|
||
struct model_pressure_limit {
|
||
/* The maximum register pressure seen in the original model schedule. */
|
||
int orig_pressure;
|
||
|
||
/* The maximum register pressure seen in the current model schedule
|
||
(which excludes instructions that have already been scheduled). */
|
||
int pressure;
|
||
|
||
/* The point of the current model schedule at which PRESSURE is first
|
||
reached. It is set to -1 if the value needs to be recomputed. */
|
||
int point;
|
||
};
|
||
|
||
/* Describes a particular way of measuring register pressure. */
|
||
struct model_pressure_group {
|
||
/* Index PCI describes the maximum pressure on ira_pressure_classes[PCI]. */
|
||
struct model_pressure_limit limits[N_REG_CLASSES];
|
||
|
||
/* Index (POINT * ira_num_pressure_classes + PCI) describes the pressure
|
||
on register class ira_pressure_classes[PCI] at point POINT of the
|
||
current model schedule. A POINT of model_num_insns describes the
|
||
pressure at the end of the schedule. */
|
||
struct model_pressure_data *model;
|
||
};
|
||
|
||
/* Index POINT gives the instruction at point POINT of the model schedule.
|
||
This array doesn't change during main scheduling. */
|
||
static vec<rtx_insn *> model_schedule;
|
||
|
||
/* The list of instructions in the model worklist, sorted in order of
|
||
decreasing priority. */
|
||
static struct model_insn_info *model_worklist;
|
||
|
||
/* Index I describes the instruction with INSN_LUID I. */
|
||
static struct model_insn_info *model_insns;
|
||
|
||
/* The number of instructions in the model schedule. */
|
||
static int model_num_insns;
|
||
|
||
/* The index of the first instruction in model_schedule that hasn't yet been
|
||
added to the main schedule, or model_num_insns if all of them have. */
|
||
static int model_curr_point;
|
||
|
||
/* Describes the pressure before each instruction in the model schedule. */
|
||
static struct model_pressure_group model_before_pressure;
|
||
|
||
/* The first unused model_priority value (as used in model_insn_info). */
|
||
static unsigned int model_next_priority;
|
||
|
||
|
||
/* The model_pressure_data for ira_pressure_classes[PCI] in GROUP
|
||
at point POINT of the model schedule. */
|
||
#define MODEL_PRESSURE_DATA(GROUP, POINT, PCI) \
|
||
(&(GROUP)->model[(POINT) * ira_pressure_classes_num + (PCI)])
|
||
|
||
/* The maximum pressure on ira_pressure_classes[PCI] in GROUP at or
|
||
after point POINT of the model schedule. */
|
||
#define MODEL_MAX_PRESSURE(GROUP, POINT, PCI) \
|
||
(MODEL_PRESSURE_DATA (GROUP, POINT, PCI)->max_pressure)
|
||
|
||
/* The pressure on ira_pressure_classes[PCI] in GROUP at point POINT
|
||
of the model schedule. */
|
||
#define MODEL_REF_PRESSURE(GROUP, POINT, PCI) \
|
||
(MODEL_PRESSURE_DATA (GROUP, POINT, PCI)->ref_pressure)
|
||
|
||
/* Information about INSN that is used when creating the model schedule. */
|
||
#define MODEL_INSN_INFO(INSN) \
|
||
(&model_insns[INSN_LUID (INSN)])
|
||
|
||
/* The instruction at point POINT of the model schedule. */
|
||
#define MODEL_INSN(POINT) \
|
||
(model_schedule[POINT])
|
||
|
||
|
||
/* Return INSN's index in the model schedule, or model_num_insns if it
|
||
doesn't belong to that schedule. */
|
||
|
||
static int
|
||
model_index (rtx_insn *insn)
|
||
{
|
||
if (INSN_MODEL_INDEX (insn) == 0)
|
||
return model_num_insns;
|
||
return INSN_MODEL_INDEX (insn) - 1;
|
||
}
|
||
|
||
/* Make sure that GROUP->limits is up-to-date for the current point
|
||
of the model schedule. */
|
||
|
||
static void
|
||
model_update_limit_points_in_group (struct model_pressure_group *group)
|
||
{
|
||
int pci, max_pressure, point;
|
||
|
||
for (pci = 0; pci < ira_pressure_classes_num; pci++)
|
||
{
|
||
/* We may have passed the final point at which the pressure in
|
||
group->limits[pci].pressure was reached. Update the limit if so. */
|
||
max_pressure = MODEL_MAX_PRESSURE (group, model_curr_point, pci);
|
||
group->limits[pci].pressure = max_pressure;
|
||
|
||
/* Find the point at which MAX_PRESSURE is first reached. We need
|
||
to search in three cases:
|
||
|
||
- We've already moved past the previous pressure point.
|
||
In this case we search forward from model_curr_point.
|
||
|
||
- We scheduled the previous point of maximum pressure ahead of
|
||
its position in the model schedule, but doing so didn't bring
|
||
the pressure point earlier. In this case we search forward
|
||
from that previous pressure point.
|
||
|
||
- Scheduling an instruction early caused the maximum pressure
|
||
to decrease. In this case we will have set the pressure
|
||
point to -1, and we search forward from model_curr_point. */
|
||
point = MAX (group->limits[pci].point, model_curr_point);
|
||
while (point < model_num_insns
|
||
&& MODEL_REF_PRESSURE (group, point, pci) < max_pressure)
|
||
point++;
|
||
group->limits[pci].point = point;
|
||
|
||
gcc_assert (MODEL_REF_PRESSURE (group, point, pci) == max_pressure);
|
||
gcc_assert (MODEL_MAX_PRESSURE (group, point, pci) == max_pressure);
|
||
}
|
||
}
|
||
|
||
/* Make sure that all register-pressure limits are up-to-date for the
|
||
current position in the model schedule. */
|
||
|
||
static void
|
||
model_update_limit_points (void)
|
||
{
|
||
model_update_limit_points_in_group (&model_before_pressure);
|
||
}
|
||
|
||
/* Return the model_index of the last unscheduled use in chain USE
|
||
outside of USE's instruction. Return -1 if there are no other uses,
|
||
or model_num_insns if the register is live at the end of the block. */
|
||
|
||
static int
|
||
model_last_use_except (struct reg_use_data *use)
|
||
{
|
||
struct reg_use_data *next;
|
||
int last, index;
|
||
|
||
last = -1;
|
||
for (next = use->next_regno_use; next != use; next = next->next_regno_use)
|
||
if (NONDEBUG_INSN_P (next->insn)
|
||
&& QUEUE_INDEX (next->insn) != QUEUE_SCHEDULED)
|
||
{
|
||
index = model_index (next->insn);
|
||
if (index == model_num_insns)
|
||
return model_num_insns;
|
||
if (last < index)
|
||
last = index;
|
||
}
|
||
return last;
|
||
}
|
||
|
||
/* An instruction with model_index POINT has just been scheduled, and it
|
||
adds DELTA to the pressure on ira_pressure_classes[PCI] after POINT - 1.
|
||
Update MODEL_REF_PRESSURE (GROUP, POINT, PCI) and
|
||
MODEL_MAX_PRESSURE (GROUP, POINT, PCI) accordingly. */
|
||
|
||
static void
|
||
model_start_update_pressure (struct model_pressure_group *group,
|
||
int point, int pci, int delta)
|
||
{
|
||
int next_max_pressure;
|
||
|
||
if (point == model_num_insns)
|
||
{
|
||
/* The instruction wasn't part of the model schedule; it was moved
|
||
from a different block. Update the pressure for the end of
|
||
the model schedule. */
|
||
MODEL_REF_PRESSURE (group, point, pci) += delta;
|
||
MODEL_MAX_PRESSURE (group, point, pci) += delta;
|
||
}
|
||
else
|
||
{
|
||
/* Record that this instruction has been scheduled. Nothing now
|
||
changes between POINT and POINT + 1, so get the maximum pressure
|
||
from the latter. If the maximum pressure decreases, the new
|
||
pressure point may be before POINT. */
|
||
MODEL_REF_PRESSURE (group, point, pci) = -1;
|
||
next_max_pressure = MODEL_MAX_PRESSURE (group, point + 1, pci);
|
||
if (MODEL_MAX_PRESSURE (group, point, pci) > next_max_pressure)
|
||
{
|
||
MODEL_MAX_PRESSURE (group, point, pci) = next_max_pressure;
|
||
if (group->limits[pci].point == point)
|
||
group->limits[pci].point = -1;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Record that scheduling a later instruction has changed the pressure
|
||
at point POINT of the model schedule by DELTA (which might be 0).
|
||
Update GROUP accordingly. Return nonzero if these changes might
|
||
trigger changes to previous points as well. */
|
||
|
||
static int
|
||
model_update_pressure (struct model_pressure_group *group,
|
||
int point, int pci, int delta)
|
||
{
|
||
int ref_pressure, max_pressure, next_max_pressure;
|
||
|
||
/* If POINT hasn't yet been scheduled, update its pressure. */
|
||
ref_pressure = MODEL_REF_PRESSURE (group, point, pci);
|
||
if (ref_pressure >= 0 && delta != 0)
|
||
{
|
||
ref_pressure += delta;
|
||
MODEL_REF_PRESSURE (group, point, pci) = ref_pressure;
|
||
|
||
/* Check whether the maximum pressure in the overall schedule
|
||
has increased. (This means that the MODEL_MAX_PRESSURE of
|
||
every point <= POINT will need to increase too; see below.) */
|
||
if (group->limits[pci].pressure < ref_pressure)
|
||
group->limits[pci].pressure = ref_pressure;
|
||
|
||
/* If we are at maximum pressure, and the maximum pressure
|
||
point was previously unknown or later than POINT,
|
||
bring it forward. */
|
||
if (group->limits[pci].pressure == ref_pressure
|
||
&& !IN_RANGE (group->limits[pci].point, 0, point))
|
||
group->limits[pci].point = point;
|
||
|
||
/* If POINT used to be the point of maximum pressure, but isn't
|
||
any longer, we need to recalculate it using a forward walk. */
|
||
if (group->limits[pci].pressure > ref_pressure
|
||
&& group->limits[pci].point == point)
|
||
group->limits[pci].point = -1;
|
||
}
|
||
|
||
/* Update the maximum pressure at POINT. Changes here might also
|
||
affect the maximum pressure at POINT - 1. */
|
||
next_max_pressure = MODEL_MAX_PRESSURE (group, point + 1, pci);
|
||
max_pressure = MAX (ref_pressure, next_max_pressure);
|
||
if (MODEL_MAX_PRESSURE (group, point, pci) != max_pressure)
|
||
{
|
||
MODEL_MAX_PRESSURE (group, point, pci) = max_pressure;
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* INSN has just been scheduled. Update the model schedule accordingly. */
|
||
|
||
static void
|
||
model_recompute (rtx_insn *insn)
|
||
{
|
||
struct {
|
||
int last_use;
|
||
int regno;
|
||
} uses[FIRST_PSEUDO_REGISTER + MAX_RECOG_OPERANDS];
|
||
struct reg_use_data *use;
|
||
struct reg_pressure_data *reg_pressure;
|
||
int delta[N_REG_CLASSES];
|
||
int pci, point, mix, new_last, cl, ref_pressure, queue;
|
||
unsigned int i, num_uses, num_pending_births;
|
||
bool print_p;
|
||
|
||
/* The destinations of INSN were previously live from POINT onwards, but are
|
||
now live from model_curr_point onwards. Set up DELTA accordingly. */
|
||
point = model_index (insn);
|
||
reg_pressure = INSN_REG_PRESSURE (insn);
|
||
for (pci = 0; pci < ira_pressure_classes_num; pci++)
|
||
{
|
||
cl = ira_pressure_classes[pci];
|
||
delta[cl] = reg_pressure[pci].set_increase;
|
||
}
|
||
|
||
/* Record which registers previously died at POINT, but which now die
|
||
before POINT. Adjust DELTA so that it represents the effect of
|
||
this change after POINT - 1. Set NUM_PENDING_BIRTHS to the number of
|
||
registers that will be born in the range [model_curr_point, POINT). */
|
||
num_uses = 0;
|
||
num_pending_births = 0;
|
||
for (use = INSN_REG_USE_LIST (insn); use != NULL; use = use->next_insn_use)
|
||
{
|
||
new_last = model_last_use_except (use);
|
||
if (new_last < point)
|
||
{
|
||
gcc_assert (num_uses < ARRAY_SIZE (uses));
|
||
uses[num_uses].last_use = new_last;
|
||
uses[num_uses].regno = use->regno;
|
||
/* This register is no longer live after POINT - 1. */
|
||
mark_regno_birth_or_death (NULL, delta, use->regno, false);
|
||
num_uses++;
|
||
if (new_last >= 0)
|
||
num_pending_births++;
|
||
}
|
||
}
|
||
|
||
/* Update the MODEL_REF_PRESSURE and MODEL_MAX_PRESSURE for POINT.
|
||
Also set each group pressure limit for POINT. */
|
||
for (pci = 0; pci < ira_pressure_classes_num; pci++)
|
||
{
|
||
cl = ira_pressure_classes[pci];
|
||
model_start_update_pressure (&model_before_pressure,
|
||
point, pci, delta[cl]);
|
||
}
|
||
|
||
/* Walk the model schedule backwards, starting immediately before POINT. */
|
||
print_p = false;
|
||
if (point != model_curr_point)
|
||
do
|
||
{
|
||
point--;
|
||
insn = MODEL_INSN (point);
|
||
queue = QUEUE_INDEX (insn);
|
||
|
||
if (queue != QUEUE_SCHEDULED)
|
||
{
|
||
/* DELTA describes the effect of the move on the register pressure
|
||
after POINT. Make it describe the effect on the pressure
|
||
before POINT. */
|
||
i = 0;
|
||
while (i < num_uses)
|
||
{
|
||
if (uses[i].last_use == point)
|
||
{
|
||
/* This register is now live again. */
|
||
mark_regno_birth_or_death (NULL, delta,
|
||
uses[i].regno, true);
|
||
|
||
/* Remove this use from the array. */
|
||
uses[i] = uses[num_uses - 1];
|
||
num_uses--;
|
||
num_pending_births--;
|
||
}
|
||
else
|
||
i++;
|
||
}
|
||
|
||
if (sched_verbose >= 5)
|
||
{
|
||
if (!print_p)
|
||
{
|
||
fprintf (sched_dump, MODEL_BAR);
|
||
fprintf (sched_dump, ";;\t\t| New pressure for model"
|
||
" schedule\n");
|
||
fprintf (sched_dump, MODEL_BAR);
|
||
print_p = true;
|
||
}
|
||
|
||
fprintf (sched_dump, ";;\t\t| %3d %4d %-30s ",
|
||
point, INSN_UID (insn),
|
||
str_pattern_slim (PATTERN (insn)));
|
||
for (pci = 0; pci < ira_pressure_classes_num; pci++)
|
||
{
|
||
cl = ira_pressure_classes[pci];
|
||
ref_pressure = MODEL_REF_PRESSURE (&model_before_pressure,
|
||
point, pci);
|
||
fprintf (sched_dump, " %s:[%d->%d]",
|
||
reg_class_names[ira_pressure_classes[pci]],
|
||
ref_pressure, ref_pressure + delta[cl]);
|
||
}
|
||
fprintf (sched_dump, "\n");
|
||
}
|
||
}
|
||
|
||
/* Adjust the pressure at POINT. Set MIX to nonzero if POINT - 1
|
||
might have changed as well. */
|
||
mix = num_pending_births;
|
||
for (pci = 0; pci < ira_pressure_classes_num; pci++)
|
||
{
|
||
cl = ira_pressure_classes[pci];
|
||
mix |= delta[cl];
|
||
mix |= model_update_pressure (&model_before_pressure,
|
||
point, pci, delta[cl]);
|
||
}
|
||
}
|
||
while (mix && point > model_curr_point);
|
||
|
||
if (print_p)
|
||
fprintf (sched_dump, MODEL_BAR);
|
||
}
|
||
|
||
/* After DEP, which was cancelled, has been resolved for insn NEXT,
|
||
check whether the insn's pattern needs restoring. */
|
||
static bool
|
||
must_restore_pattern_p (rtx_insn *next, dep_t dep)
|
||
{
|
||
if (QUEUE_INDEX (next) == QUEUE_SCHEDULED)
|
||
return false;
|
||
|
||
if (DEP_TYPE (dep) == REG_DEP_CONTROL)
|
||
{
|
||
gcc_assert (ORIG_PAT (next) != NULL_RTX);
|
||
gcc_assert (next == DEP_CON (dep));
|
||
}
|
||
else
|
||
{
|
||
struct dep_replacement *desc = DEP_REPLACE (dep);
|
||
if (desc->insn != next)
|
||
{
|
||
gcc_assert (*desc->loc == desc->orig);
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/* model_spill_cost (CL, P, P') returns the cost of increasing the
|
||
pressure on CL from P to P'. We use this to calculate a "base ECC",
|
||
baseECC (CL, X), for each pressure class CL and each instruction X.
|
||
Supposing X changes the pressure on CL from P to P', and that the
|
||
maximum pressure on CL in the current model schedule is MP', then:
|
||
|
||
* if X occurs before or at the next point of maximum pressure in
|
||
the model schedule and P' > MP', then:
|
||
|
||
baseECC (CL, X) = model_spill_cost (CL, MP, P')
|
||
|
||
The idea is that the pressure after scheduling a fixed set of
|
||
instructions -- in this case, the set up to and including the
|
||
next maximum pressure point -- is going to be the same regardless
|
||
of the order; we simply want to keep the intermediate pressure
|
||
under control. Thus X has a cost of zero unless scheduling it
|
||
now would exceed MP'.
|
||
|
||
If all increases in the set are by the same amount, no zero-cost
|
||
instruction will ever cause the pressure to exceed MP'. However,
|
||
if X is instead moved past an instruction X' with pressure in the
|
||
range (MP' - (P' - P), MP'), the pressure at X' will increase
|
||
beyond MP'. Since baseECC is very much a heuristic anyway,
|
||
it doesn't seem worth the overhead of tracking cases like these.
|
||
|
||
The cost of exceeding MP' is always based on the original maximum
|
||
pressure MP. This is so that going 2 registers over the original
|
||
limit has the same cost regardless of whether it comes from two
|
||
separate +1 deltas or from a single +2 delta.
|
||
|
||
* if X occurs after the next point of maximum pressure in the model
|
||
schedule and P' > P, then:
|
||
|
||
baseECC (CL, X) = model_spill_cost (CL, MP, MP' + (P' - P))
|
||
|
||
That is, if we move X forward across a point of maximum pressure,
|
||
and if X increases the pressure by P' - P, then we conservatively
|
||
assume that scheduling X next would increase the maximum pressure
|
||
by P' - P. Again, the cost of doing this is based on the original
|
||
maximum pressure MP, for the same reason as above.
|
||
|
||
* if P' < P, P > MP, and X occurs at or after the next point of
|
||
maximum pressure, then:
|
||
|
||
baseECC (CL, X) = -model_spill_cost (CL, MAX (MP, P'), P)
|
||
|
||
That is, if we have already exceeded the original maximum pressure MP,
|
||
and if X might reduce the maximum pressure again -- or at least push
|
||
it further back, and thus allow more scheduling freedom -- it is given
|
||
a negative cost to reflect the improvement.
|
||
|
||
* otherwise,
|
||
|
||
baseECC (CL, X) = 0
|
||
|
||
In this case, X is not expected to affect the maximum pressure MP',
|
||
so it has zero cost.
|
||
|
||
We then create a combined value baseECC (X) that is the sum of
|
||
baseECC (CL, X) for each pressure class CL.
|
||
|
||
baseECC (X) could itself be used as the ECC value described above.
|
||
However, this is often too conservative, in the sense that it
|
||
tends to make high-priority instructions that increase pressure
|
||
wait too long in cases where introducing a spill would be better.
|
||
For this reason the final ECC is a priority-adjusted form of
|
||
baseECC (X). Specifically, we calculate:
|
||
|
||
P (X) = INSN_PRIORITY (X) - insn_delay (X) - baseECC (X)
|
||
baseP = MAX { P (X) | baseECC (X) <= 0 }
|
||
|
||
Then:
|
||
|
||
ECC (X) = MAX (MIN (baseP - P (X), baseECC (X)), 0)
|
||
|
||
Thus an instruction's effect on pressure is ignored if it has a high
|
||
enough priority relative to the ones that don't increase pressure.
|
||
Negative values of baseECC (X) do not increase the priority of X
|
||
itself, but they do make it harder for other instructions to
|
||
increase the pressure further.
|
||
|
||
This pressure cost is deliberately timid. The intention has been
|
||
to choose a heuristic that rarely interferes with the normal list
|
||
scheduler in cases where that scheduler would produce good code.
|
||
We simply want to curb some of its worst excesses. */
|
||
|
||
/* Return the cost of increasing the pressure in class CL from FROM to TO.
|
||
|
||
Here we use the very simplistic cost model that every register above
|
||
sched_class_regs_num[CL] has a spill cost of 1. We could use other
|
||
measures instead, such as one based on MEMORY_MOVE_COST. However:
|
||
|
||
(1) In order for an instruction to be scheduled, the higher cost
|
||
would need to be justified in a single saving of that many stalls.
|
||
This is overly pessimistic, because the benefit of spilling is
|
||
often to avoid a sequence of several short stalls rather than
|
||
a single long one.
|
||
|
||
(2) The cost is still arbitrary. Because we are not allocating
|
||
registers during scheduling, we have no way of knowing for
|
||
sure how many memory accesses will be required by each spill,
|
||
where the spills will be placed within the block, or even
|
||
which block(s) will contain the spills.
|
||
|
||
So a higher cost than 1 is often too conservative in practice,
|
||
forcing blocks to contain unnecessary stalls instead of spill code.
|
||
The simple cost below seems to be the best compromise. It reduces
|
||
the interference with the normal list scheduler, which helps make
|
||
it more suitable for a default-on option. */
|
||
|
||
static int
|
||
model_spill_cost (int cl, int from, int to)
|
||
{
|
||
from = MAX (from, sched_class_regs_num[cl]);
|
||
return MAX (to, from) - from;
|
||
}
|
||
|
||
/* Return baseECC (ira_pressure_classes[PCI], POINT), given that
|
||
P = curr_reg_pressure[ira_pressure_classes[PCI]] and that
|
||
P' = P + DELTA. */
|
||
|
||
static int
|
||
model_excess_group_cost (struct model_pressure_group *group,
|
||
int point, int pci, int delta)
|
||
{
|
||
int pressure, cl;
|
||
|
||
cl = ira_pressure_classes[pci];
|
||
if (delta < 0 && point >= group->limits[pci].point)
|
||
{
|
||
pressure = MAX (group->limits[pci].orig_pressure,
|
||
curr_reg_pressure[cl] + delta);
|
||
return -model_spill_cost (cl, pressure, curr_reg_pressure[cl]);
|
||
}
|
||
|
||
if (delta > 0)
|
||
{
|
||
if (point > group->limits[pci].point)
|
||
pressure = group->limits[pci].pressure + delta;
|
||
else
|
||
pressure = curr_reg_pressure[cl] + delta;
|
||
|
||
if (pressure > group->limits[pci].pressure)
|
||
return model_spill_cost (cl, group->limits[pci].orig_pressure,
|
||
pressure);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Return baseECC (MODEL_INSN (INSN)). Dump the costs to sched_dump
|
||
if PRINT_P. */
|
||
|
||
static int
|
||
model_excess_cost (rtx_insn *insn, bool print_p)
|
||
{
|
||
int point, pci, cl, cost, this_cost, delta;
|
||
struct reg_pressure_data *insn_reg_pressure;
|
||
int insn_death[N_REG_CLASSES];
|
||
|
||
calculate_reg_deaths (insn, insn_death);
|
||
point = model_index (insn);
|
||
insn_reg_pressure = INSN_REG_PRESSURE (insn);
|
||
cost = 0;
|
||
|
||
if (print_p)
|
||
fprintf (sched_dump, ";;\t\t| %3d %4d | %4d %+3d |", point,
|
||
INSN_UID (insn), INSN_PRIORITY (insn), insn_delay (insn));
|
||
|
||
/* Sum up the individual costs for each register class. */
|
||
for (pci = 0; pci < ira_pressure_classes_num; pci++)
|
||
{
|
||
cl = ira_pressure_classes[pci];
|
||
delta = insn_reg_pressure[pci].set_increase - insn_death[cl];
|
||
this_cost = model_excess_group_cost (&model_before_pressure,
|
||
point, pci, delta);
|
||
cost += this_cost;
|
||
if (print_p)
|
||
fprintf (sched_dump, " %s:[%d base cost %d]",
|
||
reg_class_names[cl], delta, this_cost);
|
||
}
|
||
|
||
if (print_p)
|
||
fprintf (sched_dump, "\n");
|
||
|
||
return cost;
|
||
}
|
||
|
||
/* Dump the next points of maximum pressure for GROUP. */
|
||
|
||
static void
|
||
model_dump_pressure_points (struct model_pressure_group *group)
|
||
{
|
||
int pci, cl;
|
||
|
||
fprintf (sched_dump, ";;\t\t| pressure points");
|
||
for (pci = 0; pci < ira_pressure_classes_num; pci++)
|
||
{
|
||
cl = ira_pressure_classes[pci];
|
||
fprintf (sched_dump, " %s:[%d->%d at ", reg_class_names[cl],
|
||
curr_reg_pressure[cl], group->limits[pci].pressure);
|
||
if (group->limits[pci].point < model_num_insns)
|
||
fprintf (sched_dump, "%d:%d]", group->limits[pci].point,
|
||
INSN_UID (MODEL_INSN (group->limits[pci].point)));
|
||
else
|
||
fprintf (sched_dump, "end]");
|
||
}
|
||
fprintf (sched_dump, "\n");
|
||
}
|
||
|
||
/* Set INSN_REG_PRESSURE_EXCESS_COST_CHANGE for INSNS[0...COUNT-1]. */
|
||
|
||
static void
|
||
model_set_excess_costs (rtx_insn **insns, int count)
|
||
{
|
||
int i, cost, priority_base, priority;
|
||
bool print_p;
|
||
|
||
/* Record the baseECC value for each instruction in the model schedule,
|
||
except that negative costs are converted to zero ones now rather than
|
||
later. Do not assign a cost to debug instructions, since they must
|
||
not change code-generation decisions. Experiments suggest we also
|
||
get better results by not assigning a cost to instructions from
|
||
a different block.
|
||
|
||
Set PRIORITY_BASE to baseP in the block comment above. This is the
|
||
maximum priority of the "cheap" instructions, which should always
|
||
include the next model instruction. */
|
||
priority_base = 0;
|
||
print_p = false;
|
||
for (i = 0; i < count; i++)
|
||
if (INSN_MODEL_INDEX (insns[i]))
|
||
{
|
||
if (sched_verbose >= 6 && !print_p)
|
||
{
|
||
fprintf (sched_dump, MODEL_BAR);
|
||
fprintf (sched_dump, ";;\t\t| Pressure costs for ready queue\n");
|
||
model_dump_pressure_points (&model_before_pressure);
|
||
fprintf (sched_dump, MODEL_BAR);
|
||
print_p = true;
|
||
}
|
||
cost = model_excess_cost (insns[i], print_p);
|
||
if (cost <= 0)
|
||
{
|
||
priority = INSN_PRIORITY (insns[i]) - insn_delay (insns[i]) - cost;
|
||
priority_base = MAX (priority_base, priority);
|
||
cost = 0;
|
||
}
|
||
INSN_REG_PRESSURE_EXCESS_COST_CHANGE (insns[i]) = cost;
|
||
}
|
||
if (print_p)
|
||
fprintf (sched_dump, MODEL_BAR);
|
||
|
||
/* Use MAX (baseECC, 0) and baseP to calculcate ECC for each
|
||
instruction. */
|
||
for (i = 0; i < count; i++)
|
||
{
|
||
cost = INSN_REG_PRESSURE_EXCESS_COST_CHANGE (insns[i]);
|
||
priority = INSN_PRIORITY (insns[i]) - insn_delay (insns[i]);
|
||
if (cost > 0 && priority > priority_base)
|
||
{
|
||
cost += priority_base - priority;
|
||
INSN_REG_PRESSURE_EXCESS_COST_CHANGE (insns[i]) = MAX (cost, 0);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/* Enum of rank_for_schedule heuristic decisions. */
|
||
enum rfs_decision {
|
||
RFS_LIVE_RANGE_SHRINK1, RFS_LIVE_RANGE_SHRINK2,
|
||
RFS_SCHED_GROUP, RFS_PRESSURE_DELAY, RFS_PRESSURE_TICK,
|
||
RFS_FEEDS_BACKTRACK_INSN, RFS_PRIORITY, RFS_SPECULATION,
|
||
RFS_SCHED_RANK, RFS_LAST_INSN, RFS_PRESSURE_INDEX,
|
||
RFS_DEP_COUNT, RFS_TIE, RFS_FUSION, RFS_N };
|
||
|
||
/* Corresponding strings for print outs. */
|
||
static const char *rfs_str[RFS_N] = {
|
||
"RFS_LIVE_RANGE_SHRINK1", "RFS_LIVE_RANGE_SHRINK2",
|
||
"RFS_SCHED_GROUP", "RFS_PRESSURE_DELAY", "RFS_PRESSURE_TICK",
|
||
"RFS_FEEDS_BACKTRACK_INSN", "RFS_PRIORITY", "RFS_SPECULATION",
|
||
"RFS_SCHED_RANK", "RFS_LAST_INSN", "RFS_PRESSURE_INDEX",
|
||
"RFS_DEP_COUNT", "RFS_TIE", "RFS_FUSION" };
|
||
|
||
/* Statistical breakdown of rank_for_schedule decisions. */
|
||
struct rank_for_schedule_stats_t { unsigned stats[RFS_N]; };
|
||
static rank_for_schedule_stats_t rank_for_schedule_stats;
|
||
|
||
/* Return the result of comparing insns TMP and TMP2 and update
|
||
Rank_For_Schedule statistics. */
|
||
static int
|
||
rfs_result (enum rfs_decision decision, int result, rtx tmp, rtx tmp2)
|
||
{
|
||
++rank_for_schedule_stats.stats[decision];
|
||
if (result < 0)
|
||
INSN_LAST_RFS_WIN (tmp) = decision;
|
||
else if (result > 0)
|
||
INSN_LAST_RFS_WIN (tmp2) = decision;
|
||
else
|
||
gcc_unreachable ();
|
||
return result;
|
||
}
|
||
|
||
/* Sorting predicate to move DEBUG_INSNs to the top of ready list, while
|
||
keeping normal insns in original order. */
|
||
|
||
static int
|
||
rank_for_schedule_debug (const void *x, const void *y)
|
||
{
|
||
rtx_insn *tmp = *(rtx_insn * const *) y;
|
||
rtx_insn *tmp2 = *(rtx_insn * const *) x;
|
||
|
||
/* Schedule debug insns as early as possible. */
|
||
if (DEBUG_INSN_P (tmp) && !DEBUG_INSN_P (tmp2))
|
||
return -1;
|
||
else if (!DEBUG_INSN_P (tmp) && DEBUG_INSN_P (tmp2))
|
||
return 1;
|
||
else if (DEBUG_INSN_P (tmp) && DEBUG_INSN_P (tmp2))
|
||
return INSN_LUID (tmp) - INSN_LUID (tmp2);
|
||
else
|
||
return INSN_RFS_DEBUG_ORIG_ORDER (tmp2) - INSN_RFS_DEBUG_ORIG_ORDER (tmp);
|
||
}
|
||
|
||
/* Returns a positive value if x is preferred; returns a negative value if
|
||
y is preferred. Should never return 0, since that will make the sort
|
||
unstable. */
|
||
|
||
static int
|
||
rank_for_schedule (const void *x, const void *y)
|
||
{
|
||
rtx_insn *tmp = *(rtx_insn * const *) y;
|
||
rtx_insn *tmp2 = *(rtx_insn * const *) x;
|
||
int tmp_class, tmp2_class;
|
||
int val, priority_val, info_val, diff;
|
||
|
||
if (live_range_shrinkage_p)
|
||
{
|
||
/* Don't use SCHED_PRESSURE_MODEL -- it results in much worse
|
||
code. */
|
||
gcc_assert (sched_pressure == SCHED_PRESSURE_WEIGHTED);
|
||
if ((INSN_REG_PRESSURE_EXCESS_COST_CHANGE (tmp) < 0
|
||
|| INSN_REG_PRESSURE_EXCESS_COST_CHANGE (tmp2) < 0)
|
||
&& (diff = (INSN_REG_PRESSURE_EXCESS_COST_CHANGE (tmp)
|
||
- INSN_REG_PRESSURE_EXCESS_COST_CHANGE (tmp2))) != 0)
|
||
return rfs_result (RFS_LIVE_RANGE_SHRINK1, diff, tmp, tmp2);
|
||
/* Sort by INSN_LUID (original insn order), so that we make the
|
||
sort stable. This minimizes instruction movement, thus
|
||
minimizing sched's effect on debugging and cross-jumping. */
|
||
return rfs_result (RFS_LIVE_RANGE_SHRINK2,
|
||
INSN_LUID (tmp) - INSN_LUID (tmp2), tmp, tmp2);
|
||
}
|
||
|
||
/* The insn in a schedule group should be issued the first. */
|
||
if (flag_sched_group_heuristic &&
|
||
SCHED_GROUP_P (tmp) != SCHED_GROUP_P (tmp2))
|
||
return rfs_result (RFS_SCHED_GROUP, SCHED_GROUP_P (tmp2) ? 1 : -1,
|
||
tmp, tmp2);
|
||
|
||
/* Make sure that priority of TMP and TMP2 are initialized. */
|
||
gcc_assert (INSN_PRIORITY_KNOWN (tmp) && INSN_PRIORITY_KNOWN (tmp2));
|
||
|
||
if (sched_fusion)
|
||
{
|
||
/* The instruction that has the same fusion priority as the last
|
||
instruction is the instruction we picked next. If that is not
|
||
the case, we sort ready list firstly by fusion priority, then
|
||
by priority, and at last by INSN_LUID. */
|
||
int a = INSN_FUSION_PRIORITY (tmp);
|
||
int b = INSN_FUSION_PRIORITY (tmp2);
|
||
int last = -1;
|
||
|
||
if (last_nondebug_scheduled_insn
|
||
&& !NOTE_P (last_nondebug_scheduled_insn)
|
||
&& BLOCK_FOR_INSN (tmp)
|
||
== BLOCK_FOR_INSN (last_nondebug_scheduled_insn))
|
||
last = INSN_FUSION_PRIORITY (last_nondebug_scheduled_insn);
|
||
|
||
if (a != last && b != last)
|
||
{
|
||
if (a == b)
|
||
{
|
||
a = INSN_PRIORITY (tmp);
|
||
b = INSN_PRIORITY (tmp2);
|
||
}
|
||
if (a != b)
|
||
return rfs_result (RFS_FUSION, b - a, tmp, tmp2);
|
||
else
|
||
return rfs_result (RFS_FUSION,
|
||
INSN_LUID (tmp) - INSN_LUID (tmp2), tmp, tmp2);
|
||
}
|
||
else if (a == b)
|
||
{
|
||
gcc_assert (last_nondebug_scheduled_insn
|
||
&& !NOTE_P (last_nondebug_scheduled_insn));
|
||
last = INSN_PRIORITY (last_nondebug_scheduled_insn);
|
||
|
||
a = abs (INSN_PRIORITY (tmp) - last);
|
||
b = abs (INSN_PRIORITY (tmp2) - last);
|
||
if (a != b)
|
||
return rfs_result (RFS_FUSION, a - b, tmp, tmp2);
|
||
else
|
||
return rfs_result (RFS_FUSION,
|
||
INSN_LUID (tmp) - INSN_LUID (tmp2), tmp, tmp2);
|
||
}
|
||
else if (a == last)
|
||
return rfs_result (RFS_FUSION, -1, tmp, tmp2);
|
||
else
|
||
return rfs_result (RFS_FUSION, 1, tmp, tmp2);
|
||
}
|
||
|
||
if (sched_pressure != SCHED_PRESSURE_NONE)
|
||
{
|
||
/* Prefer insn whose scheduling results in the smallest register
|
||
pressure excess. */
|
||
if ((diff = (INSN_REG_PRESSURE_EXCESS_COST_CHANGE (tmp)
|
||
+ insn_delay (tmp)
|
||
- INSN_REG_PRESSURE_EXCESS_COST_CHANGE (tmp2)
|
||
- insn_delay (tmp2))))
|
||
return rfs_result (RFS_PRESSURE_DELAY, diff, tmp, tmp2);
|
||
}
|
||
|
||
if (sched_pressure != SCHED_PRESSURE_NONE
|
||
&& (INSN_TICK (tmp2) > clock_var || INSN_TICK (tmp) > clock_var)
|
||
&& INSN_TICK (tmp2) != INSN_TICK (tmp))
|
||
{
|
||
diff = INSN_TICK (tmp) - INSN_TICK (tmp2);
|
||
return rfs_result (RFS_PRESSURE_TICK, diff, tmp, tmp2);
|
||
}
|
||
|
||
/* If we are doing backtracking in this schedule, prefer insns that
|
||
have forward dependencies with negative cost against an insn that
|
||
was already scheduled. */
|
||
if (current_sched_info->flags & DO_BACKTRACKING)
|
||
{
|
||
priority_val = FEEDS_BACKTRACK_INSN (tmp2) - FEEDS_BACKTRACK_INSN (tmp);
|
||
if (priority_val)
|
||
return rfs_result (RFS_FEEDS_BACKTRACK_INSN, priority_val, tmp, tmp2);
|
||
}
|
||
|
||
/* Prefer insn with higher priority. */
|
||
priority_val = INSN_PRIORITY (tmp2) - INSN_PRIORITY (tmp);
|
||
|
||
if (flag_sched_critical_path_heuristic && priority_val)
|
||
return rfs_result (RFS_PRIORITY, priority_val, tmp, tmp2);
|
||
|
||
if (PARAM_VALUE (PARAM_SCHED_AUTOPREF_QUEUE_DEPTH) >= 0)
|
||
{
|
||
int autopref = autopref_rank_for_schedule (tmp, tmp2);
|
||
if (autopref != 0)
|
||
return autopref;
|
||
}
|
||
|
||
/* Prefer speculative insn with greater dependencies weakness. */
|
||
if (flag_sched_spec_insn_heuristic && spec_info)
|
||
{
|
||
ds_t ds1, ds2;
|
||
dw_t dw1, dw2;
|
||
int dw;
|
||
|
||
ds1 = TODO_SPEC (tmp) & SPECULATIVE;
|
||
if (ds1)
|
||
dw1 = ds_weak (ds1);
|
||
else
|
||
dw1 = NO_DEP_WEAK;
|
||
|
||
ds2 = TODO_SPEC (tmp2) & SPECULATIVE;
|
||
if (ds2)
|
||
dw2 = ds_weak (ds2);
|
||
else
|
||
dw2 = NO_DEP_WEAK;
|
||
|
||
dw = dw2 - dw1;
|
||
if (dw > (NO_DEP_WEAK / 8) || dw < -(NO_DEP_WEAK / 8))
|
||
return rfs_result (RFS_SPECULATION, dw, tmp, tmp2);
|
||
}
|
||
|
||
info_val = (*current_sched_info->rank) (tmp, tmp2);
|
||
if (flag_sched_rank_heuristic && info_val)
|
||
return rfs_result (RFS_SCHED_RANK, info_val, tmp, tmp2);
|
||
|
||
/* Compare insns based on their relation to the last scheduled
|
||
non-debug insn. */
|
||
if (flag_sched_last_insn_heuristic && last_nondebug_scheduled_insn)
|
||
{
|
||
dep_t dep1;
|
||
dep_t dep2;
|
||
rtx_insn *last = last_nondebug_scheduled_insn;
|
||
|
||
/* Classify the instructions into three classes:
|
||
1) Data dependent on last schedule insn.
|
||
2) Anti/Output dependent on last scheduled insn.
|
||
3) Independent of last scheduled insn, or has latency of one.
|
||
Choose the insn from the highest numbered class if different. */
|
||
dep1 = sd_find_dep_between (last, tmp, true);
|
||
|
||
if (dep1 == NULL || dep_cost (dep1) == 1)
|
||
tmp_class = 3;
|
||
else if (/* Data dependence. */
|
||
DEP_TYPE (dep1) == REG_DEP_TRUE)
|
||
tmp_class = 1;
|
||
else
|
||
tmp_class = 2;
|
||
|
||
dep2 = sd_find_dep_between (last, tmp2, true);
|
||
|
||
if (dep2 == NULL || dep_cost (dep2) == 1)
|
||
tmp2_class = 3;
|
||
else if (/* Data dependence. */
|
||
DEP_TYPE (dep2) == REG_DEP_TRUE)
|
||
tmp2_class = 1;
|
||
else
|
||
tmp2_class = 2;
|
||
|
||
if ((val = tmp2_class - tmp_class))
|
||
return rfs_result (RFS_LAST_INSN, val, tmp, tmp2);
|
||
}
|
||
|
||
/* Prefer instructions that occur earlier in the model schedule. */
|
||
if (sched_pressure == SCHED_PRESSURE_MODEL
|
||
&& INSN_BB (tmp) == target_bb && INSN_BB (tmp2) == target_bb)
|
||
{
|
||
diff = model_index (tmp) - model_index (tmp2);
|
||
gcc_assert (diff != 0);
|
||
return rfs_result (RFS_PRESSURE_INDEX, diff, tmp, tmp2);
|
||
}
|
||
|
||
/* Prefer the insn which has more later insns that depend on it.
|
||
This gives the scheduler more freedom when scheduling later
|
||
instructions at the expense of added register pressure. */
|
||
|
||
val = (dep_list_size (tmp2, SD_LIST_FORW)
|
||
- dep_list_size (tmp, SD_LIST_FORW));
|
||
|
||
if (flag_sched_dep_count_heuristic && val != 0)
|
||
return rfs_result (RFS_DEP_COUNT, val, tmp, tmp2);
|
||
|
||
/* If insns are equally good, sort by INSN_LUID (original insn order),
|
||
so that we make the sort stable. This minimizes instruction movement,
|
||
thus minimizing sched's effect on debugging and cross-jumping. */
|
||
return rfs_result (RFS_TIE, INSN_LUID (tmp) - INSN_LUID (tmp2), tmp, tmp2);
|
||
}
|
||
|
||
/* Resort the array A in which only element at index N may be out of order. */
|
||
|
||
HAIFA_INLINE static void
|
||
swap_sort (rtx_insn **a, int n)
|
||
{
|
||
rtx_insn *insn = a[n - 1];
|
||
int i = n - 2;
|
||
|
||
while (i >= 0 && rank_for_schedule (a + i, &insn) >= 0)
|
||
{
|
||
a[i + 1] = a[i];
|
||
i -= 1;
|
||
}
|
||
a[i + 1] = insn;
|
||
}
|
||
|
||
/* Add INSN to the insn queue so that it can be executed at least
|
||
N_CYCLES after the currently executing insn. Preserve insns
|
||
chain for debugging purposes. REASON will be printed in debugging
|
||
output. */
|
||
|
||
HAIFA_INLINE static void
|
||
queue_insn (rtx_insn *insn, int n_cycles, const char *reason)
|
||
{
|
||
int next_q = NEXT_Q_AFTER (q_ptr, n_cycles);
|
||
rtx_insn_list *link = alloc_INSN_LIST (insn, insn_queue[next_q]);
|
||
int new_tick;
|
||
|
||
gcc_assert (n_cycles <= max_insn_queue_index);
|
||
gcc_assert (!DEBUG_INSN_P (insn));
|
||
|
||
insn_queue[next_q] = link;
|
||
q_size += 1;
|
||
|
||
if (sched_verbose >= 2)
|
||
{
|
||
fprintf (sched_dump, ";;\t\tReady-->Q: insn %s: ",
|
||
(*current_sched_info->print_insn) (insn, 0));
|
||
|
||
fprintf (sched_dump, "queued for %d cycles (%s).\n", n_cycles, reason);
|
||
}
|
||
|
||
QUEUE_INDEX (insn) = next_q;
|
||
|
||
if (current_sched_info->flags & DO_BACKTRACKING)
|
||
{
|
||
new_tick = clock_var + n_cycles;
|
||
if (INSN_TICK (insn) == INVALID_TICK || INSN_TICK (insn) < new_tick)
|
||
INSN_TICK (insn) = new_tick;
|
||
|
||
if (INSN_EXACT_TICK (insn) != INVALID_TICK
|
||
&& INSN_EXACT_TICK (insn) < clock_var + n_cycles)
|
||
{
|
||
must_backtrack = true;
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump, ";;\t\tcausing a backtrack.\n");
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Remove INSN from queue. */
|
||
static void
|
||
queue_remove (rtx_insn *insn)
|
||
{
|
||
gcc_assert (QUEUE_INDEX (insn) >= 0);
|
||
remove_free_INSN_LIST_elem (insn, &insn_queue[QUEUE_INDEX (insn)]);
|
||
q_size--;
|
||
QUEUE_INDEX (insn) = QUEUE_NOWHERE;
|
||
}
|
||
|
||
/* Return a pointer to the bottom of the ready list, i.e. the insn
|
||
with the lowest priority. */
|
||
|
||
rtx_insn **
|
||
ready_lastpos (struct ready_list *ready)
|
||
{
|
||
gcc_assert (ready->n_ready >= 1);
|
||
return ready->vec + ready->first - ready->n_ready + 1;
|
||
}
|
||
|
||
/* Add an element INSN to the ready list so that it ends up with the
|
||
lowest/highest priority depending on FIRST_P. */
|
||
|
||
HAIFA_INLINE static void
|
||
ready_add (struct ready_list *ready, rtx_insn *insn, bool first_p)
|
||
{
|
||
if (!first_p)
|
||
{
|
||
if (ready->first == ready->n_ready)
|
||
{
|
||
memmove (ready->vec + ready->veclen - ready->n_ready,
|
||
ready_lastpos (ready),
|
||
ready->n_ready * sizeof (rtx));
|
||
ready->first = ready->veclen - 1;
|
||
}
|
||
ready->vec[ready->first - ready->n_ready] = insn;
|
||
}
|
||
else
|
||
{
|
||
if (ready->first == ready->veclen - 1)
|
||
{
|
||
if (ready->n_ready)
|
||
/* ready_lastpos() fails when called with (ready->n_ready == 0). */
|
||
memmove (ready->vec + ready->veclen - ready->n_ready - 1,
|
||
ready_lastpos (ready),
|
||
ready->n_ready * sizeof (rtx));
|
||
ready->first = ready->veclen - 2;
|
||
}
|
||
ready->vec[++(ready->first)] = insn;
|
||
}
|
||
|
||
ready->n_ready++;
|
||
if (DEBUG_INSN_P (insn))
|
||
ready->n_debug++;
|
||
|
||
gcc_assert (QUEUE_INDEX (insn) != QUEUE_READY);
|
||
QUEUE_INDEX (insn) = QUEUE_READY;
|
||
|
||
if (INSN_EXACT_TICK (insn) != INVALID_TICK
|
||
&& INSN_EXACT_TICK (insn) < clock_var)
|
||
{
|
||
must_backtrack = true;
|
||
}
|
||
}
|
||
|
||
/* Remove the element with the highest priority from the ready list and
|
||
return it. */
|
||
|
||
HAIFA_INLINE static rtx_insn *
|
||
ready_remove_first (struct ready_list *ready)
|
||
{
|
||
rtx_insn *t;
|
||
|
||
gcc_assert (ready->n_ready);
|
||
t = ready->vec[ready->first--];
|
||
ready->n_ready--;
|
||
if (DEBUG_INSN_P (t))
|
||
ready->n_debug--;
|
||
/* If the queue becomes empty, reset it. */
|
||
if (ready->n_ready == 0)
|
||
ready->first = ready->veclen - 1;
|
||
|
||
gcc_assert (QUEUE_INDEX (t) == QUEUE_READY);
|
||
QUEUE_INDEX (t) = QUEUE_NOWHERE;
|
||
|
||
return t;
|
||
}
|
||
|
||
/* The following code implements multi-pass scheduling for the first
|
||
cycle. In other words, we will try to choose ready insn which
|
||
permits to start maximum number of insns on the same cycle. */
|
||
|
||
/* Return a pointer to the element INDEX from the ready. INDEX for
|
||
insn with the highest priority is 0, and the lowest priority has
|
||
N_READY - 1. */
|
||
|
||
rtx_insn *
|
||
ready_element (struct ready_list *ready, int index)
|
||
{
|
||
gcc_assert (ready->n_ready && index < ready->n_ready);
|
||
|
||
return ready->vec[ready->first - index];
|
||
}
|
||
|
||
/* Remove the element INDEX from the ready list and return it. INDEX
|
||
for insn with the highest priority is 0, and the lowest priority
|
||
has N_READY - 1. */
|
||
|
||
HAIFA_INLINE static rtx_insn *
|
||
ready_remove (struct ready_list *ready, int index)
|
||
{
|
||
rtx_insn *t;
|
||
int i;
|
||
|
||
if (index == 0)
|
||
return ready_remove_first (ready);
|
||
gcc_assert (ready->n_ready && index < ready->n_ready);
|
||
t = ready->vec[ready->first - index];
|
||
ready->n_ready--;
|
||
if (DEBUG_INSN_P (t))
|
||
ready->n_debug--;
|
||
for (i = index; i < ready->n_ready; i++)
|
||
ready->vec[ready->first - i] = ready->vec[ready->first - i - 1];
|
||
QUEUE_INDEX (t) = QUEUE_NOWHERE;
|
||
return t;
|
||
}
|
||
|
||
/* Remove INSN from the ready list. */
|
||
static void
|
||
ready_remove_insn (rtx_insn *insn)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < readyp->n_ready; i++)
|
||
if (ready_element (readyp, i) == insn)
|
||
{
|
||
ready_remove (readyp, i);
|
||
return;
|
||
}
|
||
gcc_unreachable ();
|
||
}
|
||
|
||
/* Calculate difference of two statistics set WAS and NOW.
|
||
Result returned in WAS. */
|
||
static void
|
||
rank_for_schedule_stats_diff (rank_for_schedule_stats_t *was,
|
||
const rank_for_schedule_stats_t *now)
|
||
{
|
||
for (int i = 0; i < RFS_N; ++i)
|
||
was->stats[i] = now->stats[i] - was->stats[i];
|
||
}
|
||
|
||
/* Print rank_for_schedule statistics. */
|
||
static void
|
||
print_rank_for_schedule_stats (const char *prefix,
|
||
const rank_for_schedule_stats_t *stats,
|
||
struct ready_list *ready)
|
||
{
|
||
for (int i = 0; i < RFS_N; ++i)
|
||
if (stats->stats[i])
|
||
{
|
||
fprintf (sched_dump, "%s%20s: %u", prefix, rfs_str[i], stats->stats[i]);
|
||
|
||
if (ready != NULL)
|
||
/* Print out insns that won due to RFS_<I>. */
|
||
{
|
||
rtx_insn **p = ready_lastpos (ready);
|
||
|
||
fprintf (sched_dump, ":");
|
||
/* Start with 1 since least-priority insn didn't have any wins. */
|
||
for (int j = 1; j < ready->n_ready; ++j)
|
||
if (INSN_LAST_RFS_WIN (p[j]) == i)
|
||
fprintf (sched_dump, " %s",
|
||
(*current_sched_info->print_insn) (p[j], 0));
|
||
}
|
||
fprintf (sched_dump, "\n");
|
||
}
|
||
}
|
||
|
||
/* Separate DEBUG_INSNS from normal insns. DEBUG_INSNs go to the end
|
||
of array. */
|
||
static void
|
||
ready_sort_debug (struct ready_list *ready)
|
||
{
|
||
int i;
|
||
rtx_insn **first = ready_lastpos (ready);
|
||
|
||
for (i = 0; i < ready->n_ready; ++i)
|
||
if (!DEBUG_INSN_P (first[i]))
|
||
INSN_RFS_DEBUG_ORIG_ORDER (first[i]) = i;
|
||
|
||
qsort (first, ready->n_ready, sizeof (rtx), rank_for_schedule_debug);
|
||
}
|
||
|
||
/* Sort non-debug insns in the ready list READY by ascending priority.
|
||
Assumes that all debug insns are separated from the real insns. */
|
||
static void
|
||
ready_sort_real (struct ready_list *ready)
|
||
{
|
||
int i;
|
||
rtx_insn **first = ready_lastpos (ready);
|
||
int n_ready_real = ready->n_ready - ready->n_debug;
|
||
|
||
if (sched_pressure == SCHED_PRESSURE_WEIGHTED)
|
||
for (i = 0; i < n_ready_real; ++i)
|
||
setup_insn_reg_pressure_info (first[i]);
|
||
else if (sched_pressure == SCHED_PRESSURE_MODEL
|
||
&& model_curr_point < model_num_insns)
|
||
model_set_excess_costs (first, n_ready_real);
|
||
|
||
rank_for_schedule_stats_t stats1;
|
||
if (sched_verbose >= 4)
|
||
stats1 = rank_for_schedule_stats;
|
||
|
||
if (n_ready_real == 2)
|
||
swap_sort (first, n_ready_real);
|
||
else if (n_ready_real > 2)
|
||
qsort (first, n_ready_real, sizeof (rtx), rank_for_schedule);
|
||
|
||
if (sched_verbose >= 4)
|
||
{
|
||
rank_for_schedule_stats_diff (&stats1, &rank_for_schedule_stats);
|
||
print_rank_for_schedule_stats (";;\t\t", &stats1, ready);
|
||
}
|
||
}
|
||
|
||
/* Sort the ready list READY by ascending priority. */
|
||
static void
|
||
ready_sort (struct ready_list *ready)
|
||
{
|
||
if (ready->n_debug > 0)
|
||
ready_sort_debug (ready);
|
||
else
|
||
ready_sort_real (ready);
|
||
}
|
||
|
||
/* PREV is an insn that is ready to execute. Adjust its priority if that
|
||
will help shorten or lengthen register lifetimes as appropriate. Also
|
||
provide a hook for the target to tweak itself. */
|
||
|
||
HAIFA_INLINE static void
|
||
adjust_priority (rtx_insn *prev)
|
||
{
|
||
/* ??? There used to be code here to try and estimate how an insn
|
||
affected register lifetimes, but it did it by looking at REG_DEAD
|
||
notes, which we removed in schedule_region. Nor did it try to
|
||
take into account register pressure or anything useful like that.
|
||
|
||
Revisit when we have a machine model to work with and not before. */
|
||
|
||
if (targetm.sched.adjust_priority)
|
||
INSN_PRIORITY (prev) =
|
||
targetm.sched.adjust_priority (prev, INSN_PRIORITY (prev));
|
||
}
|
||
|
||
/* Advance DFA state STATE on one cycle. */
|
||
void
|
||
advance_state (state_t state)
|
||
{
|
||
if (targetm.sched.dfa_pre_advance_cycle)
|
||
targetm.sched.dfa_pre_advance_cycle ();
|
||
|
||
if (targetm.sched.dfa_pre_cycle_insn)
|
||
state_transition (state,
|
||
targetm.sched.dfa_pre_cycle_insn ());
|
||
|
||
state_transition (state, NULL);
|
||
|
||
if (targetm.sched.dfa_post_cycle_insn)
|
||
state_transition (state,
|
||
targetm.sched.dfa_post_cycle_insn ());
|
||
|
||
if (targetm.sched.dfa_post_advance_cycle)
|
||
targetm.sched.dfa_post_advance_cycle ();
|
||
}
|
||
|
||
/* Advance time on one cycle. */
|
||
HAIFA_INLINE static void
|
||
advance_one_cycle (void)
|
||
{
|
||
advance_state (curr_state);
|
||
if (sched_verbose >= 4)
|
||
fprintf (sched_dump, ";;\tAdvance the current state.\n");
|
||
}
|
||
|
||
/* Update register pressure after scheduling INSN. */
|
||
static void
|
||
update_register_pressure (rtx_insn *insn)
|
||
{
|
||
struct reg_use_data *use;
|
||
struct reg_set_data *set;
|
||
|
||
gcc_checking_assert (!DEBUG_INSN_P (insn));
|
||
|
||
for (use = INSN_REG_USE_LIST (insn); use != NULL; use = use->next_insn_use)
|
||
if (dying_use_p (use))
|
||
mark_regno_birth_or_death (curr_reg_live, curr_reg_pressure,
|
||
use->regno, false);
|
||
for (set = INSN_REG_SET_LIST (insn); set != NULL; set = set->next_insn_set)
|
||
mark_regno_birth_or_death (curr_reg_live, curr_reg_pressure,
|
||
set->regno, true);
|
||
}
|
||
|
||
/* Set up or update (if UPDATE_P) max register pressure (see its
|
||
meaning in sched-int.h::_haifa_insn_data) for all current BB insns
|
||
after insn AFTER. */
|
||
static void
|
||
setup_insn_max_reg_pressure (rtx_insn *after, bool update_p)
|
||
{
|
||
int i, p;
|
||
bool eq_p;
|
||
rtx_insn *insn;
|
||
static int max_reg_pressure[N_REG_CLASSES];
|
||
|
||
save_reg_pressure ();
|
||
for (i = 0; i < ira_pressure_classes_num; i++)
|
||
max_reg_pressure[ira_pressure_classes[i]]
|
||
= curr_reg_pressure[ira_pressure_classes[i]];
|
||
for (insn = NEXT_INSN (after);
|
||
insn != NULL_RTX && ! BARRIER_P (insn)
|
||
&& BLOCK_FOR_INSN (insn) == BLOCK_FOR_INSN (after);
|
||
insn = NEXT_INSN (insn))
|
||
if (NONDEBUG_INSN_P (insn))
|
||
{
|
||
eq_p = true;
|
||
for (i = 0; i < ira_pressure_classes_num; i++)
|
||
{
|
||
p = max_reg_pressure[ira_pressure_classes[i]];
|
||
if (INSN_MAX_REG_PRESSURE (insn)[i] != p)
|
||
{
|
||
eq_p = false;
|
||
INSN_MAX_REG_PRESSURE (insn)[i]
|
||
= max_reg_pressure[ira_pressure_classes[i]];
|
||
}
|
||
}
|
||
if (update_p && eq_p)
|
||
break;
|
||
update_register_pressure (insn);
|
||
for (i = 0; i < ira_pressure_classes_num; i++)
|
||
if (max_reg_pressure[ira_pressure_classes[i]]
|
||
< curr_reg_pressure[ira_pressure_classes[i]])
|
||
max_reg_pressure[ira_pressure_classes[i]]
|
||
= curr_reg_pressure[ira_pressure_classes[i]];
|
||
}
|
||
restore_reg_pressure ();
|
||
}
|
||
|
||
/* Update the current register pressure after scheduling INSN. Update
|
||
also max register pressure for unscheduled insns of the current
|
||
BB. */
|
||
static void
|
||
update_reg_and_insn_max_reg_pressure (rtx_insn *insn)
|
||
{
|
||
int i;
|
||
int before[N_REG_CLASSES];
|
||
|
||
for (i = 0; i < ira_pressure_classes_num; i++)
|
||
before[i] = curr_reg_pressure[ira_pressure_classes[i]];
|
||
update_register_pressure (insn);
|
||
for (i = 0; i < ira_pressure_classes_num; i++)
|
||
if (curr_reg_pressure[ira_pressure_classes[i]] != before[i])
|
||
break;
|
||
if (i < ira_pressure_classes_num)
|
||
setup_insn_max_reg_pressure (insn, true);
|
||
}
|
||
|
||
/* Set up register pressure at the beginning of basic block BB whose
|
||
insns starting after insn AFTER. Set up also max register pressure
|
||
for all insns of the basic block. */
|
||
void
|
||
sched_setup_bb_reg_pressure_info (basic_block bb, rtx_insn *after)
|
||
{
|
||
gcc_assert (sched_pressure == SCHED_PRESSURE_WEIGHTED);
|
||
initiate_bb_reg_pressure_info (bb);
|
||
setup_insn_max_reg_pressure (after, false);
|
||
}
|
||
|
||
/* If doing predication while scheduling, verify whether INSN, which
|
||
has just been scheduled, clobbers the conditions of any
|
||
instructions that must be predicated in order to break their
|
||
dependencies. If so, remove them from the queues so that they will
|
||
only be scheduled once their control dependency is resolved. */
|
||
|
||
static void
|
||
check_clobbered_conditions (rtx_insn *insn)
|
||
{
|
||
HARD_REG_SET t;
|
||
int i;
|
||
|
||
if ((current_sched_info->flags & DO_PREDICATION) == 0)
|
||
return;
|
||
|
||
find_all_hard_reg_sets (insn, &t, true);
|
||
|
||
restart:
|
||
for (i = 0; i < ready.n_ready; i++)
|
||
{
|
||
rtx_insn *x = ready_element (&ready, i);
|
||
if (TODO_SPEC (x) == DEP_CONTROL && cond_clobbered_p (x, t))
|
||
{
|
||
ready_remove_insn (x);
|
||
goto restart;
|
||
}
|
||
}
|
||
for (i = 0; i <= max_insn_queue_index; i++)
|
||
{
|
||
rtx_insn_list *link;
|
||
int q = NEXT_Q_AFTER (q_ptr, i);
|
||
|
||
restart_queue:
|
||
for (link = insn_queue[q]; link; link = link->next ())
|
||
{
|
||
rtx_insn *x = link->insn ();
|
||
if (TODO_SPEC (x) == DEP_CONTROL && cond_clobbered_p (x, t))
|
||
{
|
||
queue_remove (x);
|
||
goto restart_queue;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Return (in order):
|
||
|
||
- positive if INSN adversely affects the pressure on one
|
||
register class
|
||
|
||
- negative if INSN reduces the pressure on one register class
|
||
|
||
- 0 if INSN doesn't affect the pressure on any register class. */
|
||
|
||
static int
|
||
model_classify_pressure (struct model_insn_info *insn)
|
||
{
|
||
struct reg_pressure_data *reg_pressure;
|
||
int death[N_REG_CLASSES];
|
||
int pci, cl, sum;
|
||
|
||
calculate_reg_deaths (insn->insn, death);
|
||
reg_pressure = INSN_REG_PRESSURE (insn->insn);
|
||
sum = 0;
|
||
for (pci = 0; pci < ira_pressure_classes_num; pci++)
|
||
{
|
||
cl = ira_pressure_classes[pci];
|
||
if (death[cl] < reg_pressure[pci].set_increase)
|
||
return 1;
|
||
sum += reg_pressure[pci].set_increase - death[cl];
|
||
}
|
||
return sum;
|
||
}
|
||
|
||
/* Return true if INSN1 should come before INSN2 in the model schedule. */
|
||
|
||
static int
|
||
model_order_p (struct model_insn_info *insn1, struct model_insn_info *insn2)
|
||
{
|
||
unsigned int height1, height2;
|
||
unsigned int priority1, priority2;
|
||
|
||
/* Prefer instructions with a higher model priority. */
|
||
if (insn1->model_priority != insn2->model_priority)
|
||
return insn1->model_priority > insn2->model_priority;
|
||
|
||
/* Combine the length of the longest path of satisfied true dependencies
|
||
that leads to each instruction (depth) with the length of the longest
|
||
path of any dependencies that leads from the instruction (alap).
|
||
Prefer instructions with the greatest combined length. If the combined
|
||
lengths are equal, prefer instructions with the greatest depth.
|
||
|
||
The idea is that, if we have a set S of "equal" instructions that each
|
||
have ALAP value X, and we pick one such instruction I, any true-dependent
|
||
successors of I that have ALAP value X - 1 should be preferred over S.
|
||
This encourages the schedule to be "narrow" rather than "wide".
|
||
However, if I is a low-priority instruction that we decided to
|
||
schedule because of its model_classify_pressure, and if there
|
||
is a set of higher-priority instructions T, the aforementioned
|
||
successors of I should not have the edge over T. */
|
||
height1 = insn1->depth + insn1->alap;
|
||
height2 = insn2->depth + insn2->alap;
|
||
if (height1 != height2)
|
||
return height1 > height2;
|
||
if (insn1->depth != insn2->depth)
|
||
return insn1->depth > insn2->depth;
|
||
|
||
/* We have no real preference between INSN1 an INSN2 as far as attempts
|
||
to reduce pressure go. Prefer instructions with higher priorities. */
|
||
priority1 = INSN_PRIORITY (insn1->insn);
|
||
priority2 = INSN_PRIORITY (insn2->insn);
|
||
if (priority1 != priority2)
|
||
return priority1 > priority2;
|
||
|
||
/* Use the original rtl sequence as a tie-breaker. */
|
||
return insn1 < insn2;
|
||
}
|
||
|
||
/* Add INSN to the model worklist immediately after PREV. Add it to the
|
||
beginning of the list if PREV is null. */
|
||
|
||
static void
|
||
model_add_to_worklist_at (struct model_insn_info *insn,
|
||
struct model_insn_info *prev)
|
||
{
|
||
gcc_assert (QUEUE_INDEX (insn->insn) == QUEUE_NOWHERE);
|
||
QUEUE_INDEX (insn->insn) = QUEUE_READY;
|
||
|
||
insn->prev = prev;
|
||
if (prev)
|
||
{
|
||
insn->next = prev->next;
|
||
prev->next = insn;
|
||
}
|
||
else
|
||
{
|
||
insn->next = model_worklist;
|
||
model_worklist = insn;
|
||
}
|
||
if (insn->next)
|
||
insn->next->prev = insn;
|
||
}
|
||
|
||
/* Remove INSN from the model worklist. */
|
||
|
||
static void
|
||
model_remove_from_worklist (struct model_insn_info *insn)
|
||
{
|
||
gcc_assert (QUEUE_INDEX (insn->insn) == QUEUE_READY);
|
||
QUEUE_INDEX (insn->insn) = QUEUE_NOWHERE;
|
||
|
||
if (insn->prev)
|
||
insn->prev->next = insn->next;
|
||
else
|
||
model_worklist = insn->next;
|
||
if (insn->next)
|
||
insn->next->prev = insn->prev;
|
||
}
|
||
|
||
/* Add INSN to the model worklist. Start looking for a suitable position
|
||
between neighbors PREV and NEXT, testing at most MAX_SCHED_READY_INSNS
|
||
insns either side. A null PREV indicates the beginning of the list and
|
||
a null NEXT indicates the end. */
|
||
|
||
static void
|
||
model_add_to_worklist (struct model_insn_info *insn,
|
||
struct model_insn_info *prev,
|
||
struct model_insn_info *next)
|
||
{
|
||
int count;
|
||
|
||
count = MAX_SCHED_READY_INSNS;
|
||
if (count > 0 && prev && model_order_p (insn, prev))
|
||
do
|
||
{
|
||
count--;
|
||
prev = prev->prev;
|
||
}
|
||
while (count > 0 && prev && model_order_p (insn, prev));
|
||
else
|
||
while (count > 0 && next && model_order_p (next, insn))
|
||
{
|
||
count--;
|
||
prev = next;
|
||
next = next->next;
|
||
}
|
||
model_add_to_worklist_at (insn, prev);
|
||
}
|
||
|
||
/* INSN may now have a higher priority (in the model_order_p sense)
|
||
than before. Move it up the worklist if necessary. */
|
||
|
||
static void
|
||
model_promote_insn (struct model_insn_info *insn)
|
||
{
|
||
struct model_insn_info *prev;
|
||
int count;
|
||
|
||
prev = insn->prev;
|
||
count = MAX_SCHED_READY_INSNS;
|
||
while (count > 0 && prev && model_order_p (insn, prev))
|
||
{
|
||
count--;
|
||
prev = prev->prev;
|
||
}
|
||
if (prev != insn->prev)
|
||
{
|
||
model_remove_from_worklist (insn);
|
||
model_add_to_worklist_at (insn, prev);
|
||
}
|
||
}
|
||
|
||
/* Add INSN to the end of the model schedule. */
|
||
|
||
static void
|
||
model_add_to_schedule (rtx_insn *insn)
|
||
{
|
||
unsigned int point;
|
||
|
||
gcc_assert (QUEUE_INDEX (insn) == QUEUE_NOWHERE);
|
||
QUEUE_INDEX (insn) = QUEUE_SCHEDULED;
|
||
|
||
point = model_schedule.length ();
|
||
model_schedule.quick_push (insn);
|
||
INSN_MODEL_INDEX (insn) = point + 1;
|
||
}
|
||
|
||
/* Analyze the instructions that are to be scheduled, setting up
|
||
MODEL_INSN_INFO (...) and model_num_insns accordingly. Add ready
|
||
instructions to model_worklist. */
|
||
|
||
static void
|
||
model_analyze_insns (void)
|
||
{
|
||
rtx_insn *start, *end, *iter;
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
struct model_insn_info *insn, *con;
|
||
|
||
model_num_insns = 0;
|
||
start = PREV_INSN (current_sched_info->next_tail);
|
||
end = current_sched_info->prev_head;
|
||
for (iter = start; iter != end; iter = PREV_INSN (iter))
|
||
if (NONDEBUG_INSN_P (iter))
|
||
{
|
||
insn = MODEL_INSN_INFO (iter);
|
||
insn->insn = iter;
|
||
FOR_EACH_DEP (iter, SD_LIST_FORW, sd_it, dep)
|
||
{
|
||
con = MODEL_INSN_INFO (DEP_CON (dep));
|
||
if (con->insn && insn->alap < con->alap + 1)
|
||
insn->alap = con->alap + 1;
|
||
}
|
||
|
||
insn->old_queue = QUEUE_INDEX (iter);
|
||
QUEUE_INDEX (iter) = QUEUE_NOWHERE;
|
||
|
||
insn->unscheduled_preds = dep_list_size (iter, SD_LIST_HARD_BACK);
|
||
if (insn->unscheduled_preds == 0)
|
||
model_add_to_worklist (insn, NULL, model_worklist);
|
||
|
||
model_num_insns++;
|
||
}
|
||
}
|
||
|
||
/* The global state describes the register pressure at the start of the
|
||
model schedule. Initialize GROUP accordingly. */
|
||
|
||
static void
|
||
model_init_pressure_group (struct model_pressure_group *group)
|
||
{
|
||
int pci, cl;
|
||
|
||
for (pci = 0; pci < ira_pressure_classes_num; pci++)
|
||
{
|
||
cl = ira_pressure_classes[pci];
|
||
group->limits[pci].pressure = curr_reg_pressure[cl];
|
||
group->limits[pci].point = 0;
|
||
}
|
||
/* Use index model_num_insns to record the state after the last
|
||
instruction in the model schedule. */
|
||
group->model = XNEWVEC (struct model_pressure_data,
|
||
(model_num_insns + 1) * ira_pressure_classes_num);
|
||
}
|
||
|
||
/* Record that MODEL_REF_PRESSURE (GROUP, POINT, PCI) is PRESSURE.
|
||
Update the maximum pressure for the whole schedule. */
|
||
|
||
static void
|
||
model_record_pressure (struct model_pressure_group *group,
|
||
int point, int pci, int pressure)
|
||
{
|
||
MODEL_REF_PRESSURE (group, point, pci) = pressure;
|
||
if (group->limits[pci].pressure < pressure)
|
||
{
|
||
group->limits[pci].pressure = pressure;
|
||
group->limits[pci].point = point;
|
||
}
|
||
}
|
||
|
||
/* INSN has just been added to the end of the model schedule. Record its
|
||
register-pressure information. */
|
||
|
||
static void
|
||
model_record_pressures (struct model_insn_info *insn)
|
||
{
|
||
struct reg_pressure_data *reg_pressure;
|
||
int point, pci, cl, delta;
|
||
int death[N_REG_CLASSES];
|
||
|
||
point = model_index (insn->insn);
|
||
if (sched_verbose >= 2)
|
||
{
|
||
if (point == 0)
|
||
{
|
||
fprintf (sched_dump, "\n;;\tModel schedule:\n;;\n");
|
||
fprintf (sched_dump, ";;\t| idx insn | mpri hght dpth prio |\n");
|
||
}
|
||
fprintf (sched_dump, ";;\t| %3d %4d | %4d %4d %4d %4d | %-30s ",
|
||
point, INSN_UID (insn->insn), insn->model_priority,
|
||
insn->depth + insn->alap, insn->depth,
|
||
INSN_PRIORITY (insn->insn),
|
||
str_pattern_slim (PATTERN (insn->insn)));
|
||
}
|
||
calculate_reg_deaths (insn->insn, death);
|
||
reg_pressure = INSN_REG_PRESSURE (insn->insn);
|
||
for (pci = 0; pci < ira_pressure_classes_num; pci++)
|
||
{
|
||
cl = ira_pressure_classes[pci];
|
||
delta = reg_pressure[pci].set_increase - death[cl];
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump, " %s:[%d,%+d]", reg_class_names[cl],
|
||
curr_reg_pressure[cl], delta);
|
||
model_record_pressure (&model_before_pressure, point, pci,
|
||
curr_reg_pressure[cl]);
|
||
}
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump, "\n");
|
||
}
|
||
|
||
/* All instructions have been added to the model schedule. Record the
|
||
final register pressure in GROUP and set up all MODEL_MAX_PRESSUREs. */
|
||
|
||
static void
|
||
model_record_final_pressures (struct model_pressure_group *group)
|
||
{
|
||
int point, pci, max_pressure, ref_pressure, cl;
|
||
|
||
for (pci = 0; pci < ira_pressure_classes_num; pci++)
|
||
{
|
||
/* Record the final pressure for this class. */
|
||
cl = ira_pressure_classes[pci];
|
||
point = model_num_insns;
|
||
ref_pressure = curr_reg_pressure[cl];
|
||
model_record_pressure (group, point, pci, ref_pressure);
|
||
|
||
/* Record the original maximum pressure. */
|
||
group->limits[pci].orig_pressure = group->limits[pci].pressure;
|
||
|
||
/* Update the MODEL_MAX_PRESSURE for every point of the schedule. */
|
||
max_pressure = ref_pressure;
|
||
MODEL_MAX_PRESSURE (group, point, pci) = max_pressure;
|
||
while (point > 0)
|
||
{
|
||
point--;
|
||
ref_pressure = MODEL_REF_PRESSURE (group, point, pci);
|
||
max_pressure = MAX (max_pressure, ref_pressure);
|
||
MODEL_MAX_PRESSURE (group, point, pci) = max_pressure;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Update all successors of INSN, given that INSN has just been scheduled. */
|
||
|
||
static void
|
||
model_add_successors_to_worklist (struct model_insn_info *insn)
|
||
{
|
||
sd_iterator_def sd_it;
|
||
struct model_insn_info *con;
|
||
dep_t dep;
|
||
|
||
FOR_EACH_DEP (insn->insn, SD_LIST_FORW, sd_it, dep)
|
||
{
|
||
con = MODEL_INSN_INFO (DEP_CON (dep));
|
||
/* Ignore debug instructions, and instructions from other blocks. */
|
||
if (con->insn)
|
||
{
|
||
con->unscheduled_preds--;
|
||
|
||
/* Update the depth field of each true-dependent successor.
|
||
Increasing the depth gives them a higher priority than
|
||
before. */
|
||
if (DEP_TYPE (dep) == REG_DEP_TRUE && con->depth < insn->depth + 1)
|
||
{
|
||
con->depth = insn->depth + 1;
|
||
if (QUEUE_INDEX (con->insn) == QUEUE_READY)
|
||
model_promote_insn (con);
|
||
}
|
||
|
||
/* If this is a true dependency, or if there are no remaining
|
||
dependencies for CON (meaning that CON only had non-true
|
||
dependencies), make sure that CON is on the worklist.
|
||
We don't bother otherwise because it would tend to fill the
|
||
worklist with a lot of low-priority instructions that are not
|
||
yet ready to issue. */
|
||
if ((con->depth > 0 || con->unscheduled_preds == 0)
|
||
&& QUEUE_INDEX (con->insn) == QUEUE_NOWHERE)
|
||
model_add_to_worklist (con, insn, insn->next);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Give INSN a higher priority than any current instruction, then give
|
||
unscheduled predecessors of INSN a higher priority still. If any of
|
||
those predecessors are not on the model worklist, do the same for its
|
||
predecessors, and so on. */
|
||
|
||
static void
|
||
model_promote_predecessors (struct model_insn_info *insn)
|
||
{
|
||
struct model_insn_info *pro, *first;
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
|
||
if (sched_verbose >= 7)
|
||
fprintf (sched_dump, ";;\t+--- priority of %d = %d, priority of",
|
||
INSN_UID (insn->insn), model_next_priority);
|
||
insn->model_priority = model_next_priority++;
|
||
model_remove_from_worklist (insn);
|
||
model_add_to_worklist_at (insn, NULL);
|
||
|
||
first = NULL;
|
||
for (;;)
|
||
{
|
||
FOR_EACH_DEP (insn->insn, SD_LIST_HARD_BACK, sd_it, dep)
|
||
{
|
||
pro = MODEL_INSN_INFO (DEP_PRO (dep));
|
||
/* The first test is to ignore debug instructions, and instructions
|
||
from other blocks. */
|
||
if (pro->insn
|
||
&& pro->model_priority != model_next_priority
|
||
&& QUEUE_INDEX (pro->insn) != QUEUE_SCHEDULED)
|
||
{
|
||
pro->model_priority = model_next_priority;
|
||
if (sched_verbose >= 7)
|
||
fprintf (sched_dump, " %d", INSN_UID (pro->insn));
|
||
if (QUEUE_INDEX (pro->insn) == QUEUE_READY)
|
||
{
|
||
/* PRO is already in the worklist, but it now has
|
||
a higher priority than before. Move it at the
|
||
appropriate place. */
|
||
model_remove_from_worklist (pro);
|
||
model_add_to_worklist (pro, NULL, model_worklist);
|
||
}
|
||
else
|
||
{
|
||
/* PRO isn't in the worklist. Recursively process
|
||
its predecessors until we find one that is. */
|
||
pro->next = first;
|
||
first = pro;
|
||
}
|
||
}
|
||
}
|
||
if (!first)
|
||
break;
|
||
insn = first;
|
||
first = insn->next;
|
||
}
|
||
if (sched_verbose >= 7)
|
||
fprintf (sched_dump, " = %d\n", model_next_priority);
|
||
model_next_priority++;
|
||
}
|
||
|
||
/* Pick one instruction from model_worklist and process it. */
|
||
|
||
static void
|
||
model_choose_insn (void)
|
||
{
|
||
struct model_insn_info *insn, *fallback;
|
||
int count;
|
||
|
||
if (sched_verbose >= 7)
|
||
{
|
||
fprintf (sched_dump, ";;\t+--- worklist:\n");
|
||
insn = model_worklist;
|
||
count = MAX_SCHED_READY_INSNS;
|
||
while (count > 0 && insn)
|
||
{
|
||
fprintf (sched_dump, ";;\t+--- %d [%d, %d, %d, %d]\n",
|
||
INSN_UID (insn->insn), insn->model_priority,
|
||
insn->depth + insn->alap, insn->depth,
|
||
INSN_PRIORITY (insn->insn));
|
||
count--;
|
||
insn = insn->next;
|
||
}
|
||
}
|
||
|
||
/* Look for a ready instruction whose model_classify_priority is zero
|
||
or negative, picking the highest-priority one. Adding such an
|
||
instruction to the schedule now should do no harm, and may actually
|
||
do some good.
|
||
|
||
Failing that, see whether there is an instruction with the highest
|
||
extant model_priority that is not yet ready, but which would reduce
|
||
pressure if it became ready. This is designed to catch cases like:
|
||
|
||
(set (mem (reg R1)) (reg R2))
|
||
|
||
where the instruction is the last remaining use of R1 and where the
|
||
value of R2 is not yet available (or vice versa). The death of R1
|
||
means that this instruction already reduces pressure. It is of
|
||
course possible that the computation of R2 involves other registers
|
||
that are hard to kill, but such cases are rare enough for this
|
||
heuristic to be a win in general.
|
||
|
||
Failing that, just pick the highest-priority instruction in the
|
||
worklist. */
|
||
count = MAX_SCHED_READY_INSNS;
|
||
insn = model_worklist;
|
||
fallback = 0;
|
||
for (;;)
|
||
{
|
||
if (count == 0 || !insn)
|
||
{
|
||
insn = fallback ? fallback : model_worklist;
|
||
break;
|
||
}
|
||
if (insn->unscheduled_preds)
|
||
{
|
||
if (model_worklist->model_priority == insn->model_priority
|
||
&& !fallback
|
||
&& model_classify_pressure (insn) < 0)
|
||
fallback = insn;
|
||
}
|
||
else
|
||
{
|
||
if (model_classify_pressure (insn) <= 0)
|
||
break;
|
||
}
|
||
count--;
|
||
insn = insn->next;
|
||
}
|
||
|
||
if (sched_verbose >= 7 && insn != model_worklist)
|
||
{
|
||
if (insn->unscheduled_preds)
|
||
fprintf (sched_dump, ";;\t+--- promoting insn %d, with dependencies\n",
|
||
INSN_UID (insn->insn));
|
||
else
|
||
fprintf (sched_dump, ";;\t+--- promoting insn %d, which is ready\n",
|
||
INSN_UID (insn->insn));
|
||
}
|
||
if (insn->unscheduled_preds)
|
||
/* INSN isn't yet ready to issue. Give all its predecessors the
|
||
highest priority. */
|
||
model_promote_predecessors (insn);
|
||
else
|
||
{
|
||
/* INSN is ready. Add it to the end of model_schedule and
|
||
process its successors. */
|
||
model_add_successors_to_worklist (insn);
|
||
model_remove_from_worklist (insn);
|
||
model_add_to_schedule (insn->insn);
|
||
model_record_pressures (insn);
|
||
update_register_pressure (insn->insn);
|
||
}
|
||
}
|
||
|
||
/* Restore all QUEUE_INDEXs to the values that they had before
|
||
model_start_schedule was called. */
|
||
|
||
static void
|
||
model_reset_queue_indices (void)
|
||
{
|
||
unsigned int i;
|
||
rtx_insn *insn;
|
||
|
||
FOR_EACH_VEC_ELT (model_schedule, i, insn)
|
||
QUEUE_INDEX (insn) = MODEL_INSN_INFO (insn)->old_queue;
|
||
}
|
||
|
||
/* We have calculated the model schedule and spill costs. Print a summary
|
||
to sched_dump. */
|
||
|
||
static void
|
||
model_dump_pressure_summary (void)
|
||
{
|
||
int pci, cl;
|
||
|
||
fprintf (sched_dump, ";; Pressure summary:");
|
||
for (pci = 0; pci < ira_pressure_classes_num; pci++)
|
||
{
|
||
cl = ira_pressure_classes[pci];
|
||
fprintf (sched_dump, " %s:%d", reg_class_names[cl],
|
||
model_before_pressure.limits[pci].pressure);
|
||
}
|
||
fprintf (sched_dump, "\n\n");
|
||
}
|
||
|
||
/* Initialize the SCHED_PRESSURE_MODEL information for the current
|
||
scheduling region. */
|
||
|
||
static void
|
||
model_start_schedule (basic_block bb)
|
||
{
|
||
model_next_priority = 1;
|
||
model_schedule.create (sched_max_luid);
|
||
model_insns = XCNEWVEC (struct model_insn_info, sched_max_luid);
|
||
|
||
gcc_assert (bb == BLOCK_FOR_INSN (NEXT_INSN (current_sched_info->prev_head)));
|
||
initiate_reg_pressure_info (df_get_live_in (bb));
|
||
|
||
model_analyze_insns ();
|
||
model_init_pressure_group (&model_before_pressure);
|
||
while (model_worklist)
|
||
model_choose_insn ();
|
||
gcc_assert (model_num_insns == (int) model_schedule.length ());
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump, "\n");
|
||
|
||
model_record_final_pressures (&model_before_pressure);
|
||
model_reset_queue_indices ();
|
||
|
||
XDELETEVEC (model_insns);
|
||
|
||
model_curr_point = 0;
|
||
initiate_reg_pressure_info (df_get_live_in (bb));
|
||
if (sched_verbose >= 1)
|
||
model_dump_pressure_summary ();
|
||
}
|
||
|
||
/* Free the information associated with GROUP. */
|
||
|
||
static void
|
||
model_finalize_pressure_group (struct model_pressure_group *group)
|
||
{
|
||
XDELETEVEC (group->model);
|
||
}
|
||
|
||
/* Free the information created by model_start_schedule. */
|
||
|
||
static void
|
||
model_end_schedule (void)
|
||
{
|
||
model_finalize_pressure_group (&model_before_pressure);
|
||
model_schedule.release ();
|
||
}
|
||
|
||
/* Prepare reg pressure scheduling for basic block BB. */
|
||
static void
|
||
sched_pressure_start_bb (basic_block bb)
|
||
{
|
||
/* Set the number of available registers for each class taking into account
|
||
relative probability of current basic block versus function prologue and
|
||
epilogue.
|
||
* If the basic block executes much more often than the prologue/epilogue
|
||
(e.g., inside a hot loop), then cost of spill in the prologue is close to
|
||
nil, so the effective number of available registers is
|
||
(ira_class_hard_regs_num[cl] - 0).
|
||
* If the basic block executes as often as the prologue/epilogue,
|
||
then spill in the block is as costly as in the prologue, so the effective
|
||
number of available registers is
|
||
(ira_class_hard_regs_num[cl] - call_used_regs_num[cl]).
|
||
Note that all-else-equal, we prefer to spill in the prologue, since that
|
||
allows "extra" registers for other basic blocks of the function.
|
||
* If the basic block is on the cold path of the function and executes
|
||
rarely, then we should always prefer to spill in the block, rather than
|
||
in the prologue/epilogue. The effective number of available register is
|
||
(ira_class_hard_regs_num[cl] - call_used_regs_num[cl]). */
|
||
{
|
||
int i;
|
||
int entry_freq = ENTRY_BLOCK_PTR_FOR_FN (cfun)->frequency;
|
||
int bb_freq = bb->frequency;
|
||
|
||
if (bb_freq == 0)
|
||
{
|
||
if (entry_freq == 0)
|
||
entry_freq = bb_freq = 1;
|
||
}
|
||
if (bb_freq < entry_freq)
|
||
bb_freq = entry_freq;
|
||
|
||
for (i = 0; i < ira_pressure_classes_num; ++i)
|
||
{
|
||
enum reg_class cl = ira_pressure_classes[i];
|
||
sched_class_regs_num[cl] = ira_class_hard_regs_num[cl];
|
||
sched_class_regs_num[cl]
|
||
-= (call_used_regs_num[cl] * entry_freq) / bb_freq;
|
||
}
|
||
}
|
||
|
||
if (sched_pressure == SCHED_PRESSURE_MODEL)
|
||
model_start_schedule (bb);
|
||
}
|
||
|
||
/* A structure that holds local state for the loop in schedule_block. */
|
||
struct sched_block_state
|
||
{
|
||
/* True if no real insns have been scheduled in the current cycle. */
|
||
bool first_cycle_insn_p;
|
||
/* True if a shadow insn has been scheduled in the current cycle, which
|
||
means that no more normal insns can be issued. */
|
||
bool shadows_only_p;
|
||
/* True if we're winding down a modulo schedule, which means that we only
|
||
issue insns with INSN_EXACT_TICK set. */
|
||
bool modulo_epilogue;
|
||
/* Initialized with the machine's issue rate every cycle, and updated
|
||
by calls to the variable_issue hook. */
|
||
int can_issue_more;
|
||
};
|
||
|
||
/* INSN is the "currently executing insn". Launch each insn which was
|
||
waiting on INSN. READY is the ready list which contains the insns
|
||
that are ready to fire. CLOCK is the current cycle. The function
|
||
returns necessary cycle advance after issuing the insn (it is not
|
||
zero for insns in a schedule group). */
|
||
|
||
static int
|
||
schedule_insn (rtx_insn *insn)
|
||
{
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
int i;
|
||
int advance = 0;
|
||
|
||
if (sched_verbose >= 1)
|
||
{
|
||
struct reg_pressure_data *pressure_info;
|
||
fprintf (sched_dump, ";;\t%3i--> %s %-40s:",
|
||
clock_var, (*current_sched_info->print_insn) (insn, 1),
|
||
str_pattern_slim (PATTERN (insn)));
|
||
|
||
if (recog_memoized (insn) < 0)
|
||
fprintf (sched_dump, "nothing");
|
||
else
|
||
print_reservation (sched_dump, insn);
|
||
pressure_info = INSN_REG_PRESSURE (insn);
|
||
if (pressure_info != NULL)
|
||
{
|
||
fputc (':', sched_dump);
|
||
for (i = 0; i < ira_pressure_classes_num; i++)
|
||
fprintf (sched_dump, "%s%s%+d(%d)",
|
||
scheduled_insns.length () > 1
|
||
&& INSN_LUID (insn)
|
||
< INSN_LUID (scheduled_insns[scheduled_insns.length () - 2]) ? "@" : "",
|
||
reg_class_names[ira_pressure_classes[i]],
|
||
pressure_info[i].set_increase, pressure_info[i].change);
|
||
}
|
||
if (sched_pressure == SCHED_PRESSURE_MODEL
|
||
&& model_curr_point < model_num_insns
|
||
&& model_index (insn) == model_curr_point)
|
||
fprintf (sched_dump, ":model %d", model_curr_point);
|
||
fputc ('\n', sched_dump);
|
||
}
|
||
|
||
if (sched_pressure == SCHED_PRESSURE_WEIGHTED && !DEBUG_INSN_P (insn))
|
||
update_reg_and_insn_max_reg_pressure (insn);
|
||
|
||
/* Scheduling instruction should have all its dependencies resolved and
|
||
should have been removed from the ready list. */
|
||
gcc_assert (sd_lists_empty_p (insn, SD_LIST_HARD_BACK));
|
||
|
||
/* Reset debug insns invalidated by moving this insn. */
|
||
if (MAY_HAVE_DEBUG_INSNS && !DEBUG_INSN_P (insn))
|
||
for (sd_it = sd_iterator_start (insn, SD_LIST_BACK);
|
||
sd_iterator_cond (&sd_it, &dep);)
|
||
{
|
||
rtx_insn *dbg = DEP_PRO (dep);
|
||
struct reg_use_data *use, *next;
|
||
|
||
if (DEP_STATUS (dep) & DEP_CANCELLED)
|
||
{
|
||
sd_iterator_next (&sd_it);
|
||
continue;
|
||
}
|
||
|
||
gcc_assert (DEBUG_INSN_P (dbg));
|
||
|
||
if (sched_verbose >= 6)
|
||
fprintf (sched_dump, ";;\t\tresetting: debug insn %d\n",
|
||
INSN_UID (dbg));
|
||
|
||
/* ??? Rather than resetting the debug insn, we might be able
|
||
to emit a debug temp before the just-scheduled insn, but
|
||
this would involve checking that the expression at the
|
||
point of the debug insn is equivalent to the expression
|
||
before the just-scheduled insn. They might not be: the
|
||
expression in the debug insn may depend on other insns not
|
||
yet scheduled that set MEMs, REGs or even other debug
|
||
insns. It's not clear that attempting to preserve debug
|
||
information in these cases is worth the effort, given how
|
||
uncommon these resets are and the likelihood that the debug
|
||
temps introduced won't survive the schedule change. */
|
||
INSN_VAR_LOCATION_LOC (dbg) = gen_rtx_UNKNOWN_VAR_LOC ();
|
||
df_insn_rescan (dbg);
|
||
|
||
/* Unknown location doesn't use any registers. */
|
||
for (use = INSN_REG_USE_LIST (dbg); use != NULL; use = next)
|
||
{
|
||
struct reg_use_data *prev = use;
|
||
|
||
/* Remove use from the cyclic next_regno_use chain first. */
|
||
while (prev->next_regno_use != use)
|
||
prev = prev->next_regno_use;
|
||
prev->next_regno_use = use->next_regno_use;
|
||
next = use->next_insn_use;
|
||
free (use);
|
||
}
|
||
INSN_REG_USE_LIST (dbg) = NULL;
|
||
|
||
/* We delete rather than resolve these deps, otherwise we
|
||
crash in sched_free_deps(), because forward deps are
|
||
expected to be released before backward deps. */
|
||
sd_delete_dep (sd_it);
|
||
}
|
||
|
||
gcc_assert (QUEUE_INDEX (insn) == QUEUE_NOWHERE);
|
||
QUEUE_INDEX (insn) = QUEUE_SCHEDULED;
|
||
|
||
if (sched_pressure == SCHED_PRESSURE_MODEL
|
||
&& model_curr_point < model_num_insns
|
||
&& NONDEBUG_INSN_P (insn))
|
||
{
|
||
if (model_index (insn) == model_curr_point)
|
||
do
|
||
model_curr_point++;
|
||
while (model_curr_point < model_num_insns
|
||
&& (QUEUE_INDEX (MODEL_INSN (model_curr_point))
|
||
== QUEUE_SCHEDULED));
|
||
else
|
||
model_recompute (insn);
|
||
model_update_limit_points ();
|
||
update_register_pressure (insn);
|
||
if (sched_verbose >= 2)
|
||
print_curr_reg_pressure ();
|
||
}
|
||
|
||
gcc_assert (INSN_TICK (insn) >= MIN_TICK);
|
||
if (INSN_TICK (insn) > clock_var)
|
||
/* INSN has been prematurely moved from the queue to the ready list.
|
||
This is possible only if following flags are set. */
|
||
gcc_assert (flag_sched_stalled_insns || sched_fusion);
|
||
|
||
/* ??? Probably, if INSN is scheduled prematurely, we should leave
|
||
INSN_TICK untouched. This is a machine-dependent issue, actually. */
|
||
INSN_TICK (insn) = clock_var;
|
||
|
||
check_clobbered_conditions (insn);
|
||
|
||
/* Update dependent instructions. First, see if by scheduling this insn
|
||
now we broke a dependence in a way that requires us to change another
|
||
insn. */
|
||
for (sd_it = sd_iterator_start (insn, SD_LIST_SPEC_BACK);
|
||
sd_iterator_cond (&sd_it, &dep); sd_iterator_next (&sd_it))
|
||
{
|
||
struct dep_replacement *desc = DEP_REPLACE (dep);
|
||
rtx_insn *pro = DEP_PRO (dep);
|
||
if (QUEUE_INDEX (pro) != QUEUE_SCHEDULED
|
||
&& desc != NULL && desc->insn == pro)
|
||
apply_replacement (dep, false);
|
||
}
|
||
|
||
/* Go through and resolve forward dependencies. */
|
||
for (sd_it = sd_iterator_start (insn, SD_LIST_FORW);
|
||
sd_iterator_cond (&sd_it, &dep);)
|
||
{
|
||
rtx_insn *next = DEP_CON (dep);
|
||
bool cancelled = (DEP_STATUS (dep) & DEP_CANCELLED) != 0;
|
||
|
||
/* Resolve the dependence between INSN and NEXT.
|
||
sd_resolve_dep () moves current dep to another list thus
|
||
advancing the iterator. */
|
||
sd_resolve_dep (sd_it);
|
||
|
||
if (cancelled)
|
||
{
|
||
if (must_restore_pattern_p (next, dep))
|
||
restore_pattern (dep, false);
|
||
continue;
|
||
}
|
||
|
||
/* Don't bother trying to mark next as ready if insn is a debug
|
||
insn. If insn is the last hard dependency, it will have
|
||
already been discounted. */
|
||
if (DEBUG_INSN_P (insn) && !DEBUG_INSN_P (next))
|
||
continue;
|
||
|
||
if (!IS_SPECULATION_BRANCHY_CHECK_P (insn))
|
||
{
|
||
int effective_cost;
|
||
|
||
effective_cost = try_ready (next);
|
||
|
||
if (effective_cost >= 0
|
||
&& SCHED_GROUP_P (next)
|
||
&& advance < effective_cost)
|
||
advance = effective_cost;
|
||
}
|
||
else
|
||
/* Check always has only one forward dependence (to the first insn in
|
||
the recovery block), therefore, this will be executed only once. */
|
||
{
|
||
gcc_assert (sd_lists_empty_p (insn, SD_LIST_FORW));
|
||
fix_recovery_deps (RECOVERY_BLOCK (insn));
|
||
}
|
||
}
|
||
|
||
/* Annotate the instruction with issue information -- TImode
|
||
indicates that the instruction is expected not to be able
|
||
to issue on the same cycle as the previous insn. A machine
|
||
may use this information to decide how the instruction should
|
||
be aligned. */
|
||
if (issue_rate > 1
|
||
&& GET_CODE (PATTERN (insn)) != USE
|
||
&& GET_CODE (PATTERN (insn)) != CLOBBER
|
||
&& !DEBUG_INSN_P (insn))
|
||
{
|
||
if (reload_completed)
|
||
PUT_MODE (insn, clock_var > last_clock_var ? TImode : VOIDmode);
|
||
last_clock_var = clock_var;
|
||
}
|
||
|
||
if (nonscheduled_insns_begin != NULL_RTX)
|
||
/* Indicate to debug counters that INSN is scheduled. */
|
||
nonscheduled_insns_begin = insn;
|
||
|
||
return advance;
|
||
}
|
||
|
||
/* Functions for handling of notes. */
|
||
|
||
/* Add note list that ends on FROM_END to the end of TO_ENDP. */
|
||
void
|
||
concat_note_lists (rtx_insn *from_end, rtx_insn **to_endp)
|
||
{
|
||
rtx_insn *from_start;
|
||
|
||
/* It's easy when have nothing to concat. */
|
||
if (from_end == NULL)
|
||
return;
|
||
|
||
/* It's also easy when destination is empty. */
|
||
if (*to_endp == NULL)
|
||
{
|
||
*to_endp = from_end;
|
||
return;
|
||
}
|
||
|
||
from_start = from_end;
|
||
while (PREV_INSN (from_start) != NULL)
|
||
from_start = PREV_INSN (from_start);
|
||
|
||
SET_PREV_INSN (from_start) = *to_endp;
|
||
SET_NEXT_INSN (*to_endp) = from_start;
|
||
*to_endp = from_end;
|
||
}
|
||
|
||
/* Delete notes between HEAD and TAIL and put them in the chain
|
||
of notes ended by NOTE_LIST. */
|
||
void
|
||
remove_notes (rtx_insn *head, rtx_insn *tail)
|
||
{
|
||
rtx_insn *next_tail, *insn, *next;
|
||
|
||
note_list = 0;
|
||
if (head == tail && !INSN_P (head))
|
||
return;
|
||
|
||
next_tail = NEXT_INSN (tail);
|
||
for (insn = head; insn != next_tail; insn = next)
|
||
{
|
||
next = NEXT_INSN (insn);
|
||
if (!NOTE_P (insn))
|
||
continue;
|
||
|
||
switch (NOTE_KIND (insn))
|
||
{
|
||
case NOTE_INSN_BASIC_BLOCK:
|
||
continue;
|
||
|
||
case NOTE_INSN_EPILOGUE_BEG:
|
||
if (insn != tail)
|
||
{
|
||
remove_insn (insn);
|
||
add_reg_note (next, REG_SAVE_NOTE,
|
||
GEN_INT (NOTE_INSN_EPILOGUE_BEG));
|
||
break;
|
||
}
|
||
/* FALLTHRU */
|
||
|
||
default:
|
||
remove_insn (insn);
|
||
|
||
/* Add the note to list that ends at NOTE_LIST. */
|
||
SET_PREV_INSN (insn) = note_list;
|
||
SET_NEXT_INSN (insn) = NULL_RTX;
|
||
if (note_list)
|
||
SET_NEXT_INSN (note_list) = insn;
|
||
note_list = insn;
|
||
break;
|
||
}
|
||
|
||
gcc_assert ((sel_sched_p () || insn != tail) && insn != head);
|
||
}
|
||
}
|
||
|
||
/* A structure to record enough data to allow us to backtrack the scheduler to
|
||
a previous state. */
|
||
struct haifa_saved_data
|
||
{
|
||
/* Next entry on the list. */
|
||
struct haifa_saved_data *next;
|
||
|
||
/* Backtracking is associated with scheduling insns that have delay slots.
|
||
DELAY_PAIR points to the structure that contains the insns involved, and
|
||
the number of cycles between them. */
|
||
struct delay_pair *delay_pair;
|
||
|
||
/* Data used by the frontend (e.g. sched-ebb or sched-rgn). */
|
||
void *fe_saved_data;
|
||
/* Data used by the backend. */
|
||
void *be_saved_data;
|
||
|
||
/* Copies of global state. */
|
||
int clock_var, last_clock_var;
|
||
struct ready_list ready;
|
||
state_t curr_state;
|
||
|
||
rtx_insn *last_scheduled_insn;
|
||
rtx_insn *last_nondebug_scheduled_insn;
|
||
rtx_insn *nonscheduled_insns_begin;
|
||
int cycle_issued_insns;
|
||
|
||
/* Copies of state used in the inner loop of schedule_block. */
|
||
struct sched_block_state sched_block;
|
||
|
||
/* We don't need to save q_ptr, as its value is arbitrary and we can set it
|
||
to 0 when restoring. */
|
||
int q_size;
|
||
rtx_insn_list **insn_queue;
|
||
|
||
/* Describe pattern replacements that occurred since this backtrack point
|
||
was queued. */
|
||
vec<dep_t> replacement_deps;
|
||
vec<int> replace_apply;
|
||
|
||
/* A copy of the next-cycle replacement vectors at the time of the backtrack
|
||
point. */
|
||
vec<dep_t> next_cycle_deps;
|
||
vec<int> next_cycle_apply;
|
||
};
|
||
|
||
/* A record, in reverse order, of all scheduled insns which have delay slots
|
||
and may require backtracking. */
|
||
static struct haifa_saved_data *backtrack_queue;
|
||
|
||
/* For every dependency of INSN, set the FEEDS_BACKTRACK_INSN bit according
|
||
to SET_P. */
|
||
static void
|
||
mark_backtrack_feeds (rtx_insn *insn, int set_p)
|
||
{
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
FOR_EACH_DEP (insn, SD_LIST_HARD_BACK, sd_it, dep)
|
||
{
|
||
FEEDS_BACKTRACK_INSN (DEP_PRO (dep)) = set_p;
|
||
}
|
||
}
|
||
|
||
/* Save the current scheduler state so that we can backtrack to it
|
||
later if necessary. PAIR gives the insns that make it necessary to
|
||
save this point. SCHED_BLOCK is the local state of schedule_block
|
||
that need to be saved. */
|
||
static void
|
||
save_backtrack_point (struct delay_pair *pair,
|
||
struct sched_block_state sched_block)
|
||
{
|
||
int i;
|
||
struct haifa_saved_data *save = XNEW (struct haifa_saved_data);
|
||
|
||
save->curr_state = xmalloc (dfa_state_size);
|
||
memcpy (save->curr_state, curr_state, dfa_state_size);
|
||
|
||
save->ready.first = ready.first;
|
||
save->ready.n_ready = ready.n_ready;
|
||
save->ready.n_debug = ready.n_debug;
|
||
save->ready.veclen = ready.veclen;
|
||
save->ready.vec = XNEWVEC (rtx_insn *, ready.veclen);
|
||
memcpy (save->ready.vec, ready.vec, ready.veclen * sizeof (rtx));
|
||
|
||
save->insn_queue = XNEWVEC (rtx_insn_list *, max_insn_queue_index + 1);
|
||
save->q_size = q_size;
|
||
for (i = 0; i <= max_insn_queue_index; i++)
|
||
{
|
||
int q = NEXT_Q_AFTER (q_ptr, i);
|
||
save->insn_queue[i] = copy_INSN_LIST (insn_queue[q]);
|
||
}
|
||
|
||
save->clock_var = clock_var;
|
||
save->last_clock_var = last_clock_var;
|
||
save->cycle_issued_insns = cycle_issued_insns;
|
||
save->last_scheduled_insn = last_scheduled_insn;
|
||
save->last_nondebug_scheduled_insn = last_nondebug_scheduled_insn;
|
||
save->nonscheduled_insns_begin = nonscheduled_insns_begin;
|
||
|
||
save->sched_block = sched_block;
|
||
|
||
save->replacement_deps.create (0);
|
||
save->replace_apply.create (0);
|
||
save->next_cycle_deps = next_cycle_replace_deps.copy ();
|
||
save->next_cycle_apply = next_cycle_apply.copy ();
|
||
|
||
if (current_sched_info->save_state)
|
||
save->fe_saved_data = (*current_sched_info->save_state) ();
|
||
|
||
if (targetm.sched.alloc_sched_context)
|
||
{
|
||
save->be_saved_data = targetm.sched.alloc_sched_context ();
|
||
targetm.sched.init_sched_context (save->be_saved_data, false);
|
||
}
|
||
else
|
||
save->be_saved_data = NULL;
|
||
|
||
save->delay_pair = pair;
|
||
|
||
save->next = backtrack_queue;
|
||
backtrack_queue = save;
|
||
|
||
while (pair)
|
||
{
|
||
mark_backtrack_feeds (pair->i2, 1);
|
||
INSN_TICK (pair->i2) = INVALID_TICK;
|
||
INSN_EXACT_TICK (pair->i2) = clock_var + pair_delay (pair);
|
||
SHADOW_P (pair->i2) = pair->stages == 0;
|
||
pair = pair->next_same_i1;
|
||
}
|
||
}
|
||
|
||
/* Walk the ready list and all queues. If any insns have unresolved backwards
|
||
dependencies, these must be cancelled deps, broken by predication. Set or
|
||
clear (depending on SET) the DEP_CANCELLED bit in DEP_STATUS. */
|
||
|
||
static void
|
||
toggle_cancelled_flags (bool set)
|
||
{
|
||
int i;
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
|
||
if (ready.n_ready > 0)
|
||
{
|
||
rtx_insn **first = ready_lastpos (&ready);
|
||
for (i = 0; i < ready.n_ready; i++)
|
||
FOR_EACH_DEP (first[i], SD_LIST_BACK, sd_it, dep)
|
||
if (!DEBUG_INSN_P (DEP_PRO (dep)))
|
||
{
|
||
if (set)
|
||
DEP_STATUS (dep) |= DEP_CANCELLED;
|
||
else
|
||
DEP_STATUS (dep) &= ~DEP_CANCELLED;
|
||
}
|
||
}
|
||
for (i = 0; i <= max_insn_queue_index; i++)
|
||
{
|
||
int q = NEXT_Q_AFTER (q_ptr, i);
|
||
rtx_insn_list *link;
|
||
for (link = insn_queue[q]; link; link = link->next ())
|
||
{
|
||
rtx_insn *insn = link->insn ();
|
||
FOR_EACH_DEP (insn, SD_LIST_BACK, sd_it, dep)
|
||
if (!DEBUG_INSN_P (DEP_PRO (dep)))
|
||
{
|
||
if (set)
|
||
DEP_STATUS (dep) |= DEP_CANCELLED;
|
||
else
|
||
DEP_STATUS (dep) &= ~DEP_CANCELLED;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Undo the replacements that have occurred after backtrack point SAVE
|
||
was placed. */
|
||
static void
|
||
undo_replacements_for_backtrack (struct haifa_saved_data *save)
|
||
{
|
||
while (!save->replacement_deps.is_empty ())
|
||
{
|
||
dep_t dep = save->replacement_deps.pop ();
|
||
int apply_p = save->replace_apply.pop ();
|
||
|
||
if (apply_p)
|
||
restore_pattern (dep, true);
|
||
else
|
||
apply_replacement (dep, true);
|
||
}
|
||
save->replacement_deps.release ();
|
||
save->replace_apply.release ();
|
||
}
|
||
|
||
/* Pop entries from the SCHEDULED_INSNS vector up to and including INSN.
|
||
Restore their dependencies to an unresolved state, and mark them as
|
||
queued nowhere. */
|
||
|
||
static void
|
||
unschedule_insns_until (rtx_insn *insn)
|
||
{
|
||
auto_vec<rtx_insn *> recompute_vec;
|
||
|
||
/* Make two passes over the insns to be unscheduled. First, we clear out
|
||
dependencies and other trivial bookkeeping. */
|
||
for (;;)
|
||
{
|
||
rtx_insn *last;
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
|
||
last = scheduled_insns.pop ();
|
||
|
||
/* This will be changed by restore_backtrack_point if the insn is in
|
||
any queue. */
|
||
QUEUE_INDEX (last) = QUEUE_NOWHERE;
|
||
if (last != insn)
|
||
INSN_TICK (last) = INVALID_TICK;
|
||
|
||
if (modulo_ii > 0 && INSN_UID (last) < modulo_iter0_max_uid)
|
||
modulo_insns_scheduled--;
|
||
|
||
for (sd_it = sd_iterator_start (last, SD_LIST_RES_FORW);
|
||
sd_iterator_cond (&sd_it, &dep);)
|
||
{
|
||
rtx_insn *con = DEP_CON (dep);
|
||
sd_unresolve_dep (sd_it);
|
||
if (!MUST_RECOMPUTE_SPEC_P (con))
|
||
{
|
||
MUST_RECOMPUTE_SPEC_P (con) = 1;
|
||
recompute_vec.safe_push (con);
|
||
}
|
||
}
|
||
|
||
if (last == insn)
|
||
break;
|
||
}
|
||
|
||
/* A second pass, to update ready and speculation status for insns
|
||
depending on the unscheduled ones. The first pass must have
|
||
popped the scheduled_insns vector up to the point where we
|
||
restart scheduling, as recompute_todo_spec requires it to be
|
||
up-to-date. */
|
||
while (!recompute_vec.is_empty ())
|
||
{
|
||
rtx_insn *con;
|
||
|
||
con = recompute_vec.pop ();
|
||
MUST_RECOMPUTE_SPEC_P (con) = 0;
|
||
if (!sd_lists_empty_p (con, SD_LIST_HARD_BACK))
|
||
{
|
||
TODO_SPEC (con) = HARD_DEP;
|
||
INSN_TICK (con) = INVALID_TICK;
|
||
if (PREDICATED_PAT (con) != NULL_RTX)
|
||
haifa_change_pattern (con, ORIG_PAT (con));
|
||
}
|
||
else if (QUEUE_INDEX (con) != QUEUE_SCHEDULED)
|
||
TODO_SPEC (con) = recompute_todo_spec (con, true);
|
||
}
|
||
}
|
||
|
||
/* Restore scheduler state from the topmost entry on the backtracking queue.
|
||
PSCHED_BLOCK_P points to the local data of schedule_block that we must
|
||
overwrite with the saved data.
|
||
The caller must already have called unschedule_insns_until. */
|
||
|
||
static void
|
||
restore_last_backtrack_point (struct sched_block_state *psched_block)
|
||
{
|
||
int i;
|
||
struct haifa_saved_data *save = backtrack_queue;
|
||
|
||
backtrack_queue = save->next;
|
||
|
||
if (current_sched_info->restore_state)
|
||
(*current_sched_info->restore_state) (save->fe_saved_data);
|
||
|
||
if (targetm.sched.alloc_sched_context)
|
||
{
|
||
targetm.sched.set_sched_context (save->be_saved_data);
|
||
targetm.sched.free_sched_context (save->be_saved_data);
|
||
}
|
||
|
||
/* Do this first since it clobbers INSN_TICK of the involved
|
||
instructions. */
|
||
undo_replacements_for_backtrack (save);
|
||
|
||
/* Clear the QUEUE_INDEX of everything in the ready list or one
|
||
of the queues. */
|
||
if (ready.n_ready > 0)
|
||
{
|
||
rtx_insn **first = ready_lastpos (&ready);
|
||
for (i = 0; i < ready.n_ready; i++)
|
||
{
|
||
rtx_insn *insn = first[i];
|
||
QUEUE_INDEX (insn) = QUEUE_NOWHERE;
|
||
INSN_TICK (insn) = INVALID_TICK;
|
||
}
|
||
}
|
||
for (i = 0; i <= max_insn_queue_index; i++)
|
||
{
|
||
int q = NEXT_Q_AFTER (q_ptr, i);
|
||
|
||
for (rtx_insn_list *link = insn_queue[q]; link; link = link->next ())
|
||
{
|
||
rtx_insn *x = link->insn ();
|
||
QUEUE_INDEX (x) = QUEUE_NOWHERE;
|
||
INSN_TICK (x) = INVALID_TICK;
|
||
}
|
||
free_INSN_LIST_list (&insn_queue[q]);
|
||
}
|
||
|
||
free (ready.vec);
|
||
ready = save->ready;
|
||
|
||
if (ready.n_ready > 0)
|
||
{
|
||
rtx_insn **first = ready_lastpos (&ready);
|
||
for (i = 0; i < ready.n_ready; i++)
|
||
{
|
||
rtx_insn *insn = first[i];
|
||
QUEUE_INDEX (insn) = QUEUE_READY;
|
||
TODO_SPEC (insn) = recompute_todo_spec (insn, true);
|
||
INSN_TICK (insn) = save->clock_var;
|
||
}
|
||
}
|
||
|
||
q_ptr = 0;
|
||
q_size = save->q_size;
|
||
for (i = 0; i <= max_insn_queue_index; i++)
|
||
{
|
||
int q = NEXT_Q_AFTER (q_ptr, i);
|
||
|
||
insn_queue[q] = save->insn_queue[q];
|
||
|
||
for (rtx_insn_list *link = insn_queue[q]; link; link = link->next ())
|
||
{
|
||
rtx_insn *x = link->insn ();
|
||
QUEUE_INDEX (x) = i;
|
||
TODO_SPEC (x) = recompute_todo_spec (x, true);
|
||
INSN_TICK (x) = save->clock_var + i;
|
||
}
|
||
}
|
||
free (save->insn_queue);
|
||
|
||
toggle_cancelled_flags (true);
|
||
|
||
clock_var = save->clock_var;
|
||
last_clock_var = save->last_clock_var;
|
||
cycle_issued_insns = save->cycle_issued_insns;
|
||
last_scheduled_insn = save->last_scheduled_insn;
|
||
last_nondebug_scheduled_insn = save->last_nondebug_scheduled_insn;
|
||
nonscheduled_insns_begin = save->nonscheduled_insns_begin;
|
||
|
||
*psched_block = save->sched_block;
|
||
|
||
memcpy (curr_state, save->curr_state, dfa_state_size);
|
||
free (save->curr_state);
|
||
|
||
mark_backtrack_feeds (save->delay_pair->i2, 0);
|
||
|
||
gcc_assert (next_cycle_replace_deps.is_empty ());
|
||
next_cycle_replace_deps = save->next_cycle_deps.copy ();
|
||
next_cycle_apply = save->next_cycle_apply.copy ();
|
||
|
||
free (save);
|
||
|
||
for (save = backtrack_queue; save; save = save->next)
|
||
{
|
||
mark_backtrack_feeds (save->delay_pair->i2, 1);
|
||
}
|
||
}
|
||
|
||
/* Discard all data associated with the topmost entry in the backtrack
|
||
queue. If RESET_TICK is false, we just want to free the data. If true,
|
||
we are doing this because we discovered a reason to backtrack. In the
|
||
latter case, also reset the INSN_TICK for the shadow insn. */
|
||
static void
|
||
free_topmost_backtrack_point (bool reset_tick)
|
||
{
|
||
struct haifa_saved_data *save = backtrack_queue;
|
||
int i;
|
||
|
||
backtrack_queue = save->next;
|
||
|
||
if (reset_tick)
|
||
{
|
||
struct delay_pair *pair = save->delay_pair;
|
||
while (pair)
|
||
{
|
||
INSN_TICK (pair->i2) = INVALID_TICK;
|
||
INSN_EXACT_TICK (pair->i2) = INVALID_TICK;
|
||
pair = pair->next_same_i1;
|
||
}
|
||
undo_replacements_for_backtrack (save);
|
||
}
|
||
else
|
||
{
|
||
save->replacement_deps.release ();
|
||
save->replace_apply.release ();
|
||
}
|
||
|
||
if (targetm.sched.free_sched_context)
|
||
targetm.sched.free_sched_context (save->be_saved_data);
|
||
if (current_sched_info->restore_state)
|
||
free (save->fe_saved_data);
|
||
for (i = 0; i <= max_insn_queue_index; i++)
|
||
free_INSN_LIST_list (&save->insn_queue[i]);
|
||
free (save->insn_queue);
|
||
free (save->curr_state);
|
||
free (save->ready.vec);
|
||
free (save);
|
||
}
|
||
|
||
/* Free the entire backtrack queue. */
|
||
static void
|
||
free_backtrack_queue (void)
|
||
{
|
||
while (backtrack_queue)
|
||
free_topmost_backtrack_point (false);
|
||
}
|
||
|
||
/* Apply a replacement described by DESC. If IMMEDIATELY is false, we
|
||
may have to postpone the replacement until the start of the next cycle,
|
||
at which point we will be called again with IMMEDIATELY true. This is
|
||
only done for machines which have instruction packets with explicit
|
||
parallelism however. */
|
||
static void
|
||
apply_replacement (dep_t dep, bool immediately)
|
||
{
|
||
struct dep_replacement *desc = DEP_REPLACE (dep);
|
||
if (!immediately && targetm.sched.exposed_pipeline && reload_completed)
|
||
{
|
||
next_cycle_replace_deps.safe_push (dep);
|
||
next_cycle_apply.safe_push (1);
|
||
}
|
||
else
|
||
{
|
||
bool success;
|
||
|
||
if (QUEUE_INDEX (desc->insn) == QUEUE_SCHEDULED)
|
||
return;
|
||
|
||
if (sched_verbose >= 5)
|
||
fprintf (sched_dump, "applying replacement for insn %d\n",
|
||
INSN_UID (desc->insn));
|
||
|
||
success = validate_change (desc->insn, desc->loc, desc->newval, 0);
|
||
gcc_assert (success);
|
||
|
||
update_insn_after_change (desc->insn);
|
||
if ((TODO_SPEC (desc->insn) & (HARD_DEP | DEP_POSTPONED)) == 0)
|
||
fix_tick_ready (desc->insn);
|
||
|
||
if (backtrack_queue != NULL)
|
||
{
|
||
backtrack_queue->replacement_deps.safe_push (dep);
|
||
backtrack_queue->replace_apply.safe_push (1);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* We have determined that a pattern involved in DEP must be restored.
|
||
If IMMEDIATELY is false, we may have to postpone the replacement
|
||
until the start of the next cycle, at which point we will be called
|
||
again with IMMEDIATELY true. */
|
||
static void
|
||
restore_pattern (dep_t dep, bool immediately)
|
||
{
|
||
rtx_insn *next = DEP_CON (dep);
|
||
int tick = INSN_TICK (next);
|
||
|
||
/* If we already scheduled the insn, the modified version is
|
||
correct. */
|
||
if (QUEUE_INDEX (next) == QUEUE_SCHEDULED)
|
||
return;
|
||
|
||
if (!immediately && targetm.sched.exposed_pipeline && reload_completed)
|
||
{
|
||
next_cycle_replace_deps.safe_push (dep);
|
||
next_cycle_apply.safe_push (0);
|
||
return;
|
||
}
|
||
|
||
|
||
if (DEP_TYPE (dep) == REG_DEP_CONTROL)
|
||
{
|
||
if (sched_verbose >= 5)
|
||
fprintf (sched_dump, "restoring pattern for insn %d\n",
|
||
INSN_UID (next));
|
||
haifa_change_pattern (next, ORIG_PAT (next));
|
||
}
|
||
else
|
||
{
|
||
struct dep_replacement *desc = DEP_REPLACE (dep);
|
||
bool success;
|
||
|
||
if (sched_verbose >= 5)
|
||
fprintf (sched_dump, "restoring pattern for insn %d\n",
|
||
INSN_UID (desc->insn));
|
||
tick = INSN_TICK (desc->insn);
|
||
|
||
success = validate_change (desc->insn, desc->loc, desc->orig, 0);
|
||
gcc_assert (success);
|
||
update_insn_after_change (desc->insn);
|
||
if (backtrack_queue != NULL)
|
||
{
|
||
backtrack_queue->replacement_deps.safe_push (dep);
|
||
backtrack_queue->replace_apply.safe_push (0);
|
||
}
|
||
}
|
||
INSN_TICK (next) = tick;
|
||
if (TODO_SPEC (next) == DEP_POSTPONED)
|
||
return;
|
||
|
||
if (sd_lists_empty_p (next, SD_LIST_BACK))
|
||
TODO_SPEC (next) = 0;
|
||
else if (!sd_lists_empty_p (next, SD_LIST_HARD_BACK))
|
||
TODO_SPEC (next) = HARD_DEP;
|
||
}
|
||
|
||
/* Perform pattern replacements that were queued up until the next
|
||
cycle. */
|
||
static void
|
||
perform_replacements_new_cycle (void)
|
||
{
|
||
int i;
|
||
dep_t dep;
|
||
FOR_EACH_VEC_ELT (next_cycle_replace_deps, i, dep)
|
||
{
|
||
int apply_p = next_cycle_apply[i];
|
||
if (apply_p)
|
||
apply_replacement (dep, true);
|
||
else
|
||
restore_pattern (dep, true);
|
||
}
|
||
next_cycle_replace_deps.truncate (0);
|
||
next_cycle_apply.truncate (0);
|
||
}
|
||
|
||
/* Compute INSN_TICK_ESTIMATE for INSN. PROCESSED is a bitmap of
|
||
instructions we've previously encountered, a set bit prevents
|
||
recursion. BUDGET is a limit on how far ahead we look, it is
|
||
reduced on recursive calls. Return true if we produced a good
|
||
estimate, or false if we exceeded the budget. */
|
||
static bool
|
||
estimate_insn_tick (bitmap processed, rtx_insn *insn, int budget)
|
||
{
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
int earliest = INSN_TICK (insn);
|
||
|
||
FOR_EACH_DEP (insn, SD_LIST_BACK, sd_it, dep)
|
||
{
|
||
rtx_insn *pro = DEP_PRO (dep);
|
||
int t;
|
||
|
||
if (DEP_STATUS (dep) & DEP_CANCELLED)
|
||
continue;
|
||
|
||
if (QUEUE_INDEX (pro) == QUEUE_SCHEDULED)
|
||
gcc_assert (INSN_TICK (pro) + dep_cost (dep) <= INSN_TICK (insn));
|
||
else
|
||
{
|
||
int cost = dep_cost (dep);
|
||
if (cost >= budget)
|
||
return false;
|
||
if (!bitmap_bit_p (processed, INSN_LUID (pro)))
|
||
{
|
||
if (!estimate_insn_tick (processed, pro, budget - cost))
|
||
return false;
|
||
}
|
||
gcc_assert (INSN_TICK_ESTIMATE (pro) != INVALID_TICK);
|
||
t = INSN_TICK_ESTIMATE (pro) + cost;
|
||
if (earliest == INVALID_TICK || t > earliest)
|
||
earliest = t;
|
||
}
|
||
}
|
||
bitmap_set_bit (processed, INSN_LUID (insn));
|
||
INSN_TICK_ESTIMATE (insn) = earliest;
|
||
return true;
|
||
}
|
||
|
||
/* Examine the pair of insns in P, and estimate (optimistically, assuming
|
||
infinite resources) the cycle in which the delayed shadow can be issued.
|
||
Return the number of cycles that must pass before the real insn can be
|
||
issued in order to meet this constraint. */
|
||
static int
|
||
estimate_shadow_tick (struct delay_pair *p)
|
||
{
|
||
bitmap_head processed;
|
||
int t;
|
||
bool cutoff;
|
||
bitmap_initialize (&processed, 0);
|
||
|
||
cutoff = !estimate_insn_tick (&processed, p->i2,
|
||
max_insn_queue_index + pair_delay (p));
|
||
bitmap_clear (&processed);
|
||
if (cutoff)
|
||
return max_insn_queue_index;
|
||
t = INSN_TICK_ESTIMATE (p->i2) - (clock_var + pair_delay (p) + 1);
|
||
if (t > 0)
|
||
return t;
|
||
return 0;
|
||
}
|
||
|
||
/* If INSN has no unresolved backwards dependencies, add it to the schedule and
|
||
recursively resolve all its forward dependencies. */
|
||
static void
|
||
resolve_dependencies (rtx_insn *insn)
|
||
{
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
|
||
/* Don't use sd_lists_empty_p; it ignores debug insns. */
|
||
if (DEPS_LIST_FIRST (INSN_HARD_BACK_DEPS (insn)) != NULL
|
||
|| DEPS_LIST_FIRST (INSN_SPEC_BACK_DEPS (insn)) != NULL)
|
||
return;
|
||
|
||
if (sched_verbose >= 4)
|
||
fprintf (sched_dump, ";;\tquickly resolving %d\n", INSN_UID (insn));
|
||
|
||
if (QUEUE_INDEX (insn) >= 0)
|
||
queue_remove (insn);
|
||
|
||
scheduled_insns.safe_push (insn);
|
||
|
||
/* Update dependent instructions. */
|
||
for (sd_it = sd_iterator_start (insn, SD_LIST_FORW);
|
||
sd_iterator_cond (&sd_it, &dep);)
|
||
{
|
||
rtx_insn *next = DEP_CON (dep);
|
||
|
||
if (sched_verbose >= 4)
|
||
fprintf (sched_dump, ";;\t\tdep %d against %d\n", INSN_UID (insn),
|
||
INSN_UID (next));
|
||
|
||
/* Resolve the dependence between INSN and NEXT.
|
||
sd_resolve_dep () moves current dep to another list thus
|
||
advancing the iterator. */
|
||
sd_resolve_dep (sd_it);
|
||
|
||
if (!IS_SPECULATION_BRANCHY_CHECK_P (insn))
|
||
{
|
||
resolve_dependencies (next);
|
||
}
|
||
else
|
||
/* Check always has only one forward dependence (to the first insn in
|
||
the recovery block), therefore, this will be executed only once. */
|
||
{
|
||
gcc_assert (sd_lists_empty_p (insn, SD_LIST_FORW));
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/* Return the head and tail pointers of ebb starting at BEG and ending
|
||
at END. */
|
||
void
|
||
get_ebb_head_tail (basic_block beg, basic_block end,
|
||
rtx_insn **headp, rtx_insn **tailp)
|
||
{
|
||
rtx_insn *beg_head = BB_HEAD (beg);
|
||
rtx_insn * beg_tail = BB_END (beg);
|
||
rtx_insn * end_head = BB_HEAD (end);
|
||
rtx_insn * end_tail = BB_END (end);
|
||
|
||
/* Don't include any notes or labels at the beginning of the BEG
|
||
basic block, or notes at the end of the END basic blocks. */
|
||
|
||
if (LABEL_P (beg_head))
|
||
beg_head = NEXT_INSN (beg_head);
|
||
|
||
while (beg_head != beg_tail)
|
||
if (NOTE_P (beg_head))
|
||
beg_head = NEXT_INSN (beg_head);
|
||
else if (DEBUG_INSN_P (beg_head))
|
||
{
|
||
rtx_insn * note, *next;
|
||
|
||
for (note = NEXT_INSN (beg_head);
|
||
note != beg_tail;
|
||
note = next)
|
||
{
|
||
next = NEXT_INSN (note);
|
||
if (NOTE_P (note))
|
||
{
|
||
if (sched_verbose >= 9)
|
||
fprintf (sched_dump, "reorder %i\n", INSN_UID (note));
|
||
|
||
reorder_insns_nobb (note, note, PREV_INSN (beg_head));
|
||
|
||
if (BLOCK_FOR_INSN (note) != beg)
|
||
df_insn_change_bb (note, beg);
|
||
}
|
||
else if (!DEBUG_INSN_P (note))
|
||
break;
|
||
}
|
||
|
||
break;
|
||
}
|
||
else
|
||
break;
|
||
|
||
*headp = beg_head;
|
||
|
||
if (beg == end)
|
||
end_head = beg_head;
|
||
else if (LABEL_P (end_head))
|
||
end_head = NEXT_INSN (end_head);
|
||
|
||
while (end_head != end_tail)
|
||
if (NOTE_P (end_tail))
|
||
end_tail = PREV_INSN (end_tail);
|
||
else if (DEBUG_INSN_P (end_tail))
|
||
{
|
||
rtx_insn * note, *prev;
|
||
|
||
for (note = PREV_INSN (end_tail);
|
||
note != end_head;
|
||
note = prev)
|
||
{
|
||
prev = PREV_INSN (note);
|
||
if (NOTE_P (note))
|
||
{
|
||
if (sched_verbose >= 9)
|
||
fprintf (sched_dump, "reorder %i\n", INSN_UID (note));
|
||
|
||
reorder_insns_nobb (note, note, end_tail);
|
||
|
||
if (end_tail == BB_END (end))
|
||
BB_END (end) = note;
|
||
|
||
if (BLOCK_FOR_INSN (note) != end)
|
||
df_insn_change_bb (note, end);
|
||
}
|
||
else if (!DEBUG_INSN_P (note))
|
||
break;
|
||
}
|
||
|
||
break;
|
||
}
|
||
else
|
||
break;
|
||
|
||
*tailp = end_tail;
|
||
}
|
||
|
||
/* Return nonzero if there are no real insns in the range [ HEAD, TAIL ]. */
|
||
|
||
int
|
||
no_real_insns_p (const rtx_insn *head, const rtx_insn *tail)
|
||
{
|
||
while (head != NEXT_INSN (tail))
|
||
{
|
||
if (!NOTE_P (head) && !LABEL_P (head))
|
||
return 0;
|
||
head = NEXT_INSN (head);
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
/* Restore-other-notes: NOTE_LIST is the end of a chain of notes
|
||
previously found among the insns. Insert them just before HEAD. */
|
||
rtx_insn *
|
||
restore_other_notes (rtx_insn *head, basic_block head_bb)
|
||
{
|
||
if (note_list != 0)
|
||
{
|
||
rtx_insn *note_head = note_list;
|
||
|
||
if (head)
|
||
head_bb = BLOCK_FOR_INSN (head);
|
||
else
|
||
head = NEXT_INSN (bb_note (head_bb));
|
||
|
||
while (PREV_INSN (note_head))
|
||
{
|
||
set_block_for_insn (note_head, head_bb);
|
||
note_head = PREV_INSN (note_head);
|
||
}
|
||
/* In the above cycle we've missed this note. */
|
||
set_block_for_insn (note_head, head_bb);
|
||
|
||
SET_PREV_INSN (note_head) = PREV_INSN (head);
|
||
SET_NEXT_INSN (PREV_INSN (head)) = note_head;
|
||
SET_PREV_INSN (head) = note_list;
|
||
SET_NEXT_INSN (note_list) = head;
|
||
|
||
if (BLOCK_FOR_INSN (head) != head_bb)
|
||
BB_END (head_bb) = note_list;
|
||
|
||
head = note_head;
|
||
}
|
||
|
||
return head;
|
||
}
|
||
|
||
/* When we know we are going to discard the schedule due to a failed attempt
|
||
at modulo scheduling, undo all replacements. */
|
||
static void
|
||
undo_all_replacements (void)
|
||
{
|
||
rtx_insn *insn;
|
||
int i;
|
||
|
||
FOR_EACH_VEC_ELT (scheduled_insns, i, insn)
|
||
{
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
|
||
/* See if we must undo a replacement. */
|
||
for (sd_it = sd_iterator_start (insn, SD_LIST_RES_FORW);
|
||
sd_iterator_cond (&sd_it, &dep); sd_iterator_next (&sd_it))
|
||
{
|
||
struct dep_replacement *desc = DEP_REPLACE (dep);
|
||
if (desc != NULL)
|
||
validate_change (desc->insn, desc->loc, desc->orig, 0);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Return first non-scheduled insn in the current scheduling block.
|
||
This is mostly used for debug-counter purposes. */
|
||
static rtx_insn *
|
||
first_nonscheduled_insn (void)
|
||
{
|
||
rtx_insn *insn = (nonscheduled_insns_begin != NULL_RTX
|
||
? nonscheduled_insns_begin
|
||
: current_sched_info->prev_head);
|
||
|
||
do
|
||
{
|
||
insn = next_nonnote_nondebug_insn (insn);
|
||
}
|
||
while (QUEUE_INDEX (insn) == QUEUE_SCHEDULED);
|
||
|
||
return insn;
|
||
}
|
||
|
||
/* Move insns that became ready to fire from queue to ready list. */
|
||
|
||
static void
|
||
queue_to_ready (struct ready_list *ready)
|
||
{
|
||
rtx_insn *insn;
|
||
rtx_insn_list *link;
|
||
rtx_insn *skip_insn;
|
||
|
||
q_ptr = NEXT_Q (q_ptr);
|
||
|
||
if (dbg_cnt (sched_insn) == false)
|
||
/* If debug counter is activated do not requeue the first
|
||
nonscheduled insn. */
|
||
skip_insn = first_nonscheduled_insn ();
|
||
else
|
||
skip_insn = NULL;
|
||
|
||
/* Add all pending insns that can be scheduled without stalls to the
|
||
ready list. */
|
||
for (link = insn_queue[q_ptr]; link; link = link->next ())
|
||
{
|
||
insn = link->insn ();
|
||
q_size -= 1;
|
||
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump, ";;\t\tQ-->Ready: insn %s: ",
|
||
(*current_sched_info->print_insn) (insn, 0));
|
||
|
||
/* If the ready list is full, delay the insn for 1 cycle.
|
||
See the comment in schedule_block for the rationale. */
|
||
if (!reload_completed
|
||
&& (ready->n_ready - ready->n_debug > MAX_SCHED_READY_INSNS
|
||
|| (sched_pressure == SCHED_PRESSURE_MODEL
|
||
/* Limit pressure recalculations to MAX_SCHED_READY_INSNS
|
||
instructions too. */
|
||
&& model_index (insn) > (model_curr_point
|
||
+ MAX_SCHED_READY_INSNS)))
|
||
&& !(sched_pressure == SCHED_PRESSURE_MODEL
|
||
&& model_curr_point < model_num_insns
|
||
/* Always allow the next model instruction to issue. */
|
||
&& model_index (insn) == model_curr_point)
|
||
&& !SCHED_GROUP_P (insn)
|
||
&& insn != skip_insn)
|
||
{
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump, "keeping in queue, ready full\n");
|
||
queue_insn (insn, 1, "ready full");
|
||
}
|
||
else
|
||
{
|
||
ready_add (ready, insn, false);
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump, "moving to ready without stalls\n");
|
||
}
|
||
}
|
||
free_INSN_LIST_list (&insn_queue[q_ptr]);
|
||
|
||
/* If there are no ready insns, stall until one is ready and add all
|
||
of the pending insns at that point to the ready list. */
|
||
if (ready->n_ready == 0)
|
||
{
|
||
int stalls;
|
||
|
||
for (stalls = 1; stalls <= max_insn_queue_index; stalls++)
|
||
{
|
||
if ((link = insn_queue[NEXT_Q_AFTER (q_ptr, stalls)]))
|
||
{
|
||
for (; link; link = link->next ())
|
||
{
|
||
insn = link->insn ();
|
||
q_size -= 1;
|
||
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump, ";;\t\tQ-->Ready: insn %s: ",
|
||
(*current_sched_info->print_insn) (insn, 0));
|
||
|
||
ready_add (ready, insn, false);
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump, "moving to ready with %d stalls\n", stalls);
|
||
}
|
||
free_INSN_LIST_list (&insn_queue[NEXT_Q_AFTER (q_ptr, stalls)]);
|
||
|
||
advance_one_cycle ();
|
||
|
||
break;
|
||
}
|
||
|
||
advance_one_cycle ();
|
||
}
|
||
|
||
q_ptr = NEXT_Q_AFTER (q_ptr, stalls);
|
||
clock_var += stalls;
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump, ";;\tAdvancing clock by %d cycle[s] to %d\n",
|
||
stalls, clock_var);
|
||
}
|
||
}
|
||
|
||
/* Used by early_queue_to_ready. Determines whether it is "ok" to
|
||
prematurely move INSN from the queue to the ready list. Currently,
|
||
if a target defines the hook 'is_costly_dependence', this function
|
||
uses the hook to check whether there exist any dependences which are
|
||
considered costly by the target, between INSN and other insns that
|
||
have already been scheduled. Dependences are checked up to Y cycles
|
||
back, with default Y=1; The flag -fsched-stalled-insns-dep=Y allows
|
||
controlling this value.
|
||
(Other considerations could be taken into account instead (or in
|
||
addition) depending on user flags and target hooks. */
|
||
|
||
static bool
|
||
ok_for_early_queue_removal (rtx_insn *insn)
|
||
{
|
||
if (targetm.sched.is_costly_dependence)
|
||
{
|
||
int n_cycles;
|
||
int i = scheduled_insns.length ();
|
||
for (n_cycles = flag_sched_stalled_insns_dep; n_cycles; n_cycles--)
|
||
{
|
||
while (i-- > 0)
|
||
{
|
||
int cost;
|
||
|
||
rtx_insn *prev_insn = scheduled_insns[i];
|
||
|
||
if (!NOTE_P (prev_insn))
|
||
{
|
||
dep_t dep;
|
||
|
||
dep = sd_find_dep_between (prev_insn, insn, true);
|
||
|
||
if (dep != NULL)
|
||
{
|
||
cost = dep_cost (dep);
|
||
|
||
if (targetm.sched.is_costly_dependence (dep, cost,
|
||
flag_sched_stalled_insns_dep - n_cycles))
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (GET_MODE (prev_insn) == TImode) /* end of dispatch group */
|
||
break;
|
||
}
|
||
|
||
if (i == 0)
|
||
break;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
/* Remove insns from the queue, before they become "ready" with respect
|
||
to FU latency considerations. */
|
||
|
||
static int
|
||
early_queue_to_ready (state_t state, struct ready_list *ready)
|
||
{
|
||
rtx_insn *insn;
|
||
rtx_insn_list *link;
|
||
rtx_insn_list *next_link;
|
||
rtx_insn_list *prev_link;
|
||
bool move_to_ready;
|
||
int cost;
|
||
state_t temp_state = alloca (dfa_state_size);
|
||
int stalls;
|
||
int insns_removed = 0;
|
||
|
||
/*
|
||
Flag '-fsched-stalled-insns=X' determines the aggressiveness of this
|
||
function:
|
||
|
||
X == 0: There is no limit on how many queued insns can be removed
|
||
prematurely. (flag_sched_stalled_insns = -1).
|
||
|
||
X >= 1: Only X queued insns can be removed prematurely in each
|
||
invocation. (flag_sched_stalled_insns = X).
|
||
|
||
Otherwise: Early queue removal is disabled.
|
||
(flag_sched_stalled_insns = 0)
|
||
*/
|
||
|
||
if (! flag_sched_stalled_insns)
|
||
return 0;
|
||
|
||
for (stalls = 0; stalls <= max_insn_queue_index; stalls++)
|
||
{
|
||
if ((link = insn_queue[NEXT_Q_AFTER (q_ptr, stalls)]))
|
||
{
|
||
if (sched_verbose > 6)
|
||
fprintf (sched_dump, ";; look at index %d + %d\n", q_ptr, stalls);
|
||
|
||
prev_link = 0;
|
||
while (link)
|
||
{
|
||
next_link = link->next ();
|
||
insn = link->insn ();
|
||
if (insn && sched_verbose > 6)
|
||
print_rtl_single (sched_dump, insn);
|
||
|
||
memcpy (temp_state, state, dfa_state_size);
|
||
if (recog_memoized (insn) < 0)
|
||
/* non-negative to indicate that it's not ready
|
||
to avoid infinite Q->R->Q->R... */
|
||
cost = 0;
|
||
else
|
||
cost = state_transition (temp_state, insn);
|
||
|
||
if (sched_verbose >= 6)
|
||
fprintf (sched_dump, "transition cost = %d\n", cost);
|
||
|
||
move_to_ready = false;
|
||
if (cost < 0)
|
||
{
|
||
move_to_ready = ok_for_early_queue_removal (insn);
|
||
if (move_to_ready == true)
|
||
{
|
||
/* move from Q to R */
|
||
q_size -= 1;
|
||
ready_add (ready, insn, false);
|
||
|
||
if (prev_link)
|
||
XEXP (prev_link, 1) = next_link;
|
||
else
|
||
insn_queue[NEXT_Q_AFTER (q_ptr, stalls)] = next_link;
|
||
|
||
free_INSN_LIST_node (link);
|
||
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump, ";;\t\tEarly Q-->Ready: insn %s\n",
|
||
(*current_sched_info->print_insn) (insn, 0));
|
||
|
||
insns_removed++;
|
||
if (insns_removed == flag_sched_stalled_insns)
|
||
/* Remove no more than flag_sched_stalled_insns insns
|
||
from Q at a time. */
|
||
return insns_removed;
|
||
}
|
||
}
|
||
|
||
if (move_to_ready == false)
|
||
prev_link = link;
|
||
|
||
link = next_link;
|
||
} /* while link */
|
||
} /* if link */
|
||
|
||
} /* for stalls.. */
|
||
|
||
return insns_removed;
|
||
}
|
||
|
||
|
||
/* Print the ready list for debugging purposes.
|
||
If READY_TRY is non-zero then only print insns that max_issue
|
||
will consider. */
|
||
static void
|
||
debug_ready_list_1 (struct ready_list *ready, signed char *ready_try)
|
||
{
|
||
rtx_insn **p;
|
||
int i;
|
||
|
||
if (ready->n_ready == 0)
|
||
{
|
||
fprintf (sched_dump, "\n");
|
||
return;
|
||
}
|
||
|
||
p = ready_lastpos (ready);
|
||
for (i = 0; i < ready->n_ready; i++)
|
||
{
|
||
if (ready_try != NULL && ready_try[ready->n_ready - i - 1])
|
||
continue;
|
||
|
||
fprintf (sched_dump, " %s:%d",
|
||
(*current_sched_info->print_insn) (p[i], 0),
|
||
INSN_LUID (p[i]));
|
||
if (sched_pressure != SCHED_PRESSURE_NONE)
|
||
fprintf (sched_dump, "(cost=%d",
|
||
INSN_REG_PRESSURE_EXCESS_COST_CHANGE (p[i]));
|
||
fprintf (sched_dump, ":prio=%d", INSN_PRIORITY (p[i]));
|
||
if (INSN_TICK (p[i]) > clock_var)
|
||
fprintf (sched_dump, ":delay=%d", INSN_TICK (p[i]) - clock_var);
|
||
if (sched_pressure == SCHED_PRESSURE_MODEL)
|
||
fprintf (sched_dump, ":idx=%d",
|
||
model_index (p[i]));
|
||
if (sched_pressure != SCHED_PRESSURE_NONE)
|
||
fprintf (sched_dump, ")");
|
||
}
|
||
fprintf (sched_dump, "\n");
|
||
}
|
||
|
||
/* Print the ready list. Callable from debugger. */
|
||
static void
|
||
debug_ready_list (struct ready_list *ready)
|
||
{
|
||
debug_ready_list_1 (ready, NULL);
|
||
}
|
||
|
||
/* Search INSN for REG_SAVE_NOTE notes and convert them back into insn
|
||
NOTEs. This is used for NOTE_INSN_EPILOGUE_BEG, so that sched-ebb
|
||
replaces the epilogue note in the correct basic block. */
|
||
void
|
||
reemit_notes (rtx_insn *insn)
|
||
{
|
||
rtx note;
|
||
rtx_insn *last = insn;
|
||
|
||
for (note = REG_NOTES (insn); note; note = XEXP (note, 1))
|
||
{
|
||
if (REG_NOTE_KIND (note) == REG_SAVE_NOTE)
|
||
{
|
||
enum insn_note note_type = (enum insn_note) INTVAL (XEXP (note, 0));
|
||
|
||
last = emit_note_before (note_type, last);
|
||
remove_note (insn, note);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Move INSN. Reemit notes if needed. Update CFG, if needed. */
|
||
static void
|
||
move_insn (rtx_insn *insn, rtx_insn *last, rtx nt)
|
||
{
|
||
if (PREV_INSN (insn) != last)
|
||
{
|
||
basic_block bb;
|
||
rtx_insn *note;
|
||
int jump_p = 0;
|
||
|
||
bb = BLOCK_FOR_INSN (insn);
|
||
|
||
/* BB_HEAD is either LABEL or NOTE. */
|
||
gcc_assert (BB_HEAD (bb) != insn);
|
||
|
||
if (BB_END (bb) == insn)
|
||
/* If this is last instruction in BB, move end marker one
|
||
instruction up. */
|
||
{
|
||
/* Jumps are always placed at the end of basic block. */
|
||
jump_p = control_flow_insn_p (insn);
|
||
|
||
gcc_assert (!jump_p
|
||
|| ((common_sched_info->sched_pass_id == SCHED_RGN_PASS)
|
||
&& IS_SPECULATION_BRANCHY_CHECK_P (insn))
|
||
|| (common_sched_info->sched_pass_id
|
||
== SCHED_EBB_PASS));
|
||
|
||
gcc_assert (BLOCK_FOR_INSN (PREV_INSN (insn)) == bb);
|
||
|
||
BB_END (bb) = PREV_INSN (insn);
|
||
}
|
||
|
||
gcc_assert (BB_END (bb) != last);
|
||
|
||
if (jump_p)
|
||
/* We move the block note along with jump. */
|
||
{
|
||
gcc_assert (nt);
|
||
|
||
note = NEXT_INSN (insn);
|
||
while (NOTE_NOT_BB_P (note) && note != nt)
|
||
note = NEXT_INSN (note);
|
||
|
||
if (note != nt
|
||
&& (LABEL_P (note)
|
||
|| BARRIER_P (note)))
|
||
note = NEXT_INSN (note);
|
||
|
||
gcc_assert (NOTE_INSN_BASIC_BLOCK_P (note));
|
||
}
|
||
else
|
||
note = insn;
|
||
|
||
SET_NEXT_INSN (PREV_INSN (insn)) = NEXT_INSN (note);
|
||
SET_PREV_INSN (NEXT_INSN (note)) = PREV_INSN (insn);
|
||
|
||
SET_NEXT_INSN (note) = NEXT_INSN (last);
|
||
SET_PREV_INSN (NEXT_INSN (last)) = note;
|
||
|
||
SET_NEXT_INSN (last) = insn;
|
||
SET_PREV_INSN (insn) = last;
|
||
|
||
bb = BLOCK_FOR_INSN (last);
|
||
|
||
if (jump_p)
|
||
{
|
||
fix_jump_move (insn);
|
||
|
||
if (BLOCK_FOR_INSN (insn) != bb)
|
||
move_block_after_check (insn);
|
||
|
||
gcc_assert (BB_END (bb) == last);
|
||
}
|
||
|
||
df_insn_change_bb (insn, bb);
|
||
|
||
/* Update BB_END, if needed. */
|
||
if (BB_END (bb) == last)
|
||
BB_END (bb) = insn;
|
||
}
|
||
|
||
SCHED_GROUP_P (insn) = 0;
|
||
}
|
||
|
||
/* Return true if scheduling INSN will finish current clock cycle. */
|
||
static bool
|
||
insn_finishes_cycle_p (rtx_insn *insn)
|
||
{
|
||
if (SCHED_GROUP_P (insn))
|
||
/* After issuing INSN, rest of the sched_group will be forced to issue
|
||
in order. Don't make any plans for the rest of cycle. */
|
||
return true;
|
||
|
||
/* Finishing the block will, apparently, finish the cycle. */
|
||
if (current_sched_info->insn_finishes_block_p
|
||
&& current_sched_info->insn_finishes_block_p (insn))
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
/* Functions to model cache auto-prefetcher.
|
||
|
||
Some of the CPUs have cache auto-prefetcher, which /seems/ to initiate
|
||
memory prefetches if it sees instructions with consequitive memory accesses
|
||
in the instruction stream. Details of such hardware units are not published,
|
||
so we can only guess what exactly is going on there.
|
||
In the scheduler, we model abstract auto-prefetcher. If there are memory
|
||
insns in the ready list (or the queue) that have same memory base, but
|
||
different offsets, then we delay the insns with larger offsets until insns
|
||
with smaller offsets get scheduled. If PARAM_SCHED_AUTOPREF_QUEUE_DEPTH
|
||
is "1", then we look at the ready list; if it is N>1, then we also look
|
||
through N-1 queue entries.
|
||
If the param is N>=0, then rank_for_schedule will consider auto-prefetching
|
||
among its heuristics.
|
||
Param value of "-1" disables modelling of the auto-prefetcher. */
|
||
|
||
/* Initialize autoprefetcher model data for INSN. */
|
||
static void
|
||
autopref_multipass_init (const rtx_insn *insn, int write)
|
||
{
|
||
autopref_multipass_data_t data = &INSN_AUTOPREF_MULTIPASS_DATA (insn)[write];
|
||
|
||
gcc_assert (data->status == AUTOPREF_MULTIPASS_DATA_UNINITIALIZED);
|
||
data->base = NULL_RTX;
|
||
data->offset = 0;
|
||
/* Set insn entry initialized, but not relevant for auto-prefetcher. */
|
||
data->status = AUTOPREF_MULTIPASS_DATA_IRRELEVANT;
|
||
|
||
rtx set = single_set (insn);
|
||
if (set == NULL_RTX)
|
||
return;
|
||
|
||
rtx mem = write ? SET_DEST (set) : SET_SRC (set);
|
||
if (!MEM_P (mem))
|
||
return;
|
||
|
||
struct address_info info;
|
||
decompose_mem_address (&info, mem);
|
||
|
||
/* TODO: Currently only (base+const) addressing is supported. */
|
||
if (info.base == NULL || !REG_P (*info.base)
|
||
|| (info.disp != NULL && !CONST_INT_P (*info.disp)))
|
||
return;
|
||
|
||
/* This insn is relevant for auto-prefetcher. */
|
||
data->base = *info.base;
|
||
data->offset = info.disp ? INTVAL (*info.disp) : 0;
|
||
data->status = AUTOPREF_MULTIPASS_DATA_NORMAL;
|
||
}
|
||
|
||
/* Helper function for rank_for_schedule sorting. */
|
||
static int
|
||
autopref_rank_for_schedule (const rtx_insn *insn1, const rtx_insn *insn2)
|
||
{
|
||
for (int write = 0; write < 2; ++write)
|
||
{
|
||
autopref_multipass_data_t data1
|
||
= &INSN_AUTOPREF_MULTIPASS_DATA (insn1)[write];
|
||
autopref_multipass_data_t data2
|
||
= &INSN_AUTOPREF_MULTIPASS_DATA (insn2)[write];
|
||
|
||
if (data1->status == AUTOPREF_MULTIPASS_DATA_UNINITIALIZED)
|
||
autopref_multipass_init (insn1, write);
|
||
if (data1->status == AUTOPREF_MULTIPASS_DATA_IRRELEVANT)
|
||
continue;
|
||
|
||
if (data2->status == AUTOPREF_MULTIPASS_DATA_UNINITIALIZED)
|
||
autopref_multipass_init (insn2, write);
|
||
if (data2->status == AUTOPREF_MULTIPASS_DATA_IRRELEVANT)
|
||
continue;
|
||
|
||
if (!rtx_equal_p (data1->base, data2->base))
|
||
continue;
|
||
|
||
return data1->offset - data2->offset;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* True if header of debug dump was printed. */
|
||
static bool autopref_multipass_dfa_lookahead_guard_started_dump_p;
|
||
|
||
/* Helper for autopref_multipass_dfa_lookahead_guard.
|
||
Return "1" if INSN1 should be delayed in favor of INSN2. */
|
||
static int
|
||
autopref_multipass_dfa_lookahead_guard_1 (const rtx_insn *insn1,
|
||
const rtx_insn *insn2, int write)
|
||
{
|
||
autopref_multipass_data_t data1
|
||
= &INSN_AUTOPREF_MULTIPASS_DATA (insn1)[write];
|
||
autopref_multipass_data_t data2
|
||
= &INSN_AUTOPREF_MULTIPASS_DATA (insn2)[write];
|
||
|
||
if (data2->status == AUTOPREF_MULTIPASS_DATA_UNINITIALIZED)
|
||
autopref_multipass_init (insn2, write);
|
||
if (data2->status == AUTOPREF_MULTIPASS_DATA_IRRELEVANT)
|
||
return 0;
|
||
|
||
if (rtx_equal_p (data1->base, data2->base)
|
||
&& data1->offset > data2->offset)
|
||
{
|
||
if (sched_verbose >= 2)
|
||
{
|
||
if (!autopref_multipass_dfa_lookahead_guard_started_dump_p)
|
||
{
|
||
fprintf (sched_dump,
|
||
";;\t\tnot trying in max_issue due to autoprefetch "
|
||
"model: ");
|
||
autopref_multipass_dfa_lookahead_guard_started_dump_p = true;
|
||
}
|
||
|
||
fprintf (sched_dump, " %d(%d)", INSN_UID (insn1), INSN_UID (insn2));
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* General note:
|
||
|
||
We could have also hooked autoprefetcher model into
|
||
first_cycle_multipass_backtrack / first_cycle_multipass_issue hooks
|
||
to enable intelligent selection of "[r1+0]=r2; [r1+4]=r3" on the same cycle
|
||
(e.g., once "[r1+0]=r2" is issued in max_issue(), "[r1+4]=r3" gets
|
||
unblocked). We don't bother about this yet because target of interest
|
||
(ARM Cortex-A15) can issue only 1 memory operation per cycle. */
|
||
|
||
/* Implementation of first_cycle_multipass_dfa_lookahead_guard hook.
|
||
Return "1" if INSN1 should not be considered in max_issue due to
|
||
auto-prefetcher considerations. */
|
||
int
|
||
autopref_multipass_dfa_lookahead_guard (rtx_insn *insn1, int ready_index)
|
||
{
|
||
int r = 0;
|
||
|
||
if (PARAM_VALUE (PARAM_SCHED_AUTOPREF_QUEUE_DEPTH) <= 0)
|
||
return 0;
|
||
|
||
if (sched_verbose >= 2 && ready_index == 0)
|
||
autopref_multipass_dfa_lookahead_guard_started_dump_p = false;
|
||
|
||
for (int write = 0; write < 2; ++write)
|
||
{
|
||
autopref_multipass_data_t data1
|
||
= &INSN_AUTOPREF_MULTIPASS_DATA (insn1)[write];
|
||
|
||
if (data1->status == AUTOPREF_MULTIPASS_DATA_UNINITIALIZED)
|
||
autopref_multipass_init (insn1, write);
|
||
if (data1->status == AUTOPREF_MULTIPASS_DATA_IRRELEVANT)
|
||
continue;
|
||
|
||
if (ready_index == 0
|
||
&& data1->status == AUTOPREF_MULTIPASS_DATA_DONT_DELAY)
|
||
/* We allow only a single delay on priviledged instructions.
|
||
Doing otherwise would cause infinite loop. */
|
||
{
|
||
if (sched_verbose >= 2)
|
||
{
|
||
if (!autopref_multipass_dfa_lookahead_guard_started_dump_p)
|
||
{
|
||
fprintf (sched_dump,
|
||
";;\t\tnot trying in max_issue due to autoprefetch "
|
||
"model: ");
|
||
autopref_multipass_dfa_lookahead_guard_started_dump_p = true;
|
||
}
|
||
|
||
fprintf (sched_dump, " *%d*", INSN_UID (insn1));
|
||
}
|
||
continue;
|
||
}
|
||
|
||
for (int i2 = 0; i2 < ready.n_ready; ++i2)
|
||
{
|
||
rtx_insn *insn2 = get_ready_element (i2);
|
||
if (insn1 == insn2)
|
||
continue;
|
||
r = autopref_multipass_dfa_lookahead_guard_1 (insn1, insn2, write);
|
||
if (r)
|
||
{
|
||
if (ready_index == 0)
|
||
{
|
||
r = -1;
|
||
data1->status = AUTOPREF_MULTIPASS_DATA_DONT_DELAY;
|
||
}
|
||
goto finish;
|
||
}
|
||
}
|
||
|
||
if (PARAM_VALUE (PARAM_SCHED_AUTOPREF_QUEUE_DEPTH) == 1)
|
||
continue;
|
||
|
||
/* Everything from the current queue slot should have been moved to
|
||
the ready list. */
|
||
gcc_assert (insn_queue[NEXT_Q_AFTER (q_ptr, 0)] == NULL_RTX);
|
||
|
||
int n_stalls = PARAM_VALUE (PARAM_SCHED_AUTOPREF_QUEUE_DEPTH) - 1;
|
||
if (n_stalls > max_insn_queue_index)
|
||
n_stalls = max_insn_queue_index;
|
||
|
||
for (int stalls = 1; stalls <= n_stalls; ++stalls)
|
||
{
|
||
for (rtx_insn_list *link = insn_queue[NEXT_Q_AFTER (q_ptr, stalls)];
|
||
link != NULL_RTX;
|
||
link = link->next ())
|
||
{
|
||
rtx_insn *insn2 = link->insn ();
|
||
r = autopref_multipass_dfa_lookahead_guard_1 (insn1, insn2,
|
||
write);
|
||
if (r)
|
||
{
|
||
/* Queue INSN1 until INSN2 can issue. */
|
||
r = -stalls;
|
||
if (ready_index == 0)
|
||
data1->status = AUTOPREF_MULTIPASS_DATA_DONT_DELAY;
|
||
goto finish;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
finish:
|
||
if (sched_verbose >= 2
|
||
&& autopref_multipass_dfa_lookahead_guard_started_dump_p
|
||
&& (ready_index == ready.n_ready - 1 || r < 0))
|
||
/* This does not /always/ trigger. We don't output EOL if the last
|
||
insn is not recognized (INSN_CODE < 0) and lookahead_guard is not
|
||
called. We can live with this. */
|
||
fprintf (sched_dump, "\n");
|
||
|
||
return r;
|
||
}
|
||
|
||
/* Define type for target data used in multipass scheduling. */
|
||
#ifndef TARGET_SCHED_FIRST_CYCLE_MULTIPASS_DATA_T
|
||
# define TARGET_SCHED_FIRST_CYCLE_MULTIPASS_DATA_T int
|
||
#endif
|
||
typedef TARGET_SCHED_FIRST_CYCLE_MULTIPASS_DATA_T first_cycle_multipass_data_t;
|
||
|
||
/* The following structure describe an entry of the stack of choices. */
|
||
struct choice_entry
|
||
{
|
||
/* Ordinal number of the issued insn in the ready queue. */
|
||
int index;
|
||
/* The number of the rest insns whose issues we should try. */
|
||
int rest;
|
||
/* The number of issued essential insns. */
|
||
int n;
|
||
/* State after issuing the insn. */
|
||
state_t state;
|
||
/* Target-specific data. */
|
||
first_cycle_multipass_data_t target_data;
|
||
};
|
||
|
||
/* The following array is used to implement a stack of choices used in
|
||
function max_issue. */
|
||
static struct choice_entry *choice_stack;
|
||
|
||
/* This holds the value of the target dfa_lookahead hook. */
|
||
int dfa_lookahead;
|
||
|
||
/* The following variable value is maximal number of tries of issuing
|
||
insns for the first cycle multipass insn scheduling. We define
|
||
this value as constant*(DFA_LOOKAHEAD**ISSUE_RATE). We would not
|
||
need this constraint if all real insns (with non-negative codes)
|
||
had reservations because in this case the algorithm complexity is
|
||
O(DFA_LOOKAHEAD**ISSUE_RATE). Unfortunately, the dfa descriptions
|
||
might be incomplete and such insn might occur. For such
|
||
descriptions, the complexity of algorithm (without the constraint)
|
||
could achieve DFA_LOOKAHEAD ** N , where N is the queue length. */
|
||
static int max_lookahead_tries;
|
||
|
||
/* The following function returns maximal (or close to maximal) number
|
||
of insns which can be issued on the same cycle and one of which
|
||
insns is insns with the best rank (the first insn in READY). To
|
||
make this function tries different samples of ready insns. READY
|
||
is current queue `ready'. Global array READY_TRY reflects what
|
||
insns are already issued in this try. The function stops immediately,
|
||
if it reached the such a solution, that all instruction can be issued.
|
||
INDEX will contain index of the best insn in READY. The following
|
||
function is used only for first cycle multipass scheduling.
|
||
|
||
PRIVILEGED_N >= 0
|
||
|
||
This function expects recognized insns only. All USEs,
|
||
CLOBBERs, etc must be filtered elsewhere. */
|
||
int
|
||
max_issue (struct ready_list *ready, int privileged_n, state_t state,
|
||
bool first_cycle_insn_p, int *index)
|
||
{
|
||
int n, i, all, n_ready, best, delay, tries_num;
|
||
int more_issue;
|
||
struct choice_entry *top;
|
||
rtx_insn *insn;
|
||
|
||
if (sched_fusion)
|
||
return 0;
|
||
|
||
n_ready = ready->n_ready;
|
||
gcc_assert (dfa_lookahead >= 1 && privileged_n >= 0
|
||
&& privileged_n <= n_ready);
|
||
|
||
/* Init MAX_LOOKAHEAD_TRIES. */
|
||
if (max_lookahead_tries == 0)
|
||
{
|
||
max_lookahead_tries = 100;
|
||
for (i = 0; i < issue_rate; i++)
|
||
max_lookahead_tries *= dfa_lookahead;
|
||
}
|
||
|
||
/* Init max_points. */
|
||
more_issue = issue_rate - cycle_issued_insns;
|
||
gcc_assert (more_issue >= 0);
|
||
|
||
/* The number of the issued insns in the best solution. */
|
||
best = 0;
|
||
|
||
top = choice_stack;
|
||
|
||
/* Set initial state of the search. */
|
||
memcpy (top->state, state, dfa_state_size);
|
||
top->rest = dfa_lookahead;
|
||
top->n = 0;
|
||
if (targetm.sched.first_cycle_multipass_begin)
|
||
targetm.sched.first_cycle_multipass_begin (&top->target_data,
|
||
ready_try, n_ready,
|
||
first_cycle_insn_p);
|
||
|
||
/* Count the number of the insns to search among. */
|
||
for (all = i = 0; i < n_ready; i++)
|
||
if (!ready_try [i])
|
||
all++;
|
||
|
||
if (sched_verbose >= 2)
|
||
{
|
||
fprintf (sched_dump, ";;\t\tmax_issue among %d insns:", all);
|
||
debug_ready_list_1 (ready, ready_try);
|
||
}
|
||
|
||
/* I is the index of the insn to try next. */
|
||
i = 0;
|
||
tries_num = 0;
|
||
for (;;)
|
||
{
|
||
if (/* If we've reached a dead end or searched enough of what we have
|
||
been asked... */
|
||
top->rest == 0
|
||
/* or have nothing else to try... */
|
||
|| i >= n_ready
|
||
/* or should not issue more. */
|
||
|| top->n >= more_issue)
|
||
{
|
||
/* ??? (... || i == n_ready). */
|
||
gcc_assert (i <= n_ready);
|
||
|
||
/* We should not issue more than issue_rate instructions. */
|
||
gcc_assert (top->n <= more_issue);
|
||
|
||
if (top == choice_stack)
|
||
break;
|
||
|
||
if (best < top - choice_stack)
|
||
{
|
||
if (privileged_n)
|
||
{
|
||
n = privileged_n;
|
||
/* Try to find issued privileged insn. */
|
||
while (n && !ready_try[--n])
|
||
;
|
||
}
|
||
|
||
if (/* If all insns are equally good... */
|
||
privileged_n == 0
|
||
/* Or a privileged insn will be issued. */
|
||
|| ready_try[n])
|
||
/* Then we have a solution. */
|
||
{
|
||
best = top - choice_stack;
|
||
/* This is the index of the insn issued first in this
|
||
solution. */
|
||
*index = choice_stack [1].index;
|
||
if (top->n == more_issue || best == all)
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Set ready-list index to point to the last insn
|
||
('i++' below will advance it to the next insn). */
|
||
i = top->index;
|
||
|
||
/* Backtrack. */
|
||
ready_try [i] = 0;
|
||
|
||
if (targetm.sched.first_cycle_multipass_backtrack)
|
||
targetm.sched.first_cycle_multipass_backtrack (&top->target_data,
|
||
ready_try, n_ready);
|
||
|
||
top--;
|
||
memcpy (state, top->state, dfa_state_size);
|
||
}
|
||
else if (!ready_try [i])
|
||
{
|
||
tries_num++;
|
||
if (tries_num > max_lookahead_tries)
|
||
break;
|
||
insn = ready_element (ready, i);
|
||
delay = state_transition (state, insn);
|
||
if (delay < 0)
|
||
{
|
||
if (state_dead_lock_p (state)
|
||
|| insn_finishes_cycle_p (insn))
|
||
/* We won't issue any more instructions in the next
|
||
choice_state. */
|
||
top->rest = 0;
|
||
else
|
||
top->rest--;
|
||
|
||
n = top->n;
|
||
if (memcmp (top->state, state, dfa_state_size) != 0)
|
||
n++;
|
||
|
||
/* Advance to the next choice_entry. */
|
||
top++;
|
||
/* Initialize it. */
|
||
top->rest = dfa_lookahead;
|
||
top->index = i;
|
||
top->n = n;
|
||
memcpy (top->state, state, dfa_state_size);
|
||
ready_try [i] = 1;
|
||
|
||
if (targetm.sched.first_cycle_multipass_issue)
|
||
targetm.sched.first_cycle_multipass_issue (&top->target_data,
|
||
ready_try, n_ready,
|
||
insn,
|
||
&((top - 1)
|
||
->target_data));
|
||
|
||
i = -1;
|
||
}
|
||
}
|
||
|
||
/* Increase ready-list index. */
|
||
i++;
|
||
}
|
||
|
||
if (targetm.sched.first_cycle_multipass_end)
|
||
targetm.sched.first_cycle_multipass_end (best != 0
|
||
? &choice_stack[1].target_data
|
||
: NULL);
|
||
|
||
/* Restore the original state of the DFA. */
|
||
memcpy (state, choice_stack->state, dfa_state_size);
|
||
|
||
return best;
|
||
}
|
||
|
||
/* The following function chooses insn from READY and modifies
|
||
READY. The following function is used only for first
|
||
cycle multipass scheduling.
|
||
Return:
|
||
-1 if cycle should be advanced,
|
||
0 if INSN_PTR is set to point to the desirable insn,
|
||
1 if choose_ready () should be restarted without advancing the cycle. */
|
||
static int
|
||
choose_ready (struct ready_list *ready, bool first_cycle_insn_p,
|
||
rtx_insn **insn_ptr)
|
||
{
|
||
if (dbg_cnt (sched_insn) == false)
|
||
{
|
||
if (nonscheduled_insns_begin == NULL_RTX)
|
||
nonscheduled_insns_begin = current_sched_info->prev_head;
|
||
|
||
rtx_insn *insn = first_nonscheduled_insn ();
|
||
|
||
if (QUEUE_INDEX (insn) == QUEUE_READY)
|
||
/* INSN is in the ready_list. */
|
||
{
|
||
ready_remove_insn (insn);
|
||
*insn_ptr = insn;
|
||
return 0;
|
||
}
|
||
|
||
/* INSN is in the queue. Advance cycle to move it to the ready list. */
|
||
gcc_assert (QUEUE_INDEX (insn) >= 0);
|
||
return -1;
|
||
}
|
||
|
||
if (dfa_lookahead <= 0 || SCHED_GROUP_P (ready_element (ready, 0))
|
||
|| DEBUG_INSN_P (ready_element (ready, 0)))
|
||
{
|
||
if (targetm.sched.dispatch (NULL, IS_DISPATCH_ON))
|
||
*insn_ptr = ready_remove_first_dispatch (ready);
|
||
else
|
||
*insn_ptr = ready_remove_first (ready);
|
||
|
||
return 0;
|
||
}
|
||
else
|
||
{
|
||
/* Try to choose the best insn. */
|
||
int index = 0, i;
|
||
rtx_insn *insn;
|
||
|
||
insn = ready_element (ready, 0);
|
||
if (INSN_CODE (insn) < 0)
|
||
{
|
||
*insn_ptr = ready_remove_first (ready);
|
||
return 0;
|
||
}
|
||
|
||
/* Filter the search space. */
|
||
for (i = 0; i < ready->n_ready; i++)
|
||
{
|
||
ready_try[i] = 0;
|
||
|
||
insn = ready_element (ready, i);
|
||
|
||
/* If this insn is recognizable we should have already
|
||
recognized it earlier.
|
||
??? Not very clear where this is supposed to be done.
|
||
See dep_cost_1. */
|
||
gcc_checking_assert (INSN_CODE (insn) >= 0
|
||
|| recog_memoized (insn) < 0);
|
||
if (INSN_CODE (insn) < 0)
|
||
{
|
||
/* Non-recognized insns at position 0 are handled above. */
|
||
gcc_assert (i > 0);
|
||
ready_try[i] = 1;
|
||
continue;
|
||
}
|
||
|
||
if (targetm.sched.first_cycle_multipass_dfa_lookahead_guard)
|
||
{
|
||
ready_try[i]
|
||
= (targetm.sched.first_cycle_multipass_dfa_lookahead_guard
|
||
(insn, i));
|
||
|
||
if (ready_try[i] < 0)
|
||
/* Queue instruction for several cycles.
|
||
We need to restart choose_ready as we have changed
|
||
the ready list. */
|
||
{
|
||
change_queue_index (insn, -ready_try[i]);
|
||
return 1;
|
||
}
|
||
|
||
/* Make sure that we didn't end up with 0'th insn filtered out.
|
||
Don't be tempted to make life easier for backends and just
|
||
requeue 0'th insn if (ready_try[0] == 0) and restart
|
||
choose_ready. Backends should be very considerate about
|
||
requeueing instructions -- especially the highest priority
|
||
one at position 0. */
|
||
gcc_assert (ready_try[i] == 0 || i > 0);
|
||
if (ready_try[i])
|
||
continue;
|
||
}
|
||
|
||
gcc_assert (ready_try[i] == 0);
|
||
/* INSN made it through the scrutiny of filters! */
|
||
}
|
||
|
||
if (max_issue (ready, 1, curr_state, first_cycle_insn_p, &index) == 0)
|
||
{
|
||
*insn_ptr = ready_remove_first (ready);
|
||
if (sched_verbose >= 4)
|
||
fprintf (sched_dump, ";;\t\tChosen insn (but can't issue) : %s \n",
|
||
(*current_sched_info->print_insn) (*insn_ptr, 0));
|
||
return 0;
|
||
}
|
||
else
|
||
{
|
||
if (sched_verbose >= 4)
|
||
fprintf (sched_dump, ";;\t\tChosen insn : %s\n",
|
||
(*current_sched_info->print_insn)
|
||
(ready_element (ready, index), 0));
|
||
|
||
*insn_ptr = ready_remove (ready, index);
|
||
return 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* This function is called when we have successfully scheduled a
|
||
block. It uses the schedule stored in the scheduled_insns vector
|
||
to rearrange the RTL. PREV_HEAD is used as the anchor to which we
|
||
append the scheduled insns; TAIL is the insn after the scheduled
|
||
block. TARGET_BB is the argument passed to schedule_block. */
|
||
|
||
static void
|
||
commit_schedule (rtx_insn *prev_head, rtx_insn *tail, basic_block *target_bb)
|
||
{
|
||
unsigned int i;
|
||
rtx_insn *insn;
|
||
|
||
last_scheduled_insn = prev_head;
|
||
for (i = 0;
|
||
scheduled_insns.iterate (i, &insn);
|
||
i++)
|
||
{
|
||
if (control_flow_insn_p (last_scheduled_insn)
|
||
|| current_sched_info->advance_target_bb (*target_bb, insn))
|
||
{
|
||
*target_bb = current_sched_info->advance_target_bb (*target_bb, 0);
|
||
|
||
if (sched_verbose)
|
||
{
|
||
rtx_insn *x;
|
||
|
||
x = next_real_insn (last_scheduled_insn);
|
||
gcc_assert (x);
|
||
dump_new_block_header (1, *target_bb, x, tail);
|
||
}
|
||
|
||
last_scheduled_insn = bb_note (*target_bb);
|
||
}
|
||
|
||
if (current_sched_info->begin_move_insn)
|
||
(*current_sched_info->begin_move_insn) (insn, last_scheduled_insn);
|
||
move_insn (insn, last_scheduled_insn,
|
||
current_sched_info->next_tail);
|
||
if (!DEBUG_INSN_P (insn))
|
||
reemit_notes (insn);
|
||
last_scheduled_insn = insn;
|
||
}
|
||
|
||
scheduled_insns.truncate (0);
|
||
}
|
||
|
||
/* Examine all insns on the ready list and queue those which can't be
|
||
issued in this cycle. TEMP_STATE is temporary scheduler state we
|
||
can use as scratch space. If FIRST_CYCLE_INSN_P is true, no insns
|
||
have been issued for the current cycle, which means it is valid to
|
||
issue an asm statement.
|
||
|
||
If SHADOWS_ONLY_P is true, we eliminate all real insns and only
|
||
leave those for which SHADOW_P is true. If MODULO_EPILOGUE is true,
|
||
we only leave insns which have an INSN_EXACT_TICK. */
|
||
|
||
static void
|
||
prune_ready_list (state_t temp_state, bool first_cycle_insn_p,
|
||
bool shadows_only_p, bool modulo_epilogue_p)
|
||
{
|
||
int i, pass;
|
||
bool sched_group_found = false;
|
||
int min_cost_group = 1;
|
||
|
||
if (sched_fusion)
|
||
return;
|
||
|
||
for (i = 0; i < ready.n_ready; i++)
|
||
{
|
||
rtx_insn *insn = ready_element (&ready, i);
|
||
if (SCHED_GROUP_P (insn))
|
||
{
|
||
sched_group_found = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Make two passes if there's a SCHED_GROUP_P insn; make sure to handle
|
||
such an insn first and note its cost, then schedule all other insns
|
||
for one cycle later. */
|
||
for (pass = sched_group_found ? 0 : 1; pass < 2; )
|
||
{
|
||
int n = ready.n_ready;
|
||
for (i = 0; i < n; i++)
|
||
{
|
||
rtx_insn *insn = ready_element (&ready, i);
|
||
int cost = 0;
|
||
const char *reason = "resource conflict";
|
||
|
||
if (DEBUG_INSN_P (insn))
|
||
continue;
|
||
|
||
if (sched_group_found && !SCHED_GROUP_P (insn))
|
||
{
|
||
if (pass == 0)
|
||
continue;
|
||
cost = min_cost_group;
|
||
reason = "not in sched group";
|
||
}
|
||
else if (modulo_epilogue_p
|
||
&& INSN_EXACT_TICK (insn) == INVALID_TICK)
|
||
{
|
||
cost = max_insn_queue_index;
|
||
reason = "not an epilogue insn";
|
||
}
|
||
else if (shadows_only_p && !SHADOW_P (insn))
|
||
{
|
||
cost = 1;
|
||
reason = "not a shadow";
|
||
}
|
||
else if (recog_memoized (insn) < 0)
|
||
{
|
||
if (!first_cycle_insn_p
|
||
&& (GET_CODE (PATTERN (insn)) == ASM_INPUT
|
||
|| asm_noperands (PATTERN (insn)) >= 0))
|
||
cost = 1;
|
||
reason = "asm";
|
||
}
|
||
else if (sched_pressure != SCHED_PRESSURE_NONE)
|
||
{
|
||
if (sched_pressure == SCHED_PRESSURE_MODEL
|
||
&& INSN_TICK (insn) <= clock_var)
|
||
{
|
||
memcpy (temp_state, curr_state, dfa_state_size);
|
||
if (state_transition (temp_state, insn) >= 0)
|
||
INSN_TICK (insn) = clock_var + 1;
|
||
}
|
||
cost = 0;
|
||
}
|
||
else
|
||
{
|
||
int delay_cost = 0;
|
||
|
||
if (delay_htab)
|
||
{
|
||
struct delay_pair *delay_entry;
|
||
delay_entry
|
||
= delay_htab->find_with_hash (insn,
|
||
htab_hash_pointer (insn));
|
||
while (delay_entry && delay_cost == 0)
|
||
{
|
||
delay_cost = estimate_shadow_tick (delay_entry);
|
||
if (delay_cost > max_insn_queue_index)
|
||
delay_cost = max_insn_queue_index;
|
||
delay_entry = delay_entry->next_same_i1;
|
||
}
|
||
}
|
||
|
||
memcpy (temp_state, curr_state, dfa_state_size);
|
||
cost = state_transition (temp_state, insn);
|
||
if (cost < 0)
|
||
cost = 0;
|
||
else if (cost == 0)
|
||
cost = 1;
|
||
if (cost < delay_cost)
|
||
{
|
||
cost = delay_cost;
|
||
reason = "shadow tick";
|
||
}
|
||
}
|
||
if (cost >= 1)
|
||
{
|
||
if (SCHED_GROUP_P (insn) && cost > min_cost_group)
|
||
min_cost_group = cost;
|
||
ready_remove (&ready, i);
|
||
/* Normally we'd want to queue INSN for COST cycles. However,
|
||
if SCHED_GROUP_P is set, then we must ensure that nothing
|
||
else comes between INSN and its predecessor. If there is
|
||
some other insn ready to fire on the next cycle, then that
|
||
invariant would be broken.
|
||
|
||
So when SCHED_GROUP_P is set, just queue this insn for a
|
||
single cycle. */
|
||
queue_insn (insn, SCHED_GROUP_P (insn) ? 1 : cost, reason);
|
||
if (i + 1 < n)
|
||
break;
|
||
}
|
||
}
|
||
if (i == n)
|
||
pass++;
|
||
}
|
||
}
|
||
|
||
/* Called when we detect that the schedule is impossible. We examine the
|
||
backtrack queue to find the earliest insn that caused this condition. */
|
||
|
||
static struct haifa_saved_data *
|
||
verify_shadows (void)
|
||
{
|
||
struct haifa_saved_data *save, *earliest_fail = NULL;
|
||
for (save = backtrack_queue; save; save = save->next)
|
||
{
|
||
int t;
|
||
struct delay_pair *pair = save->delay_pair;
|
||
rtx_insn *i1 = pair->i1;
|
||
|
||
for (; pair; pair = pair->next_same_i1)
|
||
{
|
||
rtx_insn *i2 = pair->i2;
|
||
|
||
if (QUEUE_INDEX (i2) == QUEUE_SCHEDULED)
|
||
continue;
|
||
|
||
t = INSN_TICK (i1) + pair_delay (pair);
|
||
if (t < clock_var)
|
||
{
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump,
|
||
";;\t\tfailed delay requirements for %d/%d (%d->%d)"
|
||
", not ready\n",
|
||
INSN_UID (pair->i1), INSN_UID (pair->i2),
|
||
INSN_TICK (pair->i1), INSN_EXACT_TICK (pair->i2));
|
||
earliest_fail = save;
|
||
break;
|
||
}
|
||
if (QUEUE_INDEX (i2) >= 0)
|
||
{
|
||
int queued_for = INSN_TICK (i2);
|
||
|
||
if (t < queued_for)
|
||
{
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump,
|
||
";;\t\tfailed delay requirements for %d/%d"
|
||
" (%d->%d), queued too late\n",
|
||
INSN_UID (pair->i1), INSN_UID (pair->i2),
|
||
INSN_TICK (pair->i1), INSN_EXACT_TICK (pair->i2));
|
||
earliest_fail = save;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return earliest_fail;
|
||
}
|
||
|
||
/* Print instructions together with useful scheduling information between
|
||
HEAD and TAIL (inclusive). */
|
||
static void
|
||
dump_insn_stream (rtx_insn *head, rtx_insn *tail)
|
||
{
|
||
fprintf (sched_dump, ";;\t| insn | prio |\n");
|
||
|
||
rtx_insn *next_tail = NEXT_INSN (tail);
|
||
for (rtx_insn *insn = head; insn != next_tail; insn = NEXT_INSN (insn))
|
||
{
|
||
int priority = NOTE_P (insn) ? 0 : INSN_PRIORITY (insn);
|
||
const char *pattern = (NOTE_P (insn)
|
||
? "note"
|
||
: str_pattern_slim (PATTERN (insn)));
|
||
|
||
fprintf (sched_dump, ";;\t| %4d | %4d | %-30s ",
|
||
INSN_UID (insn), priority, pattern);
|
||
|
||
if (sched_verbose >= 4)
|
||
{
|
||
if (NOTE_P (insn) || recog_memoized (insn) < 0)
|
||
fprintf (sched_dump, "nothing");
|
||
else
|
||
print_reservation (sched_dump, insn);
|
||
}
|
||
fprintf (sched_dump, "\n");
|
||
}
|
||
}
|
||
|
||
/* Use forward list scheduling to rearrange insns of block pointed to by
|
||
TARGET_BB, possibly bringing insns from subsequent blocks in the same
|
||
region. */
|
||
|
||
bool
|
||
schedule_block (basic_block *target_bb, state_t init_state)
|
||
{
|
||
int i;
|
||
bool success = modulo_ii == 0;
|
||
struct sched_block_state ls;
|
||
state_t temp_state = NULL; /* It is used for multipass scheduling. */
|
||
int sort_p, advance, start_clock_var;
|
||
|
||
/* Head/tail info for this block. */
|
||
rtx_insn *prev_head = current_sched_info->prev_head;
|
||
rtx_insn *next_tail = current_sched_info->next_tail;
|
||
rtx_insn *head = NEXT_INSN (prev_head);
|
||
rtx_insn *tail = PREV_INSN (next_tail);
|
||
|
||
if ((current_sched_info->flags & DONT_BREAK_DEPENDENCIES) == 0
|
||
&& sched_pressure != SCHED_PRESSURE_MODEL && !sched_fusion)
|
||
find_modifiable_mems (head, tail);
|
||
|
||
/* We used to have code to avoid getting parameters moved from hard
|
||
argument registers into pseudos.
|
||
|
||
However, it was removed when it proved to be of marginal benefit
|
||
and caused problems because schedule_block and compute_forward_dependences
|
||
had different notions of what the "head" insn was. */
|
||
|
||
gcc_assert (head != tail || INSN_P (head));
|
||
|
||
haifa_recovery_bb_recently_added_p = false;
|
||
|
||
backtrack_queue = NULL;
|
||
|
||
/* Debug info. */
|
||
if (sched_verbose)
|
||
{
|
||
dump_new_block_header (0, *target_bb, head, tail);
|
||
|
||
if (sched_verbose >= 2)
|
||
{
|
||
dump_insn_stream (head, tail);
|
||
memset (&rank_for_schedule_stats, 0,
|
||
sizeof (rank_for_schedule_stats));
|
||
}
|
||
}
|
||
|
||
if (init_state == NULL)
|
||
state_reset (curr_state);
|
||
else
|
||
memcpy (curr_state, init_state, dfa_state_size);
|
||
|
||
/* Clear the ready list. */
|
||
ready.first = ready.veclen - 1;
|
||
ready.n_ready = 0;
|
||
ready.n_debug = 0;
|
||
|
||
/* It is used for first cycle multipass scheduling. */
|
||
temp_state = alloca (dfa_state_size);
|
||
|
||
if (targetm.sched.init)
|
||
targetm.sched.init (sched_dump, sched_verbose, ready.veclen);
|
||
|
||
/* We start inserting insns after PREV_HEAD. */
|
||
last_scheduled_insn = prev_head;
|
||
last_nondebug_scheduled_insn = NULL;
|
||
nonscheduled_insns_begin = NULL;
|
||
|
||
gcc_assert ((NOTE_P (last_scheduled_insn)
|
||
|| DEBUG_INSN_P (last_scheduled_insn))
|
||
&& BLOCK_FOR_INSN (last_scheduled_insn) == *target_bb);
|
||
|
||
/* Initialize INSN_QUEUE. Q_SIZE is the total number of insns in the
|
||
queue. */
|
||
q_ptr = 0;
|
||
q_size = 0;
|
||
|
||
insn_queue = XALLOCAVEC (rtx_insn_list *, max_insn_queue_index + 1);
|
||
memset (insn_queue, 0, (max_insn_queue_index + 1) * sizeof (rtx));
|
||
|
||
/* Start just before the beginning of time. */
|
||
clock_var = -1;
|
||
|
||
/* We need queue and ready lists and clock_var be initialized
|
||
in try_ready () (which is called through init_ready_list ()). */
|
||
(*current_sched_info->init_ready_list) ();
|
||
|
||
if (sched_pressure)
|
||
sched_pressure_start_bb (*target_bb);
|
||
|
||
/* The algorithm is O(n^2) in the number of ready insns at any given
|
||
time in the worst case. Before reload we are more likely to have
|
||
big lists so truncate them to a reasonable size. */
|
||
if (!reload_completed
|
||
&& ready.n_ready - ready.n_debug > MAX_SCHED_READY_INSNS)
|
||
{
|
||
ready_sort_debug (&ready);
|
||
ready_sort_real (&ready);
|
||
|
||
/* Find first free-standing insn past MAX_SCHED_READY_INSNS.
|
||
If there are debug insns, we know they're first. */
|
||
for (i = MAX_SCHED_READY_INSNS + ready.n_debug; i < ready.n_ready; i++)
|
||
if (!SCHED_GROUP_P (ready_element (&ready, i)))
|
||
break;
|
||
|
||
if (sched_verbose >= 2)
|
||
{
|
||
fprintf (sched_dump,
|
||
";;\t\tReady list on entry: %d insns: ", ready.n_ready);
|
||
debug_ready_list (&ready);
|
||
fprintf (sched_dump,
|
||
";;\t\t before reload => truncated to %d insns\n", i);
|
||
}
|
||
|
||
/* Delay all insns past it for 1 cycle. If debug counter is
|
||
activated make an exception for the insn right after
|
||
nonscheduled_insns_begin. */
|
||
{
|
||
rtx_insn *skip_insn;
|
||
|
||
if (dbg_cnt (sched_insn) == false)
|
||
skip_insn = first_nonscheduled_insn ();
|
||
else
|
||
skip_insn = NULL;
|
||
|
||
while (i < ready.n_ready)
|
||
{
|
||
rtx_insn *insn;
|
||
|
||
insn = ready_remove (&ready, i);
|
||
|
||
if (insn != skip_insn)
|
||
queue_insn (insn, 1, "list truncated");
|
||
}
|
||
if (skip_insn)
|
||
ready_add (&ready, skip_insn, true);
|
||
}
|
||
}
|
||
|
||
/* Now we can restore basic block notes and maintain precise cfg. */
|
||
restore_bb_notes (*target_bb);
|
||
|
||
last_clock_var = -1;
|
||
|
||
advance = 0;
|
||
|
||
gcc_assert (scheduled_insns.length () == 0);
|
||
sort_p = TRUE;
|
||
must_backtrack = false;
|
||
modulo_insns_scheduled = 0;
|
||
|
||
ls.modulo_epilogue = false;
|
||
ls.first_cycle_insn_p = true;
|
||
|
||
/* Loop until all the insns in BB are scheduled. */
|
||
while ((*current_sched_info->schedule_more_p) ())
|
||
{
|
||
perform_replacements_new_cycle ();
|
||
do
|
||
{
|
||
start_clock_var = clock_var;
|
||
|
||
clock_var++;
|
||
|
||
advance_one_cycle ();
|
||
|
||
/* Add to the ready list all pending insns that can be issued now.
|
||
If there are no ready insns, increment clock until one
|
||
is ready and add all pending insns at that point to the ready
|
||
list. */
|
||
queue_to_ready (&ready);
|
||
|
||
gcc_assert (ready.n_ready);
|
||
|
||
if (sched_verbose >= 2)
|
||
{
|
||
fprintf (sched_dump, ";;\t\tReady list after queue_to_ready:");
|
||
debug_ready_list (&ready);
|
||
}
|
||
advance -= clock_var - start_clock_var;
|
||
}
|
||
while (advance > 0);
|
||
|
||
if (ls.modulo_epilogue)
|
||
{
|
||
int stage = clock_var / modulo_ii;
|
||
if (stage > modulo_last_stage * 2 + 2)
|
||
{
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump,
|
||
";;\t\tmodulo scheduled succeeded at II %d\n",
|
||
modulo_ii);
|
||
success = true;
|
||
goto end_schedule;
|
||
}
|
||
}
|
||
else if (modulo_ii > 0)
|
||
{
|
||
int stage = clock_var / modulo_ii;
|
||
if (stage > modulo_max_stages)
|
||
{
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump,
|
||
";;\t\tfailing schedule due to excessive stages\n");
|
||
goto end_schedule;
|
||
}
|
||
if (modulo_n_insns == modulo_insns_scheduled
|
||
&& stage > modulo_last_stage)
|
||
{
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump,
|
||
";;\t\tfound kernel after %d stages, II %d\n",
|
||
stage, modulo_ii);
|
||
ls.modulo_epilogue = true;
|
||
}
|
||
}
|
||
|
||
prune_ready_list (temp_state, true, false, ls.modulo_epilogue);
|
||
if (ready.n_ready == 0)
|
||
continue;
|
||
if (must_backtrack)
|
||
goto do_backtrack;
|
||
|
||
ls.shadows_only_p = false;
|
||
cycle_issued_insns = 0;
|
||
ls.can_issue_more = issue_rate;
|
||
for (;;)
|
||
{
|
||
rtx_insn *insn;
|
||
int cost;
|
||
bool asm_p;
|
||
|
||
if (sort_p && ready.n_ready > 0)
|
||
{
|
||
/* Sort the ready list based on priority. This must be
|
||
done every iteration through the loop, as schedule_insn
|
||
may have readied additional insns that will not be
|
||
sorted correctly. */
|
||
ready_sort (&ready);
|
||
|
||
if (sched_verbose >= 2)
|
||
{
|
||
fprintf (sched_dump,
|
||
";;\t\tReady list after ready_sort: ");
|
||
debug_ready_list (&ready);
|
||
}
|
||
}
|
||
|
||
/* We don't want md sched reorder to even see debug isns, so put
|
||
them out right away. */
|
||
if (ready.n_ready && DEBUG_INSN_P (ready_element (&ready, 0))
|
||
&& (*current_sched_info->schedule_more_p) ())
|
||
{
|
||
while (ready.n_ready && DEBUG_INSN_P (ready_element (&ready, 0)))
|
||
{
|
||
rtx_insn *insn = ready_remove_first (&ready);
|
||
gcc_assert (DEBUG_INSN_P (insn));
|
||
(*current_sched_info->begin_schedule_ready) (insn);
|
||
scheduled_insns.safe_push (insn);
|
||
last_scheduled_insn = insn;
|
||
advance = schedule_insn (insn);
|
||
gcc_assert (advance == 0);
|
||
if (ready.n_ready > 0)
|
||
ready_sort (&ready);
|
||
}
|
||
}
|
||
|
||
if (ls.first_cycle_insn_p && !ready.n_ready)
|
||
break;
|
||
|
||
resume_after_backtrack:
|
||
/* Allow the target to reorder the list, typically for
|
||
better instruction bundling. */
|
||
if (sort_p
|
||
&& (ready.n_ready == 0
|
||
|| !SCHED_GROUP_P (ready_element (&ready, 0))))
|
||
{
|
||
if (ls.first_cycle_insn_p && targetm.sched.reorder)
|
||
ls.can_issue_more
|
||
= targetm.sched.reorder (sched_dump, sched_verbose,
|
||
ready_lastpos (&ready),
|
||
&ready.n_ready, clock_var);
|
||
else if (!ls.first_cycle_insn_p && targetm.sched.reorder2)
|
||
ls.can_issue_more
|
||
= targetm.sched.reorder2 (sched_dump, sched_verbose,
|
||
ready.n_ready
|
||
? ready_lastpos (&ready) : NULL,
|
||
&ready.n_ready, clock_var);
|
||
}
|
||
|
||
restart_choose_ready:
|
||
if (sched_verbose >= 2)
|
||
{
|
||
fprintf (sched_dump, ";;\tReady list (t = %3d): ",
|
||
clock_var);
|
||
debug_ready_list (&ready);
|
||
if (sched_pressure == SCHED_PRESSURE_WEIGHTED)
|
||
print_curr_reg_pressure ();
|
||
}
|
||
|
||
if (ready.n_ready == 0
|
||
&& ls.can_issue_more
|
||
&& reload_completed)
|
||
{
|
||
/* Allow scheduling insns directly from the queue in case
|
||
there's nothing better to do (ready list is empty) but
|
||
there are still vacant dispatch slots in the current cycle. */
|
||
if (sched_verbose >= 6)
|
||
fprintf (sched_dump,";;\t\tSecond chance\n");
|
||
memcpy (temp_state, curr_state, dfa_state_size);
|
||
if (early_queue_to_ready (temp_state, &ready))
|
||
ready_sort (&ready);
|
||
}
|
||
|
||
if (ready.n_ready == 0
|
||
|| !ls.can_issue_more
|
||
|| state_dead_lock_p (curr_state)
|
||
|| !(*current_sched_info->schedule_more_p) ())
|
||
break;
|
||
|
||
/* Select and remove the insn from the ready list. */
|
||
if (sort_p)
|
||
{
|
||
int res;
|
||
|
||
insn = NULL;
|
||
res = choose_ready (&ready, ls.first_cycle_insn_p, &insn);
|
||
|
||
if (res < 0)
|
||
/* Finish cycle. */
|
||
break;
|
||
if (res > 0)
|
||
goto restart_choose_ready;
|
||
|
||
gcc_assert (insn != NULL_RTX);
|
||
}
|
||
else
|
||
insn = ready_remove_first (&ready);
|
||
|
||
if (sched_pressure != SCHED_PRESSURE_NONE
|
||
&& INSN_TICK (insn) > clock_var)
|
||
{
|
||
ready_add (&ready, insn, true);
|
||
advance = 1;
|
||
break;
|
||
}
|
||
|
||
if (targetm.sched.dfa_new_cycle
|
||
&& targetm.sched.dfa_new_cycle (sched_dump, sched_verbose,
|
||
insn, last_clock_var,
|
||
clock_var, &sort_p))
|
||
/* SORT_P is used by the target to override sorting
|
||
of the ready list. This is needed when the target
|
||
has modified its internal structures expecting that
|
||
the insn will be issued next. As we need the insn
|
||
to have the highest priority (so it will be returned by
|
||
the ready_remove_first call above), we invoke
|
||
ready_add (&ready, insn, true).
|
||
But, still, there is one issue: INSN can be later
|
||
discarded by scheduler's front end through
|
||
current_sched_info->can_schedule_ready_p, hence, won't
|
||
be issued next. */
|
||
{
|
||
ready_add (&ready, insn, true);
|
||
break;
|
||
}
|
||
|
||
sort_p = TRUE;
|
||
|
||
if (current_sched_info->can_schedule_ready_p
|
||
&& ! (*current_sched_info->can_schedule_ready_p) (insn))
|
||
/* We normally get here only if we don't want to move
|
||
insn from the split block. */
|
||
{
|
||
TODO_SPEC (insn) = DEP_POSTPONED;
|
||
goto restart_choose_ready;
|
||
}
|
||
|
||
if (delay_htab)
|
||
{
|
||
/* If this insn is the first part of a delay-slot pair, record a
|
||
backtrack point. */
|
||
struct delay_pair *delay_entry;
|
||
delay_entry
|
||
= delay_htab->find_with_hash (insn, htab_hash_pointer (insn));
|
||
if (delay_entry)
|
||
{
|
||
save_backtrack_point (delay_entry, ls);
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump, ";;\t\tsaving backtrack point\n");
|
||
}
|
||
}
|
||
|
||
/* DECISION is made. */
|
||
|
||
if (modulo_ii > 0 && INSN_UID (insn) < modulo_iter0_max_uid)
|
||
{
|
||
modulo_insns_scheduled++;
|
||
modulo_last_stage = clock_var / modulo_ii;
|
||
}
|
||
if (TODO_SPEC (insn) & SPECULATIVE)
|
||
generate_recovery_code (insn);
|
||
|
||
if (targetm.sched.dispatch (NULL, IS_DISPATCH_ON))
|
||
targetm.sched.dispatch_do (insn, ADD_TO_DISPATCH_WINDOW);
|
||
|
||
/* Update counters, etc in the scheduler's front end. */
|
||
(*current_sched_info->begin_schedule_ready) (insn);
|
||
scheduled_insns.safe_push (insn);
|
||
gcc_assert (NONDEBUG_INSN_P (insn));
|
||
last_nondebug_scheduled_insn = last_scheduled_insn = insn;
|
||
|
||
if (recog_memoized (insn) >= 0)
|
||
{
|
||
memcpy (temp_state, curr_state, dfa_state_size);
|
||
cost = state_transition (curr_state, insn);
|
||
if (sched_pressure != SCHED_PRESSURE_WEIGHTED && !sched_fusion)
|
||
gcc_assert (cost < 0);
|
||
if (memcmp (temp_state, curr_state, dfa_state_size) != 0)
|
||
cycle_issued_insns++;
|
||
asm_p = false;
|
||
}
|
||
else
|
||
asm_p = (GET_CODE (PATTERN (insn)) == ASM_INPUT
|
||
|| asm_noperands (PATTERN (insn)) >= 0);
|
||
|
||
if (targetm.sched.variable_issue)
|
||
ls.can_issue_more =
|
||
targetm.sched.variable_issue (sched_dump, sched_verbose,
|
||
insn, ls.can_issue_more);
|
||
/* A naked CLOBBER or USE generates no instruction, so do
|
||
not count them against the issue rate. */
|
||
else if (GET_CODE (PATTERN (insn)) != USE
|
||
&& GET_CODE (PATTERN (insn)) != CLOBBER)
|
||
ls.can_issue_more--;
|
||
advance = schedule_insn (insn);
|
||
|
||
if (SHADOW_P (insn))
|
||
ls.shadows_only_p = true;
|
||
|
||
/* After issuing an asm insn we should start a new cycle. */
|
||
if (advance == 0 && asm_p)
|
||
advance = 1;
|
||
|
||
if (must_backtrack)
|
||
break;
|
||
|
||
if (advance != 0)
|
||
break;
|
||
|
||
ls.first_cycle_insn_p = false;
|
||
if (ready.n_ready > 0)
|
||
prune_ready_list (temp_state, false, ls.shadows_only_p,
|
||
ls.modulo_epilogue);
|
||
}
|
||
|
||
do_backtrack:
|
||
if (!must_backtrack)
|
||
for (i = 0; i < ready.n_ready; i++)
|
||
{
|
||
rtx_insn *insn = ready_element (&ready, i);
|
||
if (INSN_EXACT_TICK (insn) == clock_var)
|
||
{
|
||
must_backtrack = true;
|
||
clock_var++;
|
||
break;
|
||
}
|
||
}
|
||
if (must_backtrack && modulo_ii > 0)
|
||
{
|
||
if (modulo_backtracks_left == 0)
|
||
goto end_schedule;
|
||
modulo_backtracks_left--;
|
||
}
|
||
while (must_backtrack)
|
||
{
|
||
struct haifa_saved_data *failed;
|
||
rtx_insn *failed_insn;
|
||
|
||
must_backtrack = false;
|
||
failed = verify_shadows ();
|
||
gcc_assert (failed);
|
||
|
||
failed_insn = failed->delay_pair->i1;
|
||
/* Clear these queues. */
|
||
perform_replacements_new_cycle ();
|
||
toggle_cancelled_flags (false);
|
||
unschedule_insns_until (failed_insn);
|
||
while (failed != backtrack_queue)
|
||
free_topmost_backtrack_point (true);
|
||
restore_last_backtrack_point (&ls);
|
||
if (sched_verbose >= 2)
|
||
fprintf (sched_dump, ";;\t\trewind to cycle %d\n", clock_var);
|
||
/* Delay by at least a cycle. This could cause additional
|
||
backtracking. */
|
||
queue_insn (failed_insn, 1, "backtracked");
|
||
advance = 0;
|
||
if (must_backtrack)
|
||
continue;
|
||
if (ready.n_ready > 0)
|
||
goto resume_after_backtrack;
|
||
else
|
||
{
|
||
if (clock_var == 0 && ls.first_cycle_insn_p)
|
||
goto end_schedule;
|
||
advance = 1;
|
||
break;
|
||
}
|
||
}
|
||
ls.first_cycle_insn_p = true;
|
||
}
|
||
if (ls.modulo_epilogue)
|
||
success = true;
|
||
end_schedule:
|
||
if (!ls.first_cycle_insn_p || advance)
|
||
advance_one_cycle ();
|
||
perform_replacements_new_cycle ();
|
||
if (modulo_ii > 0)
|
||
{
|
||
/* Once again, debug insn suckiness: they can be on the ready list
|
||
even if they have unresolved dependencies. To make our view
|
||
of the world consistent, remove such "ready" insns. */
|
||
restart_debug_insn_loop:
|
||
for (i = ready.n_ready - 1; i >= 0; i--)
|
||
{
|
||
rtx_insn *x;
|
||
|
||
x = ready_element (&ready, i);
|
||
if (DEPS_LIST_FIRST (INSN_HARD_BACK_DEPS (x)) != NULL
|
||
|| DEPS_LIST_FIRST (INSN_SPEC_BACK_DEPS (x)) != NULL)
|
||
{
|
||
ready_remove (&ready, i);
|
||
goto restart_debug_insn_loop;
|
||
}
|
||
}
|
||
for (i = ready.n_ready - 1; i >= 0; i--)
|
||
{
|
||
rtx_insn *x;
|
||
|
||
x = ready_element (&ready, i);
|
||
resolve_dependencies (x);
|
||
}
|
||
for (i = 0; i <= max_insn_queue_index; i++)
|
||
{
|
||
rtx_insn_list *link;
|
||
while ((link = insn_queue[i]) != NULL)
|
||
{
|
||
rtx_insn *x = link->insn ();
|
||
insn_queue[i] = link->next ();
|
||
QUEUE_INDEX (x) = QUEUE_NOWHERE;
|
||
free_INSN_LIST_node (link);
|
||
resolve_dependencies (x);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!success)
|
||
undo_all_replacements ();
|
||
|
||
/* Debug info. */
|
||
if (sched_verbose)
|
||
{
|
||
fprintf (sched_dump, ";;\tReady list (final): ");
|
||
debug_ready_list (&ready);
|
||
}
|
||
|
||
if (modulo_ii == 0 && current_sched_info->queue_must_finish_empty)
|
||
/* Sanity check -- queue must be empty now. Meaningless if region has
|
||
multiple bbs. */
|
||
gcc_assert (!q_size && !ready.n_ready && !ready.n_debug);
|
||
else if (modulo_ii == 0)
|
||
{
|
||
/* We must maintain QUEUE_INDEX between blocks in region. */
|
||
for (i = ready.n_ready - 1; i >= 0; i--)
|
||
{
|
||
rtx_insn *x;
|
||
|
||
x = ready_element (&ready, i);
|
||
QUEUE_INDEX (x) = QUEUE_NOWHERE;
|
||
TODO_SPEC (x) = HARD_DEP;
|
||
}
|
||
|
||
if (q_size)
|
||
for (i = 0; i <= max_insn_queue_index; i++)
|
||
{
|
||
rtx_insn_list *link;
|
||
for (link = insn_queue[i]; link; link = link->next ())
|
||
{
|
||
rtx_insn *x;
|
||
|
||
x = link->insn ();
|
||
QUEUE_INDEX (x) = QUEUE_NOWHERE;
|
||
TODO_SPEC (x) = HARD_DEP;
|
||
}
|
||
free_INSN_LIST_list (&insn_queue[i]);
|
||
}
|
||
}
|
||
|
||
if (sched_pressure == SCHED_PRESSURE_MODEL)
|
||
model_end_schedule ();
|
||
|
||
if (success)
|
||
{
|
||
commit_schedule (prev_head, tail, target_bb);
|
||
if (sched_verbose)
|
||
fprintf (sched_dump, ";; total time = %d\n", clock_var);
|
||
}
|
||
else
|
||
last_scheduled_insn = tail;
|
||
|
||
scheduled_insns.truncate (0);
|
||
|
||
if (!current_sched_info->queue_must_finish_empty
|
||
|| haifa_recovery_bb_recently_added_p)
|
||
{
|
||
/* INSN_TICK (minimum clock tick at which the insn becomes
|
||
ready) may be not correct for the insn in the subsequent
|
||
blocks of the region. We should use a correct value of
|
||
`clock_var' or modify INSN_TICK. It is better to keep
|
||
clock_var value equal to 0 at the start of a basic block.
|
||
Therefore we modify INSN_TICK here. */
|
||
fix_inter_tick (NEXT_INSN (prev_head), last_scheduled_insn);
|
||
}
|
||
|
||
if (targetm.sched.finish)
|
||
{
|
||
targetm.sched.finish (sched_dump, sched_verbose);
|
||
/* Target might have added some instructions to the scheduled block
|
||
in its md_finish () hook. These new insns don't have any data
|
||
initialized and to identify them we extend h_i_d so that they'll
|
||
get zero luids. */
|
||
sched_extend_luids ();
|
||
}
|
||
|
||
/* Update head/tail boundaries. */
|
||
head = NEXT_INSN (prev_head);
|
||
tail = last_scheduled_insn;
|
||
|
||
if (sched_verbose)
|
||
{
|
||
fprintf (sched_dump, ";; new head = %d\n;; new tail = %d\n",
|
||
INSN_UID (head), INSN_UID (tail));
|
||
|
||
if (sched_verbose >= 2)
|
||
{
|
||
dump_insn_stream (head, tail);
|
||
print_rank_for_schedule_stats (";; TOTAL ", &rank_for_schedule_stats,
|
||
NULL);
|
||
}
|
||
|
||
fprintf (sched_dump, "\n");
|
||
}
|
||
|
||
head = restore_other_notes (head, NULL);
|
||
|
||
current_sched_info->head = head;
|
||
current_sched_info->tail = tail;
|
||
|
||
free_backtrack_queue ();
|
||
|
||
return success;
|
||
}
|
||
|
||
/* Set_priorities: compute priority of each insn in the block. */
|
||
|
||
int
|
||
set_priorities (rtx_insn *head, rtx_insn *tail)
|
||
{
|
||
rtx_insn *insn;
|
||
int n_insn;
|
||
int sched_max_insns_priority =
|
||
current_sched_info->sched_max_insns_priority;
|
||
rtx_insn *prev_head;
|
||
|
||
if (head == tail && ! INSN_P (head))
|
||
gcc_unreachable ();
|
||
|
||
n_insn = 0;
|
||
|
||
prev_head = PREV_INSN (head);
|
||
for (insn = tail; insn != prev_head; insn = PREV_INSN (insn))
|
||
{
|
||
if (!INSN_P (insn))
|
||
continue;
|
||
|
||
n_insn++;
|
||
(void) priority (insn);
|
||
|
||
gcc_assert (INSN_PRIORITY_KNOWN (insn));
|
||
|
||
sched_max_insns_priority = MAX (sched_max_insns_priority,
|
||
INSN_PRIORITY (insn));
|
||
}
|
||
|
||
current_sched_info->sched_max_insns_priority = sched_max_insns_priority;
|
||
|
||
return n_insn;
|
||
}
|
||
|
||
/* Set dump and sched_verbose for the desired debugging output. If no
|
||
dump-file was specified, but -fsched-verbose=N (any N), print to stderr.
|
||
For -fsched-verbose=N, N>=10, print everything to stderr. */
|
||
void
|
||
setup_sched_dump (void)
|
||
{
|
||
sched_verbose = sched_verbose_param;
|
||
if (sched_verbose_param == 0 && dump_file)
|
||
sched_verbose = 1;
|
||
sched_dump = ((sched_verbose_param >= 10 || !dump_file)
|
||
? stderr : dump_file);
|
||
}
|
||
|
||
/* Allocate data for register pressure sensitive scheduling. */
|
||
static void
|
||
alloc_global_sched_pressure_data (void)
|
||
{
|
||
if (sched_pressure != SCHED_PRESSURE_NONE)
|
||
{
|
||
int i, max_regno = max_reg_num ();
|
||
|
||
if (sched_dump != NULL)
|
||
/* We need info about pseudos for rtl dumps about pseudo
|
||
classes and costs. */
|
||
regstat_init_n_sets_and_refs ();
|
||
ira_set_pseudo_classes (true, sched_verbose ? sched_dump : NULL);
|
||
sched_regno_pressure_class
|
||
= (enum reg_class *) xmalloc (max_regno * sizeof (enum reg_class));
|
||
for (i = 0; i < max_regno; i++)
|
||
sched_regno_pressure_class[i]
|
||
= (i < FIRST_PSEUDO_REGISTER
|
||
? ira_pressure_class_translate[REGNO_REG_CLASS (i)]
|
||
: ira_pressure_class_translate[reg_allocno_class (i)]);
|
||
curr_reg_live = BITMAP_ALLOC (NULL);
|
||
if (sched_pressure == SCHED_PRESSURE_WEIGHTED)
|
||
{
|
||
saved_reg_live = BITMAP_ALLOC (NULL);
|
||
region_ref_regs = BITMAP_ALLOC (NULL);
|
||
}
|
||
|
||
/* Calculate number of CALL_USED_REGS in register classes that
|
||
we calculate register pressure for. */
|
||
for (int c = 0; c < ira_pressure_classes_num; ++c)
|
||
{
|
||
enum reg_class cl = ira_pressure_classes[c];
|
||
|
||
call_used_regs_num[cl] = 0;
|
||
|
||
for (int i = 0; i < ira_class_hard_regs_num[cl]; ++i)
|
||
if (call_used_regs[ira_class_hard_regs[cl][i]])
|
||
++call_used_regs_num[cl];
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Free data for register pressure sensitive scheduling. Also called
|
||
from schedule_region when stopping sched-pressure early. */
|
||
void
|
||
free_global_sched_pressure_data (void)
|
||
{
|
||
if (sched_pressure != SCHED_PRESSURE_NONE)
|
||
{
|
||
if (regstat_n_sets_and_refs != NULL)
|
||
regstat_free_n_sets_and_refs ();
|
||
if (sched_pressure == SCHED_PRESSURE_WEIGHTED)
|
||
{
|
||
BITMAP_FREE (region_ref_regs);
|
||
BITMAP_FREE (saved_reg_live);
|
||
}
|
||
BITMAP_FREE (curr_reg_live);
|
||
free (sched_regno_pressure_class);
|
||
}
|
||
}
|
||
|
||
/* Initialize some global state for the scheduler. This function works
|
||
with the common data shared between all the schedulers. It is called
|
||
from the scheduler specific initialization routine. */
|
||
|
||
void
|
||
sched_init (void)
|
||
{
|
||
/* Disable speculative loads in their presence if cc0 defined. */
|
||
if (HAVE_cc0)
|
||
flag_schedule_speculative_load = 0;
|
||
|
||
if (targetm.sched.dispatch (NULL, IS_DISPATCH_ON))
|
||
targetm.sched.dispatch_do (NULL, DISPATCH_INIT);
|
||
|
||
if (live_range_shrinkage_p)
|
||
sched_pressure = SCHED_PRESSURE_WEIGHTED;
|
||
else if (flag_sched_pressure
|
||
&& !reload_completed
|
||
&& common_sched_info->sched_pass_id == SCHED_RGN_PASS)
|
||
sched_pressure = ((enum sched_pressure_algorithm)
|
||
PARAM_VALUE (PARAM_SCHED_PRESSURE_ALGORITHM));
|
||
else
|
||
sched_pressure = SCHED_PRESSURE_NONE;
|
||
|
||
if (sched_pressure != SCHED_PRESSURE_NONE)
|
||
ira_setup_eliminable_regset ();
|
||
|
||
/* Initialize SPEC_INFO. */
|
||
if (targetm.sched.set_sched_flags)
|
||
{
|
||
spec_info = &spec_info_var;
|
||
targetm.sched.set_sched_flags (spec_info);
|
||
|
||
if (spec_info->mask != 0)
|
||
{
|
||
spec_info->data_weakness_cutoff =
|
||
(PARAM_VALUE (PARAM_SCHED_SPEC_PROB_CUTOFF) * MAX_DEP_WEAK) / 100;
|
||
spec_info->control_weakness_cutoff =
|
||
(PARAM_VALUE (PARAM_SCHED_SPEC_PROB_CUTOFF)
|
||
* REG_BR_PROB_BASE) / 100;
|
||
}
|
||
else
|
||
/* So we won't read anything accidentally. */
|
||
spec_info = NULL;
|
||
|
||
}
|
||
else
|
||
/* So we won't read anything accidentally. */
|
||
spec_info = 0;
|
||
|
||
/* Initialize issue_rate. */
|
||
if (targetm.sched.issue_rate)
|
||
issue_rate = targetm.sched.issue_rate ();
|
||
else
|
||
issue_rate = 1;
|
||
|
||
if (targetm.sched.first_cycle_multipass_dfa_lookahead
|
||
/* Don't use max_issue with reg_pressure scheduling. Multipass
|
||
scheduling and reg_pressure scheduling undo each other's decisions. */
|
||
&& sched_pressure == SCHED_PRESSURE_NONE)
|
||
dfa_lookahead = targetm.sched.first_cycle_multipass_dfa_lookahead ();
|
||
else
|
||
dfa_lookahead = 0;
|
||
|
||
/* Set to "0" so that we recalculate. */
|
||
max_lookahead_tries = 0;
|
||
|
||
if (targetm.sched.init_dfa_pre_cycle_insn)
|
||
targetm.sched.init_dfa_pre_cycle_insn ();
|
||
|
||
if (targetm.sched.init_dfa_post_cycle_insn)
|
||
targetm.sched.init_dfa_post_cycle_insn ();
|
||
|
||
dfa_start ();
|
||
dfa_state_size = state_size ();
|
||
|
||
init_alias_analysis ();
|
||
|
||
if (!sched_no_dce)
|
||
df_set_flags (DF_LR_RUN_DCE);
|
||
df_note_add_problem ();
|
||
|
||
/* More problems needed for interloop dep calculation in SMS. */
|
||
if (common_sched_info->sched_pass_id == SCHED_SMS_PASS)
|
||
{
|
||
df_rd_add_problem ();
|
||
df_chain_add_problem (DF_DU_CHAIN + DF_UD_CHAIN);
|
||
}
|
||
|
||
df_analyze ();
|
||
|
||
/* Do not run DCE after reload, as this can kill nops inserted
|
||
by bundling. */
|
||
if (reload_completed)
|
||
df_clear_flags (DF_LR_RUN_DCE);
|
||
|
||
regstat_compute_calls_crossed ();
|
||
|
||
if (targetm.sched.init_global)
|
||
targetm.sched.init_global (sched_dump, sched_verbose, get_max_uid () + 1);
|
||
|
||
alloc_global_sched_pressure_data ();
|
||
|
||
curr_state = xmalloc (dfa_state_size);
|
||
}
|
||
|
||
static void haifa_init_only_bb (basic_block, basic_block);
|
||
|
||
/* Initialize data structures specific to the Haifa scheduler. */
|
||
void
|
||
haifa_sched_init (void)
|
||
{
|
||
setup_sched_dump ();
|
||
sched_init ();
|
||
|
||
scheduled_insns.create (0);
|
||
|
||
if (spec_info != NULL)
|
||
{
|
||
sched_deps_info->use_deps_list = 1;
|
||
sched_deps_info->generate_spec_deps = 1;
|
||
}
|
||
|
||
/* Initialize luids, dependency caches, target and h_i_d for the
|
||
whole function. */
|
||
{
|
||
bb_vec_t bbs;
|
||
bbs.create (n_basic_blocks_for_fn (cfun));
|
||
basic_block bb;
|
||
|
||
sched_init_bbs ();
|
||
|
||
FOR_EACH_BB_FN (bb, cfun)
|
||
bbs.quick_push (bb);
|
||
sched_init_luids (bbs);
|
||
sched_deps_init (true);
|
||
sched_extend_target ();
|
||
haifa_init_h_i_d (bbs);
|
||
|
||
bbs.release ();
|
||
}
|
||
|
||
sched_init_only_bb = haifa_init_only_bb;
|
||
sched_split_block = sched_split_block_1;
|
||
sched_create_empty_bb = sched_create_empty_bb_1;
|
||
haifa_recovery_bb_ever_added_p = false;
|
||
|
||
nr_begin_data = nr_begin_control = nr_be_in_data = nr_be_in_control = 0;
|
||
before_recovery = 0;
|
||
after_recovery = 0;
|
||
|
||
modulo_ii = 0;
|
||
}
|
||
|
||
/* Finish work with the data specific to the Haifa scheduler. */
|
||
void
|
||
haifa_sched_finish (void)
|
||
{
|
||
sched_create_empty_bb = NULL;
|
||
sched_split_block = NULL;
|
||
sched_init_only_bb = NULL;
|
||
|
||
if (spec_info && spec_info->dump)
|
||
{
|
||
char c = reload_completed ? 'a' : 'b';
|
||
|
||
fprintf (spec_info->dump,
|
||
";; %s:\n", current_function_name ());
|
||
|
||
fprintf (spec_info->dump,
|
||
";; Procedure %cr-begin-data-spec motions == %d\n",
|
||
c, nr_begin_data);
|
||
fprintf (spec_info->dump,
|
||
";; Procedure %cr-be-in-data-spec motions == %d\n",
|
||
c, nr_be_in_data);
|
||
fprintf (spec_info->dump,
|
||
";; Procedure %cr-begin-control-spec motions == %d\n",
|
||
c, nr_begin_control);
|
||
fprintf (spec_info->dump,
|
||
";; Procedure %cr-be-in-control-spec motions == %d\n",
|
||
c, nr_be_in_control);
|
||
}
|
||
|
||
scheduled_insns.release ();
|
||
|
||
/* Finalize h_i_d, dependency caches, and luids for the whole
|
||
function. Target will be finalized in md_global_finish (). */
|
||
sched_deps_finish ();
|
||
sched_finish_luids ();
|
||
current_sched_info = NULL;
|
||
sched_finish ();
|
||
}
|
||
|
||
/* Free global data used during insn scheduling. This function works with
|
||
the common data shared between the schedulers. */
|
||
|
||
void
|
||
sched_finish (void)
|
||
{
|
||
haifa_finish_h_i_d ();
|
||
free_global_sched_pressure_data ();
|
||
free (curr_state);
|
||
|
||
if (targetm.sched.finish_global)
|
||
targetm.sched.finish_global (sched_dump, sched_verbose);
|
||
|
||
end_alias_analysis ();
|
||
|
||
regstat_free_calls_crossed ();
|
||
|
||
dfa_finish ();
|
||
}
|
||
|
||
/* Free all delay_pair structures that were recorded. */
|
||
void
|
||
free_delay_pairs (void)
|
||
{
|
||
if (delay_htab)
|
||
{
|
||
delay_htab->empty ();
|
||
delay_htab_i2->empty ();
|
||
}
|
||
}
|
||
|
||
/* Fix INSN_TICKs of the instructions in the current block as well as
|
||
INSN_TICKs of their dependents.
|
||
HEAD and TAIL are the begin and the end of the current scheduled block. */
|
||
static void
|
||
fix_inter_tick (rtx_insn *head, rtx_insn *tail)
|
||
{
|
||
/* Set of instructions with corrected INSN_TICK. */
|
||
bitmap_head processed;
|
||
/* ??? It is doubtful if we should assume that cycle advance happens on
|
||
basic block boundaries. Basically insns that are unconditionally ready
|
||
on the start of the block are more preferable then those which have
|
||
a one cycle dependency over insn from the previous block. */
|
||
int next_clock = clock_var + 1;
|
||
|
||
bitmap_initialize (&processed, 0);
|
||
|
||
/* Iterates over scheduled instructions and fix their INSN_TICKs and
|
||
INSN_TICKs of dependent instructions, so that INSN_TICKs are consistent
|
||
across different blocks. */
|
||
for (tail = NEXT_INSN (tail); head != tail; head = NEXT_INSN (head))
|
||
{
|
||
if (INSN_P (head))
|
||
{
|
||
int tick;
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
|
||
tick = INSN_TICK (head);
|
||
gcc_assert (tick >= MIN_TICK);
|
||
|
||
/* Fix INSN_TICK of instruction from just scheduled block. */
|
||
if (bitmap_set_bit (&processed, INSN_LUID (head)))
|
||
{
|
||
tick -= next_clock;
|
||
|
||
if (tick < MIN_TICK)
|
||
tick = MIN_TICK;
|
||
|
||
INSN_TICK (head) = tick;
|
||
}
|
||
|
||
if (DEBUG_INSN_P (head))
|
||
continue;
|
||
|
||
FOR_EACH_DEP (head, SD_LIST_RES_FORW, sd_it, dep)
|
||
{
|
||
rtx_insn *next;
|
||
|
||
next = DEP_CON (dep);
|
||
tick = INSN_TICK (next);
|
||
|
||
if (tick != INVALID_TICK
|
||
/* If NEXT has its INSN_TICK calculated, fix it.
|
||
If not - it will be properly calculated from
|
||
scratch later in fix_tick_ready. */
|
||
&& bitmap_set_bit (&processed, INSN_LUID (next)))
|
||
{
|
||
tick -= next_clock;
|
||
|
||
if (tick < MIN_TICK)
|
||
tick = MIN_TICK;
|
||
|
||
if (tick > INTER_TICK (next))
|
||
INTER_TICK (next) = tick;
|
||
else
|
||
tick = INTER_TICK (next);
|
||
|
||
INSN_TICK (next) = tick;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
bitmap_clear (&processed);
|
||
}
|
||
|
||
/* Check if NEXT is ready to be added to the ready or queue list.
|
||
If "yes", add it to the proper list.
|
||
Returns:
|
||
-1 - is not ready yet,
|
||
0 - added to the ready list,
|
||
0 < N - queued for N cycles. */
|
||
int
|
||
try_ready (rtx_insn *next)
|
||
{
|
||
ds_t old_ts, new_ts;
|
||
|
||
old_ts = TODO_SPEC (next);
|
||
|
||
gcc_assert (!(old_ts & ~(SPECULATIVE | HARD_DEP | DEP_CONTROL | DEP_POSTPONED))
|
||
&& (old_ts == HARD_DEP
|
||
|| old_ts == DEP_POSTPONED
|
||
|| (old_ts & SPECULATIVE)
|
||
|| old_ts == DEP_CONTROL));
|
||
|
||
new_ts = recompute_todo_spec (next, false);
|
||
|
||
if (new_ts & (HARD_DEP | DEP_POSTPONED))
|
||
gcc_assert (new_ts == old_ts
|
||
&& QUEUE_INDEX (next) == QUEUE_NOWHERE);
|
||
else if (current_sched_info->new_ready)
|
||
new_ts = current_sched_info->new_ready (next, new_ts);
|
||
|
||
/* * if !(old_ts & SPECULATIVE) (e.g. HARD_DEP or 0), then insn might
|
||
have its original pattern or changed (speculative) one. This is due
|
||
to changing ebb in region scheduling.
|
||
* But if (old_ts & SPECULATIVE), then we are pretty sure that insn
|
||
has speculative pattern.
|
||
|
||
We can't assert (!(new_ts & HARD_DEP) || new_ts == old_ts) here because
|
||
control-speculative NEXT could have been discarded by sched-rgn.c
|
||
(the same case as when discarded by can_schedule_ready_p ()). */
|
||
|
||
if ((new_ts & SPECULATIVE)
|
||
/* If (old_ts == new_ts), then (old_ts & SPECULATIVE) and we don't
|
||
need to change anything. */
|
||
&& new_ts != old_ts)
|
||
{
|
||
int res;
|
||
rtx new_pat;
|
||
|
||
gcc_assert ((new_ts & SPECULATIVE) && !(new_ts & ~SPECULATIVE));
|
||
|
||
res = haifa_speculate_insn (next, new_ts, &new_pat);
|
||
|
||
switch (res)
|
||
{
|
||
case -1:
|
||
/* It would be nice to change DEP_STATUS of all dependences,
|
||
which have ((DEP_STATUS & SPECULATIVE) == new_ts) to HARD_DEP,
|
||
so we won't reanalyze anything. */
|
||
new_ts = HARD_DEP;
|
||
break;
|
||
|
||
case 0:
|
||
/* We follow the rule, that every speculative insn
|
||
has non-null ORIG_PAT. */
|
||
if (!ORIG_PAT (next))
|
||
ORIG_PAT (next) = PATTERN (next);
|
||
break;
|
||
|
||
case 1:
|
||
if (!ORIG_PAT (next))
|
||
/* If we gonna to overwrite the original pattern of insn,
|
||
save it. */
|
||
ORIG_PAT (next) = PATTERN (next);
|
||
|
||
res = haifa_change_pattern (next, new_pat);
|
||
gcc_assert (res);
|
||
break;
|
||
|
||
default:
|
||
gcc_unreachable ();
|
||
}
|
||
}
|
||
|
||
/* We need to restore pattern only if (new_ts == 0), because otherwise it is
|
||
either correct (new_ts & SPECULATIVE),
|
||
or we simply don't care (new_ts & HARD_DEP). */
|
||
|
||
gcc_assert (!ORIG_PAT (next)
|
||
|| !IS_SPECULATION_BRANCHY_CHECK_P (next));
|
||
|
||
TODO_SPEC (next) = new_ts;
|
||
|
||
if (new_ts & (HARD_DEP | DEP_POSTPONED))
|
||
{
|
||
/* We can't assert (QUEUE_INDEX (next) == QUEUE_NOWHERE) here because
|
||
control-speculative NEXT could have been discarded by sched-rgn.c
|
||
(the same case as when discarded by can_schedule_ready_p ()). */
|
||
/*gcc_assert (QUEUE_INDEX (next) == QUEUE_NOWHERE);*/
|
||
|
||
change_queue_index (next, QUEUE_NOWHERE);
|
||
|
||
return -1;
|
||
}
|
||
else if (!(new_ts & BEGIN_SPEC)
|
||
&& ORIG_PAT (next) && PREDICATED_PAT (next) == NULL_RTX
|
||
&& !IS_SPECULATION_CHECK_P (next))
|
||
/* We should change pattern of every previously speculative
|
||
instruction - and we determine if NEXT was speculative by using
|
||
ORIG_PAT field. Except one case - speculation checks have ORIG_PAT
|
||
pat too, so skip them. */
|
||
{
|
||
bool success = haifa_change_pattern (next, ORIG_PAT (next));
|
||
gcc_assert (success);
|
||
ORIG_PAT (next) = 0;
|
||
}
|
||
|
||
if (sched_verbose >= 2)
|
||
{
|
||
fprintf (sched_dump, ";;\t\tdependencies resolved: insn %s",
|
||
(*current_sched_info->print_insn) (next, 0));
|
||
|
||
if (spec_info && spec_info->dump)
|
||
{
|
||
if (new_ts & BEGIN_DATA)
|
||
fprintf (spec_info->dump, "; data-spec;");
|
||
if (new_ts & BEGIN_CONTROL)
|
||
fprintf (spec_info->dump, "; control-spec;");
|
||
if (new_ts & BE_IN_CONTROL)
|
||
fprintf (spec_info->dump, "; in-control-spec;");
|
||
}
|
||
if (TODO_SPEC (next) & DEP_CONTROL)
|
||
fprintf (sched_dump, " predicated");
|
||
fprintf (sched_dump, "\n");
|
||
}
|
||
|
||
adjust_priority (next);
|
||
|
||
return fix_tick_ready (next);
|
||
}
|
||
|
||
/* Calculate INSN_TICK of NEXT and add it to either ready or queue list. */
|
||
static int
|
||
fix_tick_ready (rtx_insn *next)
|
||
{
|
||
int tick, delay;
|
||
|
||
if (!DEBUG_INSN_P (next) && !sd_lists_empty_p (next, SD_LIST_RES_BACK))
|
||
{
|
||
int full_p;
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
|
||
tick = INSN_TICK (next);
|
||
/* if tick is not equal to INVALID_TICK, then update
|
||
INSN_TICK of NEXT with the most recent resolved dependence
|
||
cost. Otherwise, recalculate from scratch. */
|
||
full_p = (tick == INVALID_TICK);
|
||
|
||
FOR_EACH_DEP (next, SD_LIST_RES_BACK, sd_it, dep)
|
||
{
|
||
rtx_insn *pro = DEP_PRO (dep);
|
||
int tick1;
|
||
|
||
gcc_assert (INSN_TICK (pro) >= MIN_TICK);
|
||
|
||
tick1 = INSN_TICK (pro) + dep_cost (dep);
|
||
if (tick1 > tick)
|
||
tick = tick1;
|
||
|
||
if (!full_p)
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
tick = -1;
|
||
|
||
INSN_TICK (next) = tick;
|
||
|
||
delay = tick - clock_var;
|
||
if (delay <= 0 || sched_pressure != SCHED_PRESSURE_NONE || sched_fusion)
|
||
delay = QUEUE_READY;
|
||
|
||
change_queue_index (next, delay);
|
||
|
||
return delay;
|
||
}
|
||
|
||
/* Move NEXT to the proper queue list with (DELAY >= 1),
|
||
or add it to the ready list (DELAY == QUEUE_READY),
|
||
or remove it from ready and queue lists at all (DELAY == QUEUE_NOWHERE). */
|
||
static void
|
||
change_queue_index (rtx_insn *next, int delay)
|
||
{
|
||
int i = QUEUE_INDEX (next);
|
||
|
||
gcc_assert (QUEUE_NOWHERE <= delay && delay <= max_insn_queue_index
|
||
&& delay != 0);
|
||
gcc_assert (i != QUEUE_SCHEDULED);
|
||
|
||
if ((delay > 0 && NEXT_Q_AFTER (q_ptr, delay) == i)
|
||
|| (delay < 0 && delay == i))
|
||
/* We have nothing to do. */
|
||
return;
|
||
|
||
/* Remove NEXT from wherever it is now. */
|
||
if (i == QUEUE_READY)
|
||
ready_remove_insn (next);
|
||
else if (i >= 0)
|
||
queue_remove (next);
|
||
|
||
/* Add it to the proper place. */
|
||
if (delay == QUEUE_READY)
|
||
ready_add (readyp, next, false);
|
||
else if (delay >= 1)
|
||
queue_insn (next, delay, "change queue index");
|
||
|
||
if (sched_verbose >= 2)
|
||
{
|
||
fprintf (sched_dump, ";;\t\ttick updated: insn %s",
|
||
(*current_sched_info->print_insn) (next, 0));
|
||
|
||
if (delay == QUEUE_READY)
|
||
fprintf (sched_dump, " into ready\n");
|
||
else if (delay >= 1)
|
||
fprintf (sched_dump, " into queue with cost=%d\n", delay);
|
||
else
|
||
fprintf (sched_dump, " removed from ready or queue lists\n");
|
||
}
|
||
}
|
||
|
||
static int sched_ready_n_insns = -1;
|
||
|
||
/* Initialize per region data structures. */
|
||
void
|
||
sched_extend_ready_list (int new_sched_ready_n_insns)
|
||
{
|
||
int i;
|
||
|
||
if (sched_ready_n_insns == -1)
|
||
/* At the first call we need to initialize one more choice_stack
|
||
entry. */
|
||
{
|
||
i = 0;
|
||
sched_ready_n_insns = 0;
|
||
scheduled_insns.reserve (new_sched_ready_n_insns);
|
||
}
|
||
else
|
||
i = sched_ready_n_insns + 1;
|
||
|
||
ready.veclen = new_sched_ready_n_insns + issue_rate;
|
||
ready.vec = XRESIZEVEC (rtx_insn *, ready.vec, ready.veclen);
|
||
|
||
gcc_assert (new_sched_ready_n_insns >= sched_ready_n_insns);
|
||
|
||
ready_try = (signed char *) xrecalloc (ready_try, new_sched_ready_n_insns,
|
||
sched_ready_n_insns,
|
||
sizeof (*ready_try));
|
||
|
||
/* We allocate +1 element to save initial state in the choice_stack[0]
|
||
entry. */
|
||
choice_stack = XRESIZEVEC (struct choice_entry, choice_stack,
|
||
new_sched_ready_n_insns + 1);
|
||
|
||
for (; i <= new_sched_ready_n_insns; i++)
|
||
{
|
||
choice_stack[i].state = xmalloc (dfa_state_size);
|
||
|
||
if (targetm.sched.first_cycle_multipass_init)
|
||
targetm.sched.first_cycle_multipass_init (&(choice_stack[i]
|
||
.target_data));
|
||
}
|
||
|
||
sched_ready_n_insns = new_sched_ready_n_insns;
|
||
}
|
||
|
||
/* Free per region data structures. */
|
||
void
|
||
sched_finish_ready_list (void)
|
||
{
|
||
int i;
|
||
|
||
free (ready.vec);
|
||
ready.vec = NULL;
|
||
ready.veclen = 0;
|
||
|
||
free (ready_try);
|
||
ready_try = NULL;
|
||
|
||
for (i = 0; i <= sched_ready_n_insns; i++)
|
||
{
|
||
if (targetm.sched.first_cycle_multipass_fini)
|
||
targetm.sched.first_cycle_multipass_fini (&(choice_stack[i]
|
||
.target_data));
|
||
|
||
free (choice_stack [i].state);
|
||
}
|
||
free (choice_stack);
|
||
choice_stack = NULL;
|
||
|
||
sched_ready_n_insns = -1;
|
||
}
|
||
|
||
static int
|
||
haifa_luid_for_non_insn (rtx x)
|
||
{
|
||
gcc_assert (NOTE_P (x) || LABEL_P (x));
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Generates recovery code for INSN. */
|
||
static void
|
||
generate_recovery_code (rtx_insn *insn)
|
||
{
|
||
if (TODO_SPEC (insn) & BEGIN_SPEC)
|
||
begin_speculative_block (insn);
|
||
|
||
/* Here we have insn with no dependencies to
|
||
instructions other then CHECK_SPEC ones. */
|
||
|
||
if (TODO_SPEC (insn) & BE_IN_SPEC)
|
||
add_to_speculative_block (insn);
|
||
}
|
||
|
||
/* Helper function.
|
||
Tries to add speculative dependencies of type FS between instructions
|
||
in deps_list L and TWIN. */
|
||
static void
|
||
process_insn_forw_deps_be_in_spec (rtx_insn *insn, rtx_insn *twin, ds_t fs)
|
||
{
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
|
||
FOR_EACH_DEP (insn, SD_LIST_FORW, sd_it, dep)
|
||
{
|
||
ds_t ds;
|
||
rtx_insn *consumer;
|
||
|
||
consumer = DEP_CON (dep);
|
||
|
||
ds = DEP_STATUS (dep);
|
||
|
||
if (/* If we want to create speculative dep. */
|
||
fs
|
||
/* And we can do that because this is a true dep. */
|
||
&& (ds & DEP_TYPES) == DEP_TRUE)
|
||
{
|
||
gcc_assert (!(ds & BE_IN_SPEC));
|
||
|
||
if (/* If this dep can be overcome with 'begin speculation'. */
|
||
ds & BEGIN_SPEC)
|
||
/* Then we have a choice: keep the dep 'begin speculative'
|
||
or transform it into 'be in speculative'. */
|
||
{
|
||
if (/* In try_ready we assert that if insn once became ready
|
||
it can be removed from the ready (or queue) list only
|
||
due to backend decision. Hence we can't let the
|
||
probability of the speculative dep to decrease. */
|
||
ds_weak (ds) <= ds_weak (fs))
|
||
{
|
||
ds_t new_ds;
|
||
|
||
new_ds = (ds & ~BEGIN_SPEC) | fs;
|
||
|
||
if (/* consumer can 'be in speculative'. */
|
||
sched_insn_is_legitimate_for_speculation_p (consumer,
|
||
new_ds))
|
||
/* Transform it to be in speculative. */
|
||
ds = new_ds;
|
||
}
|
||
}
|
||
else
|
||
/* Mark the dep as 'be in speculative'. */
|
||
ds |= fs;
|
||
}
|
||
|
||
{
|
||
dep_def _new_dep, *new_dep = &_new_dep;
|
||
|
||
init_dep_1 (new_dep, twin, consumer, DEP_TYPE (dep), ds);
|
||
sd_add_dep (new_dep, false);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Generates recovery code for BEGIN speculative INSN. */
|
||
static void
|
||
begin_speculative_block (rtx_insn *insn)
|
||
{
|
||
if (TODO_SPEC (insn) & BEGIN_DATA)
|
||
nr_begin_data++;
|
||
if (TODO_SPEC (insn) & BEGIN_CONTROL)
|
||
nr_begin_control++;
|
||
|
||
create_check_block_twin (insn, false);
|
||
|
||
TODO_SPEC (insn) &= ~BEGIN_SPEC;
|
||
}
|
||
|
||
static void haifa_init_insn (rtx_insn *);
|
||
|
||
/* Generates recovery code for BE_IN speculative INSN. */
|
||
static void
|
||
add_to_speculative_block (rtx_insn *insn)
|
||
{
|
||
ds_t ts;
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
rtx_insn_list *twins = NULL;
|
||
rtx_vec_t priorities_roots;
|
||
|
||
ts = TODO_SPEC (insn);
|
||
gcc_assert (!(ts & ~BE_IN_SPEC));
|
||
|
||
if (ts & BE_IN_DATA)
|
||
nr_be_in_data++;
|
||
if (ts & BE_IN_CONTROL)
|
||
nr_be_in_control++;
|
||
|
||
TODO_SPEC (insn) &= ~BE_IN_SPEC;
|
||
gcc_assert (!TODO_SPEC (insn));
|
||
|
||
DONE_SPEC (insn) |= ts;
|
||
|
||
/* First we convert all simple checks to branchy. */
|
||
for (sd_it = sd_iterator_start (insn, SD_LIST_SPEC_BACK);
|
||
sd_iterator_cond (&sd_it, &dep);)
|
||
{
|
||
rtx_insn *check = DEP_PRO (dep);
|
||
|
||
if (IS_SPECULATION_SIMPLE_CHECK_P (check))
|
||
{
|
||
create_check_block_twin (check, true);
|
||
|
||
/* Restart search. */
|
||
sd_it = sd_iterator_start (insn, SD_LIST_SPEC_BACK);
|
||
}
|
||
else
|
||
/* Continue search. */
|
||
sd_iterator_next (&sd_it);
|
||
}
|
||
|
||
priorities_roots.create (0);
|
||
clear_priorities (insn, &priorities_roots);
|
||
|
||
while (1)
|
||
{
|
||
rtx_insn *check, *twin;
|
||
basic_block rec;
|
||
|
||
/* Get the first backward dependency of INSN. */
|
||
sd_it = sd_iterator_start (insn, SD_LIST_SPEC_BACK);
|
||
if (!sd_iterator_cond (&sd_it, &dep))
|
||
/* INSN has no backward dependencies left. */
|
||
break;
|
||
|
||
gcc_assert ((DEP_STATUS (dep) & BEGIN_SPEC) == 0
|
||
&& (DEP_STATUS (dep) & BE_IN_SPEC) != 0
|
||
&& (DEP_STATUS (dep) & DEP_TYPES) == DEP_TRUE);
|
||
|
||
check = DEP_PRO (dep);
|
||
|
||
gcc_assert (!IS_SPECULATION_CHECK_P (check) && !ORIG_PAT (check)
|
||
&& QUEUE_INDEX (check) == QUEUE_NOWHERE);
|
||
|
||
rec = BLOCK_FOR_INSN (check);
|
||
|
||
twin = emit_insn_before (copy_insn (PATTERN (insn)), BB_END (rec));
|
||
haifa_init_insn (twin);
|
||
|
||
sd_copy_back_deps (twin, insn, true);
|
||
|
||
if (sched_verbose && spec_info->dump)
|
||
/* INSN_BB (insn) isn't determined for twin insns yet.
|
||
So we can't use current_sched_info->print_insn. */
|
||
fprintf (spec_info->dump, ";;\t\tGenerated twin insn : %d/rec%d\n",
|
||
INSN_UID (twin), rec->index);
|
||
|
||
twins = alloc_INSN_LIST (twin, twins);
|
||
|
||
/* Add dependences between TWIN and all appropriate
|
||
instructions from REC. */
|
||
FOR_EACH_DEP (insn, SD_LIST_SPEC_BACK, sd_it, dep)
|
||
{
|
||
rtx_insn *pro = DEP_PRO (dep);
|
||
|
||
gcc_assert (DEP_TYPE (dep) == REG_DEP_TRUE);
|
||
|
||
/* INSN might have dependencies from the instructions from
|
||
several recovery blocks. At this iteration we process those
|
||
producers that reside in REC. */
|
||
if (BLOCK_FOR_INSN (pro) == rec)
|
||
{
|
||
dep_def _new_dep, *new_dep = &_new_dep;
|
||
|
||
init_dep (new_dep, pro, twin, REG_DEP_TRUE);
|
||
sd_add_dep (new_dep, false);
|
||
}
|
||
}
|
||
|
||
process_insn_forw_deps_be_in_spec (insn, twin, ts);
|
||
|
||
/* Remove all dependencies between INSN and insns in REC. */
|
||
for (sd_it = sd_iterator_start (insn, SD_LIST_SPEC_BACK);
|
||
sd_iterator_cond (&sd_it, &dep);)
|
||
{
|
||
rtx_insn *pro = DEP_PRO (dep);
|
||
|
||
if (BLOCK_FOR_INSN (pro) == rec)
|
||
sd_delete_dep (sd_it);
|
||
else
|
||
sd_iterator_next (&sd_it);
|
||
}
|
||
}
|
||
|
||
/* We couldn't have added the dependencies between INSN and TWINS earlier
|
||
because that would make TWINS appear in the INSN_BACK_DEPS (INSN). */
|
||
while (twins)
|
||
{
|
||
rtx_insn *twin;
|
||
rtx_insn_list *next_node;
|
||
|
||
twin = twins->insn ();
|
||
|
||
{
|
||
dep_def _new_dep, *new_dep = &_new_dep;
|
||
|
||
init_dep (new_dep, insn, twin, REG_DEP_OUTPUT);
|
||
sd_add_dep (new_dep, false);
|
||
}
|
||
|
||
next_node = twins->next ();
|
||
free_INSN_LIST_node (twins);
|
||
twins = next_node;
|
||
}
|
||
|
||
calc_priorities (priorities_roots);
|
||
priorities_roots.release ();
|
||
}
|
||
|
||
/* Extends and fills with zeros (only the new part) array pointed to by P. */
|
||
void *
|
||
xrecalloc (void *p, size_t new_nmemb, size_t old_nmemb, size_t size)
|
||
{
|
||
gcc_assert (new_nmemb >= old_nmemb);
|
||
p = XRESIZEVAR (void, p, new_nmemb * size);
|
||
memset (((char *) p) + old_nmemb * size, 0, (new_nmemb - old_nmemb) * size);
|
||
return p;
|
||
}
|
||
|
||
/* Helper function.
|
||
Find fallthru edge from PRED. */
|
||
edge
|
||
find_fallthru_edge_from (basic_block pred)
|
||
{
|
||
edge e;
|
||
basic_block succ;
|
||
|
||
succ = pred->next_bb;
|
||
gcc_assert (succ->prev_bb == pred);
|
||
|
||
if (EDGE_COUNT (pred->succs) <= EDGE_COUNT (succ->preds))
|
||
{
|
||
e = find_fallthru_edge (pred->succs);
|
||
|
||
if (e)
|
||
{
|
||
gcc_assert (e->dest == succ);
|
||
return e;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
e = find_fallthru_edge (succ->preds);
|
||
|
||
if (e)
|
||
{
|
||
gcc_assert (e->src == pred);
|
||
return e;
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
/* Extend per basic block data structures. */
|
||
static void
|
||
sched_extend_bb (void)
|
||
{
|
||
/* The following is done to keep current_sched_info->next_tail non null. */
|
||
rtx_insn *end = BB_END (EXIT_BLOCK_PTR_FOR_FN (cfun)->prev_bb);
|
||
rtx_insn *insn = DEBUG_INSN_P (end) ? prev_nondebug_insn (end) : end;
|
||
if (NEXT_INSN (end) == 0
|
||
|| (!NOTE_P (insn)
|
||
&& !LABEL_P (insn)
|
||
/* Don't emit a NOTE if it would end up before a BARRIER. */
|
||
&& !BARRIER_P (NEXT_INSN (end))))
|
||
{
|
||
rtx_note *note = emit_note_after (NOTE_INSN_DELETED, end);
|
||
/* Make note appear outside BB. */
|
||
set_block_for_insn (note, NULL);
|
||
BB_END (EXIT_BLOCK_PTR_FOR_FN (cfun)->prev_bb) = end;
|
||
}
|
||
}
|
||
|
||
/* Init per basic block data structures. */
|
||
void
|
||
sched_init_bbs (void)
|
||
{
|
||
sched_extend_bb ();
|
||
}
|
||
|
||
/* Initialize BEFORE_RECOVERY variable. */
|
||
static void
|
||
init_before_recovery (basic_block *before_recovery_ptr)
|
||
{
|
||
basic_block last;
|
||
edge e;
|
||
|
||
last = EXIT_BLOCK_PTR_FOR_FN (cfun)->prev_bb;
|
||
e = find_fallthru_edge_from (last);
|
||
|
||
if (e)
|
||
{
|
||
/* We create two basic blocks:
|
||
1. Single instruction block is inserted right after E->SRC
|
||
and has jump to
|
||
2. Empty block right before EXIT_BLOCK.
|
||
Between these two blocks recovery blocks will be emitted. */
|
||
|
||
basic_block single, empty;
|
||
|
||
/* If the fallthrough edge to exit we've found is from the block we've
|
||
created before, don't do anything more. */
|
||
if (last == after_recovery)
|
||
return;
|
||
|
||
adding_bb_to_current_region_p = false;
|
||
|
||
single = sched_create_empty_bb (last);
|
||
empty = sched_create_empty_bb (single);
|
||
|
||
/* Add new blocks to the root loop. */
|
||
if (current_loops != NULL)
|
||
{
|
||
add_bb_to_loop (single, (*current_loops->larray)[0]);
|
||
add_bb_to_loop (empty, (*current_loops->larray)[0]);
|
||
}
|
||
|
||
single->count = last->count;
|
||
empty->count = last->count;
|
||
single->frequency = last->frequency;
|
||
empty->frequency = last->frequency;
|
||
BB_COPY_PARTITION (single, last);
|
||
BB_COPY_PARTITION (empty, last);
|
||
|
||
redirect_edge_succ (e, single);
|
||
make_single_succ_edge (single, empty, 0);
|
||
make_single_succ_edge (empty, EXIT_BLOCK_PTR_FOR_FN (cfun),
|
||
EDGE_FALLTHRU);
|
||
|
||
rtx_code_label *label = block_label (empty);
|
||
rtx_jump_insn *x = emit_jump_insn_after (targetm.gen_jump (label),
|
||
BB_END (single));
|
||
JUMP_LABEL (x) = label;
|
||
LABEL_NUSES (label)++;
|
||
haifa_init_insn (x);
|
||
|
||
emit_barrier_after (x);
|
||
|
||
sched_init_only_bb (empty, NULL);
|
||
sched_init_only_bb (single, NULL);
|
||
sched_extend_bb ();
|
||
|
||
adding_bb_to_current_region_p = true;
|
||
before_recovery = single;
|
||
after_recovery = empty;
|
||
|
||
if (before_recovery_ptr)
|
||
*before_recovery_ptr = before_recovery;
|
||
|
||
if (sched_verbose >= 2 && spec_info->dump)
|
||
fprintf (spec_info->dump,
|
||
";;\t\tFixed fallthru to EXIT : %d->>%d->%d->>EXIT\n",
|
||
last->index, single->index, empty->index);
|
||
}
|
||
else
|
||
before_recovery = last;
|
||
}
|
||
|
||
/* Returns new recovery block. */
|
||
basic_block
|
||
sched_create_recovery_block (basic_block *before_recovery_ptr)
|
||
{
|
||
rtx_insn *barrier;
|
||
basic_block rec;
|
||
|
||
haifa_recovery_bb_recently_added_p = true;
|
||
haifa_recovery_bb_ever_added_p = true;
|
||
|
||
init_before_recovery (before_recovery_ptr);
|
||
|
||
barrier = get_last_bb_insn (before_recovery);
|
||
gcc_assert (BARRIER_P (barrier));
|
||
|
||
rtx_insn *label = emit_label_after (gen_label_rtx (), barrier);
|
||
|
||
rec = create_basic_block (label, label, before_recovery);
|
||
|
||
/* A recovery block always ends with an unconditional jump. */
|
||
emit_barrier_after (BB_END (rec));
|
||
|
||
if (BB_PARTITION (before_recovery) != BB_UNPARTITIONED)
|
||
BB_SET_PARTITION (rec, BB_COLD_PARTITION);
|
||
|
||
if (sched_verbose && spec_info->dump)
|
||
fprintf (spec_info->dump, ";;\t\tGenerated recovery block rec%d\n",
|
||
rec->index);
|
||
|
||
return rec;
|
||
}
|
||
|
||
/* Create edges: FIRST_BB -> REC; FIRST_BB -> SECOND_BB; REC -> SECOND_BB
|
||
and emit necessary jumps. */
|
||
void
|
||
sched_create_recovery_edges (basic_block first_bb, basic_block rec,
|
||
basic_block second_bb)
|
||
{
|
||
int edge_flags;
|
||
|
||
/* This is fixing of incoming edge. */
|
||
/* ??? Which other flags should be specified? */
|
||
if (BB_PARTITION (first_bb) != BB_PARTITION (rec))
|
||
/* Partition type is the same, if it is "unpartitioned". */
|
||
edge_flags = EDGE_CROSSING;
|
||
else
|
||
edge_flags = 0;
|
||
|
||
make_edge (first_bb, rec, edge_flags);
|
||
rtx_code_label *label = block_label (second_bb);
|
||
rtx_jump_insn *jump = emit_jump_insn_after (targetm.gen_jump (label),
|
||
BB_END (rec));
|
||
JUMP_LABEL (jump) = label;
|
||
LABEL_NUSES (label)++;
|
||
|
||
if (BB_PARTITION (second_bb) != BB_PARTITION (rec))
|
||
/* Partition type is the same, if it is "unpartitioned". */
|
||
{
|
||
/* Rewritten from cfgrtl.c. */
|
||
if (flag_reorder_blocks_and_partition
|
||
&& targetm_common.have_named_sections)
|
||
{
|
||
/* We don't need the same note for the check because
|
||
any_condjump_p (check) == true. */
|
||
CROSSING_JUMP_P (jump) = 1;
|
||
}
|
||
edge_flags = EDGE_CROSSING;
|
||
}
|
||
else
|
||
edge_flags = 0;
|
||
|
||
make_single_succ_edge (rec, second_bb, edge_flags);
|
||
if (dom_info_available_p (CDI_DOMINATORS))
|
||
set_immediate_dominator (CDI_DOMINATORS, rec, first_bb);
|
||
}
|
||
|
||
/* This function creates recovery code for INSN. If MUTATE_P is nonzero,
|
||
INSN is a simple check, that should be converted to branchy one. */
|
||
static void
|
||
create_check_block_twin (rtx_insn *insn, bool mutate_p)
|
||
{
|
||
basic_block rec;
|
||
rtx_insn *label, *check, *twin;
|
||
rtx check_pat;
|
||
ds_t fs;
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
dep_def _new_dep, *new_dep = &_new_dep;
|
||
ds_t todo_spec;
|
||
|
||
gcc_assert (ORIG_PAT (insn) != NULL_RTX);
|
||
|
||
if (!mutate_p)
|
||
todo_spec = TODO_SPEC (insn);
|
||
else
|
||
{
|
||
gcc_assert (IS_SPECULATION_SIMPLE_CHECK_P (insn)
|
||
&& (TODO_SPEC (insn) & SPECULATIVE) == 0);
|
||
|
||
todo_spec = CHECK_SPEC (insn);
|
||
}
|
||
|
||
todo_spec &= SPECULATIVE;
|
||
|
||
/* Create recovery block. */
|
||
if (mutate_p || targetm.sched.needs_block_p (todo_spec))
|
||
{
|
||
rec = sched_create_recovery_block (NULL);
|
||
label = BB_HEAD (rec);
|
||
}
|
||
else
|
||
{
|
||
rec = EXIT_BLOCK_PTR_FOR_FN (cfun);
|
||
label = NULL;
|
||
}
|
||
|
||
/* Emit CHECK. */
|
||
check_pat = targetm.sched.gen_spec_check (insn, label, todo_spec);
|
||
|
||
if (rec != EXIT_BLOCK_PTR_FOR_FN (cfun))
|
||
{
|
||
/* To have mem_reg alive at the beginning of second_bb,
|
||
we emit check BEFORE insn, so insn after splitting
|
||
insn will be at the beginning of second_bb, which will
|
||
provide us with the correct life information. */
|
||
check = emit_jump_insn_before (check_pat, insn);
|
||
JUMP_LABEL (check) = label;
|
||
LABEL_NUSES (label)++;
|
||
}
|
||
else
|
||
check = emit_insn_before (check_pat, insn);
|
||
|
||
/* Extend data structures. */
|
||
haifa_init_insn (check);
|
||
|
||
/* CHECK is being added to current region. Extend ready list. */
|
||
gcc_assert (sched_ready_n_insns != -1);
|
||
sched_extend_ready_list (sched_ready_n_insns + 1);
|
||
|
||
if (current_sched_info->add_remove_insn)
|
||
current_sched_info->add_remove_insn (insn, 0);
|
||
|
||
RECOVERY_BLOCK (check) = rec;
|
||
|
||
if (sched_verbose && spec_info->dump)
|
||
fprintf (spec_info->dump, ";;\t\tGenerated check insn : %s\n",
|
||
(*current_sched_info->print_insn) (check, 0));
|
||
|
||
gcc_assert (ORIG_PAT (insn));
|
||
|
||
/* Initialize TWIN (twin is a duplicate of original instruction
|
||
in the recovery block). */
|
||
if (rec != EXIT_BLOCK_PTR_FOR_FN (cfun))
|
||
{
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
|
||
FOR_EACH_DEP (insn, SD_LIST_RES_BACK, sd_it, dep)
|
||
if ((DEP_STATUS (dep) & DEP_OUTPUT) != 0)
|
||
{
|
||
struct _dep _dep2, *dep2 = &_dep2;
|
||
|
||
init_dep (dep2, DEP_PRO (dep), check, REG_DEP_TRUE);
|
||
|
||
sd_add_dep (dep2, true);
|
||
}
|
||
|
||
twin = emit_insn_after (ORIG_PAT (insn), BB_END (rec));
|
||
haifa_init_insn (twin);
|
||
|
||
if (sched_verbose && spec_info->dump)
|
||
/* INSN_BB (insn) isn't determined for twin insns yet.
|
||
So we can't use current_sched_info->print_insn. */
|
||
fprintf (spec_info->dump, ";;\t\tGenerated twin insn : %d/rec%d\n",
|
||
INSN_UID (twin), rec->index);
|
||
}
|
||
else
|
||
{
|
||
ORIG_PAT (check) = ORIG_PAT (insn);
|
||
HAS_INTERNAL_DEP (check) = 1;
|
||
twin = check;
|
||
/* ??? We probably should change all OUTPUT dependencies to
|
||
(TRUE | OUTPUT). */
|
||
}
|
||
|
||
/* Copy all resolved back dependencies of INSN to TWIN. This will
|
||
provide correct value for INSN_TICK (TWIN). */
|
||
sd_copy_back_deps (twin, insn, true);
|
||
|
||
if (rec != EXIT_BLOCK_PTR_FOR_FN (cfun))
|
||
/* In case of branchy check, fix CFG. */
|
||
{
|
||
basic_block first_bb, second_bb;
|
||
rtx_insn *jump;
|
||
|
||
first_bb = BLOCK_FOR_INSN (check);
|
||
second_bb = sched_split_block (first_bb, check);
|
||
|
||
sched_create_recovery_edges (first_bb, rec, second_bb);
|
||
|
||
sched_init_only_bb (second_bb, first_bb);
|
||
sched_init_only_bb (rec, EXIT_BLOCK_PTR_FOR_FN (cfun));
|
||
|
||
jump = BB_END (rec);
|
||
haifa_init_insn (jump);
|
||
}
|
||
|
||
/* Move backward dependences from INSN to CHECK and
|
||
move forward dependences from INSN to TWIN. */
|
||
|
||
/* First, create dependencies between INSN's producers and CHECK & TWIN. */
|
||
FOR_EACH_DEP (insn, SD_LIST_BACK, sd_it, dep)
|
||
{
|
||
rtx_insn *pro = DEP_PRO (dep);
|
||
ds_t ds;
|
||
|
||
/* If BEGIN_DATA: [insn ~~TRUE~~> producer]:
|
||
check --TRUE--> producer ??? or ANTI ???
|
||
twin --TRUE--> producer
|
||
twin --ANTI--> check
|
||
|
||
If BEGIN_CONTROL: [insn ~~ANTI~~> producer]:
|
||
check --ANTI--> producer
|
||
twin --ANTI--> producer
|
||
twin --ANTI--> check
|
||
|
||
If BE_IN_SPEC: [insn ~~TRUE~~> producer]:
|
||
check ~~TRUE~~> producer
|
||
twin ~~TRUE~~> producer
|
||
twin --ANTI--> check */
|
||
|
||
ds = DEP_STATUS (dep);
|
||
|
||
if (ds & BEGIN_SPEC)
|
||
{
|
||
gcc_assert (!mutate_p);
|
||
ds &= ~BEGIN_SPEC;
|
||
}
|
||
|
||
init_dep_1 (new_dep, pro, check, DEP_TYPE (dep), ds);
|
||
sd_add_dep (new_dep, false);
|
||
|
||
if (rec != EXIT_BLOCK_PTR_FOR_FN (cfun))
|
||
{
|
||
DEP_CON (new_dep) = twin;
|
||
sd_add_dep (new_dep, false);
|
||
}
|
||
}
|
||
|
||
/* Second, remove backward dependencies of INSN. */
|
||
for (sd_it = sd_iterator_start (insn, SD_LIST_SPEC_BACK);
|
||
sd_iterator_cond (&sd_it, &dep);)
|
||
{
|
||
if ((DEP_STATUS (dep) & BEGIN_SPEC)
|
||
|| mutate_p)
|
||
/* We can delete this dep because we overcome it with
|
||
BEGIN_SPECULATION. */
|
||
sd_delete_dep (sd_it);
|
||
else
|
||
sd_iterator_next (&sd_it);
|
||
}
|
||
|
||
/* Future Speculations. Determine what BE_IN speculations will be like. */
|
||
fs = 0;
|
||
|
||
/* Fields (DONE_SPEC (x) & BEGIN_SPEC) and CHECK_SPEC (x) are set only
|
||
here. */
|
||
|
||
gcc_assert (!DONE_SPEC (insn));
|
||
|
||
if (!mutate_p)
|
||
{
|
||
ds_t ts = TODO_SPEC (insn);
|
||
|
||
DONE_SPEC (insn) = ts & BEGIN_SPEC;
|
||
CHECK_SPEC (check) = ts & BEGIN_SPEC;
|
||
|
||
/* Luckiness of future speculations solely depends upon initial
|
||
BEGIN speculation. */
|
||
if (ts & BEGIN_DATA)
|
||
fs = set_dep_weak (fs, BE_IN_DATA, get_dep_weak (ts, BEGIN_DATA));
|
||
if (ts & BEGIN_CONTROL)
|
||
fs = set_dep_weak (fs, BE_IN_CONTROL,
|
||
get_dep_weak (ts, BEGIN_CONTROL));
|
||
}
|
||
else
|
||
CHECK_SPEC (check) = CHECK_SPEC (insn);
|
||
|
||
/* Future speculations: call the helper. */
|
||
process_insn_forw_deps_be_in_spec (insn, twin, fs);
|
||
|
||
if (rec != EXIT_BLOCK_PTR_FOR_FN (cfun))
|
||
{
|
||
/* Which types of dependencies should we use here is,
|
||
generally, machine-dependent question... But, for now,
|
||
it is not. */
|
||
|
||
if (!mutate_p)
|
||
{
|
||
init_dep (new_dep, insn, check, REG_DEP_TRUE);
|
||
sd_add_dep (new_dep, false);
|
||
|
||
init_dep (new_dep, insn, twin, REG_DEP_OUTPUT);
|
||
sd_add_dep (new_dep, false);
|
||
}
|
||
else
|
||
{
|
||
if (spec_info->dump)
|
||
fprintf (spec_info->dump, ";;\t\tRemoved simple check : %s\n",
|
||
(*current_sched_info->print_insn) (insn, 0));
|
||
|
||
/* Remove all dependencies of the INSN. */
|
||
{
|
||
sd_it = sd_iterator_start (insn, (SD_LIST_FORW
|
||
| SD_LIST_BACK
|
||
| SD_LIST_RES_BACK));
|
||
while (sd_iterator_cond (&sd_it, &dep))
|
||
sd_delete_dep (sd_it);
|
||
}
|
||
|
||
/* If former check (INSN) already was moved to the ready (or queue)
|
||
list, add new check (CHECK) there too. */
|
||
if (QUEUE_INDEX (insn) != QUEUE_NOWHERE)
|
||
try_ready (check);
|
||
|
||
/* Remove old check from instruction stream and free its
|
||
data. */
|
||
sched_remove_insn (insn);
|
||
}
|
||
|
||
init_dep (new_dep, check, twin, REG_DEP_ANTI);
|
||
sd_add_dep (new_dep, false);
|
||
}
|
||
else
|
||
{
|
||
init_dep_1 (new_dep, insn, check, REG_DEP_TRUE, DEP_TRUE | DEP_OUTPUT);
|
||
sd_add_dep (new_dep, false);
|
||
}
|
||
|
||
if (!mutate_p)
|
||
/* Fix priorities. If MUTATE_P is nonzero, this is not necessary,
|
||
because it'll be done later in add_to_speculative_block. */
|
||
{
|
||
rtx_vec_t priorities_roots = rtx_vec_t ();
|
||
|
||
clear_priorities (twin, &priorities_roots);
|
||
calc_priorities (priorities_roots);
|
||
priorities_roots.release ();
|
||
}
|
||
}
|
||
|
||
/* Removes dependency between instructions in the recovery block REC
|
||
and usual region instructions. It keeps inner dependences so it
|
||
won't be necessary to recompute them. */
|
||
static void
|
||
fix_recovery_deps (basic_block rec)
|
||
{
|
||
rtx_insn *note, *insn, *jump;
|
||
rtx_insn_list *ready_list = 0;
|
||
bitmap_head in_ready;
|
||
rtx_insn_list *link;
|
||
|
||
bitmap_initialize (&in_ready, 0);
|
||
|
||
/* NOTE - a basic block note. */
|
||
note = NEXT_INSN (BB_HEAD (rec));
|
||
gcc_assert (NOTE_INSN_BASIC_BLOCK_P (note));
|
||
insn = BB_END (rec);
|
||
gcc_assert (JUMP_P (insn));
|
||
insn = PREV_INSN (insn);
|
||
|
||
do
|
||
{
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
|
||
for (sd_it = sd_iterator_start (insn, SD_LIST_FORW);
|
||
sd_iterator_cond (&sd_it, &dep);)
|
||
{
|
||
rtx_insn *consumer = DEP_CON (dep);
|
||
|
||
if (BLOCK_FOR_INSN (consumer) != rec)
|
||
{
|
||
sd_delete_dep (sd_it);
|
||
|
||
if (bitmap_set_bit (&in_ready, INSN_LUID (consumer)))
|
||
ready_list = alloc_INSN_LIST (consumer, ready_list);
|
||
}
|
||
else
|
||
{
|
||
gcc_assert ((DEP_STATUS (dep) & DEP_TYPES) == DEP_TRUE);
|
||
|
||
sd_iterator_next (&sd_it);
|
||
}
|
||
}
|
||
|
||
insn = PREV_INSN (insn);
|
||
}
|
||
while (insn != note);
|
||
|
||
bitmap_clear (&in_ready);
|
||
|
||
/* Try to add instructions to the ready or queue list. */
|
||
for (link = ready_list; link; link = link->next ())
|
||
try_ready (link->insn ());
|
||
free_INSN_LIST_list (&ready_list);
|
||
|
||
/* Fixing jump's dependences. */
|
||
insn = BB_HEAD (rec);
|
||
jump = BB_END (rec);
|
||
|
||
gcc_assert (LABEL_P (insn));
|
||
insn = NEXT_INSN (insn);
|
||
|
||
gcc_assert (NOTE_INSN_BASIC_BLOCK_P (insn));
|
||
add_jump_dependencies (insn, jump);
|
||
}
|
||
|
||
/* Change pattern of INSN to NEW_PAT. Invalidate cached haifa
|
||
instruction data. */
|
||
static bool
|
||
haifa_change_pattern (rtx_insn *insn, rtx new_pat)
|
||
{
|
||
int t;
|
||
|
||
t = validate_change (insn, &PATTERN (insn), new_pat, 0);
|
||
if (!t)
|
||
return false;
|
||
|
||
update_insn_after_change (insn);
|
||
return true;
|
||
}
|
||
|
||
/* -1 - can't speculate,
|
||
0 - for speculation with REQUEST mode it is OK to use
|
||
current instruction pattern,
|
||
1 - need to change pattern for *NEW_PAT to be speculative. */
|
||
int
|
||
sched_speculate_insn (rtx_insn *insn, ds_t request, rtx *new_pat)
|
||
{
|
||
gcc_assert (current_sched_info->flags & DO_SPECULATION
|
||
&& (request & SPECULATIVE)
|
||
&& sched_insn_is_legitimate_for_speculation_p (insn, request));
|
||
|
||
if ((request & spec_info->mask) != request)
|
||
return -1;
|
||
|
||
if (request & BE_IN_SPEC
|
||
&& !(request & BEGIN_SPEC))
|
||
return 0;
|
||
|
||
return targetm.sched.speculate_insn (insn, request, new_pat);
|
||
}
|
||
|
||
static int
|
||
haifa_speculate_insn (rtx_insn *insn, ds_t request, rtx *new_pat)
|
||
{
|
||
gcc_assert (sched_deps_info->generate_spec_deps
|
||
&& !IS_SPECULATION_CHECK_P (insn));
|
||
|
||
if (HAS_INTERNAL_DEP (insn)
|
||
|| SCHED_GROUP_P (insn))
|
||
return -1;
|
||
|
||
return sched_speculate_insn (insn, request, new_pat);
|
||
}
|
||
|
||
/* Print some information about block BB, which starts with HEAD and
|
||
ends with TAIL, before scheduling it.
|
||
I is zero, if scheduler is about to start with the fresh ebb. */
|
||
static void
|
||
dump_new_block_header (int i, basic_block bb, rtx_insn *head, rtx_insn *tail)
|
||
{
|
||
if (!i)
|
||
fprintf (sched_dump,
|
||
";; ======================================================\n");
|
||
else
|
||
fprintf (sched_dump,
|
||
";; =====================ADVANCING TO=====================\n");
|
||
fprintf (sched_dump,
|
||
";; -- basic block %d from %d to %d -- %s reload\n",
|
||
bb->index, INSN_UID (head), INSN_UID (tail),
|
||
(reload_completed ? "after" : "before"));
|
||
fprintf (sched_dump,
|
||
";; ======================================================\n");
|
||
fprintf (sched_dump, "\n");
|
||
}
|
||
|
||
/* Unlink basic block notes and labels and saves them, so they
|
||
can be easily restored. We unlink basic block notes in EBB to
|
||
provide back-compatibility with the previous code, as target backends
|
||
assume, that there'll be only instructions between
|
||
current_sched_info->{head and tail}. We restore these notes as soon
|
||
as we can.
|
||
FIRST (LAST) is the first (last) basic block in the ebb.
|
||
NB: In usual case (FIRST == LAST) nothing is really done. */
|
||
void
|
||
unlink_bb_notes (basic_block first, basic_block last)
|
||
{
|
||
/* We DON'T unlink basic block notes of the first block in the ebb. */
|
||
if (first == last)
|
||
return;
|
||
|
||
bb_header = XNEWVEC (rtx_insn *, last_basic_block_for_fn (cfun));
|
||
|
||
/* Make a sentinel. */
|
||
if (last->next_bb != EXIT_BLOCK_PTR_FOR_FN (cfun))
|
||
bb_header[last->next_bb->index] = 0;
|
||
|
||
first = first->next_bb;
|
||
do
|
||
{
|
||
rtx_insn *prev, *label, *note, *next;
|
||
|
||
label = BB_HEAD (last);
|
||
if (LABEL_P (label))
|
||
note = NEXT_INSN (label);
|
||
else
|
||
note = label;
|
||
gcc_assert (NOTE_INSN_BASIC_BLOCK_P (note));
|
||
|
||
prev = PREV_INSN (label);
|
||
next = NEXT_INSN (note);
|
||
gcc_assert (prev && next);
|
||
|
||
SET_NEXT_INSN (prev) = next;
|
||
SET_PREV_INSN (next) = prev;
|
||
|
||
bb_header[last->index] = label;
|
||
|
||
if (last == first)
|
||
break;
|
||
|
||
last = last->prev_bb;
|
||
}
|
||
while (1);
|
||
}
|
||
|
||
/* Restore basic block notes.
|
||
FIRST is the first basic block in the ebb. */
|
||
static void
|
||
restore_bb_notes (basic_block first)
|
||
{
|
||
if (!bb_header)
|
||
return;
|
||
|
||
/* We DON'T unlink basic block notes of the first block in the ebb. */
|
||
first = first->next_bb;
|
||
/* Remember: FIRST is actually a second basic block in the ebb. */
|
||
|
||
while (first != EXIT_BLOCK_PTR_FOR_FN (cfun)
|
||
&& bb_header[first->index])
|
||
{
|
||
rtx_insn *prev, *label, *note, *next;
|
||
|
||
label = bb_header[first->index];
|
||
prev = PREV_INSN (label);
|
||
next = NEXT_INSN (prev);
|
||
|
||
if (LABEL_P (label))
|
||
note = NEXT_INSN (label);
|
||
else
|
||
note = label;
|
||
gcc_assert (NOTE_INSN_BASIC_BLOCK_P (note));
|
||
|
||
bb_header[first->index] = 0;
|
||
|
||
SET_NEXT_INSN (prev) = label;
|
||
SET_NEXT_INSN (note) = next;
|
||
SET_PREV_INSN (next) = note;
|
||
|
||
first = first->next_bb;
|
||
}
|
||
|
||
free (bb_header);
|
||
bb_header = 0;
|
||
}
|
||
|
||
/* Helper function.
|
||
Fix CFG after both in- and inter-block movement of
|
||
control_flow_insn_p JUMP. */
|
||
static void
|
||
fix_jump_move (rtx_insn *jump)
|
||
{
|
||
basic_block bb, jump_bb, jump_bb_next;
|
||
|
||
bb = BLOCK_FOR_INSN (PREV_INSN (jump));
|
||
jump_bb = BLOCK_FOR_INSN (jump);
|
||
jump_bb_next = jump_bb->next_bb;
|
||
|
||
gcc_assert (common_sched_info->sched_pass_id == SCHED_EBB_PASS
|
||
|| IS_SPECULATION_BRANCHY_CHECK_P (jump));
|
||
|
||
if (!NOTE_INSN_BASIC_BLOCK_P (BB_END (jump_bb_next)))
|
||
/* if jump_bb_next is not empty. */
|
||
BB_END (jump_bb) = BB_END (jump_bb_next);
|
||
|
||
if (BB_END (bb) != PREV_INSN (jump))
|
||
/* Then there are instruction after jump that should be placed
|
||
to jump_bb_next. */
|
||
BB_END (jump_bb_next) = BB_END (bb);
|
||
else
|
||
/* Otherwise jump_bb_next is empty. */
|
||
BB_END (jump_bb_next) = NEXT_INSN (BB_HEAD (jump_bb_next));
|
||
|
||
/* To make assertion in move_insn happy. */
|
||
BB_END (bb) = PREV_INSN (jump);
|
||
|
||
update_bb_for_insn (jump_bb_next);
|
||
}
|
||
|
||
/* Fix CFG after interblock movement of control_flow_insn_p JUMP. */
|
||
static void
|
||
move_block_after_check (rtx_insn *jump)
|
||
{
|
||
basic_block bb, jump_bb, jump_bb_next;
|
||
vec<edge, va_gc> *t;
|
||
|
||
bb = BLOCK_FOR_INSN (PREV_INSN (jump));
|
||
jump_bb = BLOCK_FOR_INSN (jump);
|
||
jump_bb_next = jump_bb->next_bb;
|
||
|
||
update_bb_for_insn (jump_bb);
|
||
|
||
gcc_assert (IS_SPECULATION_CHECK_P (jump)
|
||
|| IS_SPECULATION_CHECK_P (BB_END (jump_bb_next)));
|
||
|
||
unlink_block (jump_bb_next);
|
||
link_block (jump_bb_next, bb);
|
||
|
||
t = bb->succs;
|
||
bb->succs = 0;
|
||
move_succs (&(jump_bb->succs), bb);
|
||
move_succs (&(jump_bb_next->succs), jump_bb);
|
||
move_succs (&t, jump_bb_next);
|
||
|
||
df_mark_solutions_dirty ();
|
||
|
||
common_sched_info->fix_recovery_cfg
|
||
(bb->index, jump_bb->index, jump_bb_next->index);
|
||
}
|
||
|
||
/* Helper function for move_block_after_check.
|
||
This functions attaches edge vector pointed to by SUCCSP to
|
||
block TO. */
|
||
static void
|
||
move_succs (vec<edge, va_gc> **succsp, basic_block to)
|
||
{
|
||
edge e;
|
||
edge_iterator ei;
|
||
|
||
gcc_assert (to->succs == 0);
|
||
|
||
to->succs = *succsp;
|
||
|
||
FOR_EACH_EDGE (e, ei, to->succs)
|
||
e->src = to;
|
||
|
||
*succsp = 0;
|
||
}
|
||
|
||
/* Remove INSN from the instruction stream.
|
||
INSN should have any dependencies. */
|
||
static void
|
||
sched_remove_insn (rtx_insn *insn)
|
||
{
|
||
sd_finish_insn (insn);
|
||
|
||
change_queue_index (insn, QUEUE_NOWHERE);
|
||
current_sched_info->add_remove_insn (insn, 1);
|
||
delete_insn (insn);
|
||
}
|
||
|
||
/* Clear priorities of all instructions, that are forward dependent on INSN.
|
||
Store in vector pointed to by ROOTS_PTR insns on which priority () should
|
||
be invoked to initialize all cleared priorities. */
|
||
static void
|
||
clear_priorities (rtx_insn *insn, rtx_vec_t *roots_ptr)
|
||
{
|
||
sd_iterator_def sd_it;
|
||
dep_t dep;
|
||
bool insn_is_root_p = true;
|
||
|
||
gcc_assert (QUEUE_INDEX (insn) != QUEUE_SCHEDULED);
|
||
|
||
FOR_EACH_DEP (insn, SD_LIST_BACK, sd_it, dep)
|
||
{
|
||
rtx_insn *pro = DEP_PRO (dep);
|
||
|
||
if (INSN_PRIORITY_STATUS (pro) >= 0
|
||
&& QUEUE_INDEX (insn) != QUEUE_SCHEDULED)
|
||
{
|
||
/* If DEP doesn't contribute to priority then INSN itself should
|
||
be added to priority roots. */
|
||
if (contributes_to_priority_p (dep))
|
||
insn_is_root_p = false;
|
||
|
||
INSN_PRIORITY_STATUS (pro) = -1;
|
||
clear_priorities (pro, roots_ptr);
|
||
}
|
||
}
|
||
|
||
if (insn_is_root_p)
|
||
roots_ptr->safe_push (insn);
|
||
}
|
||
|
||
/* Recompute priorities of instructions, whose priorities might have been
|
||
changed. ROOTS is a vector of instructions whose priority computation will
|
||
trigger initialization of all cleared priorities. */
|
||
static void
|
||
calc_priorities (rtx_vec_t roots)
|
||
{
|
||
int i;
|
||
rtx_insn *insn;
|
||
|
||
FOR_EACH_VEC_ELT (roots, i, insn)
|
||
priority (insn);
|
||
}
|
||
|
||
|
||
/* Add dependences between JUMP and other instructions in the recovery
|
||
block. INSN is the first insn the recovery block. */
|
||
static void
|
||
add_jump_dependencies (rtx_insn *insn, rtx_insn *jump)
|
||
{
|
||
do
|
||
{
|
||
insn = NEXT_INSN (insn);
|
||
if (insn == jump)
|
||
break;
|
||
|
||
if (dep_list_size (insn, SD_LIST_FORW) == 0)
|
||
{
|
||
dep_def _new_dep, *new_dep = &_new_dep;
|
||
|
||
init_dep (new_dep, insn, jump, REG_DEP_ANTI);
|
||
sd_add_dep (new_dep, false);
|
||
}
|
||
}
|
||
while (1);
|
||
|
||
gcc_assert (!sd_lists_empty_p (jump, SD_LIST_BACK));
|
||
}
|
||
|
||
/* Extend data structures for logical insn UID. */
|
||
void
|
||
sched_extend_luids (void)
|
||
{
|
||
int new_luids_max_uid = get_max_uid () + 1;
|
||
|
||
sched_luids.safe_grow_cleared (new_luids_max_uid);
|
||
}
|
||
|
||
/* Initialize LUID for INSN. */
|
||
void
|
||
sched_init_insn_luid (rtx_insn *insn)
|
||
{
|
||
int i = INSN_P (insn) ? 1 : common_sched_info->luid_for_non_insn (insn);
|
||
int luid;
|
||
|
||
if (i >= 0)
|
||
{
|
||
luid = sched_max_luid;
|
||
sched_max_luid += i;
|
||
}
|
||
else
|
||
luid = -1;
|
||
|
||
SET_INSN_LUID (insn, luid);
|
||
}
|
||
|
||
/* Initialize luids for BBS.
|
||
The hook common_sched_info->luid_for_non_insn () is used to determine
|
||
if notes, labels, etc. need luids. */
|
||
void
|
||
sched_init_luids (bb_vec_t bbs)
|
||
{
|
||
int i;
|
||
basic_block bb;
|
||
|
||
sched_extend_luids ();
|
||
FOR_EACH_VEC_ELT (bbs, i, bb)
|
||
{
|
||
rtx_insn *insn;
|
||
|
||
FOR_BB_INSNS (bb, insn)
|
||
sched_init_insn_luid (insn);
|
||
}
|
||
}
|
||
|
||
/* Free LUIDs. */
|
||
void
|
||
sched_finish_luids (void)
|
||
{
|
||
sched_luids.release ();
|
||
sched_max_luid = 1;
|
||
}
|
||
|
||
/* Return logical uid of INSN. Helpful while debugging. */
|
||
int
|
||
insn_luid (rtx_insn *insn)
|
||
{
|
||
return INSN_LUID (insn);
|
||
}
|
||
|
||
/* Extend per insn data in the target. */
|
||
void
|
||
sched_extend_target (void)
|
||
{
|
||
if (targetm.sched.h_i_d_extended)
|
||
targetm.sched.h_i_d_extended ();
|
||
}
|
||
|
||
/* Extend global scheduler structures (those, that live across calls to
|
||
schedule_block) to include information about just emitted INSN. */
|
||
static void
|
||
extend_h_i_d (void)
|
||
{
|
||
int reserve = (get_max_uid () + 1 - h_i_d.length ());
|
||
if (reserve > 0
|
||
&& ! h_i_d.space (reserve))
|
||
{
|
||
h_i_d.safe_grow_cleared (3 * get_max_uid () / 2);
|
||
sched_extend_target ();
|
||
}
|
||
}
|
||
|
||
/* Initialize h_i_d entry of the INSN with default values.
|
||
Values, that are not explicitly initialized here, hold zero. */
|
||
static void
|
||
init_h_i_d (rtx_insn *insn)
|
||
{
|
||
if (INSN_LUID (insn) > 0)
|
||
{
|
||
INSN_COST (insn) = -1;
|
||
QUEUE_INDEX (insn) = QUEUE_NOWHERE;
|
||
INSN_TICK (insn) = INVALID_TICK;
|
||
INSN_EXACT_TICK (insn) = INVALID_TICK;
|
||
INTER_TICK (insn) = INVALID_TICK;
|
||
TODO_SPEC (insn) = HARD_DEP;
|
||
INSN_AUTOPREF_MULTIPASS_DATA (insn)[0].status
|
||
= AUTOPREF_MULTIPASS_DATA_UNINITIALIZED;
|
||
INSN_AUTOPREF_MULTIPASS_DATA (insn)[1].status
|
||
= AUTOPREF_MULTIPASS_DATA_UNINITIALIZED;
|
||
}
|
||
}
|
||
|
||
/* Initialize haifa_insn_data for BBS. */
|
||
void
|
||
haifa_init_h_i_d (bb_vec_t bbs)
|
||
{
|
||
int i;
|
||
basic_block bb;
|
||
|
||
extend_h_i_d ();
|
||
FOR_EACH_VEC_ELT (bbs, i, bb)
|
||
{
|
||
rtx_insn *insn;
|
||
|
||
FOR_BB_INSNS (bb, insn)
|
||
init_h_i_d (insn);
|
||
}
|
||
}
|
||
|
||
/* Finalize haifa_insn_data. */
|
||
void
|
||
haifa_finish_h_i_d (void)
|
||
{
|
||
int i;
|
||
haifa_insn_data_t data;
|
||
struct reg_use_data *use, *next;
|
||
|
||
FOR_EACH_VEC_ELT (h_i_d, i, data)
|
||
{
|
||
free (data->max_reg_pressure);
|
||
free (data->reg_pressure);
|
||
for (use = data->reg_use_list; use != NULL; use = next)
|
||
{
|
||
next = use->next_insn_use;
|
||
free (use);
|
||
}
|
||
}
|
||
h_i_d.release ();
|
||
}
|
||
|
||
/* Init data for the new insn INSN. */
|
||
static void
|
||
haifa_init_insn (rtx_insn *insn)
|
||
{
|
||
gcc_assert (insn != NULL);
|
||
|
||
sched_extend_luids ();
|
||
sched_init_insn_luid (insn);
|
||
sched_extend_target ();
|
||
sched_deps_init (false);
|
||
extend_h_i_d ();
|
||
init_h_i_d (insn);
|
||
|
||
if (adding_bb_to_current_region_p)
|
||
{
|
||
sd_init_insn (insn);
|
||
|
||
/* Extend dependency caches by one element. */
|
||
extend_dependency_caches (1, false);
|
||
}
|
||
if (sched_pressure != SCHED_PRESSURE_NONE)
|
||
init_insn_reg_pressure_info (insn);
|
||
}
|
||
|
||
/* Init data for the new basic block BB which comes after AFTER. */
|
||
static void
|
||
haifa_init_only_bb (basic_block bb, basic_block after)
|
||
{
|
||
gcc_assert (bb != NULL);
|
||
|
||
sched_init_bbs ();
|
||
|
||
if (common_sched_info->add_block)
|
||
/* This changes only data structures of the front-end. */
|
||
common_sched_info->add_block (bb, after);
|
||
}
|
||
|
||
/* A generic version of sched_split_block (). */
|
||
basic_block
|
||
sched_split_block_1 (basic_block first_bb, rtx after)
|
||
{
|
||
edge e;
|
||
|
||
e = split_block (first_bb, after);
|
||
gcc_assert (e->src == first_bb);
|
||
|
||
/* sched_split_block emits note if *check == BB_END. Probably it
|
||
is better to rip that note off. */
|
||
|
||
return e->dest;
|
||
}
|
||
|
||
/* A generic version of sched_create_empty_bb (). */
|
||
basic_block
|
||
sched_create_empty_bb_1 (basic_block after)
|
||
{
|
||
return create_empty_bb (after);
|
||
}
|
||
|
||
/* Insert PAT as an INSN into the schedule and update the necessary data
|
||
structures to account for it. */
|
||
rtx_insn *
|
||
sched_emit_insn (rtx pat)
|
||
{
|
||
rtx_insn *insn = emit_insn_before (pat, first_nonscheduled_insn ());
|
||
haifa_init_insn (insn);
|
||
|
||
if (current_sched_info->add_remove_insn)
|
||
current_sched_info->add_remove_insn (insn, 0);
|
||
|
||
(*current_sched_info->begin_schedule_ready) (insn);
|
||
scheduled_insns.safe_push (insn);
|
||
|
||
last_scheduled_insn = insn;
|
||
return insn;
|
||
}
|
||
|
||
/* This function returns a candidate satisfying dispatch constraints from
|
||
the ready list. */
|
||
|
||
static rtx_insn *
|
||
ready_remove_first_dispatch (struct ready_list *ready)
|
||
{
|
||
int i;
|
||
rtx_insn *insn = ready_element (ready, 0);
|
||
|
||
if (ready->n_ready == 1
|
||
|| !INSN_P (insn)
|
||
|| INSN_CODE (insn) < 0
|
||
|| !active_insn_p (insn)
|
||
|| targetm.sched.dispatch (insn, FITS_DISPATCH_WINDOW))
|
||
return ready_remove_first (ready);
|
||
|
||
for (i = 1; i < ready->n_ready; i++)
|
||
{
|
||
insn = ready_element (ready, i);
|
||
|
||
if (!INSN_P (insn)
|
||
|| INSN_CODE (insn) < 0
|
||
|| !active_insn_p (insn))
|
||
continue;
|
||
|
||
if (targetm.sched.dispatch (insn, FITS_DISPATCH_WINDOW))
|
||
{
|
||
/* Return ith element of ready. */
|
||
insn = ready_remove (ready, i);
|
||
return insn;
|
||
}
|
||
}
|
||
|
||
if (targetm.sched.dispatch (NULL, DISPATCH_VIOLATION))
|
||
return ready_remove_first (ready);
|
||
|
||
for (i = 1; i < ready->n_ready; i++)
|
||
{
|
||
insn = ready_element (ready, i);
|
||
|
||
if (!INSN_P (insn)
|
||
|| INSN_CODE (insn) < 0
|
||
|| !active_insn_p (insn))
|
||
continue;
|
||
|
||
/* Return i-th element of ready. */
|
||
if (targetm.sched.dispatch (insn, IS_CMP))
|
||
return ready_remove (ready, i);
|
||
}
|
||
|
||
return ready_remove_first (ready);
|
||
}
|
||
|
||
/* Get number of ready insn in the ready list. */
|
||
|
||
int
|
||
number_in_ready (void)
|
||
{
|
||
return ready.n_ready;
|
||
}
|
||
|
||
/* Get number of ready's in the ready list. */
|
||
|
||
rtx_insn *
|
||
get_ready_element (int i)
|
||
{
|
||
return ready_element (&ready, i);
|
||
}
|
||
|
||
#endif /* INSN_SCHEDULING */
|