blob: 137998163831ab7bce34fd99e6a69b855290b6f7 [file]
/* GCC minipool support for ARM.
Copyright (C) 2026 Free Software Foundation, Inc.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published
by the Free Software Foundation; either version 3, or (at your
option) any later version.
GCC is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
/* GCC's standard model for literal pools places the entire table of literals
either before or after the body of the function. However, if the function
is large, then this table can be too far away from the locations in the code
that need to load the values. One approach to handling this would be to
reserve a register pointing to the literal pool, but that's costly when
register pressure is high and may still not be sufficient if the number of
literals within the pool is very large.
This code takes an alternative approach, which is more suitable for RISC
architectures with limited PC-relative offset addressing: we divide the
literal pool into smaller pools (minipools) and place those inline within
the body of the function such that every instruction that needs to load a
literal can do so directly with a PC-relative offset.
It is important to minimize the number of minipools because very small pools
waste cache space on most Harvard architecture caches if a line contains
both code and data. Additionally, we may need to insert branches around a
minipool, which adds further instructions to the function.
Normally we can hide a minipool after an existing unconditional branch so
that there is no interruption of the flow, but in the worst case the code
looks like this:
ldr Rn, L1
...
b L2
align
L1: .long value
L2:
...
ldr Rn, L3
...
b L4
align
L3: .long value
L4:
...
We do this by performing a scan after scheduling, identifying which
instructions need to have their operands fetched from the literal pool and
building the minipools.
The algorithm starts by building a list of all the locations in the
instruction stream that need fixing up and all the natural barriers in the
function (places where constants can be dropped without breaking the flow).
For each fixup we note how far the PC-relative replacement will be able to
reach and the address of the instruction in the function.
The next step is to assign fixups to a minipool. This is done linearly
through the function: fixups are added to a minipool until adding a fixup
would break the constraints on the pool. The placement of the pool in the
instruction stream is then determined, and we attempt to satisfy further
fixups by creating backward references to the placed pool. As soon as a
backward reference fails the pool is emitted and we start building a new
minipool. The process continues until there are no more fixups to handle.
The final minipool will always be placed at the end of the function if that
can be done while still satisfying the pool's constraints.
To optimize pool size, we try to share pool entries if the fixups describe
the same value. This involves calculating the combined constraints for the
referencing fixups and shuffling the order of entries within the pool to
keep the constraints as loose as possible. Both forward and backward
references to the pool can share an entry, but if a backward reference is
unable to address an existing entry we will try to insert a second copy of
the same value.
Each minipool is structured as a series of subpools, such that every entry
in a subpool has the same alignment restriction. When a minipool is
finally emitted, the subpools are emitted in decreasing order of
alignment. This ensures that alignment is only needed before the pool and
never within it. As each pool is emitted, the relevant operand in the
referencing instruction is adjusted to fetch the required value from the
pool.
When placing a minipool we try to place it after a barrier instruction,
since control will never flow through this point. If no suitable natural
barrier exists, we select a location at which to create one, preferring
locations around jumps and labels where possible. If necessary, this may
break a linear stream of instructions. A new jump instruction and barrier
are then inserted to jump over the literal pool.
Note that this pass breaks the CFG layout data structures, so must only
be run once that information is no longer needed.
Possible enhancements to the algorithm (not implemented) are:
1) For some processors and object formats, there may be a benefit in
aligning the pools to the start of cache lines; this additional alignment
would need to be taken into account when calculating addressability of a
pool. */
#define IN_TARGET_CODE 1
#define INCLUDE_LIST
#define INCLUDE_ALGORITHM
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "backend.h"
#include "target.h"
#include "memmodel.h"
#include "rtl.h"
#include "emit-rtl.h"
#include "print-rtl.h"
#include "output.h"
#include "insn-attr.h"
#include "optabs.h"
#include "recog.h"
#include "obstack.h"
#include "minipool.h"
namespace minipools {
/* Obstack for minipool constant handling. */
static struct obstack minipool_obstack;
static char *minipool_startobj;
/* Forward definitions of types. */
typedef struct minipool_node Mnode;
typedef struct minipool_fixup Mfix;
typedef HOST_WIDE_INT minipool_address;
#define IS_MINIPOOL_ADDRESS_MIN(a) (a == HOST_WIDE_INT_MIN)
#define IS_MINIPOOL_ADDRESS_MAX(a) (a == HOST_WIDE_INT_MAX)
#define MINIPOOL_ADDRESS_MIN HOST_WIDE_INT_MIN
#define MINIPOOL_ADDRESS_MAX HOST_WIDE_INT_MAX
struct minipool_node
{
/* The highest address that this entry can be placed. While pushing fixes
for forward references, all entries are sorted in order of increasing
max_address within each subpool. */
minipool_address max_address;
/* The lowest address that this entry can be placed for a backward
reference. */
minipool_address min_address;
/* The number of fixes referencing this entry. This can become zero
if we "unpush" an entry. In this case we ignore the entry when we
come to emit the code. */
int refcount;
/* The offset from the start of the minipool. */
HOST_WIDE_INT offset;
/* The value in table. */
rtx value;
/* The mode of value. */
machine_mode mode;
/* The size of the value. */
int fix_size;
};
struct minipool_fixup
{
/* The next fixup in the insn stream. */
Mfix *next;
/* The insn that needs adjusting, or a barrier insn. */
rtx_insn *insn;
/* The address that we calculate for the placement of the instruction
relative to the start of the function. Note that excludes any
later adjustments that will occur as minipools get placed, but that
doesn't matter because we never reach over one minipool to an
earlier or later one. */
minipool_address address;
/* The size of the insn in the stream. */
unsigned insn_size;
/* Pointer to the operand in the insn that needs adjusting to reference
the minipool. */
rtx *operand_loc;
/* The machine mode associated with the literal pool entry. Necessary
because VALUE will often lack a mode. */
machine_mode mode;
/* The size of the literal pool entry that will be needed. For entries
smaller than the least aligned subpool, this is rounded up to a multiple
of the subpool alignment. */
int fix_size;
/* The value that will need to be placed in the minipool. */
rtx value;
/* Once the fix has been tentatively assigned to a minipool, the
minipool that will hold this fix. */
Mnode *minipool;
/* The highest address that this insn can use when using a forward
reference. */
minipool_address max_address;
/* The lowest address that this insn can use when using a backward reference.
This may be negative if the insn could reach back further than the start
of the function. */
minipool_address min_address;
};
/* Each minipool is divided into one or more subpools, where all the elements
in each subpool share a common alignment (though not necessarily a common
size). When we emit the pool it will be aligned to the most aligned
subpool and the subpools are emitted in decreasing order of alignment
This ensures we only need one alignment directive before the start of the
pool. */
class subpool
{
public:
explicit subpool (HOST_WIDE_INT align);
void reset ();
unsigned align () const
{ return m_alignment; }
unsigned size () const
{ return m_size; }
Mnode *add_forward_ref (Mfix *);
Mnode *add_backward_ref (Mfix *);
HOST_WIDE_INT assign_offsets (HOST_WIDE_INT);
minipool_address backwards () const
{ return m_min_address; }
minipool_address forwards () const
{ return m_max_address; }
rtx_insn *dump (rtx_insn *);
void update_bounds (void);
void debug (void) const;
private:
Mnode *move_fix_forward_ref (std::list<Mnode *>::iterator,
std::list<Mnode *>::iterator,
minipool_address);
Mnode *move_fix_backward_ref (std::list<Mnode *>::iterator,
std::list<Mnode *>::iterator,
minipool_address);
std::list<Mnode *> m_elements;
/* Current size of the subpool. */
HOST_WIDE_INT m_size;
/* The lowest address at which the end of this subpool can be located while
still allowing all the elements it contains to be reached by referencing
insns. */
minipool_address m_min_address;
/* The highest address at which the start of this subpool can be located
while still allowing all the elements it contains to be reached by
referencing insns. */
minipool_address m_max_address;
/* The subpool's alignment. */
const unsigned m_alignment;
};
subpool::subpool (HOST_WIDE_INT align)
: m_alignment (align)
{
reset ();
}
void
subpool::reset ()
{
m_elements.clear ();
m_min_address = MINIPOOL_ADDRESS_MIN;
m_max_address = MINIPOOL_ADDRESS_MAX;
m_size = 0;
}
class minipool
{
public:
explicit minipool (unsigned insn_align);
void reset ();
rtx_code_label *label ();
Mnode *add_forward_ref (Mfix *, minipool_address);
Mnode *add_backward_ref (Mfix *, minipool_address);
void assign_offsets (Mfix *);
void dump (rtx_insn *);
Mfix *create_fix_barrier (Mfix *, minipool_address);
HOST_WIDE_INT get_label_padding (rtx_insn *label) const;
HOST_WIDE_INT get_alignment_size (void) const;
minipool_address forwards (void) const
{ return m_max_address; }
unsigned size () const
{ return m_size; }
void update_bounds (void);
void debug (void) const;
private:
std::list<subpool *>::iterator find_subpool (unsigned align, Mfix *fix,
minipool_address loc,
HOST_WIDE_INT padding);
std::list<subpool *> m_subpools;
/* The aligned label for the base of the minipool. All references to
the pool will offset from this label. */
rtx_code_label *m_vector_label;
/* The highest address at which the pool can start whilst all the
subpool constraint can be met. This is only relevant while pushing
forward references. After that point the pool's location is fixed. */
minipool_address m_max_address;
/* The insn alignment. */
const unsigned m_insn_align;
/* Total size of the pool. */
unsigned m_size;
};
minipool::minipool (unsigned insn_align)
: m_insn_align (insn_align)
{
reset ();
}
void
minipool::reset ()
{
m_subpools.clear ();
m_vector_label = NULL;
m_max_address = MINIPOOL_ADDRESS_MAX;
m_size = 0;
}
rtx_code_label *
minipool::label ()
{
if (!m_vector_label)
m_vector_label = gen_label_rtx ();
return m_vector_label;
}
/* The linked list of all minipool fixes required for this function. */
Mfix *minipool_fix_head;
Mfix *minipool_fix_tail;
/* The fix entry for the current minipool, once it has been placed. */
Mfix *minipool_barrier;
static void push_minipool_barrier (rtx_insn *, HOST_WIDE_INT);
static void push_minipool_fix (rtx_insn *, HOST_WIDE_INT, rtx *,
machine_mode, rtx);
static int barrier_cost (rtx_insn *);
void add_gc_roots (void)
{
gcc_obstack_init (&minipool_obstack);
minipool_startobj = (char *) obstack_alloc (&minipool_obstack, 0);
}
/* Print a symbolic form of X to the debug file, F. */
static void
print_value (FILE *f, rtx x)
{
switch (GET_CODE (x))
{
case CONST_INT:
fprintf (f, HOST_WIDE_INT_PRINT_HEX, INTVAL (x));
return;
case CONST_DOUBLE:
{
char fpstr[20];
real_to_decimal (fpstr, CONST_DOUBLE_REAL_VALUE (x),
sizeof (fpstr), 0, 1);
fputs (fpstr, f);
}
return;
case CONST_VECTOR:
{
int i;
fprintf (f, "<");
for (i = 0; i < CONST_VECTOR_NUNITS (x); ++i)
{
fprintf (f, HOST_WIDE_INT_PRINT_HEX,
INTVAL (CONST_VECTOR_ELT (x, i)));
if (i < (CONST_VECTOR_NUNITS (x) - 1))
fputc (',', f);
}
fprintf (f, ">");
}
return;
case CONST_STRING:
fprintf (f, "\"%s\"", XSTR (x, 0));
return;
case SYMBOL_REF:
fprintf (f, "`%s'", XSTR (x, 0));
return;
case LABEL_REF:
fprintf (f, "L%d", INSN_UID (label_ref_label (x)));
return;
case CONST:
print_value (f, XEXP (x, 0));
return;
case PLUS:
print_value (f, XEXP (x, 0));
fprintf (f, "+");
print_value (f, XEXP (x, 1));
return;
case PC:
fprintf (f, "pc");
return;
default:
fprintf (f, "????");
return;
}
}
#ifndef JUMP_TABLES_IN_TEXT_SECTION
#define JUMP_TABLES_IN_TEXT_SECTION 0
#endif
static HOST_WIDE_INT
get_jump_table_size (rtx_jump_table_data *insn)
{
/* ADDR_VECs only take room if read-only data does into the text
section. */
if (JUMP_TABLES_IN_TEXT_SECTION || readonly_data_section == text_section)
{
rtx body = PATTERN (insn);
int elt = GET_CODE (body) == ADDR_DIFF_VEC ? 1 : 0;
HOST_WIDE_INT size;
HOST_WIDE_INT modesize;
modesize = GET_MODE_SIZE (GET_MODE (body));
size = modesize * XVECLEN (body, elt);
switch (modesize)
{
case 1:
/* Round up size of TBB table to a halfword boundary. */
size = (size + 1) & ~HOST_WIDE_INT_1;
break;
case 2:
/* No padding necessary for TBH. */
break;
case 4:
/* Add two bytes for alignment on Thumb. */
if (TARGET_THUMB)
size += 2;
break;
default:
gcc_unreachable ();
}
return size;
}
return 0;
}
/* Return the cost of forcibly inserting a barrier after INSN. */
static int
barrier_cost (rtx_insn *insn)
{
/* Basing the location of the pool on the loop depth is preferable, but at
the moment, the basic block information seems to be corrupt by this
stage of the compilation. */
int base_cost = 50;
rtx_insn *next = next_nonnote_nondebug_insn (insn);
if (next != NULL && LABEL_P (next))
base_cost -= 20;
switch (GET_CODE (insn))
{
case CODE_LABEL:
/* It will always be better to place the table before the label, rather
than after it. */
return 50;
case INSN:
case CALL_INSN:
return base_cost;
case JUMP_INSN:
return base_cost - 10;
default:
return base_cost + 10;
}
}
/* Fixes less than a word need padding out to a word boundary. */
#define MINIPOOL_FIX_SIZE(mode) \
(GET_MODE_SIZE ((mode)) >= 4 ? GET_MODE_SIZE ((mode)) : 4)
/* (Re-)compute the bounds of the subpool. */
void
subpool::update_bounds (void)
{
/* We don't know the size of the subpool so the forward and backward
bounds have to be calculated separately, counting from opposite
ends of the subpool. */
m_max_address = MINIPOOL_ADDRESS_MAX;
HOST_WIDE_INT offset = HOST_WIDE_INT_0;
std::list<Mnode *>::iterator mpi;
for (mpi = m_elements.begin (); mpi != m_elements.end (); ++mpi)
{
Mnode *mp = *mpi;
if (!mp->refcount)
continue;
if (!IS_MINIPOOL_ADDRESS_MAX (mp->max_address))
m_max_address = std::min (m_max_address, mp->max_address - offset);
offset += mp->fix_size;
}
m_size = offset;
m_min_address = MINIPOOL_ADDRESS_MIN;
offset = HOST_WIDE_INT_0;
std::list<Mnode *>::reverse_iterator rmpi;
for (rmpi = m_elements.rbegin (); rmpi != m_elements.rend (); ++rmpi)
{
Mnode *mp = *rmpi;
if (!mp->refcount)
continue;
offset += mp->fix_size;
if (!IS_MINIPOOL_ADDRESS_MIN (mp->min_address))
m_min_address = std::max (m_min_address, mp->min_address + offset);
}
}
/* Move a minipool fix MPI from its current location to before MAX_MPI. If
MAX_MPI points to the end of the list, then MPI doesn't need moving, but
the addressing constraints may still need updating. */
Mnode *
subpool::move_fix_forward_ref (std::list<Mnode *>::iterator mpi,
std::list<Mnode *>::iterator max_mpi,
minipool_address fix_max_address)
{
/* The code below assumes these are different. */
gcc_assert (mpi != max_mpi);
Mnode *mp = *mpi;
mp->max_address = std::min (mp->max_address, fix_max_address);
if (max_mpi != m_elements.end ())
m_elements.splice (max_mpi, m_elements, mpi);
update_bounds ();
return mp;
}
/* Add a constant to the subpool for a forward reference. Returns the
node added or NULL if the constant will not fit in this pool. */
Mnode *
subpool::add_forward_ref (Mfix *fix)
{
/* max_mpi points to the first subpool entry that has a less strict
constraint than the one we are trying to add. */
std::list<Mnode *>::iterator max_mpi = m_elements.end ();
/* Scan the subpool to see if a constant with the same value has
already been added. While we are doing this, also note the
location where we must insert the constant if it doesn't already
exist. */
std::list<Mnode *>::iterator mpi = m_elements.begin ();
for (; mpi != m_elements.end (); ++mpi)
{
Mnode *mp = *mpi;
/* This is perhaps too strict. At least on little-endian platforms
we could share CONST_INT values where they will be loaded into
different modes. */
if (GET_CODE (fix->value) == GET_CODE (mp->value)
&& fix->mode == mp->mode
&& (!LABEL_P (fix->value)
|| (CODE_LABEL_NUMBER (fix->value)
== CODE_LABEL_NUMBER (mp->value)))
&& rtx_equal_p (fix->value, mp->value))
{
/* More than one fix references this entry. */
mp->refcount++;
return move_fix_forward_ref (mpi, max_mpi, fix->max_address);
}
/* Note the insertion point if necessary. Conceptually the test here
is mp->max + mp->size > fix->max + fix->size, but that might
overflow if mp->max is MINIPOOL_ADDRESS_MAX. */
if (max_mpi == m_elements.end ()
&& (mp->max_address - fix->fix_size
> fix->max_address - mp->fix_size))
max_mpi = mpi;
}
/* The value is not currently in the subpool, so we need to create a new
entry for it. */
Mnode *mp = XNEW (Mnode);
mp->fix_size = fix->fix_size;
mp->mode = fix->mode;
mp->value = fix->value;
mp->refcount = 1;
mp->offset = 0;
/* Not yet required for a backward reference. */
mp->min_address = MINIPOOL_ADDRESS_MIN;
mp->max_address = fix->max_address;
mpi = m_elements.insert (max_mpi, mp);
update_bounds ();
return mp;
}
Mnode *
subpool::move_fix_backward_ref (std::list<Mnode *>::iterator mpi,
std::list<Mnode *>::iterator min_mpi,
minipool_address fix_min_address)
{
/* The code below assumes these are different. */
gcc_assert (std::next (mpi) != min_mpi);
Mnode *mp = *mpi;
mp->min_address = std::max (mp->min_address, fix_min_address);
/* We're moving an entry later in the list, so the insertion point
of begin () can never happen. We use this as the sentinel for
not moving the element. */
if (min_mpi != m_elements.begin ())
m_elements.splice (min_mpi, m_elements, mpi);
update_bounds ();
return mp;
}
/* Add a constant to the subpool for a backward reference. Returns the
node added or NULL if the constant will not fit in this pool.
Note that offsets for a backward reference are calculated relative to
the end of the subpool. */
Mnode *
subpool::add_backward_ref (Mfix *fix)
{
/* This can be negative, since it is only a constraint. */
HOST_WIDE_INT min_address = fix->min_address;
/* Scan the pool to see if a constant with the same value has
already been added. While we are doing this, also note the
location where we must insert the constant if it doesn't already
exist. */
std::list<Mnode *>::reverse_iterator rmpi = m_elements.rbegin ();
std::list<Mnode *>::reverse_iterator min_rmpi = m_elements.rend ();
for (; rmpi != m_elements.rend (); ++rmpi)
{
Mnode *mp = *rmpi;
/* This is perhaps too strict. At least on little-endian platforms
we could share CONST_INT values where they will be loaded into
different modes. */
if (GET_CODE (fix->value) == GET_CODE (mp->value)
&& fix->mode == mp->mode
&& (!LABEL_P (fix->value)
|| (CODE_LABEL_NUMBER (fix->value)
== CODE_LABEL_NUMBER (mp->value)))
&& rtx_equal_p (fix->value, mp->value))
{
std::list<Mnode *>::iterator before = min_rmpi.base ();
mp->refcount++;
/* If we can't safely move the entry later in the pool, then
we will need to do it in place. */
if (mp->max_address - m_size <= m_max_address)
before = m_elements.begin ();
return move_fix_backward_ref (std::prev (rmpi.base ()),
before, fix->min_address);
}
/* Note the insertion point if necessary. */
if (min_rmpi == m_elements.rend ()
&& (mp->min_address + mp->fix_size
< fix->min_address + fix->fix_size))
min_rmpi = rmpi;
}
/* We need to create a new entry. */
Mnode *mp = XNEW (Mnode);
mp->fix_size = fix->fix_size;
mp->mode = fix->mode;
mp->value = fix->value;
mp->refcount = 1;
mp->offset = 0;
mp->max_address = MINIPOOL_ADDRESS_MAX;
mp->min_address = min_address;
if (min_rmpi == m_elements.rend ())
m_elements.insert (m_elements.begin (), mp);
else
m_elements.insert (min_rmpi.base (), mp);
update_bounds ();
return mp;
}
HOST_WIDE_INT
subpool::assign_offsets (HOST_WIDE_INT offset_in)
{
HOST_WIDE_INT offset = offset_in;
std::list<Mnode *>::iterator mpi = m_elements.begin ();
for (; mpi != m_elements.end (); ++mpi)
{
Mnode *mp = *mpi;
mp->offset = offset;
if (mp->refcount > 0)
offset += mp->fix_size;
}
/* The final size of this subpool. */
m_size = offset - offset_in;
return offset;
}
rtx_insn *
subpool::dump (rtx_insn *scan)
{
if (dump_file)
fprintf (dump_file, ";; subpool with alignment %u\n", m_alignment);
std::list<Mnode *>::iterator mpi;
for (mpi = m_elements.begin (); mpi != m_elements.end (); ++mpi)
{
Mnode *mp = *mpi;
if (mp->refcount > 0)
{
if (dump_file)
{
fprintf (dump_file,
";; Offset %u, min %ld, max %ld ",
(unsigned) mp->offset,
(unsigned long) mp->min_address,
(unsigned long) mp->max_address);
print_value (dump_file, mp->value);
fputc ('\n', dump_file);
}
rtx val = copy_rtx (mp->value);
switch (GET_MODE_SIZE (mp->mode))
{
#ifdef HAVE_consttable_1
case 1:
scan = emit_insn_after (gen_consttable_1 (val), scan);
break;
#endif
#ifdef HAVE_consttable_2
case 2:
scan = emit_insn_after (gen_consttable_2 (val), scan);
break;
#endif
#ifdef HAVE_consttable_4
case 4:
scan = emit_insn_after (gen_consttable_4 (val), scan);
break;
#endif
#ifdef HAVE_consttable_8
case 8:
scan = emit_insn_after (gen_consttable_8 (val), scan);
break;
#endif
#ifdef HAVE_consttable_16
case 16:
scan = emit_insn_after (gen_consttable_16 (val), scan);
break;
#endif
default:
gcc_unreachable ();
}
}
free (mp);
}
return scan;
}
void
subpool::debug (void) const
{
fprintf (stderr, " subpool @%p", (const void *) this);
fprintf (stderr, " alignment: %u", m_alignment);
fprintf (stderr, " size: " HOST_WIDE_INT_PRINT_DEC "\n", m_size);
if (IS_MINIPOOL_ADDRESS_MIN (m_min_address))
fprintf (stderr, " min_address: -\n");
else
fprintf (stderr, " min_address: " HOST_WIDE_INT_PRINT_DEC "\n",
(HOST_WIDE_INT) m_min_address);
if (IS_MINIPOOL_ADDRESS_MAX (m_max_address))
fprintf (stderr, " max_address: -\n");
else
fprintf (stderr, " max_address: " HOST_WIDE_INT_PRINT_DEC "\n",
(HOST_WIDE_INT) m_max_address);
std::list<Mnode *>::const_iterator mpi = m_elements.begin ();
for (; mpi != m_elements.end (); ++mpi)
{
Mnode *mp = *mpi;
fprintf (stderr, " mode: %s; min: ", GET_MODE_NAME (mp->mode));
if (IS_MINIPOOL_ADDRESS_MIN (mp->min_address))
fprintf (stderr, "-");
else
fprintf (stderr, HOST_WIDE_INT_PRINT_DEC,
(HOST_WIDE_INT) mp->min_address);
fprintf (stderr, "; max: ");
if (IS_MINIPOOL_ADDRESS_MAX (mp->max_address))
fprintf (stderr, "-");
else
fprintf (stderr, HOST_WIDE_INT_PRINT_DEC,
(HOST_WIDE_INT) mp->max_address);
fprintf (stderr,
"; size: %d"
"; offset: " HOST_WIDE_INT_PRINT_DEC
"; refcount: %d"
"; value: ",
mp->fix_size, mp->offset, mp->refcount);
::debug (mp->value);
}
}
/* Return the maximum amount of padding that will be inserted before label
LABEL. */
HOST_WIDE_INT
minipool::get_label_padding (rtx_insn *label) const
{
HOST_WIDE_INT align;
align = 1 << label_to_alignment (label).levels[0].log;
return align > m_insn_align ? align - m_insn_align : 0;
}
HOST_WIDE_INT
minipool::get_alignment_size (void) const
{
if (m_subpools.empty ())
return m_insn_align;
return m_subpools.front ()->align () - m_insn_align;
}
/* Find the subpool that needs to hold an element of alignment ALIGN. We need
to ensure that this won't make existing references unreachable. */
std::list<subpool *>::iterator
minipool::find_subpool (unsigned align, Mfix *fix, minipool_address loc,
HOST_WIDE_INT padding)
{
std::list<subpool *>::iterator spi = m_subpools.begin ();
subpool *sp;
/* A new minipool has no existing placement constraints. */
if (spi == m_subpools.end ())
{
sp = XNEW (subpool);
::new (static_cast<void*> (sp)) subpool (align);
m_subpools.push_back (sp);
return m_subpools.begin ();
}
else
{
minipool_address minloc = MINIPOOL_ADDRESS_MIN;
minipool_address maxloc = MINIPOOL_ADDRESS_MAX;
sp = NULL;
std::list<subpool *>::iterator before = m_subpools.end ();
unsigned offset = 0;
for (; spi != m_subpools.end (); ++spi)
{
unsigned subpool_size = (*spi)->size ();
unsigned reverse_offset = m_size - offset - subpool_size;
if ((*spi)->align () > align)
{
/* Subpools before our subpool. Need to check this
doesn't affect backward reachability. */
minloc = std::max (minloc,
(*spi)->backwards () + reverse_offset);
}
else if ((*spi)->align () == align)
{
sp = *spi;
minloc = std::max (minloc,
(*spi)->backwards () + reverse_offset);
maxloc = std::min (maxloc, (*spi)->forwards () - offset);
}
else
{
/* Subpools after our subpool. Need to check this
doesn't affect forward reachability. */
maxloc = std::min (maxloc, (*spi)->forwards () - offset);
if (before == m_subpools.end ())
before = spi;
}
offset += (*spi)->size ();
}
/* If we're going to insert a new subpool with more alignment, we
need to check the additional padding as well. */
if (before == m_subpools.begin ())
padding += align - (*before)->align ();
if (fix->address < loc)
{
/* A forward reference, because we've not yet placed the pool
we don't need to worry about backward references. */
if (maxloc - (fix->fix_size + padding) < loc)
return m_subpools.end ();
}
else
{
/* A backward reference. In addition to checking backward
references, we need to check that inserting FIX into the pool
won't break forward references. */
if (maxloc - (fix->fix_size + padding) < loc
|| minloc + fix->fix_size > loc)
return m_subpools.end ();
}
if (sp)
return std::prev (before);
sp = XNEW (subpool);
::new (static_cast<void*> (sp)) subpool (align);
return m_subpools.insert (before, sp);
}
}
/* Try to insert FIX into the current minipool. LOC is the earliest
address at which we can insert the pool. */
Mnode *
minipool::add_forward_ref (Mfix *fix, minipool_address loc)
{
unsigned align = GET_MODE_ALIGNMENT (fix->mode) / BITS_PER_UNIT;
/* Pool entries must be at least word aligned. */
align = std::max (align, 4u);
/* And at most double-word aligned, if we require double-word
alignment. */
align = std::min (align, ARM_DOUBLEWORD_ALIGN ? 8u: 4u);
/* Check that inserting this value will still leave the pool reachable
for all existing entries. We need to consider the case where
the new insertion will increase the alignment of the overall pool. */
if (loc + std::max (get_alignment_size (),
(HOST_WIDE_INT) align - m_insn_align)
> m_max_address)
return NULL;
std::list<subpool *>::iterator spi
= find_subpool (align, fix, loc, get_alignment_size ());
if (spi == m_subpools.end ())
return NULL;
subpool *sp = *spi;
Mnode *mp = sp->add_forward_ref (fix);
unsigned offset = 0;
for (spi = m_subpools.begin (); spi != m_subpools.end (); ++spi)
{
m_max_address = std::min (m_max_address, (*spi)->forwards () - offset);
offset += (*spi)->size ();
}
m_size = offset;
return mp;
}
/* Try to insert FIX into the current minipool. LOC is the confirmed
address at which the pool will be placed; for backward refs this
will be the end of the minipool. */
Mnode *
minipool::add_backward_ref (Mfix *fix, minipool_address loc)
{
unsigned align = GET_MODE_ALIGNMENT (fix->mode) / BITS_PER_UNIT;
/* Pool entries must be at least word aligned. */
align = std::max (align, 4u);
/* And at most double-word aligned, if we require double-word
alignment. */
align = std::min (align, ARM_DOUBLEWORD_ALIGN ? 8u: 4u);
/* We don't need to take account of pool alignment for backward refs;
the pool is always a multiple of the instruction alignment. */
/* Ensure we haven't moved beyond the addressable limit of the pool. */
if (fix->min_address > loc - m_size - fix->fix_size)
return NULL;
std::list<subpool *>::iterator spi
= find_subpool (align, fix, loc, get_alignment_size ());
if (spi == m_subpools.end ())
return NULL;
subpool *sp = *spi;
unsigned old_size = sp->size ();
Mnode *mp = sp->add_backward_ref (fix);
m_size += sp->size () - old_size;
return mp;
}
void
minipool::update_bounds (void)
{
std::list<subpool *>::iterator spi = m_subpools.begin ();
unsigned size = 0;
for (; spi != m_subpools.end (); ++spi)
{
(*spi)->update_bounds ();
size += (*spi)->size ();
}
m_size = size;
}
void
minipool::assign_offsets (Mfix *barrier)
{
HOST_WIDE_INT offset = 0;
minipool_barrier = barrier;
std::list<subpool *>::iterator spi = m_subpools.begin ();
for (; spi != m_subpools.end (); ++spi)
offset = (*spi)->assign_offsets (offset);
}
/* Output the literal table. */
void
minipool::dump (rtx_insn *scan)
{
unsigned align = 0;
std::list<subpool *>::iterator spi = m_subpools.begin ();
for (; spi != m_subpools.end (); ++spi)
if ((*spi)->size ())
{
align = (*spi)->align ();
break;
}
gcc_assert (align != 0);
if (dump_file)
fprintf (dump_file,
";; Emitting minipool after insn %u; address %ld; "
"align %d (bytes)\n",
INSN_UID (scan), (unsigned long) minipool_barrier->address,
align);
scan = emit_label_after (gen_label_rtx (), scan);
scan = emit_insn_after (align == 8 ? gen_align_8 () : gen_align_4 (),
scan);
scan = emit_label_after (m_vector_label, scan);
for (spi = m_subpools.begin (); spi != m_subpools.end (); ++spi)
{
scan = (*spi)->dump (scan);
(*spi)->~subpool ();
free (*spi);
}
reset ();
scan = emit_insn_after (gen_consttable_end (), scan);
scan = emit_barrier_after (scan);
}
/* Find the best place in the insn stream in the range
(FIX->address,MAX_ADDRESS) to forcibly insert a minipool barrier.
Create the barrier by inserting a jump and add a new fix entry for
it. */
Mfix *
minipool::create_fix_barrier (Mfix *fix, minipool_address max_address)
{
HOST_WIDE_INT count = 0;
rtx_barrier *barrier;
rtx_insn *from = fix->insn;
/* The instruction after which we will insert the jump. */
rtx_insn *selected = from;
int selected_cost;
/* The address at which the jump instruction will be placed. */
HOST_WIDE_INT selected_address;
Mfix * new_fix;
HOST_WIDE_INT max_count = max_address - fix->address;
rtx_code_label *label = gen_label_rtx ();
selected_cost = barrier_cost (from);
selected_address = fix->address + fix->insn_size;
while (from && count < max_count)
{
rtx_jump_table_data *tmp;
int new_cost;
/* This code shouldn't have been called if there was a natural barrier
within range. */
gcc_assert (!BARRIER_P (from));
/* Count the length of this insn. This must stay in sync with the
code that pushes minipool fixes. */
if (LABEL_P (from))
count += get_label_padding (from);
else
count += get_attr_length (from);
/* If there is a jump table, add its length. */
if (tablejump_p (from, NULL, &tmp))
{
count += get_jump_table_size (tmp);
/* Jump tables aren't in a basic block, so base the cost on
the dispatch insn. If we select this location, we will
still put the pool after the table. */
new_cost = barrier_cost (from);
if (count <= max_count && new_cost <= selected_cost)
{
selected = tmp;
selected_cost = new_cost;
selected_address = fix->address + count;
}
/* Continue after the dispatch table. */
from = next_nonnote_nondebug_insn (tmp);
continue;
}
new_cost = barrier_cost (from);
if (count <= max_count && new_cost <= selected_cost)
{
selected = from;
selected_cost = new_cost;
selected_address = fix->address + count;
}
from = next_nonnote_nondebug_insn (from);
}
/* Create a new JUMP_INSN that branches around a barrier. */
from = emit_jump_insn_after (gen_jump (label), selected);
JUMP_LABEL (from) = label;
barrier = emit_barrier_after (from);
emit_label_after (label, barrier);
/* Create a minipool barrier entry for the new barrier. */
new_fix = (Mfix *) obstack_alloc (&minipool_obstack, sizeof (* new_fix));
new_fix->insn = barrier;
new_fix->address = selected_address;
new_fix->next = fix->next;
fix->next = new_fix;
return new_fix;
}
void
minipool::debug (void) const
{
fprintf (stderr, "Minipool @%p\n", (const void *) this);
fprintf (stderr, " insn_alignment: %u\n", m_insn_align);
fprintf (stderr, " vector_label: %p ", (const void *) m_vector_label);
::debug ((rtx) m_vector_label);
std::list<subpool *>::const_iterator spi = m_subpools.begin ();
for (; spi != m_subpools.end (); ++spi)
(*spi)->debug ();
}
/* Record that there is a natural barrier in the insn stream at
ADDRESS. */
static void
push_minipool_barrier (rtx_insn *insn, HOST_WIDE_INT address)
{
Mfix * fix = (Mfix *) obstack_alloc (&minipool_obstack, sizeof (* fix));
fix->insn = insn;
fix->address = address;
fix->next = NULL;
if (minipool_fix_head != NULL)
minipool_fix_tail->next = fix;
else
minipool_fix_head = fix;
minipool_fix_tail = fix;
}
/* Record INSN, which will need fixing up to load a value from the
minipool. ADDRESS is the offset of the insn since the start of the
function; LOC is a pointer to the part of the insn which requires
fixing; VALUE is the constant that must be loaded, which is of type
MODE. */
static void
push_minipool_fix (rtx_insn *insn, HOST_WIDE_INT address, rtx *loc,
machine_mode mode, rtx value)
{
gcc_assert (!arm_disable_literal_pool);
Mfix * fix = (Mfix *) obstack_alloc (&minipool_obstack, sizeof (* fix));
fix->insn = insn;
fix->address = address;
fix->operand_loc = loc;
fix->mode = mode;
fix->fix_size = MINIPOOL_FIX_SIZE (mode);
fix->value = value;
fix->max_address = get_attr_pool_range (insn);
fix->min_address = -get_attr_neg_pool_range (insn);
fix->insn_size = get_attr_length (insn);
fix->minipool = NULL;
/* If an insn doesn't have a range defined for it, then it isn't
expecting to be reworked by this code. Better to stop now than
to generate duff assembly code. */
gcc_assert (fix->max_address || fix->min_address);
fix->max_address += address;
fix->min_address += address;
if (dump_file)
{
fprintf (dump_file,
";; %smode fixup for i%d; addr %lu, range (%ld,%ld): ",
GET_MODE_NAME (mode),
INSN_UID (insn), (unsigned long) address,
(long)fix->min_address, (long)fix->max_address);
print_value (dump_file, fix->value);
fprintf (dump_file, "\n");
}
/* Add it to the chain of fixes. */
fix->next = NULL;
if (minipool_fix_head != NULL)
minipool_fix_tail->next = fix;
else
minipool_fix_head = fix;
minipool_fix_tail = fix;
}
/* Scan INSN and note any of its operands that need fixing.
If DO_PUSHES is false we do not actually push any of the fixups
needed. */
static void
note_invalid_constants (rtx_insn *insn, HOST_WIDE_INT address, int do_pushes)
{
int opno;
extract_constrain_insn (insn);
if (recog_data.n_alternatives == 0)
return;
/* Fill in recog_op_alt with information about the constraints of
this insn. */
preprocess_constraints (insn);
const operand_alternative *op_alt = which_op_alt ();
for (opno = 0; opno < recog_data.n_operands; opno++)
{
/* Things we need to fix can only occur in inputs. */
if (recog_data.operand_type[opno] != OP_IN)
continue;
/* If this alternative is a memory reference, then any mention
of constants in this alternative is really to fool reload
into allowing us to accept one there. We need to fix them up
now so that we output the right code. */
if (op_alt[opno].memory_ok)
{
rtx op = recog_data.operand[opno];
if (CONSTANT_P (op))
{
if (do_pushes)
push_minipool_fix (insn, address,
recog_data.operand_loc[opno],
recog_data.operand_mode[opno], op);
}
else if (MEM_P (op)
&& GET_CODE (XEXP (op, 0)) == SYMBOL_REF
&& CONSTANT_POOL_ADDRESS_P (XEXP (op, 0)))
{
if (do_pushes)
{
rtx cop = avoid_constant_pool_reference (op);
/* Casting the address of something to a mode narrower than a
word can cause avoid_constant_pool_reference to return the
pool reference itself. That's no good to us here. Lets
just hope that we can use the constant pool value
directly. */
if (op == cop)
cop = get_pool_constant (XEXP (op, 0));
push_minipool_fix (insn, address,
recog_data.operand_loc[opno],
recog_data.operand_mode[opno], cop);
}
}
}
}
return;
}
/* GCC normally builds a single literal pool for an entire function,
but PC-relative loads on Arm (and especially thumb1) have very
limited offsets, which means accessing values through a single
pool is expensive if we have to build an address for the pool entry.
To mitigate this we build tables of literals into the body of a function
if it is too large to put them at the end, then place the tables
inside the function body (ideally after unconditional branches). */
void
place_minipools (void)
{
rtx_insn *insn;
HOST_WIDE_INT address = 0;
Mfix *fix;
minipool mpool (TARGET_THUMB ? 2u : 4u);
/* Length of a branch sequence to skip a pool, if we needed to force
a barrier. */
unsigned pool_skip = TARGET_THUMB ? 2u : 4u;
/* Ensure all insns that must be split have been split at this point.
Otherwise, the pool placement code below may compute incorrect
insn lengths. Note that when optimizing, all insns have already
been split at this point. */
if (!optimize)
split_all_insns_noflow ();
minipool_fix_head = minipool_fix_tail = NULL;
/* The first insn must always be a note, or the code below won't
scan it properly. */
insn = get_insns ();
gcc_assert (NOTE_P (insn));
/* Scan all the insns and record the operands that will need fixing. */
for (insn = next_nonnote_nondebug_insn (insn); insn;
insn = next_nonnote_nondebug_insn (insn))
{
if (BARRIER_P (insn))
push_minipool_barrier (insn, address);
else if (INSN_P (insn))
{
rtx_jump_table_data *table;
note_invalid_constants (insn, address, true);
address += get_attr_length (insn);
/* If the insn is a vector jump, add the size of the table
and skip the table. */
if (tablejump_p (insn, NULL, &table))
{
address += get_jump_table_size (table);
insn = table;
}
}
else if (LABEL_P (insn))
/* Add the worst-case padding due to alignment. We don't add
the _current_ padding because the minipool insertions
themselves might change it. */
address += mpool.get_label_padding (insn);
}
fix = minipool_fix_head;
/* Now scan the fixups and perform the required changes. */
while (fix)
{
Mfix *ftmp;
Mfix *fdel;
Mfix *last_added_fix;
Mfix *last_barrier = NULL;
Mfix *this_fix;
/* Skip any further barriers before the next fix. */
while (fix && BARRIER_P (fix->insn))
fix = fix->next;
/* No more fixes. */
if (fix == NULL)
break;
last_added_fix = NULL;
for (ftmp = fix; ftmp; ftmp = ftmp->next)
{
if (BARRIER_P (ftmp->insn))
{
if (ftmp->address + mpool.get_alignment_size ()
> mpool.forwards ())
break;
last_barrier = ftmp;
}
else
{
minipool_address mp_addr
= ftmp->address + ftmp->insn_size + pool_skip;
if ((ftmp->minipool = mpool.add_forward_ref (ftmp, mp_addr))
== NULL)
break;
}
last_added_fix = ftmp; /* Keep track of the last fix added. */
}
/* If we found a barrier, drop back to that; any fixes that we could have
reached but come after the barrier will now go in the next mini-pool
(if they are not re-added as backward references). */
if (last_barrier != NULL)
{
/* Reduce the refcount for those fixes that won't go into this pool
after all. Note that if the insn supports backward references we
will probably end up re-adding this value to the same pool, so
it's not worth the effort of unlinking it from the chain. */
for (fdel = last_barrier->next;
fdel && fdel != ftmp;
fdel = fdel->next)
{
fdel->minipool->refcount--;
/* If there are no more forward references to the entry, then the
forward address is no longer constrained. */
if (fdel->minipool->refcount == 0)
fdel->minipool->max_address = MINIPOOL_ADDRESS_MAX;
fdel->minipool = NULL;
}
mpool.update_bounds ();
ftmp = last_barrier;
}
else
{
/* ftmp is first fix that we can't fit into this pool and
there no natural barriers that we could use. Insert a
new barrier in the code somewhere between the previous
fix and this one, and arrange to jump around it. */
minipool_address max_address;
/* The last item on the list of fixes must be a barrier, so
we can never run off the end of the list of fixes without
last_barrier being set. */
gcc_assert (ftmp);
max_address = (mpool.forwards ()
- (mpool.get_alignment_size () + pool_skip));
gcc_assert (max_address
>= last_added_fix->address + last_added_fix->insn_size);
/* Ensure the pool comes before ftmp. */
if (ftmp->address < max_address)
max_address = ftmp->address;
last_barrier = mpool.create_fix_barrier (last_added_fix,
max_address);
}
while (ftmp)
{
if (!BARRIER_P (ftmp->insn)
&& ((ftmp->minipool
= mpool.add_backward_ref (ftmp, last_barrier->address))
== NULL))
break;
ftmp = ftmp->next;
}
mpool.assign_offsets (last_barrier);
/* Scan over the fixes we have identified for this pool, fixing them
up and adding the constants to the pool itself. */
for (this_fix = fix; this_fix && ftmp != this_fix;
this_fix = this_fix->next)
if (!BARRIER_P (this_fix->insn))
{
rtx addr
= plus_constant (Pmode,
gen_rtx_LABEL_REF (VOIDmode,
mpool.label ()),
this_fix->minipool->offset);
*this_fix->operand_loc = gen_rtx_MEM (this_fix->mode, addr);
}
mpool.dump (last_barrier->insn);
fix = ftmp;
}
/* From now on we must synthesize any constants that we can't handle
directly. This can happen if the RTL gets split during final
instruction generation. */
cfun->machine->after_arm_reorg = 1;
/* Free the minipool memory. */
obstack_free (&minipool_obstack, minipool_startobj);
}
} // namespace minipools