X-Git-Url: https://oss.titaniummirror.com/gitweb?a=blobdiff_plain;f=gcc%2Fauto-inc-dec.c;fp=gcc%2Fauto-inc-dec.c;h=86b5a2c6f8ddf294702d156939a550f6ac293add;hb=6fed43773c9b0ce596dca5686f37ac3fc0fa11c0;hp=0000000000000000000000000000000000000000;hpb=27b11d56b743098deb193d510b337ba22dc52e5c;p=msp430-gcc.git diff --git a/gcc/auto-inc-dec.c b/gcc/auto-inc-dec.c new file mode 100644 index 00000000..86b5a2c6 --- /dev/null +++ b/gcc/auto-inc-dec.c @@ -0,0 +1,1562 @@ +/* Discovery of auto-inc and auto-dec instructions. + Copyright (C) 2006, 2007, 2008, 2009 Free Software Foundation, Inc. + Contributed by Kenneth Zadeck + +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 +. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tm.h" +#include "tree.h" +#include "rtl.h" +#include "tm_p.h" +#include "hard-reg-set.h" +#include "basic-block.h" +#include "insn-config.h" +#include "regs.h" +#include "flags.h" +#include "output.h" +#include "function.h" +#include "except.h" +#include "toplev.h" +#include "recog.h" +#include "expr.h" +#include "timevar.h" +#include "tree-pass.h" +#include "df.h" +#include "dbgcnt.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: + + a <- b + c + ... + *a + + becomes + + a <- b + ... + *(a += c) pre + a += c + ... + *a + + becomes + + *(a += c) pre + *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 + *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. + + (1) FORM_PRE_ADD + + a <- b + c + ... + *(a - c) + + becomes + + a <- b + ... + *(a += c) post + + (2) FORM_PRE_INC + + a += c + ... + *(a - c) + + becomes + + *(a += c) post + + (3) FORM_POST_ADD + + *(a + c) + ... + 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) pre + + + (4) FORM_POST_INC + + *(a + c) + ... + a <- a + c + + becomes + + *(a += c) pre +*/ +#ifdef AUTO_INC_DEC + +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, int size) +{ + if (val == 0) + return INC_ZERO; + if (val < 0) + return (val == -size) ? INC_NEG_SIZE : INC_NEG_ANY; + else + return (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; /* 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; /* 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 *reg_next_use = NULL; +static rtx *reg_next_inc_use = NULL; +static rtx *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 to_insn, rtx 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; + } +} + + +/* Create a mov insn DEST_REG <- SRC_REG and insert it before + NEXT_INSN. */ + +static rtx +insert_move_insn_before (rtx next_insn, rtx dest_reg, rtx src_reg) +{ + rtx insns; + + start_sequence (); + emit_move_insn (dest_reg, src_reg); + insns = get_insns (); + end_sequence (); + emit_insn_before (insns, next_insn); + return insns; +} + + +/* 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 = BASIC_BLOCK (BLOCK_NUM (mem_insn.insn)); + rtx mov_insn = NULL; + int regno; + rtx mem = *mem_insn.mem_loc; + enum machine_mode mode = GET_MODE (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; + + old_cost = rtx_cost (mem, 0, speed) + + rtx_cost (PATTERN (inc_insn.insn), 0, speed); + new_cost = rtx_cost (mem_tmp, 0, 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 thru 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. */ + mov_insn = insert_move_insn_before (inc_insn.insn, + inc_insn.reg_res, inc_insn.reg0); + move_dead_notes (mov_insn, inc_insn.insn, inc_insn.reg0); + + regno = REGNO (inc_insn.reg_res); + reg_next_def[regno] = mov_insn; + reg_next_use[regno] = NULL; + regno = REGNO (inc_insn.reg0); + reg_next_use[regno] = mov_insn; + df_recompute_luids (bb); + break; + + case FORM_POST_INC: + regno = REGNO (inc_insn.reg_res); + 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); + reg_next_def[regno] = mem_insn.insn; + reg_next_use[regno] = NULL; + + break; + + case FORM_POST_ADD: + mov_insn = insert_move_insn_before (mem_insn.insn, + inc_insn.reg_res, inc_insn.reg0); + 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); + reg_next_def[regno] = mem_insn.insn; + reg_next_use[regno] = NULL; + + regno = REGNO (inc_insn.reg0); + 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. */ + int size = GET_MODE_SIZE (GET_MODE (mem)); + rtx last_insn = NULL; + + 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 (Pmode, inc_reg), inc_reg); + break; + + case SIMPLE_POST_INC: /* size++ */ + if (dump_file) + fprintf (dump_file, "trying SIMPLE_POST_INC\n"); + return attempt_change (gen_rtx_POST_INC (Pmode, inc_reg), inc_reg); + break; + + case SIMPLE_PRE_DEC: /* --size */ + if (dump_file) + fprintf (dump_file, "trying SIMPLE_PRE_DEC\n"); + return attempt_change (gen_rtx_PRE_DEC (Pmode, inc_reg), inc_reg); + break; + + case SIMPLE_POST_DEC: /* size-- */ + if (dump_file) + fprintf (dump_file, "trying SIMPLE_POST_DEC\n"); + return attempt_change (gen_rtx_POST_DEC (Pmode, inc_reg), inc_reg); + break; + + case DISP_PRE: /* ++con */ + if (dump_file) + fprintf (dump_file, "trying DISP_PRE\n"); + return attempt_change (gen_rtx_PRE_MODIFY (Pmode, + inc_reg, + gen_rtx_PLUS (Pmode, + inc_reg, + inc_insn.reg1)), + inc_reg); + break; + + case DISP_POST: /* con++ */ + if (dump_file) + fprintf (dump_file, "trying POST_DISP\n"); + return attempt_change (gen_rtx_POST_MODIFY (Pmode, + inc_reg, + gen_rtx_PLUS (Pmode, + inc_reg, + inc_insn.reg1)), + inc_reg); + break; + + case REG_PRE: /* ++reg */ + if (dump_file) + fprintf (dump_file, "trying PRE_REG\n"); + return attempt_change (gen_rtx_PRE_MODIFY (Pmode, + inc_reg, + gen_rtx_PLUS (Pmode, + inc_reg, + inc_insn.reg1)), + inc_reg); + break; + + case REG_POST: /* reg++ */ + if (dump_file) + fprintf (dump_file, "trying POST_REG\n"); + return attempt_change (gen_rtx_POST_MODIFY (Pmode, + inc_reg, + gen_rtx_PLUS (Pmode, + inc_reg, + inc_insn.reg1)), + inc_reg); + break; + } +} + +/* 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 +get_next_ref (int regno, basic_block bb, rtx *next_array) +{ + rtx insn = next_array[regno]; + + /* Lazy about cleaning out the next_arrays. */ + if (insn && BASIC_BLOCK (BLOCK_NUM (insn)) != bb) + { + next_array[regno] = NULL; + insn = NULL; + } + + return insn; +} + + +/* Reverse the operands in a mem insn. */ + +static void +reverse_mem (void) +{ + rtx tmp = mem_insn.reg1; + mem_insn.reg1 = mem_insn.reg0; + mem_insn.reg0 = tmp; +} + + +/* Reverse the operands in a inc insn. */ + +static void +reverse_inc (void) +{ + rtx tmp = inc_insn.reg1; + inc_insn.reg1 = inc_insn.reg0; + inc_insn.reg0 = tmp; +} + + +/* 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, 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); + 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 (GET_CODE (XEXP (SET_SRC (pat), 1)) == CONST_INT) + { + /* 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. */ + reverse_inc (); + 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. + + -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 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 && rtx_equal_p (XEXP (x, 0), inc_insn.reg_res)) + { + /* Match with *reg0. */ + 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 && 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 (GET_CODE (b) == CONST_INT) + { + /* 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))) + 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)); + /* 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)); + /* 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; + basic_block bb = BASIC_BLOCK (BLOCK_NUM (mem_insn.insn)); + rtx other_insn; + df_ref *def_rec; + + /* 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), + BASIC_BLOCK (BLOCK_NUM (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) + { + reverse_mem (); + 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 (def_rec = DF_INSN_DEFS (mem_insn.insn); *def_rec; def_rec++) + { + df_ref def = *def_rec; + 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 other_insn = get_next_ref (REGNO (inc_insn.reg_res), + BASIC_BLOCK (BLOCK_NUM (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), + BASIC_BLOCK (BLOCK_NUM (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 (count_occurrences (PATTERN (mem_insn.insn), inc_insn.reg_res, 1) != 0) + { + 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. */ + if (GET_MODE (inc_insn.reg_res) != Pmode) + { + 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)) + reverse_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; + } + } + /* 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 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. */ + if (GET_MODE (inc_insn.reg_res) != Pmode) + { + 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) + { + reverse_mem (); + 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. */ + reverse_mem (); + 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) + { + reverse_mem (); + 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 (GET_CODE (reg1) == CONST_INT) + { + 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; + rtx 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) + { + unsigned int uid = INSN_UID (insn); + bool insn_is_add_or_inc = true; + + if (!INSN_P (insn)) + continue; + + /* This continue is deliberate. We do not want the uses of the + jump put into reg_next_use because it is not considered safe to + combine a preincrement with a jump. */ + if (JUMP_P (insn)) + 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 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)) == -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; + 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_UID_GET(uid)) + { + df_ref *def_rec; + df_ref *use_rec; + /* Need to update next use. */ + for (def_rec = DF_INSN_UID_DEFS (uid); *def_rec; def_rec++) + { + df_ref def = *def_rec; + 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 (use_rec = DF_INSN_UID_USES (uid); *use_rec; use_rec++) + { + df_ref use = *use_rec; + 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", uid); + } + + /* 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. */ + 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); + } +} + +#endif + +static unsigned int +rest_of_handle_auto_inc_dec (void) +{ +#ifdef AUTO_INC_DEC + 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 (); + + reg_next_use = XCNEWVEC (rtx, max_reg); + reg_next_inc_use = XCNEWVEC (rtx, max_reg); + reg_next_def = XCNEWVEC (rtx, max_reg); + FOR_EACH_BB (bb) + merge_in_block (max_reg, bb); + + free (reg_next_use); + free (reg_next_inc_use); + free (reg_next_def); + + mem_tmp = NULL; +#endif + return 0; +} + + +/* Discover auto-inc auto-dec instructions. */ + +static bool +gate_auto_inc_dec (void) +{ +#ifdef AUTO_INC_DEC + return (optimize > 0 && flag_auto_inc_dec); +#else + return false; +#endif +} + + +struct rtl_opt_pass pass_inc_dec = +{ + { + RTL_PASS, + "auto_inc_dec", /* name */ + gate_auto_inc_dec, /* gate */ + rest_of_handle_auto_inc_dec, /* execute */ + NULL, /* sub */ + NULL, /* next */ + 0, /* static_pass_number */ + TV_AUTO_INC_DEC, /* tv_id */ + 0, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + TODO_dump_func | + TODO_df_finish, /* todo_flags_finish */ + } +}; +