4d0cdd0ce6
2016-10-13 Thomas Preud'homme <thomas.preudhomme@arm.com> gcc/ * coretypes.h: Move MEMMODEL_* macros and enum memmodel definition into ... * memmodel.h: This file. * alias.c, asan.c, auto-inc-dec.c, bb-reorder.c, bt-load.c, caller-save.c, calls.c, ccmp.c, cfgbuild.c, cfgcleanup.c, cfgexpand.c, cfgloopanal.c, cfgrtl.c, cilk-common.c, combine.c, combine-stack-adj.c, common/config/aarch64/aarch64-common.c, common/config/arm/arm-common.c, common/config/bfin/bfin-common.c, common/config/c6x/c6x-common.c, common/config/i386/i386-common.c, common/config/ia64/ia64-common.c, common/config/nvptx/nvptx-common.c, compare-elim.c, config/aarch64/aarch64-builtins.c, config/aarch64/aarch64-c.c, config/aarch64/cortex-a57-fma-steering.c, config/arc/arc.c, config/arc/arc-c.c, config/arm/arm-builtins.c, config/arm/arm-c.c, config/avr/avr.c, config/avr/avr-c.c, config/avr/avr-log.c, config/bfin/bfin.c, config/c6x/c6x.c, config/cr16/cr16.c, config/cris/cris.c, config/darwin-c.c, config/darwin.c, config/epiphany/epiphany.c, config/epiphany/mode-switch-use.c, config/epiphany/resolve-sw-modes.c, config/fr30/fr30.c, config/frv/frv.c, config/ft32/ft32.c, config/h8300/h8300.c, config/i386/i386-c.c, config/i386/winnt.c, config/iq2000/iq2000.c, config/lm32/lm32.c, config/m32c/m32c.c, config/m32r/m32r.c, config/m68k/m68k.c, config/mcore/mcore.c, config/microblaze/microblaze.c, config/mmix/mmix.c, config/mn10300/mn10300.c, config/moxie/moxie.c, config/msp430/msp430.c, config/nds32/nds32-cost.c, config/nds32/nds32-intrinsic.c, config/nds32/nds32-md-auxiliary.c, config/nds32/nds32-memory-manipulation.c, config/nds32/nds32-predicates.c, config/nds32/nds32.c, config/nios2/nios2.c, config/nvptx/nvptx.c, config/pa/pa.c, config/pdp11/pdp11.c, config/rl78/rl78.c, config/rs6000/rs6000-c.c, config/rx/rx.c, config/s390/s390-c.c, config/s390/s390.c, config/sh/sh.c, config/sh/sh-c.c, config/sh/sh-mem.cc, config/sh/sh_treg_combine.cc, config/sol2.c, config/spu/spu.c, config/stormy16/stormy16.c, config/tilegx/tilegx.c, config/tilepro/tilepro.c, config/v850/v850.c, config/vax/vax.c, config/visium/visium.c, config/vms/vms-c.c, config/xtensa/xtensa.c, coverage.c, cppbuiltin.c, cprop.c, cse.c, cselib.c, dbxout.c, dce.c, df-core.c, df-problems.c, df-scan.c, dojump.c, dse.c, dwarf2asm.c, dwarf2cfi.c, dwarf2out.c, emit-rtl.c, except.c, explow.c, expmed.c, expr.c, final.c, fold-const.c, function.c, fwprop.c, gcse.c, ggc-page.c, haifa-sched.c, hsa-brig.c, hsa-gen.c, hw-doloop.c, ifcvt.c, init-regs.c, internal-fn.c, ira-build.c, ira-color.c, ira-conflicts.c, ira-costs.c, ira-emit.c, ira-lives.c, ira.c, jump.c, loop-doloop.c, loop-invariant.c, loop-iv.c, loop-unroll.c, lower-subreg.c, lra.c, lra-assigns.c, lra-coalesce.c, lra-constraints.c, lra-eliminations.c, lra-lives.c, lra-remat.c, lra-spills.c, mode-switching.c, modulo-sched.c, omp-low.c, passes.c, postreload-gcse.c, postreload.c, predict.c, print-rtl-function.c, recog.c, ree.c, reg-stack.c, regcprop.c, reginfo.c, regrename.c, reload.c, reload1.c, reorg.c, resource.c, rtl-chkp.c, rtl-tests.c, rtlanal.c, rtlhooks.c, sched-deps.c, sched-rgn.c, sdbout.c, sel-sched-ir.c, sel-sched.c, shrink-wrap.c, simplify-rtx.c, stack-ptr-mod.c, stmt.c, stor-layout.c, target-globals.c, targhooks.c, toplev.c, tree-nested.c, tree-outof-ssa.c, tree-profile.c, tree-ssa-coalesce.c, tree-ssa-ifcombine.c, tree-ssa-loop-ivopts.c, tree-ssa-loop.c, tree-ssa-reassoc.c, tree-ssa-sccvn.c, tree-vect-data-refs.c, ubsan.c, valtrack.c, var-tracking.c, varasm.c: Include memmodel.h. * genattrtab.c (write_header): Include memmodel.h in generated file. * genautomata.c (main): Likewise. * gengtype.c (open_base_files): Likewise. * genopinit.c (main): Likewise. * genconditions.c (write_header): Include memmodel.h earlier in generated file. * genemit.c (main): Likewise. * genoutput.c (output_prologue): Likewise. * genpeep.c (main): Likewise. * genpreds.c (write_insn_preds_c): Likewise. * genrecog.c (write_header): Likewise. * Makefile.in (PLUGIN_HEADERS): Include memmodel.h gcc/ada/ * gcc-interface/utils2.c: Include memmodel.h. gcc/c-family/ * c-cppbuiltin.c: Include memmodel.h. * c-opts.c: Likewise. * c-pragma.c: Likewise. * c-warn.c: Likewise. gcc/c/ * c-typeck.c: Include memmodel.h. gcc/cp/ * decl2.c: Include memmodel.h. * rtti.c: Likewise. gcc/fortran/ * trans-intrinsic.c: Include memmodel.h. gcc/go/ * go-backend.c: Include memmodel.h. libgcc/ * libgcov-profiler.c: Replace MEMMODEL_* macros by their __ATOMIC_* equivalent. * config/tilepro/atomic.c: Likewise and stop casting model to enum memmodel. From-SVN: r241121
1324 lines
41 KiB
C
1324 lines
41 KiB
C
/* Integrated Register Allocator. Changing code and generating moves.
|
||
Copyright (C) 2006-2016 Free Software Foundation, Inc.
|
||
Contributed by Vladimir Makarov <vmakarov@redhat.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/>. */
|
||
|
||
/* When we have more one region, we need to change the original RTL
|
||
code after coloring. Let us consider two allocnos representing the
|
||
same pseudo-register outside and inside a region respectively.
|
||
They can get different hard-registers. The reload pass works on
|
||
pseudo registers basis and there is no way to say the reload that
|
||
pseudo could be in different registers and it is even more
|
||
difficult to say in what places of the code the pseudo should have
|
||
particular hard-registers. So in this case IRA has to create and
|
||
use a new pseudo-register inside the region and adds code to move
|
||
allocno values on the region's borders. This is done by the code
|
||
in this file.
|
||
|
||
The code makes top-down traversal of the regions and generate new
|
||
pseudos and the move code on the region borders. In some
|
||
complicated cases IRA can create a new pseudo used temporarily to
|
||
move allocno values when a swap of values stored in two
|
||
hard-registers is needed (e.g. two allocnos representing different
|
||
pseudos outside region got respectively hard registers 1 and 2 and
|
||
the corresponding allocnos inside the region got respectively hard
|
||
registers 2 and 1). At this stage, the new pseudo is marked as
|
||
spilled.
|
||
|
||
IRA still creates the pseudo-register and the moves on the region
|
||
borders even when the both corresponding allocnos were assigned to
|
||
the same hard-register. It is done because, if the reload pass for
|
||
some reason spills a pseudo-register representing the original
|
||
pseudo outside or inside the region, the effect will be smaller
|
||
because another pseudo will still be in the hard-register. In most
|
||
cases, this is better then spilling the original pseudo in its
|
||
whole live-range. If reload does not change the allocation for the
|
||
two pseudo-registers, the trivial move will be removed by
|
||
post-reload optimizations.
|
||
|
||
IRA does not generate a new pseudo and moves for the allocno values
|
||
if the both allocnos representing an original pseudo inside and
|
||
outside region assigned to the same hard register when the register
|
||
pressure in the region for the corresponding pressure class is less
|
||
than number of available hard registers for given pressure class.
|
||
|
||
IRA also does some optimizations to remove redundant moves which is
|
||
transformed into stores by the reload pass on CFG edges
|
||
representing exits from the region.
|
||
|
||
IRA tries to reduce duplication of code generated on CFG edges
|
||
which are enters and exits to/from regions by moving some code to
|
||
the edge sources or destinations when it is possible. */
|
||
|
||
#include "config.h"
|
||
#include "system.h"
|
||
#include "coretypes.h"
|
||
#include "backend.h"
|
||
#include "rtl.h"
|
||
#include "tree.h"
|
||
#include "predict.h"
|
||
#include "df.h"
|
||
#include "insn-config.h"
|
||
#include "regs.h"
|
||
#include "memmodel.h"
|
||
#include "ira.h"
|
||
#include "ira-int.h"
|
||
#include "cfgrtl.h"
|
||
#include "cfgbuild.h"
|
||
#include "expr.h"
|
||
#include "reload.h"
|
||
#include "cfgloop.h"
|
||
|
||
|
||
/* Data used to emit live range split insns and to flattening IR. */
|
||
ira_emit_data_t ira_allocno_emit_data;
|
||
|
||
/* Definitions for vectors of pointers. */
|
||
typedef void *void_p;
|
||
|
||
/* Pointers to data allocated for allocnos being created during
|
||
emitting. Usually there are quite few such allocnos because they
|
||
are created only for resolving loop in register shuffling. */
|
||
static vec<void_p> new_allocno_emit_data_vec;
|
||
|
||
/* Allocate and initiate the emit data. */
|
||
void
|
||
ira_initiate_emit_data (void)
|
||
{
|
||
ira_allocno_t a;
|
||
ira_allocno_iterator ai;
|
||
|
||
ira_allocno_emit_data
|
||
= (ira_emit_data_t) ira_allocate (ira_allocnos_num
|
||
* sizeof (struct ira_emit_data));
|
||
memset (ira_allocno_emit_data, 0,
|
||
ira_allocnos_num * sizeof (struct ira_emit_data));
|
||
FOR_EACH_ALLOCNO (a, ai)
|
||
ALLOCNO_ADD_DATA (a) = ira_allocno_emit_data + ALLOCNO_NUM (a);
|
||
new_allocno_emit_data_vec.create (50);
|
||
|
||
}
|
||
|
||
/* Free the emit data. */
|
||
void
|
||
ira_finish_emit_data (void)
|
||
{
|
||
void_p p;
|
||
ira_allocno_t a;
|
||
ira_allocno_iterator ai;
|
||
|
||
ira_free (ira_allocno_emit_data);
|
||
FOR_EACH_ALLOCNO (a, ai)
|
||
ALLOCNO_ADD_DATA (a) = NULL;
|
||
for (;new_allocno_emit_data_vec.length () != 0;)
|
||
{
|
||
p = new_allocno_emit_data_vec.pop ();
|
||
ira_free (p);
|
||
}
|
||
new_allocno_emit_data_vec.release ();
|
||
}
|
||
|
||
/* Create and return a new allocno with given REGNO and
|
||
LOOP_TREE_NODE. Allocate emit data for it. */
|
||
static ira_allocno_t
|
||
create_new_allocno (int regno, ira_loop_tree_node_t loop_tree_node)
|
||
{
|
||
ira_allocno_t a;
|
||
|
||
a = ira_create_allocno (regno, false, loop_tree_node);
|
||
ALLOCNO_ADD_DATA (a) = ira_allocate (sizeof (struct ira_emit_data));
|
||
memset (ALLOCNO_ADD_DATA (a), 0, sizeof (struct ira_emit_data));
|
||
new_allocno_emit_data_vec.safe_push (ALLOCNO_ADD_DATA (a));
|
||
return a;
|
||
}
|
||
|
||
|
||
|
||
/* See comments below. */
|
||
typedef struct move *move_t;
|
||
|
||
/* The structure represents an allocno move. Both allocnos have the
|
||
same original regno but different allocation. */
|
||
struct move
|
||
{
|
||
/* The allocnos involved in the move. */
|
||
ira_allocno_t from, to;
|
||
/* The next move in the move sequence. */
|
||
move_t next;
|
||
/* Used for finding dependencies. */
|
||
bool visited_p;
|
||
/* The size of the following array. */
|
||
int deps_num;
|
||
/* Moves on which given move depends on. Dependency can be cyclic.
|
||
It means we need a temporary to generates the moves. Sequence
|
||
A1->A2, B1->B2 where A1 and B2 are assigned to reg R1 and A2 and
|
||
B1 are assigned to reg R2 is an example of the cyclic
|
||
dependencies. */
|
||
move_t *deps;
|
||
/* First insn generated for the move. */
|
||
rtx_insn *insn;
|
||
};
|
||
|
||
/* Array of moves (indexed by BB index) which should be put at the
|
||
start/end of the corresponding basic blocks. */
|
||
static move_t *at_bb_start, *at_bb_end;
|
||
|
||
/* Max regno before renaming some pseudo-registers. For example, the
|
||
same pseudo-register can be renamed in a loop if its allocation is
|
||
different outside the loop. */
|
||
static int max_regno_before_changing;
|
||
|
||
/* Return new move of allocnos TO and FROM. */
|
||
static move_t
|
||
create_move (ira_allocno_t to, ira_allocno_t from)
|
||
{
|
||
move_t move;
|
||
|
||
move = (move_t) ira_allocate (sizeof (struct move));
|
||
move->deps = NULL;
|
||
move->deps_num = 0;
|
||
move->to = to;
|
||
move->from = from;
|
||
move->next = NULL;
|
||
move->insn = NULL;
|
||
move->visited_p = false;
|
||
return move;
|
||
}
|
||
|
||
/* Free memory for MOVE and its dependencies. */
|
||
static void
|
||
free_move (move_t move)
|
||
{
|
||
if (move->deps != NULL)
|
||
ira_free (move->deps);
|
||
ira_free (move);
|
||
}
|
||
|
||
/* Free memory for list of the moves given by its HEAD. */
|
||
static void
|
||
free_move_list (move_t head)
|
||
{
|
||
move_t next;
|
||
|
||
for (; head != NULL; head = next)
|
||
{
|
||
next = head->next;
|
||
free_move (head);
|
||
}
|
||
}
|
||
|
||
/* Return TRUE if the move list LIST1 and LIST2 are equal (two
|
||
moves are equal if they involve the same allocnos). */
|
||
static bool
|
||
eq_move_lists_p (move_t list1, move_t list2)
|
||
{
|
||
for (; list1 != NULL && list2 != NULL;
|
||
list1 = list1->next, list2 = list2->next)
|
||
if (list1->from != list2->from || list1->to != list2->to)
|
||
return false;
|
||
return list1 == list2;
|
||
}
|
||
|
||
/* Print move list LIST into file F. */
|
||
static void
|
||
print_move_list (FILE *f, move_t list)
|
||
{
|
||
for (; list != NULL; list = list->next)
|
||
fprintf (f, " a%dr%d->a%dr%d",
|
||
ALLOCNO_NUM (list->from), ALLOCNO_REGNO (list->from),
|
||
ALLOCNO_NUM (list->to), ALLOCNO_REGNO (list->to));
|
||
fprintf (f, "\n");
|
||
}
|
||
|
||
extern void ira_debug_move_list (move_t list);
|
||
|
||
/* Print move list LIST into stderr. */
|
||
void
|
||
ira_debug_move_list (move_t list)
|
||
{
|
||
print_move_list (stderr, list);
|
||
}
|
||
|
||
/* This recursive function changes pseudo-registers in *LOC if it is
|
||
necessary. The function returns TRUE if a change was done. */
|
||
static bool
|
||
change_regs (rtx *loc)
|
||
{
|
||
int i, regno, result = false;
|
||
const char *fmt;
|
||
enum rtx_code code;
|
||
rtx reg;
|
||
|
||
if (*loc == NULL_RTX)
|
||
return false;
|
||
code = GET_CODE (*loc);
|
||
if (code == REG)
|
||
{
|
||
regno = REGNO (*loc);
|
||
if (regno < FIRST_PSEUDO_REGISTER)
|
||
return false;
|
||
if (regno >= max_regno_before_changing)
|
||
/* It is a shared register which was changed already. */
|
||
return false;
|
||
if (ira_curr_regno_allocno_map[regno] == NULL)
|
||
return false;
|
||
reg = allocno_emit_reg (ira_curr_regno_allocno_map[regno]);
|
||
if (reg == *loc)
|
||
return false;
|
||
*loc = reg;
|
||
return true;
|
||
}
|
||
|
||
fmt = GET_RTX_FORMAT (code);
|
||
for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--)
|
||
{
|
||
if (fmt[i] == 'e')
|
||
result = change_regs (&XEXP (*loc, i)) || result;
|
||
else if (fmt[i] == 'E')
|
||
{
|
||
int j;
|
||
|
||
for (j = XVECLEN (*loc, i) - 1; j >= 0; j--)
|
||
result = change_regs (&XVECEXP (*loc, i, j)) || result;
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
static bool
|
||
change_regs_in_insn (rtx_insn **insn_ptr)
|
||
{
|
||
rtx rtx = *insn_ptr;
|
||
bool result = change_regs (&rtx);
|
||
*insn_ptr = as_a <rtx_insn *> (rtx);
|
||
return result;
|
||
}
|
||
|
||
/* Attach MOVE to the edge E. The move is attached to the head of the
|
||
list if HEAD_P is TRUE. */
|
||
static void
|
||
add_to_edge_list (edge e, move_t move, bool head_p)
|
||
{
|
||
move_t last;
|
||
|
||
if (head_p || e->aux == NULL)
|
||
{
|
||
move->next = (move_t) e->aux;
|
||
e->aux = move;
|
||
}
|
||
else
|
||
{
|
||
for (last = (move_t) e->aux; last->next != NULL; last = last->next)
|
||
;
|
||
last->next = move;
|
||
move->next = NULL;
|
||
}
|
||
}
|
||
|
||
/* Create and return new pseudo-register with the same attributes as
|
||
ORIGINAL_REG. */
|
||
rtx
|
||
ira_create_new_reg (rtx original_reg)
|
||
{
|
||
rtx new_reg;
|
||
|
||
new_reg = gen_reg_rtx (GET_MODE (original_reg));
|
||
ORIGINAL_REGNO (new_reg) = ORIGINAL_REGNO (original_reg);
|
||
REG_USERVAR_P (new_reg) = REG_USERVAR_P (original_reg);
|
||
REG_POINTER (new_reg) = REG_POINTER (original_reg);
|
||
REG_ATTRS (new_reg) = REG_ATTRS (original_reg);
|
||
if (internal_flag_ira_verbose > 3 && ira_dump_file != NULL)
|
||
fprintf (ira_dump_file, " Creating newreg=%i from oldreg=%i\n",
|
||
REGNO (new_reg), REGNO (original_reg));
|
||
ira_expand_reg_equiv ();
|
||
return new_reg;
|
||
}
|
||
|
||
/* Return TRUE if loop given by SUBNODE inside the loop given by
|
||
NODE. */
|
||
static bool
|
||
subloop_tree_node_p (ira_loop_tree_node_t subnode, ira_loop_tree_node_t node)
|
||
{
|
||
for (; subnode != NULL; subnode = subnode->parent)
|
||
if (subnode == node)
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
/* Set up member `reg' to REG for allocnos which has the same regno as
|
||
ALLOCNO and which are inside the loop corresponding to ALLOCNO. */
|
||
static void
|
||
set_allocno_reg (ira_allocno_t allocno, rtx reg)
|
||
{
|
||
int regno;
|
||
ira_allocno_t a;
|
||
ira_loop_tree_node_t node;
|
||
|
||
node = ALLOCNO_LOOP_TREE_NODE (allocno);
|
||
for (a = ira_regno_allocno_map[ALLOCNO_REGNO (allocno)];
|
||
a != NULL;
|
||
a = ALLOCNO_NEXT_REGNO_ALLOCNO (a))
|
||
if (subloop_tree_node_p (ALLOCNO_LOOP_TREE_NODE (a), node))
|
||
ALLOCNO_EMIT_DATA (a)->reg = reg;
|
||
for (a = ALLOCNO_CAP (allocno); a != NULL; a = ALLOCNO_CAP (a))
|
||
ALLOCNO_EMIT_DATA (a)->reg = reg;
|
||
regno = ALLOCNO_REGNO (allocno);
|
||
for (a = allocno;;)
|
||
{
|
||
if (a == NULL || (a = ALLOCNO_CAP (a)) == NULL)
|
||
{
|
||
node = node->parent;
|
||
if (node == NULL)
|
||
break;
|
||
a = node->regno_allocno_map[regno];
|
||
}
|
||
if (a == NULL)
|
||
continue;
|
||
if (ALLOCNO_EMIT_DATA (a)->child_renamed_p)
|
||
break;
|
||
ALLOCNO_EMIT_DATA (a)->child_renamed_p = true;
|
||
}
|
||
}
|
||
|
||
/* Return true if there is an entry to given loop not from its parent
|
||
(or grandparent) block. For example, it is possible for two
|
||
adjacent loops inside another loop. */
|
||
static bool
|
||
entered_from_non_parent_p (ira_loop_tree_node_t loop_node)
|
||
{
|
||
ira_loop_tree_node_t bb_node, src_loop_node, parent;
|
||
edge e;
|
||
edge_iterator ei;
|
||
|
||
for (bb_node = loop_node->children;
|
||
bb_node != NULL;
|
||
bb_node = bb_node->next)
|
||
if (bb_node->bb != NULL)
|
||
{
|
||
FOR_EACH_EDGE (e, ei, bb_node->bb->preds)
|
||
if (e->src != ENTRY_BLOCK_PTR_FOR_FN (cfun)
|
||
&& (src_loop_node = IRA_BB_NODE (e->src)->parent) != loop_node)
|
||
{
|
||
for (parent = src_loop_node->parent;
|
||
parent != NULL;
|
||
parent = parent->parent)
|
||
if (parent == loop_node)
|
||
break;
|
||
if (parent != NULL)
|
||
/* That is an exit from a nested loop -- skip it. */
|
||
continue;
|
||
for (parent = loop_node->parent;
|
||
parent != NULL;
|
||
parent = parent->parent)
|
||
if (src_loop_node == parent)
|
||
break;
|
||
if (parent == NULL)
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/* Set up ENTERED_FROM_NON_PARENT_P for each loop region. */
|
||
static void
|
||
setup_entered_from_non_parent_p (void)
|
||
{
|
||
unsigned int i;
|
||
loop_p loop;
|
||
|
||
ira_assert (current_loops != NULL);
|
||
FOR_EACH_VEC_SAFE_ELT (get_loops (cfun), i, loop)
|
||
if (ira_loop_nodes[i].regno_allocno_map != NULL)
|
||
ira_loop_nodes[i].entered_from_non_parent_p
|
||
= entered_from_non_parent_p (&ira_loop_nodes[i]);
|
||
}
|
||
|
||
/* Return TRUE if move of SRC_ALLOCNO (assigned to hard register) to
|
||
DEST_ALLOCNO (assigned to memory) can be removed because it does
|
||
not change value of the destination. One possible reason for this
|
||
is the situation when SRC_ALLOCNO is not modified in the
|
||
corresponding loop. */
|
||
static bool
|
||
store_can_be_removed_p (ira_allocno_t src_allocno, ira_allocno_t dest_allocno)
|
||
{
|
||
int regno, orig_regno;
|
||
ira_allocno_t a;
|
||
ira_loop_tree_node_t node;
|
||
|
||
ira_assert (ALLOCNO_CAP_MEMBER (src_allocno) == NULL
|
||
&& ALLOCNO_CAP_MEMBER (dest_allocno) == NULL);
|
||
orig_regno = ALLOCNO_REGNO (src_allocno);
|
||
regno = REGNO (allocno_emit_reg (dest_allocno));
|
||
for (node = ALLOCNO_LOOP_TREE_NODE (src_allocno);
|
||
node != NULL;
|
||
node = node->parent)
|
||
{
|
||
a = node->regno_allocno_map[orig_regno];
|
||
ira_assert (a != NULL);
|
||
if (REGNO (allocno_emit_reg (a)) == (unsigned) regno)
|
||
/* We achieved the destination and everything is ok. */
|
||
return true;
|
||
else if (bitmap_bit_p (node->modified_regnos, orig_regno))
|
||
return false;
|
||
else if (node->entered_from_non_parent_p)
|
||
/* If there is a path from a destination loop block to the
|
||
source loop header containing basic blocks of non-parents
|
||
(grandparents) of the source loop, we should have checked
|
||
modifications of the pseudo on this path too to decide
|
||
about possibility to remove the store. It could be done by
|
||
solving a data-flow problem. Unfortunately such global
|
||
solution would complicate IR flattening. Therefore we just
|
||
prohibit removal of the store in such complicated case. */
|
||
return false;
|
||
}
|
||
/* It is actually a loop entry -- do not remove the store. */
|
||
return false;
|
||
}
|
||
|
||
/* Generate and attach moves to the edge E. This looks at the final
|
||
regnos of allocnos living on the edge with the same original regno
|
||
to figure out when moves should be generated. */
|
||
static void
|
||
generate_edge_moves (edge e)
|
||
{
|
||
ira_loop_tree_node_t src_loop_node, dest_loop_node;
|
||
unsigned int regno;
|
||
bitmap_iterator bi;
|
||
ira_allocno_t src_allocno, dest_allocno, *src_map, *dest_map;
|
||
move_t move;
|
||
bitmap regs_live_in_dest, regs_live_out_src;
|
||
|
||
src_loop_node = IRA_BB_NODE (e->src)->parent;
|
||
dest_loop_node = IRA_BB_NODE (e->dest)->parent;
|
||
e->aux = NULL;
|
||
if (src_loop_node == dest_loop_node)
|
||
return;
|
||
src_map = src_loop_node->regno_allocno_map;
|
||
dest_map = dest_loop_node->regno_allocno_map;
|
||
regs_live_in_dest = df_get_live_in (e->dest);
|
||
regs_live_out_src = df_get_live_out (e->src);
|
||
EXECUTE_IF_SET_IN_REG_SET (regs_live_in_dest,
|
||
FIRST_PSEUDO_REGISTER, regno, bi)
|
||
if (bitmap_bit_p (regs_live_out_src, regno))
|
||
{
|
||
src_allocno = src_map[regno];
|
||
dest_allocno = dest_map[regno];
|
||
if (REGNO (allocno_emit_reg (src_allocno))
|
||
== REGNO (allocno_emit_reg (dest_allocno)))
|
||
continue;
|
||
/* Remove unnecessary stores at the region exit. We should do
|
||
this for readonly memory for sure and this is guaranteed by
|
||
that we never generate moves on region borders (see
|
||
checking in function change_loop). */
|
||
if (ALLOCNO_HARD_REGNO (dest_allocno) < 0
|
||
&& ALLOCNO_HARD_REGNO (src_allocno) >= 0
|
||
&& store_can_be_removed_p (src_allocno, dest_allocno))
|
||
{
|
||
ALLOCNO_EMIT_DATA (src_allocno)->mem_optimized_dest = dest_allocno;
|
||
ALLOCNO_EMIT_DATA (dest_allocno)->mem_optimized_dest_p = true;
|
||
if (internal_flag_ira_verbose > 3 && ira_dump_file != NULL)
|
||
fprintf (ira_dump_file, " Remove r%d:a%d->a%d(mem)\n",
|
||
regno, ALLOCNO_NUM (src_allocno),
|
||
ALLOCNO_NUM (dest_allocno));
|
||
continue;
|
||
}
|
||
move = create_move (dest_allocno, src_allocno);
|
||
add_to_edge_list (e, move, true);
|
||
}
|
||
}
|
||
|
||
/* Bitmap of allocnos local for the current loop. */
|
||
static bitmap local_allocno_bitmap;
|
||
|
||
/* This bitmap is used to find that we need to generate and to use a
|
||
new pseudo-register when processing allocnos with the same original
|
||
regno. */
|
||
static bitmap used_regno_bitmap;
|
||
|
||
/* This bitmap contains regnos of allocnos which were renamed locally
|
||
because the allocnos correspond to disjoint live ranges in loops
|
||
with a common parent. */
|
||
static bitmap renamed_regno_bitmap;
|
||
|
||
/* Change (if necessary) pseudo-registers inside loop given by loop
|
||
tree node NODE. */
|
||
static void
|
||
change_loop (ira_loop_tree_node_t node)
|
||
{
|
||
bitmap_iterator bi;
|
||
unsigned int i;
|
||
int regno;
|
||
bool used_p;
|
||
ira_allocno_t allocno, parent_allocno, *map;
|
||
rtx_insn *insn;
|
||
rtx original_reg;
|
||
enum reg_class aclass, pclass;
|
||
ira_loop_tree_node_t parent;
|
||
|
||
if (node != ira_loop_tree_root)
|
||
{
|
||
ira_assert (current_loops != NULL);
|
||
|
||
if (node->bb != NULL)
|
||
{
|
||
FOR_BB_INSNS (node->bb, insn)
|
||
if (INSN_P (insn) && change_regs_in_insn (&insn))
|
||
{
|
||
df_insn_rescan (insn);
|
||
df_notes_rescan (insn);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (internal_flag_ira_verbose > 3 && ira_dump_file != NULL)
|
||
fprintf (ira_dump_file,
|
||
" Changing RTL for loop %d (header bb%d)\n",
|
||
node->loop_num, node->loop->header->index);
|
||
|
||
parent = ira_curr_loop_tree_node->parent;
|
||
map = parent->regno_allocno_map;
|
||
EXECUTE_IF_SET_IN_REG_SET (ira_curr_loop_tree_node->border_allocnos,
|
||
0, i, bi)
|
||
{
|
||
allocno = ira_allocnos[i];
|
||
regno = ALLOCNO_REGNO (allocno);
|
||
aclass = ALLOCNO_CLASS (allocno);
|
||
pclass = ira_pressure_class_translate[aclass];
|
||
parent_allocno = map[regno];
|
||
ira_assert (regno < ira_reg_equiv_len);
|
||
/* We generate the same hard register move because the
|
||
reload pass can put an allocno into memory in this case
|
||
we will have live range splitting. If it does not happen
|
||
such the same hard register moves will be removed. The
|
||
worst case when the both allocnos are put into memory by
|
||
the reload is very rare. */
|
||
if (parent_allocno != NULL
|
||
&& (ALLOCNO_HARD_REGNO (allocno)
|
||
== ALLOCNO_HARD_REGNO (parent_allocno))
|
||
&& (ALLOCNO_HARD_REGNO (allocno) < 0
|
||
|| (parent->reg_pressure[pclass] + 1
|
||
<= ira_class_hard_regs_num[pclass])
|
||
|| TEST_HARD_REG_BIT (ira_prohibited_mode_move_regs
|
||
[ALLOCNO_MODE (allocno)],
|
||
ALLOCNO_HARD_REGNO (allocno))
|
||
/* don't create copies because reload can spill an
|
||
allocno set by copy although the allocno will not
|
||
get memory slot. */
|
||
|| ira_equiv_no_lvalue_p (regno)
|
||
|| (pic_offset_table_rtx != NULL
|
||
&& (ALLOCNO_REGNO (allocno)
|
||
== (int) REGNO (pic_offset_table_rtx)))))
|
||
continue;
|
||
original_reg = allocno_emit_reg (allocno);
|
||
if (parent_allocno == NULL
|
||
|| (REGNO (allocno_emit_reg (parent_allocno))
|
||
== REGNO (original_reg)))
|
||
{
|
||
if (internal_flag_ira_verbose > 3 && ira_dump_file)
|
||
fprintf (ira_dump_file, " %i vs parent %i:",
|
||
ALLOCNO_HARD_REGNO (allocno),
|
||
ALLOCNO_HARD_REGNO (parent_allocno));
|
||
set_allocno_reg (allocno, ira_create_new_reg (original_reg));
|
||
}
|
||
}
|
||
}
|
||
/* Rename locals: Local allocnos with same regno in different loops
|
||
might get the different hard register. So we need to change
|
||
ALLOCNO_REG. */
|
||
bitmap_and_compl (local_allocno_bitmap,
|
||
ira_curr_loop_tree_node->all_allocnos,
|
||
ira_curr_loop_tree_node->border_allocnos);
|
||
EXECUTE_IF_SET_IN_REG_SET (local_allocno_bitmap, 0, i, bi)
|
||
{
|
||
allocno = ira_allocnos[i];
|
||
regno = ALLOCNO_REGNO (allocno);
|
||
if (ALLOCNO_CAP_MEMBER (allocno) != NULL)
|
||
continue;
|
||
used_p = !bitmap_set_bit (used_regno_bitmap, regno);
|
||
ALLOCNO_EMIT_DATA (allocno)->somewhere_renamed_p = true;
|
||
if (! used_p)
|
||
continue;
|
||
bitmap_set_bit (renamed_regno_bitmap, regno);
|
||
set_allocno_reg (allocno, ira_create_new_reg (allocno_emit_reg (allocno)));
|
||
}
|
||
}
|
||
|
||
/* Process to set up flag somewhere_renamed_p. */
|
||
static void
|
||
set_allocno_somewhere_renamed_p (void)
|
||
{
|
||
unsigned int regno;
|
||
ira_allocno_t allocno;
|
||
ira_allocno_iterator ai;
|
||
|
||
FOR_EACH_ALLOCNO (allocno, ai)
|
||
{
|
||
regno = ALLOCNO_REGNO (allocno);
|
||
if (bitmap_bit_p (renamed_regno_bitmap, regno)
|
||
&& REGNO (allocno_emit_reg (allocno)) == regno)
|
||
ALLOCNO_EMIT_DATA (allocno)->somewhere_renamed_p = true;
|
||
}
|
||
}
|
||
|
||
/* Return TRUE if move lists on all edges given in vector VEC are
|
||
equal. */
|
||
static bool
|
||
eq_edge_move_lists_p (vec<edge, va_gc> *vec)
|
||
{
|
||
move_t list;
|
||
int i;
|
||
|
||
list = (move_t) EDGE_I (vec, 0)->aux;
|
||
for (i = EDGE_COUNT (vec) - 1; i > 0; i--)
|
||
if (! eq_move_lists_p (list, (move_t) EDGE_I (vec, i)->aux))
|
||
return false;
|
||
return true;
|
||
}
|
||
|
||
/* Look at all entry edges (if START_P) or exit edges of basic block
|
||
BB and put move lists at the BB start or end if it is possible. In
|
||
other words, this decreases code duplication of allocno moves. */
|
||
static void
|
||
unify_moves (basic_block bb, bool start_p)
|
||
{
|
||
int i;
|
||
edge e;
|
||
move_t list;
|
||
vec<edge, va_gc> *vec;
|
||
|
||
vec = (start_p ? bb->preds : bb->succs);
|
||
if (EDGE_COUNT (vec) == 0 || ! eq_edge_move_lists_p (vec))
|
||
return;
|
||
e = EDGE_I (vec, 0);
|
||
list = (move_t) e->aux;
|
||
if (! start_p && control_flow_insn_p (BB_END (bb)))
|
||
return;
|
||
e->aux = NULL;
|
||
for (i = EDGE_COUNT (vec) - 1; i > 0; i--)
|
||
{
|
||
e = EDGE_I (vec, i);
|
||
free_move_list ((move_t) e->aux);
|
||
e->aux = NULL;
|
||
}
|
||
if (start_p)
|
||
at_bb_start[bb->index] = list;
|
||
else
|
||
at_bb_end[bb->index] = list;
|
||
}
|
||
|
||
/* Last move (in move sequence being processed) setting up the
|
||
corresponding hard register. */
|
||
static move_t hard_regno_last_set[FIRST_PSEUDO_REGISTER];
|
||
|
||
/* If the element value is equal to CURR_TICK then the corresponding
|
||
element in `hard_regno_last_set' is defined and correct. */
|
||
static int hard_regno_last_set_check[FIRST_PSEUDO_REGISTER];
|
||
|
||
/* Last move (in move sequence being processed) setting up the
|
||
corresponding allocno. */
|
||
static move_t *allocno_last_set;
|
||
|
||
/* If the element value is equal to CURR_TICK then the corresponding
|
||
element in . `allocno_last_set' is defined and correct. */
|
||
static int *allocno_last_set_check;
|
||
|
||
/* Definition of vector of moves. */
|
||
|
||
/* This vec contains moves sorted topologically (depth-first) on their
|
||
dependency graph. */
|
||
static vec<move_t> move_vec;
|
||
|
||
/* The variable value is used to check correctness of values of
|
||
elements of arrays `hard_regno_last_set' and
|
||
`allocno_last_set_check'. */
|
||
static int curr_tick;
|
||
|
||
/* This recursive function traverses dependencies of MOVE and produces
|
||
topological sorting (in depth-first order). */
|
||
static void
|
||
traverse_moves (move_t move)
|
||
{
|
||
int i;
|
||
|
||
if (move->visited_p)
|
||
return;
|
||
move->visited_p = true;
|
||
for (i = move->deps_num - 1; i >= 0; i--)
|
||
traverse_moves (move->deps[i]);
|
||
move_vec.safe_push (move);
|
||
}
|
||
|
||
/* Remove unnecessary moves in the LIST, makes topological sorting,
|
||
and removes cycles on hard reg dependencies by introducing new
|
||
allocnos assigned to memory and additional moves. It returns the
|
||
result move list. */
|
||
static move_t
|
||
modify_move_list (move_t list)
|
||
{
|
||
int i, n, nregs, hard_regno;
|
||
ira_allocno_t to, from;
|
||
move_t move, new_move, set_move, first, last;
|
||
|
||
if (list == NULL)
|
||
return NULL;
|
||
/* Create move deps. */
|
||
curr_tick++;
|
||
for (move = list; move != NULL; move = move->next)
|
||
{
|
||
to = move->to;
|
||
if ((hard_regno = ALLOCNO_HARD_REGNO (to)) < 0)
|
||
continue;
|
||
nregs = hard_regno_nregs[hard_regno][ALLOCNO_MODE (to)];
|
||
for (i = 0; i < nregs; i++)
|
||
{
|
||
hard_regno_last_set[hard_regno + i] = move;
|
||
hard_regno_last_set_check[hard_regno + i] = curr_tick;
|
||
}
|
||
}
|
||
for (move = list; move != NULL; move = move->next)
|
||
{
|
||
from = move->from;
|
||
to = move->to;
|
||
if ((hard_regno = ALLOCNO_HARD_REGNO (from)) >= 0)
|
||
{
|
||
nregs = hard_regno_nregs[hard_regno][ALLOCNO_MODE (from)];
|
||
for (n = i = 0; i < nregs; i++)
|
||
if (hard_regno_last_set_check[hard_regno + i] == curr_tick
|
||
&& (ALLOCNO_REGNO (hard_regno_last_set[hard_regno + i]->to)
|
||
!= ALLOCNO_REGNO (from)))
|
||
n++;
|
||
move->deps = (move_t *) ira_allocate (n * sizeof (move_t));
|
||
for (n = i = 0; i < nregs; i++)
|
||
if (hard_regno_last_set_check[hard_regno + i] == curr_tick
|
||
&& (ALLOCNO_REGNO (hard_regno_last_set[hard_regno + i]->to)
|
||
!= ALLOCNO_REGNO (from)))
|
||
move->deps[n++] = hard_regno_last_set[hard_regno + i];
|
||
move->deps_num = n;
|
||
}
|
||
}
|
||
/* Topological sorting: */
|
||
move_vec.truncate (0);
|
||
for (move = list; move != NULL; move = move->next)
|
||
traverse_moves (move);
|
||
last = NULL;
|
||
for (i = (int) move_vec.length () - 1; i >= 0; i--)
|
||
{
|
||
move = move_vec[i];
|
||
move->next = NULL;
|
||
if (last != NULL)
|
||
last->next = move;
|
||
last = move;
|
||
}
|
||
first = move_vec.last ();
|
||
/* Removing cycles: */
|
||
curr_tick++;
|
||
move_vec.truncate (0);
|
||
for (move = first; move != NULL; move = move->next)
|
||
{
|
||
from = move->from;
|
||
to = move->to;
|
||
if ((hard_regno = ALLOCNO_HARD_REGNO (from)) >= 0)
|
||
{
|
||
nregs = hard_regno_nregs[hard_regno][ALLOCNO_MODE (from)];
|
||
for (i = 0; i < nregs; i++)
|
||
if (hard_regno_last_set_check[hard_regno + i] == curr_tick
|
||
&& ALLOCNO_HARD_REGNO
|
||
(hard_regno_last_set[hard_regno + i]->to) >= 0)
|
||
{
|
||
int n, j;
|
||
ira_allocno_t new_allocno;
|
||
|
||
set_move = hard_regno_last_set[hard_regno + i];
|
||
/* It does not matter what loop_tree_node (of TO or
|
||
FROM) to use for the new allocno because of
|
||
subsequent IRA internal representation
|
||
flattening. */
|
||
new_allocno
|
||
= create_new_allocno (ALLOCNO_REGNO (set_move->to),
|
||
ALLOCNO_LOOP_TREE_NODE (set_move->to));
|
||
ALLOCNO_MODE (new_allocno) = ALLOCNO_MODE (set_move->to);
|
||
ira_set_allocno_class (new_allocno,
|
||
ALLOCNO_CLASS (set_move->to));
|
||
ira_create_allocno_objects (new_allocno);
|
||
ALLOCNO_ASSIGNED_P (new_allocno) = true;
|
||
ALLOCNO_HARD_REGNO (new_allocno) = -1;
|
||
ALLOCNO_EMIT_DATA (new_allocno)->reg
|
||
= ira_create_new_reg (allocno_emit_reg (set_move->to));
|
||
|
||
/* Make it possibly conflicting with all earlier
|
||
created allocnos. Cases where temporary allocnos
|
||
created to remove the cycles are quite rare. */
|
||
n = ALLOCNO_NUM_OBJECTS (new_allocno);
|
||
gcc_assert (n == ALLOCNO_NUM_OBJECTS (set_move->to));
|
||
for (j = 0; j < n; j++)
|
||
{
|
||
ira_object_t new_obj = ALLOCNO_OBJECT (new_allocno, j);
|
||
|
||
OBJECT_MIN (new_obj) = 0;
|
||
OBJECT_MAX (new_obj) = ira_objects_num - 1;
|
||
}
|
||
|
||
new_move = create_move (set_move->to, new_allocno);
|
||
set_move->to = new_allocno;
|
||
move_vec.safe_push (new_move);
|
||
ira_move_loops_num++;
|
||
if (internal_flag_ira_verbose > 2 && ira_dump_file != NULL)
|
||
fprintf (ira_dump_file,
|
||
" Creating temporary allocno a%dr%d\n",
|
||
ALLOCNO_NUM (new_allocno),
|
||
REGNO (allocno_emit_reg (new_allocno)));
|
||
}
|
||
}
|
||
if ((hard_regno = ALLOCNO_HARD_REGNO (to)) < 0)
|
||
continue;
|
||
nregs = hard_regno_nregs[hard_regno][ALLOCNO_MODE (to)];
|
||
for (i = 0; i < nregs; i++)
|
||
{
|
||
hard_regno_last_set[hard_regno + i] = move;
|
||
hard_regno_last_set_check[hard_regno + i] = curr_tick;
|
||
}
|
||
}
|
||
for (i = (int) move_vec.length () - 1; i >= 0; i--)
|
||
{
|
||
move = move_vec[i];
|
||
move->next = NULL;
|
||
last->next = move;
|
||
last = move;
|
||
}
|
||
return first;
|
||
}
|
||
|
||
/* Generate RTX move insns from the move list LIST. This updates
|
||
allocation cost using move execution frequency FREQ. */
|
||
static rtx_insn *
|
||
emit_move_list (move_t list, int freq)
|
||
{
|
||
rtx to, from, dest;
|
||
int to_regno, from_regno, cost, regno;
|
||
rtx_insn *result, *insn;
|
||
rtx set;
|
||
machine_mode mode;
|
||
enum reg_class aclass;
|
||
|
||
grow_reg_equivs ();
|
||
start_sequence ();
|
||
for (; list != NULL; list = list->next)
|
||
{
|
||
start_sequence ();
|
||
to = allocno_emit_reg (list->to);
|
||
to_regno = REGNO (to);
|
||
from = allocno_emit_reg (list->from);
|
||
from_regno = REGNO (from);
|
||
emit_move_insn (to, from);
|
||
list->insn = get_insns ();
|
||
end_sequence ();
|
||
for (insn = list->insn; insn != NULL_RTX; insn = NEXT_INSN (insn))
|
||
{
|
||
/* The reload needs to have set up insn codes. If the
|
||
reload sets up insn codes by itself, it may fail because
|
||
insns will have hard registers instead of pseudos and
|
||
there may be no machine insn with given hard
|
||
registers. */
|
||
recog_memoized (insn);
|
||
/* Add insn to equiv init insn list if it is necessary.
|
||
Otherwise reload will not remove this insn if it decides
|
||
to use the equivalence. */
|
||
if ((set = single_set (insn)) != NULL_RTX)
|
||
{
|
||
dest = SET_DEST (set);
|
||
if (GET_CODE (dest) == SUBREG)
|
||
dest = SUBREG_REG (dest);
|
||
ira_assert (REG_P (dest));
|
||
regno = REGNO (dest);
|
||
if (regno >= ira_reg_equiv_len
|
||
|| (ira_reg_equiv[regno].invariant == NULL_RTX
|
||
&& ira_reg_equiv[regno].constant == NULL_RTX))
|
||
continue; /* regno has no equivalence. */
|
||
ira_assert ((int) reg_equivs->length () > regno);
|
||
reg_equiv_init (regno)
|
||
= gen_rtx_INSN_LIST (VOIDmode, insn, reg_equiv_init (regno));
|
||
}
|
||
}
|
||
if (ira_use_lra_p)
|
||
ira_update_equiv_info_by_shuffle_insn (to_regno, from_regno, list->insn);
|
||
emit_insn (list->insn);
|
||
mode = ALLOCNO_MODE (list->to);
|
||
aclass = ALLOCNO_CLASS (list->to);
|
||
cost = 0;
|
||
if (ALLOCNO_HARD_REGNO (list->to) < 0)
|
||
{
|
||
if (ALLOCNO_HARD_REGNO (list->from) >= 0)
|
||
{
|
||
cost = ira_memory_move_cost[mode][aclass][0] * freq;
|
||
ira_store_cost += cost;
|
||
}
|
||
}
|
||
else if (ALLOCNO_HARD_REGNO (list->from) < 0)
|
||
{
|
||
if (ALLOCNO_HARD_REGNO (list->to) >= 0)
|
||
{
|
||
cost = ira_memory_move_cost[mode][aclass][0] * freq;
|
||
ira_load_cost += cost;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ira_init_register_move_cost_if_necessary (mode);
|
||
cost = ira_register_move_cost[mode][aclass][aclass] * freq;
|
||
ira_shuffle_cost += cost;
|
||
}
|
||
ira_overall_cost += cost;
|
||
}
|
||
result = get_insns ();
|
||
end_sequence ();
|
||
return result;
|
||
}
|
||
|
||
/* Generate RTX move insns from move lists attached to basic blocks
|
||
and edges. */
|
||
static void
|
||
emit_moves (void)
|
||
{
|
||
basic_block bb;
|
||
edge_iterator ei;
|
||
edge e;
|
||
rtx_insn *insns, *tmp;
|
||
|
||
FOR_EACH_BB_FN (bb, cfun)
|
||
{
|
||
if (at_bb_start[bb->index] != NULL)
|
||
{
|
||
at_bb_start[bb->index] = modify_move_list (at_bb_start[bb->index]);
|
||
insns = emit_move_list (at_bb_start[bb->index],
|
||
REG_FREQ_FROM_BB (bb));
|
||
tmp = BB_HEAD (bb);
|
||
if (LABEL_P (tmp))
|
||
tmp = NEXT_INSN (tmp);
|
||
if (NOTE_INSN_BASIC_BLOCK_P (tmp))
|
||
tmp = NEXT_INSN (tmp);
|
||
if (tmp == BB_HEAD (bb))
|
||
emit_insn_before (insns, tmp);
|
||
else if (tmp != NULL_RTX)
|
||
emit_insn_after (insns, PREV_INSN (tmp));
|
||
else
|
||
emit_insn_after (insns, get_last_insn ());
|
||
}
|
||
|
||
if (at_bb_end[bb->index] != NULL)
|
||
{
|
||
at_bb_end[bb->index] = modify_move_list (at_bb_end[bb->index]);
|
||
insns = emit_move_list (at_bb_end[bb->index], REG_FREQ_FROM_BB (bb));
|
||
ira_assert (! control_flow_insn_p (BB_END (bb)));
|
||
emit_insn_after (insns, BB_END (bb));
|
||
}
|
||
|
||
FOR_EACH_EDGE (e, ei, bb->succs)
|
||
{
|
||
if (e->aux == NULL)
|
||
continue;
|
||
ira_assert ((e->flags & EDGE_ABNORMAL) == 0
|
||
|| ! EDGE_CRITICAL_P (e));
|
||
e->aux = modify_move_list ((move_t) e->aux);
|
||
insert_insn_on_edge
|
||
(emit_move_list ((move_t) e->aux,
|
||
REG_FREQ_FROM_EDGE_FREQ (EDGE_FREQUENCY (e))),
|
||
e);
|
||
if (e->src->next_bb != e->dest)
|
||
ira_additional_jumps_num++;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Update costs of A and corresponding allocnos on upper levels on the
|
||
loop tree from reading (if READ_P) or writing A on an execution
|
||
path with FREQ. */
|
||
static void
|
||
update_costs (ira_allocno_t a, bool read_p, int freq)
|
||
{
|
||
ira_loop_tree_node_t parent;
|
||
|
||
for (;;)
|
||
{
|
||
ALLOCNO_NREFS (a)++;
|
||
ALLOCNO_FREQ (a) += freq;
|
||
ALLOCNO_MEMORY_COST (a)
|
||
+= (ira_memory_move_cost[ALLOCNO_MODE (a)][ALLOCNO_CLASS (a)]
|
||
[read_p ? 1 : 0] * freq);
|
||
if (ALLOCNO_CAP (a) != NULL)
|
||
a = ALLOCNO_CAP (a);
|
||
else if ((parent = ALLOCNO_LOOP_TREE_NODE (a)->parent) == NULL
|
||
|| (a = parent->regno_allocno_map[ALLOCNO_REGNO (a)]) == NULL)
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Process moves from LIST with execution FREQ to add ranges, copies,
|
||
and modify costs for allocnos involved in the moves. All regnos
|
||
living through the list is in LIVE_THROUGH, and the loop tree node
|
||
used to find corresponding allocnos is NODE. */
|
||
static void
|
||
add_range_and_copies_from_move_list (move_t list, ira_loop_tree_node_t node,
|
||
bitmap live_through, int freq)
|
||
{
|
||
int start, n;
|
||
unsigned int regno;
|
||
move_t move;
|
||
ira_allocno_t a;
|
||
ira_copy_t cp;
|
||
live_range_t r;
|
||
bitmap_iterator bi;
|
||
HARD_REG_SET hard_regs_live;
|
||
|
||
if (list == NULL)
|
||
return;
|
||
n = 0;
|
||
EXECUTE_IF_SET_IN_BITMAP (live_through, FIRST_PSEUDO_REGISTER, regno, bi)
|
||
n++;
|
||
REG_SET_TO_HARD_REG_SET (hard_regs_live, live_through);
|
||
/* This is a trick to guarantee that new ranges is not merged with
|
||
the old ones. */
|
||
ira_max_point++;
|
||
start = ira_max_point;
|
||
for (move = list; move != NULL; move = move->next)
|
||
{
|
||
ira_allocno_t from = move->from;
|
||
ira_allocno_t to = move->to;
|
||
int nr, i;
|
||
|
||
bitmap_clear_bit (live_through, ALLOCNO_REGNO (from));
|
||
bitmap_clear_bit (live_through, ALLOCNO_REGNO (to));
|
||
|
||
nr = ALLOCNO_NUM_OBJECTS (to);
|
||
for (i = 0; i < nr; i++)
|
||
{
|
||
ira_object_t to_obj = ALLOCNO_OBJECT (to, i);
|
||
if (OBJECT_CONFLICT_ARRAY (to_obj) == NULL)
|
||
{
|
||
if (internal_flag_ira_verbose > 2 && ira_dump_file != NULL)
|
||
fprintf (ira_dump_file, " Allocate conflicts for a%dr%d\n",
|
||
ALLOCNO_NUM (to), REGNO (allocno_emit_reg (to)));
|
||
ira_allocate_object_conflicts (to_obj, n);
|
||
}
|
||
}
|
||
ior_hard_reg_conflicts (from, &hard_regs_live);
|
||
ior_hard_reg_conflicts (to, &hard_regs_live);
|
||
|
||
update_costs (from, true, freq);
|
||
update_costs (to, false, freq);
|
||
cp = ira_add_allocno_copy (from, to, freq, false, move->insn, NULL);
|
||
if (internal_flag_ira_verbose > 2 && ira_dump_file != NULL)
|
||
fprintf (ira_dump_file, " Adding cp%d:a%dr%d-a%dr%d\n",
|
||
cp->num, ALLOCNO_NUM (cp->first),
|
||
REGNO (allocno_emit_reg (cp->first)),
|
||
ALLOCNO_NUM (cp->second),
|
||
REGNO (allocno_emit_reg (cp->second)));
|
||
|
||
nr = ALLOCNO_NUM_OBJECTS (from);
|
||
for (i = 0; i < nr; i++)
|
||
{
|
||
ira_object_t from_obj = ALLOCNO_OBJECT (from, i);
|
||
r = OBJECT_LIVE_RANGES (from_obj);
|
||
if (r == NULL || r->finish >= 0)
|
||
{
|
||
ira_add_live_range_to_object (from_obj, start, ira_max_point);
|
||
if (internal_flag_ira_verbose > 2 && ira_dump_file != NULL)
|
||
fprintf (ira_dump_file,
|
||
" Adding range [%d..%d] to allocno a%dr%d\n",
|
||
start, ira_max_point, ALLOCNO_NUM (from),
|
||
REGNO (allocno_emit_reg (from)));
|
||
}
|
||
else
|
||
{
|
||
r->finish = ira_max_point;
|
||
if (internal_flag_ira_verbose > 2 && ira_dump_file != NULL)
|
||
fprintf (ira_dump_file,
|
||
" Adding range [%d..%d] to allocno a%dr%d\n",
|
||
r->start, ira_max_point, ALLOCNO_NUM (from),
|
||
REGNO (allocno_emit_reg (from)));
|
||
}
|
||
}
|
||
ira_max_point++;
|
||
nr = ALLOCNO_NUM_OBJECTS (to);
|
||
for (i = 0; i < nr; i++)
|
||
{
|
||
ira_object_t to_obj = ALLOCNO_OBJECT (to, i);
|
||
ira_add_live_range_to_object (to_obj, ira_max_point, -1);
|
||
}
|
||
ira_max_point++;
|
||
}
|
||
for (move = list; move != NULL; move = move->next)
|
||
{
|
||
int nr, i;
|
||
nr = ALLOCNO_NUM_OBJECTS (move->to);
|
||
for (i = 0; i < nr; i++)
|
||
{
|
||
ira_object_t to_obj = ALLOCNO_OBJECT (move->to, i);
|
||
r = OBJECT_LIVE_RANGES (to_obj);
|
||
if (r->finish < 0)
|
||
{
|
||
r->finish = ira_max_point - 1;
|
||
if (internal_flag_ira_verbose > 2 && ira_dump_file != NULL)
|
||
fprintf (ira_dump_file,
|
||
" Adding range [%d..%d] to allocno a%dr%d\n",
|
||
r->start, r->finish, ALLOCNO_NUM (move->to),
|
||
REGNO (allocno_emit_reg (move->to)));
|
||
}
|
||
}
|
||
}
|
||
EXECUTE_IF_SET_IN_BITMAP (live_through, FIRST_PSEUDO_REGISTER, regno, bi)
|
||
{
|
||
ira_allocno_t to;
|
||
int nr, i;
|
||
|
||
a = node->regno_allocno_map[regno];
|
||
if ((to = ALLOCNO_EMIT_DATA (a)->mem_optimized_dest) != NULL)
|
||
a = to;
|
||
nr = ALLOCNO_NUM_OBJECTS (a);
|
||
for (i = 0; i < nr; i++)
|
||
{
|
||
ira_object_t obj = ALLOCNO_OBJECT (a, i);
|
||
ira_add_live_range_to_object (obj, start, ira_max_point - 1);
|
||
}
|
||
if (internal_flag_ira_verbose > 2 && ira_dump_file != NULL)
|
||
fprintf
|
||
(ira_dump_file,
|
||
" Adding range [%d..%d] to live through %s allocno a%dr%d\n",
|
||
start, ira_max_point - 1,
|
||
to != NULL ? "upper level" : "",
|
||
ALLOCNO_NUM (a), REGNO (allocno_emit_reg (a)));
|
||
}
|
||
}
|
||
|
||
/* Process all move list to add ranges, conflicts, copies, and modify
|
||
costs for allocnos involved in the moves. */
|
||
static void
|
||
add_ranges_and_copies (void)
|
||
{
|
||
basic_block bb;
|
||
edge_iterator ei;
|
||
edge e;
|
||
ira_loop_tree_node_t node;
|
||
bitmap live_through;
|
||
|
||
live_through = ira_allocate_bitmap ();
|
||
FOR_EACH_BB_FN (bb, cfun)
|
||
{
|
||
/* It does not matter what loop_tree_node (of source or
|
||
destination block) to use for searching allocnos by their
|
||
regnos because of subsequent IR flattening. */
|
||
node = IRA_BB_NODE (bb)->parent;
|
||
bitmap_copy (live_through, df_get_live_in (bb));
|
||
add_range_and_copies_from_move_list
|
||
(at_bb_start[bb->index], node, live_through, REG_FREQ_FROM_BB (bb));
|
||
bitmap_copy (live_through, df_get_live_out (bb));
|
||
add_range_and_copies_from_move_list
|
||
(at_bb_end[bb->index], node, live_through, REG_FREQ_FROM_BB (bb));
|
||
FOR_EACH_EDGE (e, ei, bb->succs)
|
||
{
|
||
bitmap_and (live_through,
|
||
df_get_live_in (e->dest), df_get_live_out (bb));
|
||
add_range_and_copies_from_move_list
|
||
((move_t) e->aux, node, live_through,
|
||
REG_FREQ_FROM_EDGE_FREQ (EDGE_FREQUENCY (e)));
|
||
}
|
||
}
|
||
ira_free_bitmap (live_through);
|
||
}
|
||
|
||
/* The entry function changes code and generates shuffling allocnos on
|
||
region borders for the regional (LOOPS_P is TRUE in this case)
|
||
register allocation. */
|
||
void
|
||
ira_emit (bool loops_p)
|
||
{
|
||
basic_block bb;
|
||
rtx_insn *insn;
|
||
edge_iterator ei;
|
||
edge e;
|
||
ira_allocno_t a;
|
||
ira_allocno_iterator ai;
|
||
size_t sz;
|
||
|
||
FOR_EACH_ALLOCNO (a, ai)
|
||
ALLOCNO_EMIT_DATA (a)->reg = regno_reg_rtx[ALLOCNO_REGNO (a)];
|
||
if (! loops_p)
|
||
return;
|
||
sz = sizeof (move_t) * last_basic_block_for_fn (cfun);
|
||
at_bb_start = (move_t *) ira_allocate (sz);
|
||
memset (at_bb_start, 0, sz);
|
||
at_bb_end = (move_t *) ira_allocate (sz);
|
||
memset (at_bb_end, 0, sz);
|
||
local_allocno_bitmap = ira_allocate_bitmap ();
|
||
used_regno_bitmap = ira_allocate_bitmap ();
|
||
renamed_regno_bitmap = ira_allocate_bitmap ();
|
||
max_regno_before_changing = max_reg_num ();
|
||
ira_traverse_loop_tree (true, ira_loop_tree_root, change_loop, NULL);
|
||
set_allocno_somewhere_renamed_p ();
|
||
ira_free_bitmap (used_regno_bitmap);
|
||
ira_free_bitmap (renamed_regno_bitmap);
|
||
ira_free_bitmap (local_allocno_bitmap);
|
||
setup_entered_from_non_parent_p ();
|
||
FOR_EACH_BB_FN (bb, cfun)
|
||
{
|
||
at_bb_start[bb->index] = NULL;
|
||
at_bb_end[bb->index] = NULL;
|
||
FOR_EACH_EDGE (e, ei, bb->succs)
|
||
if (e->dest != EXIT_BLOCK_PTR_FOR_FN (cfun))
|
||
generate_edge_moves (e);
|
||
}
|
||
allocno_last_set
|
||
= (move_t *) ira_allocate (sizeof (move_t) * max_reg_num ());
|
||
allocno_last_set_check
|
||
= (int *) ira_allocate (sizeof (int) * max_reg_num ());
|
||
memset (allocno_last_set_check, 0, sizeof (int) * max_reg_num ());
|
||
memset (hard_regno_last_set_check, 0, sizeof (hard_regno_last_set_check));
|
||
curr_tick = 0;
|
||
FOR_EACH_BB_FN (bb, cfun)
|
||
unify_moves (bb, true);
|
||
FOR_EACH_BB_FN (bb, cfun)
|
||
unify_moves (bb, false);
|
||
move_vec.create (ira_allocnos_num);
|
||
emit_moves ();
|
||
add_ranges_and_copies ();
|
||
/* Clean up: */
|
||
FOR_EACH_BB_FN (bb, cfun)
|
||
{
|
||
free_move_list (at_bb_start[bb->index]);
|
||
free_move_list (at_bb_end[bb->index]);
|
||
FOR_EACH_EDGE (e, ei, bb->succs)
|
||
{
|
||
free_move_list ((move_t) e->aux);
|
||
e->aux = NULL;
|
||
}
|
||
}
|
||
move_vec.release ();
|
||
ira_free (allocno_last_set_check);
|
||
ira_free (allocno_last_set);
|
||
commit_edge_insertions ();
|
||
/* Fix insn codes. It is necessary to do it before reload because
|
||
reload assumes initial insn codes defined. The insn codes can be
|
||
invalidated by CFG infrastructure for example in jump
|
||
redirection. */
|
||
FOR_EACH_BB_FN (bb, cfun)
|
||
FOR_BB_INSNS_REVERSE (bb, insn)
|
||
if (INSN_P (insn))
|
||
recog_memoized (insn);
|
||
ira_free (at_bb_end);
|
||
ira_free (at_bb_start);
|
||
}
|