1753 lines
51 KiB
C
1753 lines
51 KiB
C
/* Discovery of auto-inc and auto-dec instructions.
|
|
Copyright (C) 2006-2021 Free Software Foundation, Inc.
|
|
Contributed by Kenneth Zadeck <zadeck@naturalbridge.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/>. */
|
|
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include "coretypes.h"
|
|
#include "backend.h"
|
|
#include "target.h"
|
|
#include "rtl.h"
|
|
#include "tree.h"
|
|
#include "predict.h"
|
|
#include "df.h"
|
|
#include "insn-config.h"
|
|
#include "memmodel.h"
|
|
#include "emit-rtl.h"
|
|
#include "recog.h"
|
|
#include "cfgrtl.h"
|
|
#include "expr.h"
|
|
#include "tree-pass.h"
|
|
#include "dbgcnt.h"
|
|
#include "print-rtl.h"
|
|
#include "valtrack.h"
|
|
|
|
/* This pass was originally removed from flow.c. However there is
|
|
almost nothing that remains of that code.
|
|
|
|
There are (4) basic forms that are matched:
|
|
|
|
(1) FORM_PRE_ADD
|
|
a <- b + c
|
|
...
|
|
*a
|
|
|
|
becomes
|
|
|
|
a <- b
|
|
...
|
|
*(a += c) pre
|
|
|
|
or, alternately,
|
|
|
|
a <- b + c
|
|
...
|
|
*b
|
|
|
|
becomes
|
|
|
|
a <- b
|
|
...
|
|
*(a += c) post
|
|
|
|
This uses a post-add, but it's handled as FORM_PRE_ADD because
|
|
the "increment" insn appears before the memory access.
|
|
|
|
|
|
(2) FORM_PRE_INC
|
|
a += c
|
|
...
|
|
*a
|
|
|
|
becomes
|
|
|
|
...
|
|
*(a += c) pre
|
|
|
|
|
|
(3) FORM_POST_ADD
|
|
*a
|
|
...
|
|
b <- a + c
|
|
|
|
(For this case to be true, b must not be assigned or used between
|
|
the *a and the assignment to b. B must also be a Pmode reg.)
|
|
|
|
becomes
|
|
|
|
b <- a
|
|
*(b += c) post
|
|
...
|
|
|
|
|
|
(4) FORM_POST_INC
|
|
*a
|
|
...
|
|
a <- a + c
|
|
|
|
becomes
|
|
|
|
*(a += c) post
|
|
...
|
|
|
|
|
|
There are three types of values of c.
|
|
|
|
1) c is a constant equal to the width of the value being accessed by
|
|
the pointer. This is useful for machines that have
|
|
HAVE_PRE_INCREMENT, HAVE_POST_INCREMENT, HAVE_PRE_DECREMENT or
|
|
HAVE_POST_DECREMENT defined.
|
|
|
|
2) c is a constant not equal to the width of the value being accessed
|
|
by the pointer. This is useful for machines that have
|
|
HAVE_PRE_MODIFY_DISP, HAVE_POST_MODIFY_DISP defined.
|
|
|
|
3) c is a register. This is useful for machines that have
|
|
HAVE_PRE_MODIFY_REG, HAVE_POST_MODIFY_REG
|
|
|
|
The is one special case: if a already had an offset equal to it +-
|
|
its width and that offset is equal to -c when the increment was
|
|
before the ref or +c if the increment was after the ref, then if we
|
|
can do the combination but switch the pre/post bit. */
|
|
|
|
|
|
enum form
|
|
{
|
|
FORM_PRE_ADD,
|
|
FORM_PRE_INC,
|
|
FORM_POST_ADD,
|
|
FORM_POST_INC,
|
|
FORM_last
|
|
};
|
|
|
|
/* The states of the second operands of mem refs and inc insns. If no
|
|
second operand of the mem_ref was found, it is assumed to just be
|
|
ZERO. SIZE is the size of the mode accessed in the memref. The
|
|
ANY is used for constants that are not +-size or 0. REG is used if
|
|
the forms are reg1 + reg2. */
|
|
|
|
enum inc_state
|
|
{
|
|
INC_ZERO, /* == 0 */
|
|
INC_NEG_SIZE, /* == +size */
|
|
INC_POS_SIZE, /* == -size */
|
|
INC_NEG_ANY, /* == some -constant */
|
|
INC_POS_ANY, /* == some +constant */
|
|
INC_REG, /* == some register */
|
|
INC_last
|
|
};
|
|
|
|
/* The eight forms that pre/post inc/dec can take. */
|
|
enum gen_form
|
|
{
|
|
NOTHING,
|
|
SIMPLE_PRE_INC, /* ++size */
|
|
SIMPLE_POST_INC, /* size++ */
|
|
SIMPLE_PRE_DEC, /* --size */
|
|
SIMPLE_POST_DEC, /* size-- */
|
|
DISP_PRE, /* ++con */
|
|
DISP_POST, /* con++ */
|
|
REG_PRE, /* ++reg */
|
|
REG_POST /* reg++ */
|
|
};
|
|
|
|
/* Tmp mem rtx for use in cost modeling. */
|
|
static rtx mem_tmp;
|
|
|
|
static enum inc_state
|
|
set_inc_state (HOST_WIDE_INT val, poly_int64 size)
|
|
{
|
|
if (val == 0)
|
|
return INC_ZERO;
|
|
if (val < 0)
|
|
return known_eq (val, -size) ? INC_NEG_SIZE : INC_NEG_ANY;
|
|
else
|
|
return known_eq (val, size) ? INC_POS_SIZE : INC_POS_ANY;
|
|
}
|
|
|
|
/* The DECISION_TABLE that describes what form, if any, the increment
|
|
or decrement will take. It is a three dimensional table. The first
|
|
index is the type of constant or register found as the second
|
|
operand of the inc insn. The second index is the type of constant
|
|
or register found as the second operand of the memory reference (if
|
|
no second operand exists, 0 is used). The third index is the form
|
|
and location (relative to the mem reference) of inc insn. */
|
|
|
|
static bool initialized = false;
|
|
static enum gen_form decision_table[INC_last][INC_last][FORM_last];
|
|
|
|
static void
|
|
init_decision_table (void)
|
|
{
|
|
enum gen_form value;
|
|
|
|
if (HAVE_PRE_INCREMENT || HAVE_PRE_MODIFY_DISP)
|
|
{
|
|
/* Prefer the simple form if both are available. */
|
|
value = (HAVE_PRE_INCREMENT) ? SIMPLE_PRE_INC : DISP_PRE;
|
|
|
|
decision_table[INC_POS_SIZE][INC_ZERO][FORM_PRE_ADD] = value;
|
|
decision_table[INC_POS_SIZE][INC_ZERO][FORM_PRE_INC] = value;
|
|
|
|
decision_table[INC_POS_SIZE][INC_POS_SIZE][FORM_POST_ADD] = value;
|
|
decision_table[INC_POS_SIZE][INC_POS_SIZE][FORM_POST_INC] = value;
|
|
}
|
|
|
|
if (HAVE_POST_INCREMENT || HAVE_POST_MODIFY_DISP)
|
|
{
|
|
/* Prefer the simple form if both are available. */
|
|
value = (HAVE_POST_INCREMENT) ? SIMPLE_POST_INC : DISP_POST;
|
|
|
|
decision_table[INC_POS_SIZE][INC_ZERO][FORM_POST_ADD] = value;
|
|
decision_table[INC_POS_SIZE][INC_ZERO][FORM_POST_INC] = value;
|
|
|
|
decision_table[INC_POS_SIZE][INC_NEG_SIZE][FORM_PRE_ADD] = value;
|
|
decision_table[INC_POS_SIZE][INC_NEG_SIZE][FORM_PRE_INC] = value;
|
|
}
|
|
|
|
if (HAVE_PRE_DECREMENT || HAVE_PRE_MODIFY_DISP)
|
|
{
|
|
/* Prefer the simple form if both are available. */
|
|
value = (HAVE_PRE_DECREMENT) ? SIMPLE_PRE_DEC : DISP_PRE;
|
|
|
|
decision_table[INC_NEG_SIZE][INC_ZERO][FORM_PRE_ADD] = value;
|
|
decision_table[INC_NEG_SIZE][INC_ZERO][FORM_PRE_INC] = value;
|
|
|
|
decision_table[INC_NEG_SIZE][INC_NEG_SIZE][FORM_POST_ADD] = value;
|
|
decision_table[INC_NEG_SIZE][INC_NEG_SIZE][FORM_POST_INC] = value;
|
|
}
|
|
|
|
if (HAVE_POST_DECREMENT || HAVE_POST_MODIFY_DISP)
|
|
{
|
|
/* Prefer the simple form if both are available. */
|
|
value = (HAVE_POST_DECREMENT) ? SIMPLE_POST_DEC : DISP_POST;
|
|
|
|
decision_table[INC_NEG_SIZE][INC_ZERO][FORM_POST_ADD] = value;
|
|
decision_table[INC_NEG_SIZE][INC_ZERO][FORM_POST_INC] = value;
|
|
|
|
decision_table[INC_NEG_SIZE][INC_POS_SIZE][FORM_PRE_ADD] = value;
|
|
decision_table[INC_NEG_SIZE][INC_POS_SIZE][FORM_PRE_INC] = value;
|
|
}
|
|
|
|
if (HAVE_PRE_MODIFY_DISP)
|
|
{
|
|
decision_table[INC_POS_ANY][INC_ZERO][FORM_PRE_ADD] = DISP_PRE;
|
|
decision_table[INC_POS_ANY][INC_ZERO][FORM_PRE_INC] = DISP_PRE;
|
|
|
|
decision_table[INC_POS_ANY][INC_POS_ANY][FORM_POST_ADD] = DISP_PRE;
|
|
decision_table[INC_POS_ANY][INC_POS_ANY][FORM_POST_INC] = DISP_PRE;
|
|
|
|
decision_table[INC_NEG_ANY][INC_ZERO][FORM_PRE_ADD] = DISP_PRE;
|
|
decision_table[INC_NEG_ANY][INC_ZERO][FORM_PRE_INC] = DISP_PRE;
|
|
|
|
decision_table[INC_NEG_ANY][INC_NEG_ANY][FORM_POST_ADD] = DISP_PRE;
|
|
decision_table[INC_NEG_ANY][INC_NEG_ANY][FORM_POST_INC] = DISP_PRE;
|
|
}
|
|
|
|
if (HAVE_POST_MODIFY_DISP)
|
|
{
|
|
decision_table[INC_POS_ANY][INC_ZERO][FORM_POST_ADD] = DISP_POST;
|
|
decision_table[INC_POS_ANY][INC_ZERO][FORM_POST_INC] = DISP_POST;
|
|
|
|
decision_table[INC_POS_ANY][INC_NEG_ANY][FORM_PRE_ADD] = DISP_POST;
|
|
decision_table[INC_POS_ANY][INC_NEG_ANY][FORM_PRE_INC] = DISP_POST;
|
|
|
|
decision_table[INC_NEG_ANY][INC_ZERO][FORM_POST_ADD] = DISP_POST;
|
|
decision_table[INC_NEG_ANY][INC_ZERO][FORM_POST_INC] = DISP_POST;
|
|
|
|
decision_table[INC_NEG_ANY][INC_POS_ANY][FORM_PRE_ADD] = DISP_POST;
|
|
decision_table[INC_NEG_ANY][INC_POS_ANY][FORM_PRE_INC] = DISP_POST;
|
|
}
|
|
|
|
/* This is much simpler than the other cases because we do not look
|
|
for the reg1-reg2 case. Note that we do not have a INC_POS_REG
|
|
and INC_NEG_REG states. Most of the use of such states would be
|
|
on a target that had an R1 - R2 update address form.
|
|
|
|
There is the remote possibility that you could also catch a = a +
|
|
b; *(a - b) as a postdecrement of (a + b). However, it is
|
|
unclear if *(a - b) would ever be generated on a machine that did
|
|
not have that kind of addressing mode. The IA-64 and RS6000 will
|
|
not do this, and I cannot speak for any other. If any
|
|
architecture does have an a-b update for, these cases should be
|
|
added. */
|
|
if (HAVE_PRE_MODIFY_REG)
|
|
{
|
|
decision_table[INC_REG][INC_ZERO][FORM_PRE_ADD] = REG_PRE;
|
|
decision_table[INC_REG][INC_ZERO][FORM_PRE_INC] = REG_PRE;
|
|
|
|
decision_table[INC_REG][INC_REG][FORM_POST_ADD] = REG_PRE;
|
|
decision_table[INC_REG][INC_REG][FORM_POST_INC] = REG_PRE;
|
|
}
|
|
|
|
if (HAVE_POST_MODIFY_REG)
|
|
{
|
|
decision_table[INC_REG][INC_ZERO][FORM_POST_ADD] = REG_POST;
|
|
decision_table[INC_REG][INC_ZERO][FORM_POST_INC] = REG_POST;
|
|
}
|
|
|
|
initialized = true;
|
|
}
|
|
|
|
/* Parsed fields of an inc insn of the form "reg_res = reg0+reg1" or
|
|
"reg_res = reg0+c". */
|
|
|
|
static struct inc_insn
|
|
{
|
|
rtx_insn *insn; /* The insn being parsed. */
|
|
rtx pat; /* The pattern of the insn. */
|
|
bool reg1_is_const; /* True if reg1 is const, false if reg1 is a reg. */
|
|
enum form form;
|
|
rtx reg_res;
|
|
rtx reg0;
|
|
rtx reg1;
|
|
enum inc_state reg1_state;/* The form of the const if reg1 is a const. */
|
|
HOST_WIDE_INT reg1_val;/* Value if reg1 is const. */
|
|
} inc_insn;
|
|
|
|
|
|
/* Dump the parsed inc insn to FILE. */
|
|
|
|
static void
|
|
dump_inc_insn (FILE *file)
|
|
{
|
|
const char *f = ((inc_insn.form == FORM_PRE_ADD)
|
|
|| (inc_insn.form == FORM_PRE_INC)) ? "pre" : "post";
|
|
|
|
dump_insn_slim (file, inc_insn.insn);
|
|
|
|
switch (inc_insn.form)
|
|
{
|
|
case FORM_PRE_ADD:
|
|
case FORM_POST_ADD:
|
|
if (inc_insn.reg1_is_const)
|
|
fprintf (file, "found %s add(%d) r[%d]=r[%d]+%d\n",
|
|
f, INSN_UID (inc_insn.insn),
|
|
REGNO (inc_insn.reg_res),
|
|
REGNO (inc_insn.reg0), (int) inc_insn.reg1_val);
|
|
else
|
|
fprintf (file, "found %s add(%d) r[%d]=r[%d]+r[%d]\n",
|
|
f, INSN_UID (inc_insn.insn),
|
|
REGNO (inc_insn.reg_res),
|
|
REGNO (inc_insn.reg0), REGNO (inc_insn.reg1));
|
|
break;
|
|
|
|
case FORM_PRE_INC:
|
|
case FORM_POST_INC:
|
|
if (inc_insn.reg1_is_const)
|
|
fprintf (file, "found %s inc(%d) r[%d]+=%d\n",
|
|
f, INSN_UID (inc_insn.insn),
|
|
REGNO (inc_insn.reg_res), (int) inc_insn.reg1_val);
|
|
else
|
|
fprintf (file, "found %s inc(%d) r[%d]+=r[%d]\n",
|
|
f, INSN_UID (inc_insn.insn),
|
|
REGNO (inc_insn.reg_res), REGNO (inc_insn.reg1));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* Parsed fields of a mem ref of the form "*(reg0+reg1)" or "*(reg0+c)". */
|
|
|
|
static struct mem_insn
|
|
{
|
|
rtx_insn *insn; /* The insn being parsed. */
|
|
rtx pat; /* The pattern of the insn. */
|
|
rtx *mem_loc; /* The address of the field that holds the mem */
|
|
/* that is to be replaced. */
|
|
bool reg1_is_const; /* True if reg1 is const, false if reg1 is a reg. */
|
|
rtx reg0;
|
|
rtx reg1; /* This is either a reg or a const depending on
|
|
reg1_is_const. */
|
|
enum inc_state reg1_state;/* The form of the const if reg1 is a const. */
|
|
HOST_WIDE_INT reg1_val;/* Value if reg1 is const. */
|
|
} mem_insn;
|
|
|
|
|
|
/* Dump the parsed mem insn to FILE. */
|
|
|
|
static void
|
|
dump_mem_insn (FILE *file)
|
|
{
|
|
dump_insn_slim (file, mem_insn.insn);
|
|
|
|
if (mem_insn.reg1_is_const)
|
|
fprintf (file, "found mem(%d) *(r[%d]+%d)\n",
|
|
INSN_UID (mem_insn.insn),
|
|
REGNO (mem_insn.reg0), (int) mem_insn.reg1_val);
|
|
else
|
|
fprintf (file, "found mem(%d) *(r[%d]+r[%d])\n",
|
|
INSN_UID (mem_insn.insn),
|
|
REGNO (mem_insn.reg0), REGNO (mem_insn.reg1));
|
|
}
|
|
|
|
|
|
/* The following three arrays contain pointers to instructions. They
|
|
are indexed by REGNO. At any point in the basic block where we are
|
|
looking these three arrays contain, respectively, the next insn
|
|
that uses REGNO, the next inc or add insn that uses REGNO and the
|
|
next insn that sets REGNO.
|
|
|
|
The arrays are not cleared when we move from block to block so
|
|
whenever an insn is retrieved from these arrays, it's block number
|
|
must be compared with the current block.
|
|
*/
|
|
|
|
static rtx_insn **reg_next_debug_use = NULL;
|
|
static rtx_insn **reg_next_use = NULL;
|
|
static rtx_insn **reg_next_inc_use = NULL;
|
|
static rtx_insn **reg_next_def = NULL;
|
|
|
|
|
|
/* Move dead note that match PATTERN to TO_INSN from FROM_INSN. We do
|
|
not really care about moving any other notes from the inc or add
|
|
insn. Moving the REG_EQUAL and REG_EQUIV is clearly wrong and it
|
|
does not appear that there are any other kinds of relevant notes. */
|
|
|
|
static void
|
|
move_dead_notes (rtx_insn *to_insn, rtx_insn *from_insn, rtx pattern)
|
|
{
|
|
rtx note;
|
|
rtx next_note;
|
|
rtx prev_note = NULL;
|
|
|
|
for (note = REG_NOTES (from_insn); note; note = next_note)
|
|
{
|
|
next_note = XEXP (note, 1);
|
|
|
|
if ((REG_NOTE_KIND (note) == REG_DEAD)
|
|
&& pattern == XEXP (note, 0))
|
|
{
|
|
XEXP (note, 1) = REG_NOTES (to_insn);
|
|
REG_NOTES (to_insn) = note;
|
|
if (prev_note)
|
|
XEXP (prev_note, 1) = next_note;
|
|
else
|
|
REG_NOTES (from_insn) = next_note;
|
|
}
|
|
else prev_note = note;
|
|
}
|
|
}
|
|
|
|
/* Change mem_insn.mem_loc so that uses NEW_ADDR which has an
|
|
increment of INC_REG. To have reached this point, the change is a
|
|
legitimate one from a dataflow point of view. The only questions
|
|
are is this a valid change to the instruction and is this a
|
|
profitable change to the instruction. */
|
|
|
|
static bool
|
|
attempt_change (rtx new_addr, rtx inc_reg)
|
|
{
|
|
/* There are four cases: For the two cases that involve an add
|
|
instruction, we are going to have to delete the add and insert a
|
|
mov. We are going to assume that the mov is free. This is
|
|
fairly early in the backend and there are a lot of opportunities
|
|
for removing that move later. In particular, there is the case
|
|
where the move may be dead, this is what dead code elimination
|
|
passes are for. The two cases where we have an inc insn will be
|
|
handled mov free. */
|
|
|
|
basic_block bb = BLOCK_FOR_INSN (mem_insn.insn);
|
|
rtx_insn *mov_insn = NULL;
|
|
int regno;
|
|
rtx mem = *mem_insn.mem_loc;
|
|
machine_mode mode = GET_MODE (mem);
|
|
int align = MEM_ALIGN (mem);
|
|
rtx new_mem;
|
|
int old_cost = 0;
|
|
int new_cost = 0;
|
|
bool speed = optimize_bb_for_speed_p (bb);
|
|
|
|
PUT_MODE (mem_tmp, mode);
|
|
XEXP (mem_tmp, 0) = new_addr;
|
|
set_mem_align (mem_tmp, align);
|
|
|
|
old_cost = (set_src_cost (mem, mode, speed)
|
|
+ set_rtx_cost (PATTERN (inc_insn.insn), speed));
|
|
|
|
new_cost = set_src_cost (mem_tmp, mode, speed);
|
|
|
|
/* In the FORM_PRE_ADD and FORM_POST_ADD cases we emit an extra move
|
|
whose cost we should account for. */
|
|
if (inc_insn.form == FORM_PRE_ADD
|
|
|| inc_insn.form == FORM_POST_ADD)
|
|
{
|
|
start_sequence ();
|
|
emit_move_insn (inc_insn.reg_res, inc_insn.reg0);
|
|
mov_insn = get_insns ();
|
|
end_sequence ();
|
|
new_cost += seq_cost (mov_insn, speed);
|
|
}
|
|
|
|
/* The first item of business is to see if this is profitable. */
|
|
if (old_cost < new_cost)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "cost failure old=%d new=%d\n", old_cost, new_cost);
|
|
return false;
|
|
}
|
|
|
|
/* Jump through a lot of hoops to keep the attributes up to date. We
|
|
do not want to call one of the change address variants that take
|
|
an offset even though we know the offset in many cases. These
|
|
assume you are changing where the address is pointing by the
|
|
offset. */
|
|
new_mem = replace_equiv_address_nv (mem, new_addr);
|
|
if (! validate_change (mem_insn.insn, mem_insn.mem_loc, new_mem, 0))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "validation failure\n");
|
|
return false;
|
|
}
|
|
|
|
/* From here to the end of the function we are committed to the
|
|
change, i.e. nothing fails. Generate any necessary movs, move
|
|
any regnotes, and fix up the reg_next_{use,inc_use,def}. */
|
|
switch (inc_insn.form)
|
|
{
|
|
case FORM_PRE_ADD:
|
|
/* Replace the addition with a move. Do it at the location of
|
|
the addition since the operand of the addition may change
|
|
before the memory reference. */
|
|
gcc_assert (mov_insn);
|
|
emit_insn_before (mov_insn, inc_insn.insn);
|
|
regno = REGNO (inc_insn.reg0);
|
|
/* ??? Could REGNO possibly be used in MEM_INSN other than in
|
|
the MEM address, and still die there, so that move_dead_notes
|
|
would incorrectly move the note? */
|
|
if (reg_next_use[regno] == mem_insn.insn)
|
|
move_dead_notes (mov_insn, mem_insn.insn, inc_insn.reg0);
|
|
else
|
|
move_dead_notes (mov_insn, inc_insn.insn, inc_insn.reg0);
|
|
|
|
regno = REGNO (inc_insn.reg_res);
|
|
if (reg_next_debug_use && reg_next_debug_use[regno]
|
|
&& BLOCK_FOR_INSN (reg_next_debug_use[regno]) == bb)
|
|
{
|
|
rtx adjres = gen_rtx_PLUS (GET_MODE (inc_insn.reg_res),
|
|
inc_insn.reg_res, inc_insn.reg1);
|
|
if (dump_file)
|
|
fprintf (dump_file, "adjusting debug insns\n");
|
|
propagate_for_debug (PREV_INSN (reg_next_debug_use[regno]),
|
|
mem_insn.insn,
|
|
inc_insn.reg_res, adjres, bb);
|
|
reg_next_debug_use[regno] = NULL;
|
|
}
|
|
reg_next_def[regno] = mov_insn;
|
|
reg_next_use[regno] = NULL;
|
|
|
|
regno = REGNO (inc_insn.reg0);
|
|
if (reg_next_debug_use && reg_next_debug_use[regno]
|
|
&& BLOCK_FOR_INSN (reg_next_debug_use[regno]) == bb
|
|
&& find_reg_note (mov_insn, REG_DEAD, inc_insn.reg0))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "remapping debug insns\n");
|
|
propagate_for_debug (PREV_INSN (reg_next_debug_use[regno]),
|
|
mem_insn.insn,
|
|
inc_insn.reg0, inc_insn.reg_res, bb);
|
|
reg_next_debug_use[regno] = NULL;
|
|
}
|
|
reg_next_use[regno] = mov_insn;
|
|
df_recompute_luids (bb);
|
|
break;
|
|
|
|
case FORM_POST_INC:
|
|
regno = REGNO (inc_insn.reg_res);
|
|
if (reg_next_debug_use && reg_next_debug_use[regno]
|
|
&& BLOCK_FOR_INSN (reg_next_debug_use[regno]) == bb)
|
|
{
|
|
rtx adjres = gen_rtx_MINUS (GET_MODE (inc_insn.reg_res),
|
|
inc_insn.reg_res, inc_insn.reg1);
|
|
if (dump_file)
|
|
fprintf (dump_file, "adjusting debug insns\n");
|
|
propagate_for_debug (PREV_INSN (reg_next_debug_use[regno]),
|
|
inc_insn.insn,
|
|
inc_insn.reg_res, adjres, bb);
|
|
reg_next_debug_use[regno] = NULL;
|
|
}
|
|
if (reg_next_use[regno] == reg_next_inc_use[regno])
|
|
reg_next_inc_use[regno] = NULL;
|
|
|
|
/* Fallthru. */
|
|
case FORM_PRE_INC:
|
|
regno = REGNO (inc_insn.reg_res);
|
|
/* Despite the fall-through, we won't run this twice: we'll have
|
|
already cleared reg_next_debug_use[regno] before falling
|
|
through. */
|
|
if (reg_next_debug_use && reg_next_debug_use[regno]
|
|
&& BLOCK_FOR_INSN (reg_next_debug_use[regno]) == bb)
|
|
{
|
|
rtx adjres = gen_rtx_PLUS (GET_MODE (inc_insn.reg_res),
|
|
inc_insn.reg_res, inc_insn.reg1);
|
|
if (dump_file)
|
|
fprintf (dump_file, "adjusting debug insns\n");
|
|
propagate_for_debug (PREV_INSN (reg_next_debug_use[regno]),
|
|
mem_insn.insn,
|
|
inc_insn.reg_res, adjres, bb);
|
|
if (DF_INSN_LUID (mem_insn.insn)
|
|
< DF_INSN_LUID (reg_next_debug_use[regno]))
|
|
reg_next_debug_use[regno] = NULL;
|
|
}
|
|
reg_next_def[regno] = mem_insn.insn;
|
|
reg_next_use[regno] = NULL;
|
|
|
|
break;
|
|
|
|
case FORM_POST_ADD:
|
|
gcc_assert (mov_insn);
|
|
emit_insn_before (mov_insn, mem_insn.insn);
|
|
move_dead_notes (mov_insn, inc_insn.insn, inc_insn.reg0);
|
|
|
|
/* Do not move anything to the mov insn because the instruction
|
|
pointer for the main iteration has not yet hit that. It is
|
|
still pointing to the mem insn. */
|
|
regno = REGNO (inc_insn.reg_res);
|
|
/* The pseudo is now set earlier, so it must have been dead in
|
|
that range, and dead registers cannot be referenced in debug
|
|
insns. */
|
|
gcc_assert (!(reg_next_debug_use && reg_next_debug_use[regno]
|
|
&& BLOCK_FOR_INSN (reg_next_debug_use[regno]) == bb));
|
|
reg_next_def[regno] = mem_insn.insn;
|
|
reg_next_use[regno] = NULL;
|
|
|
|
regno = REGNO (inc_insn.reg0);
|
|
if (reg_next_debug_use && reg_next_debug_use[regno]
|
|
&& BLOCK_FOR_INSN (reg_next_debug_use[regno]) == bb
|
|
&& find_reg_note (mov_insn, REG_DEAD, inc_insn.reg0))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "remapping debug insns\n");
|
|
propagate_for_debug (PREV_INSN (reg_next_debug_use[regno]),
|
|
inc_insn.insn,
|
|
inc_insn.reg0, inc_insn.reg_res, bb);
|
|
reg_next_debug_use[regno] = NULL;
|
|
}
|
|
reg_next_use[regno] = mem_insn.insn;
|
|
if ((reg_next_use[regno] == reg_next_inc_use[regno])
|
|
|| (reg_next_inc_use[regno] == inc_insn.insn))
|
|
reg_next_inc_use[regno] = NULL;
|
|
df_recompute_luids (bb);
|
|
break;
|
|
|
|
case FORM_last:
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
|
|
if (!inc_insn.reg1_is_const)
|
|
{
|
|
regno = REGNO (inc_insn.reg1);
|
|
reg_next_use[regno] = mem_insn.insn;
|
|
if ((reg_next_use[regno] == reg_next_inc_use[regno])
|
|
|| (reg_next_inc_use[regno] == inc_insn.insn))
|
|
reg_next_inc_use[regno] = NULL;
|
|
}
|
|
|
|
delete_insn (inc_insn.insn);
|
|
|
|
if (dump_file && mov_insn)
|
|
{
|
|
fprintf (dump_file, "inserting mov ");
|
|
dump_insn_slim (dump_file, mov_insn);
|
|
}
|
|
|
|
/* Record that this insn has an implicit side effect. */
|
|
add_reg_note (mem_insn.insn, REG_INC, inc_reg);
|
|
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "****success ");
|
|
dump_insn_slim (dump_file, mem_insn.insn);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/* Try to combine the instruction in INC_INSN with the instruction in
|
|
MEM_INSN. First the form is determined using the DECISION_TABLE
|
|
and the results of parsing the INC_INSN and the MEM_INSN.
|
|
Assuming the form is ok, a prototype new address is built which is
|
|
passed to ATTEMPT_CHANGE for final processing. */
|
|
|
|
static bool
|
|
try_merge (void)
|
|
{
|
|
enum gen_form gen_form;
|
|
rtx mem = *mem_insn.mem_loc;
|
|
rtx inc_reg = inc_insn.form == FORM_POST_ADD ?
|
|
inc_insn.reg_res : mem_insn.reg0;
|
|
|
|
/* The width of the mem being accessed. */
|
|
poly_int64 size = GET_MODE_SIZE (GET_MODE (mem));
|
|
rtx_insn *last_insn = NULL;
|
|
machine_mode reg_mode = GET_MODE (inc_reg);
|
|
|
|
switch (inc_insn.form)
|
|
{
|
|
case FORM_PRE_ADD:
|
|
case FORM_PRE_INC:
|
|
last_insn = mem_insn.insn;
|
|
break;
|
|
case FORM_POST_INC:
|
|
case FORM_POST_ADD:
|
|
last_insn = inc_insn.insn;
|
|
break;
|
|
case FORM_last:
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
|
|
/* Cannot handle auto inc of the stack. */
|
|
if (inc_reg == stack_pointer_rtx)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "cannot inc stack %d failure\n", REGNO (inc_reg));
|
|
return false;
|
|
}
|
|
|
|
/* Look to see if the inc register is dead after the memory
|
|
reference. If it is, do not do the combination. */
|
|
if (find_regno_note (last_insn, REG_DEAD, REGNO (inc_reg)))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "dead failure %d\n", REGNO (inc_reg));
|
|
return false;
|
|
}
|
|
|
|
mem_insn.reg1_state = (mem_insn.reg1_is_const)
|
|
? set_inc_state (mem_insn.reg1_val, size) : INC_REG;
|
|
inc_insn.reg1_state = (inc_insn.reg1_is_const)
|
|
? set_inc_state (inc_insn.reg1_val, size) : INC_REG;
|
|
|
|
/* Now get the form that we are generating. */
|
|
gen_form = decision_table
|
|
[inc_insn.reg1_state][mem_insn.reg1_state][inc_insn.form];
|
|
|
|
if (dbg_cnt (auto_inc_dec) == false)
|
|
return false;
|
|
|
|
switch (gen_form)
|
|
{
|
|
default:
|
|
case NOTHING:
|
|
return false;
|
|
|
|
case SIMPLE_PRE_INC: /* ++size */
|
|
if (dump_file)
|
|
fprintf (dump_file, "trying SIMPLE_PRE_INC\n");
|
|
return attempt_change (gen_rtx_PRE_INC (reg_mode, inc_reg), inc_reg);
|
|
|
|
case SIMPLE_POST_INC: /* size++ */
|
|
if (dump_file)
|
|
fprintf (dump_file, "trying SIMPLE_POST_INC\n");
|
|
return attempt_change (gen_rtx_POST_INC (reg_mode, inc_reg), inc_reg);
|
|
|
|
case SIMPLE_PRE_DEC: /* --size */
|
|
if (dump_file)
|
|
fprintf (dump_file, "trying SIMPLE_PRE_DEC\n");
|
|
return attempt_change (gen_rtx_PRE_DEC (reg_mode, inc_reg), inc_reg);
|
|
|
|
case SIMPLE_POST_DEC: /* size-- */
|
|
if (dump_file)
|
|
fprintf (dump_file, "trying SIMPLE_POST_DEC\n");
|
|
return attempt_change (gen_rtx_POST_DEC (reg_mode, inc_reg), inc_reg);
|
|
|
|
case DISP_PRE: /* ++con */
|
|
if (dump_file)
|
|
fprintf (dump_file, "trying DISP_PRE\n");
|
|
return attempt_change (gen_rtx_PRE_MODIFY (reg_mode,
|
|
inc_reg,
|
|
gen_rtx_PLUS (reg_mode,
|
|
inc_reg,
|
|
inc_insn.reg1)),
|
|
inc_reg);
|
|
|
|
case DISP_POST: /* con++ */
|
|
if (dump_file)
|
|
fprintf (dump_file, "trying POST_DISP\n");
|
|
return attempt_change (gen_rtx_POST_MODIFY (reg_mode,
|
|
inc_reg,
|
|
gen_rtx_PLUS (reg_mode,
|
|
inc_reg,
|
|
inc_insn.reg1)),
|
|
inc_reg);
|
|
|
|
case REG_PRE: /* ++reg */
|
|
if (dump_file)
|
|
fprintf (dump_file, "trying PRE_REG\n");
|
|
return attempt_change (gen_rtx_PRE_MODIFY (reg_mode,
|
|
inc_reg,
|
|
gen_rtx_PLUS (reg_mode,
|
|
inc_reg,
|
|
inc_insn.reg1)),
|
|
inc_reg);
|
|
|
|
case REG_POST: /* reg++ */
|
|
if (dump_file)
|
|
fprintf (dump_file, "trying POST_REG\n");
|
|
return attempt_change (gen_rtx_POST_MODIFY (reg_mode,
|
|
inc_reg,
|
|
gen_rtx_PLUS (reg_mode,
|
|
inc_reg,
|
|
inc_insn.reg1)),
|
|
inc_reg);
|
|
}
|
|
}
|
|
|
|
/* Return the next insn that uses (if reg_next_use is passed in
|
|
NEXT_ARRAY) or defines (if reg_next_def is passed in NEXT_ARRAY)
|
|
REGNO in BB. */
|
|
|
|
static rtx_insn *
|
|
get_next_ref (int regno, basic_block bb, rtx_insn **next_array)
|
|
{
|
|
rtx_insn *insn = next_array[regno];
|
|
|
|
/* Lazy about cleaning out the next_arrays. */
|
|
if (insn && BLOCK_FOR_INSN (insn) != bb)
|
|
{
|
|
next_array[regno] = NULL;
|
|
insn = NULL;
|
|
}
|
|
|
|
return insn;
|
|
}
|
|
|
|
|
|
/* Return true if INSN is of a form "a = b op c" where a and b are
|
|
regs. op is + if c is a reg and +|- if c is a const. Fill in
|
|
INC_INSN with what is found.
|
|
|
|
This function is called in two contexts, if BEFORE_MEM is true,
|
|
this is called for each insn in the basic block. If BEFORE_MEM is
|
|
false, it is called for the instruction in the block that uses the
|
|
index register for some memory reference that is currently being
|
|
processed. */
|
|
|
|
static bool
|
|
parse_add_or_inc (rtx_insn *insn, bool before_mem)
|
|
{
|
|
rtx pat = single_set (insn);
|
|
if (!pat)
|
|
return false;
|
|
|
|
/* Result must be single reg. */
|
|
if (!REG_P (SET_DEST (pat)))
|
|
return false;
|
|
|
|
if ((GET_CODE (SET_SRC (pat)) != PLUS)
|
|
&& (GET_CODE (SET_SRC (pat)) != MINUS))
|
|
return false;
|
|
|
|
if (!REG_P (XEXP (SET_SRC (pat), 0)))
|
|
return false;
|
|
|
|
inc_insn.insn = insn;
|
|
inc_insn.pat = pat;
|
|
inc_insn.reg_res = SET_DEST (pat);
|
|
inc_insn.reg0 = XEXP (SET_SRC (pat), 0);
|
|
|
|
/* Block any auto increment of the frame pointer since it expands into
|
|
an addition and cannot be removed by copy propagation. */
|
|
if (inc_insn.reg0 == frame_pointer_rtx)
|
|
return false;
|
|
|
|
if (rtx_equal_p (inc_insn.reg_res, inc_insn.reg0))
|
|
inc_insn.form = before_mem ? FORM_PRE_INC : FORM_POST_INC;
|
|
else
|
|
inc_insn.form = before_mem ? FORM_PRE_ADD : FORM_POST_ADD;
|
|
|
|
if (CONST_INT_P (XEXP (SET_SRC (pat), 1)))
|
|
{
|
|
/* Process a = b + c where c is a const. */
|
|
inc_insn.reg1_is_const = true;
|
|
if (GET_CODE (SET_SRC (pat)) == PLUS)
|
|
{
|
|
inc_insn.reg1 = XEXP (SET_SRC (pat), 1);
|
|
inc_insn.reg1_val = INTVAL (inc_insn.reg1);
|
|
}
|
|
else
|
|
{
|
|
inc_insn.reg1_val = -INTVAL (XEXP (SET_SRC (pat), 1));
|
|
inc_insn.reg1 = GEN_INT (inc_insn.reg1_val);
|
|
}
|
|
return true;
|
|
}
|
|
else if ((HAVE_PRE_MODIFY_REG || HAVE_POST_MODIFY_REG)
|
|
&& (REG_P (XEXP (SET_SRC (pat), 1)))
|
|
&& GET_CODE (SET_SRC (pat)) == PLUS)
|
|
{
|
|
/* Process a = b + c where c is a reg. */
|
|
inc_insn.reg1 = XEXP (SET_SRC (pat), 1);
|
|
inc_insn.reg1_is_const = false;
|
|
|
|
if (inc_insn.form == FORM_PRE_INC
|
|
|| inc_insn.form == FORM_POST_INC)
|
|
return true;
|
|
else if (rtx_equal_p (inc_insn.reg_res, inc_insn.reg1))
|
|
{
|
|
/* Reverse the two operands and turn *_ADD into *_INC since
|
|
a = c + a. */
|
|
std::swap (inc_insn.reg0, inc_insn.reg1);
|
|
inc_insn.form = before_mem ? FORM_PRE_INC : FORM_POST_INC;
|
|
return true;
|
|
}
|
|
else
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/* A recursive function that checks all of the mem uses in
|
|
ADDRESS_OF_X to see if any single one of them is compatible with
|
|
what has been found in inc_insn. To avoid accidental matches, we
|
|
will only find MEMs with FINDREG, be it inc_insn.reg_res, be it
|
|
inc_insn.reg0.
|
|
|
|
-1 is returned for success. 0 is returned if nothing was found and
|
|
1 is returned for failure. */
|
|
|
|
static int
|
|
find_address (rtx *address_of_x, rtx findreg)
|
|
{
|
|
rtx x = *address_of_x;
|
|
enum rtx_code code = GET_CODE (x);
|
|
const char *const fmt = GET_RTX_FORMAT (code);
|
|
int i;
|
|
int value = 0;
|
|
int tem;
|
|
|
|
if (code == MEM && findreg == inc_insn.reg_res
|
|
&& rtx_equal_p (XEXP (x, 0), inc_insn.reg_res))
|
|
{
|
|
/* Match with *reg_res. */
|
|
mem_insn.mem_loc = address_of_x;
|
|
mem_insn.reg0 = inc_insn.reg_res;
|
|
mem_insn.reg1_is_const = true;
|
|
mem_insn.reg1_val = 0;
|
|
mem_insn.reg1 = GEN_INT (0);
|
|
return -1;
|
|
}
|
|
if (code == MEM && inc_insn.reg1_is_const && inc_insn.reg0
|
|
&& findreg == inc_insn.reg0
|
|
&& rtx_equal_p (XEXP (x, 0), inc_insn.reg0))
|
|
{
|
|
/* Match with *reg0, assumed to be equivalent to
|
|
*(reg_res - reg1_val); callers must check whether this is the case. */
|
|
mem_insn.mem_loc = address_of_x;
|
|
mem_insn.reg0 = inc_insn.reg_res;
|
|
mem_insn.reg1_is_const = true;
|
|
mem_insn.reg1_val = -inc_insn.reg1_val;
|
|
mem_insn.reg1 = GEN_INT (mem_insn.reg1_val);
|
|
return -1;
|
|
}
|
|
if (code == MEM && findreg == inc_insn.reg_res
|
|
&& GET_CODE (XEXP (x, 0)) == PLUS
|
|
&& rtx_equal_p (XEXP (XEXP (x, 0), 0), inc_insn.reg_res))
|
|
{
|
|
rtx b = XEXP (XEXP (x, 0), 1);
|
|
mem_insn.mem_loc = address_of_x;
|
|
mem_insn.reg0 = inc_insn.reg_res;
|
|
mem_insn.reg1 = b;
|
|
mem_insn.reg1_is_const = inc_insn.reg1_is_const;
|
|
if (CONST_INT_P (b))
|
|
{
|
|
/* Match with *(reg0 + reg1) where reg1 is a const. */
|
|
HOST_WIDE_INT val = INTVAL (b);
|
|
if (inc_insn.reg1_is_const
|
|
&& (inc_insn.reg1_val == val || inc_insn.reg1_val == -val))
|
|
{
|
|
mem_insn.reg1_val = val;
|
|
return -1;
|
|
}
|
|
}
|
|
else if (!inc_insn.reg1_is_const
|
|
&& rtx_equal_p (inc_insn.reg1, b))
|
|
/* Match with *(reg0 + reg1). */
|
|
return -1;
|
|
}
|
|
|
|
if (code == SIGN_EXTRACT || code == ZERO_EXTRACT)
|
|
{
|
|
/* If REG occurs inside a MEM used in a bit-field reference,
|
|
that is unacceptable. */
|
|
if (find_address (&XEXP (x, 0), findreg))
|
|
return 1;
|
|
}
|
|
|
|
if (x == inc_insn.reg_res)
|
|
return 1;
|
|
|
|
/* Time for some deep diving. */
|
|
for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--)
|
|
{
|
|
if (fmt[i] == 'e')
|
|
{
|
|
tem = find_address (&XEXP (x, i), findreg);
|
|
/* If this is the first use, let it go so the rest of the
|
|
insn can be checked. */
|
|
if (value == 0)
|
|
value = tem;
|
|
else if (tem != 0)
|
|
/* More than one match was found. */
|
|
return 1;
|
|
}
|
|
else if (fmt[i] == 'E')
|
|
{
|
|
int j;
|
|
for (j = XVECLEN (x, i) - 1; j >= 0; j--)
|
|
{
|
|
tem = find_address (&XVECEXP (x, i, j), findreg);
|
|
/* If this is the first use, let it go so the rest of
|
|
the insn can be checked. */
|
|
if (value == 0)
|
|
value = tem;
|
|
else if (tem != 0)
|
|
/* More than one match was found. */
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
/* Once a suitable mem reference has been found and the MEM_INSN
|
|
structure has been filled in, FIND_INC is called to see if there is
|
|
a suitable add or inc insn that follows the mem reference and
|
|
determine if it is suitable to merge.
|
|
|
|
In the case where the MEM_INSN has two registers in the reference,
|
|
this function may be called recursively. The first time looking
|
|
for an add of the first register, and if that fails, looking for an
|
|
add of the second register. The FIRST_TRY parameter is used to
|
|
only allow the parameters to be reversed once. */
|
|
|
|
static bool
|
|
find_inc (bool first_try)
|
|
{
|
|
rtx_insn *insn;
|
|
basic_block bb = BLOCK_FOR_INSN (mem_insn.insn);
|
|
rtx_insn *other_insn;
|
|
df_ref def;
|
|
|
|
/* Make sure this reg appears only once in this insn. */
|
|
if (count_occurrences (PATTERN (mem_insn.insn), mem_insn.reg0, 1) != 1)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "mem count failure\n");
|
|
return false;
|
|
}
|
|
|
|
if (dump_file)
|
|
dump_mem_insn (dump_file);
|
|
|
|
/* Find the next use that is an inc. */
|
|
insn = get_next_ref (REGNO (mem_insn.reg0),
|
|
BLOCK_FOR_INSN (mem_insn.insn),
|
|
reg_next_inc_use);
|
|
if (!insn)
|
|
return false;
|
|
|
|
/* Even though we know the next use is an add or inc because it came
|
|
from the reg_next_inc_use, we must still reparse. */
|
|
if (!parse_add_or_inc (insn, false))
|
|
{
|
|
/* Next use was not an add. Look for one extra case. It could be
|
|
that we have:
|
|
|
|
*(a + b)
|
|
...= a;
|
|
...= b + a
|
|
|
|
if we reverse the operands in the mem ref we would
|
|
find this. Only try it once though. */
|
|
if (first_try && !mem_insn.reg1_is_const)
|
|
{
|
|
std::swap (mem_insn.reg0, mem_insn.reg1);
|
|
return find_inc (false);
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/* Need to assure that none of the operands of the inc instruction are
|
|
assigned to by the mem insn. */
|
|
FOR_EACH_INSN_DEF (def, mem_insn.insn)
|
|
{
|
|
unsigned int regno = DF_REF_REGNO (def);
|
|
if ((regno == REGNO (inc_insn.reg0))
|
|
|| (regno == REGNO (inc_insn.reg_res)))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "inc conflicts with store failure.\n");
|
|
return false;
|
|
}
|
|
if (!inc_insn.reg1_is_const && (regno == REGNO (inc_insn.reg1)))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "inc conflicts with store failure.\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (dump_file)
|
|
dump_inc_insn (dump_file);
|
|
|
|
if (inc_insn.form == FORM_POST_ADD)
|
|
{
|
|
/* Make sure that there is no insn that assigns to inc_insn.res
|
|
between the mem_insn and the inc_insn. */
|
|
rtx_insn *other_insn = get_next_ref (REGNO (inc_insn.reg_res),
|
|
BLOCK_FOR_INSN (mem_insn.insn),
|
|
reg_next_def);
|
|
if (other_insn != inc_insn.insn)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file,
|
|
"result of add is assigned to between mem and inc insns.\n");
|
|
return false;
|
|
}
|
|
|
|
other_insn = get_next_ref (REGNO (inc_insn.reg_res),
|
|
BLOCK_FOR_INSN (mem_insn.insn),
|
|
reg_next_use);
|
|
if (other_insn
|
|
&& (other_insn != inc_insn.insn)
|
|
&& (DF_INSN_LUID (inc_insn.insn) > DF_INSN_LUID (other_insn)))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file,
|
|
"result of add is used between mem and inc insns.\n");
|
|
return false;
|
|
}
|
|
|
|
/* For the post_add to work, the result_reg of the inc must not be
|
|
used in the mem insn since this will become the new index
|
|
register. */
|
|
if (reg_overlap_mentioned_p (inc_insn.reg_res, PATTERN (mem_insn.insn)))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "base reg replacement failure.\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (mem_insn.reg1_is_const)
|
|
{
|
|
if (mem_insn.reg1_val == 0)
|
|
{
|
|
if (!inc_insn.reg1_is_const)
|
|
{
|
|
/* The mem looks like *r0 and the rhs of the add has two
|
|
registers. */
|
|
int luid = DF_INSN_LUID (inc_insn.insn);
|
|
if (inc_insn.form == FORM_POST_ADD)
|
|
{
|
|
/* The trick is that we are not going to increment r0,
|
|
we are going to increment the result of the add insn.
|
|
For this trick to be correct, the result reg of
|
|
the inc must be a valid addressing reg. */
|
|
addr_space_t as = MEM_ADDR_SPACE (*mem_insn.mem_loc);
|
|
if (GET_MODE (inc_insn.reg_res)
|
|
!= targetm.addr_space.address_mode (as))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "base reg mode failure.\n");
|
|
return false;
|
|
}
|
|
|
|
/* We also need to make sure that the next use of
|
|
inc result is after the inc. */
|
|
other_insn
|
|
= get_next_ref (REGNO (inc_insn.reg1), bb, reg_next_use);
|
|
if (other_insn && luid > DF_INSN_LUID (other_insn))
|
|
return false;
|
|
|
|
if (!rtx_equal_p (mem_insn.reg0, inc_insn.reg0))
|
|
std::swap (inc_insn.reg0, inc_insn.reg1);
|
|
}
|
|
|
|
other_insn
|
|
= get_next_ref (REGNO (inc_insn.reg1), bb, reg_next_def);
|
|
if (other_insn && luid > DF_INSN_LUID (other_insn))
|
|
return false;
|
|
}
|
|
}
|
|
/* Both the inc/add and the mem have a constant. Need to check
|
|
that the constants are ok. */
|
|
else if ((mem_insn.reg1_val != inc_insn.reg1_val)
|
|
&& (mem_insn.reg1_val != -inc_insn.reg1_val))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
/* The mem insn is of the form *(a + b) where a and b are both
|
|
regs. It may be that in order to match the add or inc we
|
|
need to treat it as if it was *(b + a). It may also be that
|
|
the add is of the form a + c where c does not match b and
|
|
then we just abandon this. */
|
|
|
|
int luid = DF_INSN_LUID (inc_insn.insn);
|
|
rtx_insn *other_insn;
|
|
|
|
/* Make sure this reg appears only once in this insn. */
|
|
if (count_occurrences (PATTERN (mem_insn.insn), mem_insn.reg1, 1) != 1)
|
|
return false;
|
|
|
|
if (inc_insn.form == FORM_POST_ADD)
|
|
{
|
|
/* For this trick to be correct, the result reg of the inc
|
|
must be a valid addressing reg. */
|
|
addr_space_t as = MEM_ADDR_SPACE (*mem_insn.mem_loc);
|
|
if (GET_MODE (inc_insn.reg_res)
|
|
!= targetm.addr_space.address_mode (as))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "base reg mode failure.\n");
|
|
return false;
|
|
}
|
|
|
|
if (rtx_equal_p (mem_insn.reg0, inc_insn.reg0))
|
|
{
|
|
if (!rtx_equal_p (mem_insn.reg1, inc_insn.reg1))
|
|
{
|
|
/* See comment above on find_inc (false) call. */
|
|
if (first_try)
|
|
{
|
|
std::swap (mem_insn.reg0, mem_insn.reg1);
|
|
return find_inc (false);
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/* Need to check that there are no assignments to b
|
|
before the add insn. */
|
|
other_insn
|
|
= get_next_ref (REGNO (inc_insn.reg1), bb, reg_next_def);
|
|
if (other_insn && luid > DF_INSN_LUID (other_insn))
|
|
return false;
|
|
/* All ok for the next step. */
|
|
}
|
|
else
|
|
{
|
|
/* We know that mem_insn.reg0 must equal inc_insn.reg1
|
|
or else we would not have found the inc insn. */
|
|
std::swap (mem_insn.reg0, mem_insn.reg1);
|
|
if (!rtx_equal_p (mem_insn.reg0, inc_insn.reg0))
|
|
{
|
|
/* See comment above on find_inc (false) call. */
|
|
if (first_try)
|
|
return find_inc (false);
|
|
else
|
|
return false;
|
|
}
|
|
/* To have gotten here know that.
|
|
*(b + a)
|
|
|
|
... = (b + a)
|
|
|
|
We also know that the lhs of the inc is not b or a. We
|
|
need to make sure that there are no assignments to b
|
|
between the mem ref and the inc. */
|
|
|
|
other_insn
|
|
= get_next_ref (REGNO (inc_insn.reg0), bb, reg_next_def);
|
|
if (other_insn && luid > DF_INSN_LUID (other_insn))
|
|
return false;
|
|
}
|
|
|
|
/* Need to check that the next use of the add result is later than
|
|
add insn since this will be the reg incremented. */
|
|
other_insn
|
|
= get_next_ref (REGNO (inc_insn.reg_res), bb, reg_next_use);
|
|
if (other_insn && luid > DF_INSN_LUID (other_insn))
|
|
return false;
|
|
}
|
|
else /* FORM_POST_INC. There is less to check here because we
|
|
know that operands must line up. */
|
|
{
|
|
if (!rtx_equal_p (mem_insn.reg1, inc_insn.reg1))
|
|
/* See comment above on find_inc (false) call. */
|
|
{
|
|
if (first_try)
|
|
{
|
|
std::swap (mem_insn.reg0, mem_insn.reg1);
|
|
return find_inc (false);
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/* To have gotten here know that.
|
|
*(a + b)
|
|
|
|
... = (a + b)
|
|
|
|
We also know that the lhs of the inc is not b. We need to make
|
|
sure that there are no assignments to b between the mem ref and
|
|
the inc. */
|
|
other_insn
|
|
= get_next_ref (REGNO (inc_insn.reg1), bb, reg_next_def);
|
|
if (other_insn && luid > DF_INSN_LUID (other_insn))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (inc_insn.form == FORM_POST_INC)
|
|
{
|
|
other_insn
|
|
= get_next_ref (REGNO (inc_insn.reg0), bb, reg_next_use);
|
|
/* When we found inc_insn, we were looking for the
|
|
next add or inc, not the next insn that used the
|
|
reg. Because we are going to increment the reg
|
|
in this form, we need to make sure that there
|
|
were no intervening uses of reg. */
|
|
if (inc_insn.insn != other_insn)
|
|
return false;
|
|
}
|
|
|
|
return try_merge ();
|
|
}
|
|
|
|
|
|
/* A recursive function that walks ADDRESS_OF_X to find all of the mem
|
|
uses in pat that could be used as an auto inc or dec. It then
|
|
calls FIND_INC for each one. */
|
|
|
|
static bool
|
|
find_mem (rtx *address_of_x)
|
|
{
|
|
rtx x = *address_of_x;
|
|
enum rtx_code code = GET_CODE (x);
|
|
const char *const fmt = GET_RTX_FORMAT (code);
|
|
int i;
|
|
|
|
if (code == MEM && REG_P (XEXP (x, 0)))
|
|
{
|
|
/* Match with *reg0. */
|
|
mem_insn.mem_loc = address_of_x;
|
|
mem_insn.reg0 = XEXP (x, 0);
|
|
mem_insn.reg1_is_const = true;
|
|
mem_insn.reg1_val = 0;
|
|
mem_insn.reg1 = GEN_INT (0);
|
|
if (find_inc (true))
|
|
return true;
|
|
}
|
|
if (code == MEM && GET_CODE (XEXP (x, 0)) == PLUS
|
|
&& REG_P (XEXP (XEXP (x, 0), 0)))
|
|
{
|
|
rtx reg1 = XEXP (XEXP (x, 0), 1);
|
|
mem_insn.mem_loc = address_of_x;
|
|
mem_insn.reg0 = XEXP (XEXP (x, 0), 0);
|
|
mem_insn.reg1 = reg1;
|
|
if (CONST_INT_P (reg1))
|
|
{
|
|
mem_insn.reg1_is_const = true;
|
|
/* Match with *(reg0 + c) where c is a const. */
|
|
mem_insn.reg1_val = INTVAL (reg1);
|
|
if (find_inc (true))
|
|
return true;
|
|
}
|
|
else if (REG_P (reg1))
|
|
{
|
|
/* Match with *(reg0 + reg1). */
|
|
mem_insn.reg1_is_const = false;
|
|
if (find_inc (true))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (code == SIGN_EXTRACT || code == ZERO_EXTRACT)
|
|
{
|
|
/* If REG occurs inside a MEM used in a bit-field reference,
|
|
that is unacceptable. */
|
|
return false;
|
|
}
|
|
|
|
/* Time for some deep diving. */
|
|
for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--)
|
|
{
|
|
if (fmt[i] == 'e')
|
|
{
|
|
if (find_mem (&XEXP (x, i)))
|
|
return true;
|
|
}
|
|
else if (fmt[i] == 'E')
|
|
{
|
|
int j;
|
|
for (j = XVECLEN (x, i) - 1; j >= 0; j--)
|
|
if (find_mem (&XVECEXP (x, i, j)))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/* Try to combine all incs and decs by constant values with memory
|
|
references in BB. */
|
|
|
|
static void
|
|
merge_in_block (int max_reg, basic_block bb)
|
|
{
|
|
rtx_insn *insn;
|
|
rtx_insn *curr;
|
|
int success_in_block = 0;
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, "\n\nstarting bb %d\n", bb->index);
|
|
|
|
FOR_BB_INSNS_REVERSE_SAFE (bb, insn, curr)
|
|
{
|
|
bool insn_is_add_or_inc = true;
|
|
|
|
if (!NONDEBUG_INSN_P (insn))
|
|
{
|
|
if (DEBUG_BIND_INSN_P (insn))
|
|
{
|
|
df_insn_info *insn_info = DF_INSN_INFO_GET (insn);
|
|
df_ref use;
|
|
|
|
if (dump_file)
|
|
dump_insn_slim (dump_file, insn);
|
|
|
|
FOR_EACH_INSN_INFO_USE (use, insn_info)
|
|
reg_next_debug_use[DF_REF_REGNO (use)] = insn;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* Reload should handle auto-inc within a jump correctly, while LRA
|
|
is known to have issues with autoinc. */
|
|
if (JUMP_P (insn) && targetm.lra_p ())
|
|
continue;
|
|
|
|
if (dump_file)
|
|
dump_insn_slim (dump_file, insn);
|
|
|
|
/* Does this instruction increment or decrement a register? */
|
|
if (parse_add_or_inc (insn, true))
|
|
{
|
|
int regno = REGNO (inc_insn.reg_res);
|
|
/* Cannot handle case where there are three separate regs
|
|
before a mem ref. Too many moves would be needed to be
|
|
profitable. */
|
|
if ((inc_insn.form == FORM_PRE_INC) || inc_insn.reg1_is_const)
|
|
{
|
|
mem_insn.insn = get_next_ref (regno, bb, reg_next_use);
|
|
if (mem_insn.insn)
|
|
{
|
|
bool ok = true;
|
|
if (!inc_insn.reg1_is_const)
|
|
{
|
|
/* We are only here if we are going to try a
|
|
HAVE_*_MODIFY_REG type transformation. c is a
|
|
reg and we must sure that the path from the
|
|
inc_insn to the mem_insn.insn is both def and use
|
|
clear of c because the inc insn is going to move
|
|
into the mem_insn.insn. */
|
|
int luid = DF_INSN_LUID (mem_insn.insn);
|
|
rtx_insn *other_insn
|
|
= get_next_ref (REGNO (inc_insn.reg1), bb, reg_next_use);
|
|
|
|
if (other_insn && luid > DF_INSN_LUID (other_insn))
|
|
ok = false;
|
|
|
|
other_insn
|
|
= get_next_ref (REGNO (inc_insn.reg1), bb, reg_next_def);
|
|
|
|
if (other_insn && luid > DF_INSN_LUID (other_insn))
|
|
ok = false;
|
|
}
|
|
|
|
if (dump_file)
|
|
dump_inc_insn (dump_file);
|
|
|
|
if (ok && find_address (&PATTERN (mem_insn.insn),
|
|
inc_insn.reg_res) == -1)
|
|
{
|
|
if (dump_file)
|
|
dump_mem_insn (dump_file);
|
|
if (try_merge ())
|
|
{
|
|
success_in_block++;
|
|
insn_is_add_or_inc = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (insn_is_add_or_inc
|
|
/* find_address will only recognize an address
|
|
with a reg0 that's not reg_res when
|
|
reg1_is_const, so cut it off early if we
|
|
already know it won't match. */
|
|
&& inc_insn.reg1_is_const
|
|
&& inc_insn.reg0
|
|
&& inc_insn.reg0 != inc_insn.reg_res)
|
|
{
|
|
/* If we identified an inc_insn that uses two
|
|
different pseudos, it's of the form
|
|
|
|
(set reg_res (plus reg0 reg1))
|
|
|
|
where reg1 is a constant (*).
|
|
|
|
The next use of reg_res was not identified by
|
|
find_address as a mem_insn that we could turn
|
|
into auto-inc, so see if we find a suitable
|
|
MEM in the next use of reg0, as long as it's
|
|
before any subsequent use of reg_res:
|
|
|
|
... (mem (... reg0 ...)) ...
|
|
|
|
... reg_res ...
|
|
|
|
In this case, we can turn the plus into a
|
|
copy, and the reg0 in the MEM address into a
|
|
post_inc of reg_res:
|
|
|
|
(set reg_res reg0)
|
|
|
|
... (mem (... (post_add reg_res reg1) ...)) ...
|
|
|
|
reg_res will then have the correct value at
|
|
subsequent uses, and reg0 will remain
|
|
unchanged.
|
|
|
|
(*) We could support non-const reg1, but then
|
|
we'd have to check that reg1 remains
|
|
unchanged all the way to the modified MEM,
|
|
and we'd have to extend find_address to
|
|
represent a non-const negated reg1. */
|
|
regno = REGNO (inc_insn.reg0);
|
|
rtx_insn *reg0_use = get_next_ref (regno, bb,
|
|
reg_next_use);
|
|
|
|
/* Give up if the next use of reg0 is after the next
|
|
use of reg_res (same insn is ok; we might have
|
|
found a MEM with reg_res before, and that failed,
|
|
but now we try reg0, which might work), or defs
|
|
of reg_res (same insn is not ok, we'd introduce
|
|
another def in the same insn) or reg0. */
|
|
if (reg0_use)
|
|
{
|
|
int luid = DF_INSN_LUID (reg0_use);
|
|
|
|
/* It might seem pointless to introduce an
|
|
auto-inc if there's no subsequent use of
|
|
reg_res (i.e., mem_insn.insn == NULL), but
|
|
the next use might be in the next iteration
|
|
of a loop, and it won't hurt if we make the
|
|
change even if it's not needed. */
|
|
if (mem_insn.insn
|
|
&& luid > DF_INSN_LUID (mem_insn.insn))
|
|
reg0_use = NULL;
|
|
|
|
rtx_insn *other_insn
|
|
= get_next_ref (REGNO (inc_insn.reg_res), bb,
|
|
reg_next_def);
|
|
|
|
if (other_insn && luid >= DF_INSN_LUID (other_insn))
|
|
reg0_use = NULL;
|
|
|
|
other_insn
|
|
= get_next_ref (REGNO (inc_insn.reg0), bb,
|
|
reg_next_def);
|
|
|
|
if (other_insn && luid > DF_INSN_LUID (other_insn))
|
|
reg0_use = NULL;
|
|
}
|
|
|
|
mem_insn.insn = reg0_use;
|
|
|
|
if (mem_insn.insn
|
|
&& find_address (&PATTERN (mem_insn.insn),
|
|
inc_insn.reg0) == -1)
|
|
{
|
|
if (dump_file)
|
|
dump_mem_insn (dump_file);
|
|
if (try_merge ())
|
|
{
|
|
success_in_block++;
|
|
insn_is_add_or_inc = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
insn_is_add_or_inc = false;
|
|
/* We can't use auto inc/dec for bare USEs and CLOBBERs,
|
|
since they aren't supposed to generate any code. */
|
|
rtx_code code = GET_CODE (PATTERN (insn));
|
|
if (code != USE && code != CLOBBER)
|
|
{
|
|
mem_insn.insn = insn;
|
|
if (find_mem (&PATTERN (insn)))
|
|
success_in_block++;
|
|
}
|
|
}
|
|
|
|
/* If the inc insn was merged with a mem, the inc insn is gone
|
|
and there is noting to update. */
|
|
if (df_insn_info *insn_info = DF_INSN_INFO_GET (insn))
|
|
{
|
|
df_ref def, use;
|
|
|
|
/* Need to update next use. */
|
|
FOR_EACH_INSN_INFO_DEF (def, insn_info)
|
|
{
|
|
if (reg_next_debug_use)
|
|
reg_next_debug_use[DF_REF_REGNO (def)] = NULL;
|
|
reg_next_use[DF_REF_REGNO (def)] = NULL;
|
|
reg_next_inc_use[DF_REF_REGNO (def)] = NULL;
|
|
reg_next_def[DF_REF_REGNO (def)] = insn;
|
|
}
|
|
|
|
FOR_EACH_INSN_INFO_USE (use, insn_info)
|
|
{
|
|
if (reg_next_debug_use)
|
|
/* This may seem surprising, but we know we may only
|
|
modify the value of a REG between an insn and the
|
|
next nondebug use thereof. Any debug uses after
|
|
the next nondebug use can be left alone, the REG
|
|
will hold the expected value there. */
|
|
reg_next_debug_use[DF_REF_REGNO (use)] = NULL;
|
|
reg_next_use[DF_REF_REGNO (use)] = insn;
|
|
if (insn_is_add_or_inc)
|
|
reg_next_inc_use[DF_REF_REGNO (use)] = insn;
|
|
else
|
|
reg_next_inc_use[DF_REF_REGNO (use)] = NULL;
|
|
}
|
|
}
|
|
else if (dump_file)
|
|
fprintf (dump_file, "skipping update of deleted insn %d\n",
|
|
INSN_UID (insn));
|
|
}
|
|
|
|
/* If we were successful, try again. There may have been several
|
|
opportunities that were interleaved. This is rare but
|
|
gcc.c-torture/compile/pr17273.c actually exhibits this. */
|
|
if (success_in_block)
|
|
{
|
|
/* In this case, we must clear these vectors since the trick of
|
|
testing if the stale insn in the block will not work. */
|
|
if (reg_next_debug_use)
|
|
memset (reg_next_debug_use, 0, max_reg * sizeof (rtx));
|
|
memset (reg_next_use, 0, max_reg * sizeof (rtx));
|
|
memset (reg_next_inc_use, 0, max_reg * sizeof (rtx));
|
|
memset (reg_next_def, 0, max_reg * sizeof (rtx));
|
|
df_recompute_luids (bb);
|
|
merge_in_block (max_reg, bb);
|
|
}
|
|
}
|
|
|
|
/* Discover auto-inc auto-dec instructions. */
|
|
|
|
namespace {
|
|
|
|
const pass_data pass_data_inc_dec =
|
|
{
|
|
RTL_PASS, /* type */
|
|
"auto_inc_dec", /* name */
|
|
OPTGROUP_NONE, /* optinfo_flags */
|
|
TV_AUTO_INC_DEC, /* tv_id */
|
|
0, /* properties_required */
|
|
0, /* properties_provided */
|
|
0, /* properties_destroyed */
|
|
0, /* todo_flags_start */
|
|
TODO_df_finish, /* todo_flags_finish */
|
|
};
|
|
|
|
class pass_inc_dec : public rtl_opt_pass
|
|
{
|
|
public:
|
|
pass_inc_dec (gcc::context *ctxt)
|
|
: rtl_opt_pass (pass_data_inc_dec, ctxt)
|
|
{}
|
|
|
|
/* opt_pass methods: */
|
|
virtual bool gate (function *)
|
|
{
|
|
if (!AUTO_INC_DEC)
|
|
return false;
|
|
|
|
return (optimize > 0 && flag_auto_inc_dec);
|
|
}
|
|
|
|
|
|
unsigned int execute (function *);
|
|
|
|
}; // class pass_inc_dec
|
|
|
|
unsigned int
|
|
pass_inc_dec::execute (function *fun ATTRIBUTE_UNUSED)
|
|
{
|
|
if (!AUTO_INC_DEC)
|
|
return 0;
|
|
|
|
basic_block bb;
|
|
int max_reg = max_reg_num ();
|
|
|
|
if (!initialized)
|
|
init_decision_table ();
|
|
|
|
mem_tmp = gen_rtx_MEM (Pmode, NULL_RTX);
|
|
|
|
df_note_add_problem ();
|
|
df_analyze ();
|
|
|
|
if (MAY_HAVE_DEBUG_BIND_INSNS)
|
|
reg_next_debug_use = XCNEWVEC (rtx_insn *, max_reg);
|
|
else
|
|
/* An earlier function may have had debug binds. */
|
|
reg_next_debug_use = NULL;
|
|
reg_next_use = XCNEWVEC (rtx_insn *, max_reg);
|
|
reg_next_inc_use = XCNEWVEC (rtx_insn *, max_reg);
|
|
reg_next_def = XCNEWVEC (rtx_insn *, max_reg);
|
|
FOR_EACH_BB_FN (bb, fun)
|
|
merge_in_block (max_reg, bb);
|
|
|
|
free (reg_next_debug_use);
|
|
free (reg_next_use);
|
|
free (reg_next_inc_use);
|
|
free (reg_next_def);
|
|
|
|
mem_tmp = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
} // anon namespace
|
|
|
|
rtl_opt_pass *
|
|
make_pass_inc_dec (gcc::context *ctxt)
|
|
{
|
|
return new pass_inc_dec (ctxt);
|
|
}
|