| /* Register renaming for the GNU compiler. |
| Copyright (C) 2000-2021 Free Software Foundation, Inc. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it |
| under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3, or (at your option) |
| any later version. |
| |
| GCC is distributed in the hope that it will be useful, but WITHOUT |
| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
| License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "backend.h" |
| #include "target.h" |
| #include "rtl.h" |
| #include "df.h" |
| #include "memmodel.h" |
| #include "tm_p.h" |
| #include "insn-config.h" |
| #include "regs.h" |
| #include "emit-rtl.h" |
| #include "recog.h" |
| #include "addresses.h" |
| #include "cfganal.h" |
| #include "tree-pass.h" |
| #include "function-abi.h" |
| #include "regrename.h" |
| |
| /* This file implements the RTL register renaming pass of the compiler. It is |
| a semi-local pass whose goal is to maximize the usage of the register file |
| of the processor by substituting registers for others in the solution given |
| by the register allocator. The algorithm is as follows: |
| |
| 1. Local def/use chains are built: within each basic block, chains are |
| opened and closed; if a chain isn't closed at the end of the block, |
| it is dropped. We pre-open chains if we have already examined a |
| predecessor block and found chains live at the end which match |
| live registers at the start of the new block. |
| |
| 2. We try to combine the local chains across basic block boundaries by |
| comparing chains that were open at the start or end of a block to |
| those in successor/predecessor blocks. |
| |
| 3. For each chain, the set of possible renaming registers is computed. |
| This takes into account the renaming of previously processed chains. |
| Optionally, a preferred class is computed for the renaming register. |
| |
| 4. The best renaming register is computed for the chain in the above set, |
| using a round-robin allocation. If a preferred class exists, then the |
| round-robin allocation is done within the class first, if possible. |
| The round-robin allocation of renaming registers itself is global. |
| |
| 5. If a renaming register has been found, it is substituted in the chain. |
| |
| Targets can parameterize the pass by specifying a preferred class for the |
| renaming register for a given (super)class of registers to be renamed. |
| |
| DEBUG_INSNs are treated specially, in particular registers occurring inside |
| them are treated as requiring ALL_REGS as a class. */ |
| |
| #if HOST_BITS_PER_WIDE_INT <= MAX_RECOG_OPERANDS |
| #error "Use a different bitmap implementation for untracked_operands." |
| #endif |
| |
| enum scan_actions |
| { |
| terminate_write, |
| terminate_dead, |
| mark_all_read, |
| mark_read, |
| mark_write, |
| /* mark_access is for marking the destination regs in |
| REG_FRAME_RELATED_EXPR notes (as if they were read) so that the |
| note is updated properly. */ |
| mark_access |
| }; |
| |
| static const char * const scan_actions_name[] = |
| { |
| "terminate_write", |
| "terminate_dead", |
| "mark_all_read", |
| "mark_read", |
| "mark_write", |
| "mark_access" |
| }; |
| |
| /* TICK and THIS_TICK are used to record the last time we saw each |
| register. */ |
| static int tick[FIRST_PSEUDO_REGISTER]; |
| static int this_tick = 0; |
| |
| static struct obstack rename_obstack; |
| |
| /* If nonnull, the code calling into the register renamer requested |
| information about insn operands, and we store it here. */ |
| vec<insn_rr_info> insn_rr; |
| |
| static void scan_rtx (rtx_insn *, rtx *, enum reg_class, enum scan_actions, |
| enum op_type); |
| static bool build_def_use (basic_block); |
| |
| /* The id to be given to the next opened chain. */ |
| static unsigned current_id; |
| |
| /* A mapping of unique id numbers to chains. */ |
| static vec<du_head_p> id_to_chain; |
| |
| /* List of currently open chains. */ |
| static class du_head *open_chains; |
| |
| /* Bitmap of open chains. The bits set always match the list found in |
| open_chains. */ |
| static bitmap_head open_chains_set; |
| |
| /* Record the registers being tracked in open_chains. */ |
| static HARD_REG_SET live_in_chains; |
| |
| /* Record the registers that are live but not tracked. The intersection |
| between this and live_in_chains is empty. */ |
| static HARD_REG_SET live_hard_regs; |
| |
| /* Set while scanning RTL if INSN_RR is nonnull, i.e. if the current analysis |
| is for a caller that requires operand data. Used in |
| record_operand_use. */ |
| static operand_rr_info *cur_operand; |
| |
| /* Set while scanning RTL if a register dies. Used to tie chains. */ |
| static class du_head *terminated_this_insn; |
| |
| /* Return the chain corresponding to id number ID. Take into account that |
| chains may have been merged. */ |
| du_head_p |
| regrename_chain_from_id (unsigned int id) |
| { |
| du_head_p first_chain = id_to_chain[id]; |
| du_head_p chain = first_chain; |
| while (chain->id != id) |
| { |
| id = chain->id; |
| chain = id_to_chain[id]; |
| } |
| first_chain->id = id; |
| return chain; |
| } |
| |
| /* Dump all def/use chains, starting at id FROM. */ |
| |
| static void |
| dump_def_use_chain (int from) |
| { |
| du_head_p head; |
| int i; |
| FOR_EACH_VEC_ELT_FROM (id_to_chain, i, head, from) |
| { |
| struct du_chain *this_du = head->first; |
| |
| fprintf (dump_file, "Register %s (%d):", |
| reg_names[head->regno], head->nregs); |
| while (this_du) |
| { |
| fprintf (dump_file, " %d [%s]", INSN_UID (this_du->insn), |
| reg_class_names[this_du->cl]); |
| this_du = this_du->next_use; |
| } |
| fprintf (dump_file, "\n"); |
| head = head->next_chain; |
| } |
| } |
| |
| static void |
| free_chain_data (void) |
| { |
| int i; |
| du_head_p ptr; |
| for (i = 0; id_to_chain.iterate (i, &ptr); i++) |
| bitmap_clear (&ptr->conflicts); |
| |
| id_to_chain.release (); |
| } |
| |
| /* Walk all chains starting with CHAINS and record that they conflict with |
| another chain whose id is ID. */ |
| |
| static void |
| mark_conflict (class du_head *chains, unsigned id) |
| { |
| while (chains) |
| { |
| bitmap_set_bit (&chains->conflicts, id); |
| chains = chains->next_chain; |
| } |
| } |
| |
| /* Examine cur_operand, and if it is nonnull, record information about the |
| use THIS_DU which is part of the chain HEAD. */ |
| |
| static void |
| record_operand_use (class du_head *head, struct du_chain *this_du) |
| { |
| if (cur_operand == NULL || cur_operand->failed) |
| return; |
| if (head->cannot_rename) |
| { |
| cur_operand->failed = true; |
| return; |
| } |
| gcc_assert (cur_operand->n_chains < MAX_REGS_PER_ADDRESS); |
| cur_operand->heads[cur_operand->n_chains] = head; |
| cur_operand->chains[cur_operand->n_chains++] = this_du; |
| } |
| |
| /* Create a new chain for THIS_NREGS registers starting at THIS_REGNO, |
| and record its occurrence in *LOC, which is being written to in INSN. |
| This access requires a register of class CL. */ |
| |
| static du_head_p |
| create_new_chain (unsigned this_regno, unsigned this_nregs, rtx *loc, |
| rtx_insn *insn, enum reg_class cl) |
| { |
| class du_head *head = XOBNEW (&rename_obstack, class du_head); |
| struct du_chain *this_du; |
| int nregs; |
| |
| memset ((void *)head, 0, sizeof *head); |
| head->next_chain = open_chains; |
| head->regno = this_regno; |
| head->nregs = this_nregs; |
| |
| id_to_chain.safe_push (head); |
| head->id = current_id++; |
| |
| bitmap_initialize (&head->conflicts, &bitmap_default_obstack); |
| bitmap_copy (&head->conflicts, &open_chains_set); |
| mark_conflict (open_chains, head->id); |
| |
| /* Since we're tracking this as a chain now, remove it from the |
| list of conflicting live hard registers and track it in |
| live_in_chains instead. */ |
| nregs = head->nregs; |
| while (nregs-- > 0) |
| { |
| SET_HARD_REG_BIT (live_in_chains, head->regno + nregs); |
| CLEAR_HARD_REG_BIT (live_hard_regs, head->regno + nregs); |
| } |
| |
| head->hard_conflicts = live_hard_regs; |
| bitmap_set_bit (&open_chains_set, head->id); |
| |
| open_chains = head; |
| |
| if (dump_file) |
| { |
| fprintf (dump_file, "Creating chain %s (%d)", |
| reg_names[head->regno], head->id); |
| if (insn != NULL_RTX) |
| fprintf (dump_file, " at insn %d", INSN_UID (insn)); |
| fprintf (dump_file, "\n"); |
| } |
| |
| if (insn == NULL_RTX) |
| { |
| head->first = head->last = NULL; |
| return head; |
| } |
| |
| this_du = XOBNEW (&rename_obstack, struct du_chain); |
| head->first = head->last = this_du; |
| |
| this_du->next_use = 0; |
| this_du->loc = loc; |
| this_du->insn = insn; |
| this_du->cl = cl; |
| record_operand_use (head, this_du); |
| return head; |
| } |
| |
| /* For a def-use chain HEAD, find which registers overlap its lifetime and |
| set the corresponding bits in *PSET. */ |
| |
| static void |
| merge_overlapping_regs (HARD_REG_SET *pset, class du_head *head) |
| { |
| bitmap_iterator bi; |
| unsigned i; |
| *pset |= head->hard_conflicts; |
| EXECUTE_IF_SET_IN_BITMAP (&head->conflicts, 0, i, bi) |
| { |
| du_head_p other = regrename_chain_from_id (i); |
| unsigned j = other->nregs; |
| gcc_assert (other != head); |
| while (j-- > 0) |
| SET_HARD_REG_BIT (*pset, other->regno + j); |
| } |
| } |
| |
| /* Return true if (reg:MODE REGNO) would be clobbered by a call covered |
| by THIS_HEAD. */ |
| |
| static bool |
| call_clobbered_in_chain_p (du_head *this_head, machine_mode mode, |
| unsigned int regno) |
| { |
| return call_clobbered_in_region_p (this_head->call_abis, |
| this_head->call_clobber_mask, |
| mode, regno); |
| } |
| |
| /* Check if NEW_REG can be the candidate register to rename for |
| REG in THIS_HEAD chain. THIS_UNAVAILABLE is a set of unavailable hard |
| registers. */ |
| |
| static bool |
| check_new_reg_p (int reg ATTRIBUTE_UNUSED, int new_reg, |
| class du_head *this_head, HARD_REG_SET this_unavailable) |
| { |
| machine_mode mode = GET_MODE (*this_head->first->loc); |
| int nregs = hard_regno_nregs (new_reg, mode); |
| int i; |
| struct du_chain *tmp; |
| |
| for (i = nregs - 1; i >= 0; --i) |
| if (TEST_HARD_REG_BIT (this_unavailable, new_reg + i) |
| || fixed_regs[new_reg + i] |
| || global_regs[new_reg + i] |
| /* Can't use regs which aren't saved by the prologue. */ |
| || (! df_regs_ever_live_p (new_reg + i) |
| && ! crtl->abi->clobbers_full_reg_p (new_reg + i)) |
| #ifdef LEAF_REGISTERS |
| /* We can't use a non-leaf register if we're in a |
| leaf function. */ |
| || (crtl->is_leaf |
| && !LEAF_REGISTERS[new_reg + i]) |
| #endif |
| || ! HARD_REGNO_RENAME_OK (reg + i, new_reg + i)) |
| return false; |
| |
| /* See whether it accepts all modes that occur in |
| definition and uses. */ |
| for (tmp = this_head->first; tmp; tmp = tmp->next_use) |
| { |
| /* Completely ignore DEBUG_INSNs, otherwise we can get |
| -fcompare-debug failures. */ |
| if (DEBUG_INSN_P (tmp->insn)) |
| continue; |
| |
| if (!targetm.hard_regno_mode_ok (new_reg, GET_MODE (*tmp->loc)) |
| || call_clobbered_in_chain_p (this_head, GET_MODE (*tmp->loc), |
| new_reg)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* For the chain THIS_HEAD, compute and return the best register to |
| rename to. SUPER_CLASS is the superunion of register classes in |
| the chain. UNAVAILABLE is a set of registers that cannot be used. |
| OLD_REG is the register currently used for the chain. BEST_RENAME |
| controls whether the register chosen must be better than the |
| current one or just respect the given constraint. */ |
| |
| int |
| find_rename_reg (du_head_p this_head, enum reg_class super_class, |
| HARD_REG_SET *unavailable, int old_reg, bool best_rename) |
| { |
| bool has_preferred_class; |
| enum reg_class preferred_class; |
| int pass; |
| int best_new_reg = old_reg; |
| |
| /* Mark registers that overlap this chain's lifetime as unavailable. */ |
| merge_overlapping_regs (unavailable, this_head); |
| |
| /* Compute preferred rename class of super union of all the classes |
| in the chain. */ |
| preferred_class |
| = (enum reg_class) targetm.preferred_rename_class (super_class); |
| |
| /* Pick and check the register from the tied chain iff the tied chain |
| is not renamed. */ |
| if (this_head->tied_chain && !this_head->tied_chain->renamed |
| && check_new_reg_p (old_reg, this_head->tied_chain->regno, |
| this_head, *unavailable)) |
| return this_head->tied_chain->regno; |
| |
| /* If PREFERRED_CLASS is not NO_REGS, we iterate in the first pass |
| over registers that belong to PREFERRED_CLASS and try to find the |
| best register within the class. If that failed, we iterate in |
| the second pass over registers that don't belong to the class. |
| If PREFERRED_CLASS is NO_REGS, we iterate over all registers in |
| ascending order without any preference. */ |
| has_preferred_class = (preferred_class != NO_REGS); |
| for (pass = (has_preferred_class ? 0 : 1); pass < 2; pass++) |
| { |
| int new_reg; |
| for (new_reg = 0; new_reg < FIRST_PSEUDO_REGISTER; new_reg++) |
| { |
| if (has_preferred_class |
| && (pass == 0) |
| != TEST_HARD_REG_BIT (reg_class_contents[preferred_class], |
| new_reg)) |
| continue; |
| |
| if (!check_new_reg_p (old_reg, new_reg, this_head, *unavailable)) |
| continue; |
| |
| if (!best_rename) |
| return new_reg; |
| |
| /* In the first pass, we force the renaming of registers that |
| don't belong to PREFERRED_CLASS to registers that do, even |
| though the latters were used not very long ago. */ |
| if ((pass == 0 |
| && !TEST_HARD_REG_BIT (reg_class_contents[preferred_class], |
| best_new_reg)) |
| || tick[best_new_reg] > tick[new_reg]) |
| best_new_reg = new_reg; |
| } |
| if (pass == 0 && best_new_reg != old_reg) |
| break; |
| } |
| return best_new_reg; |
| } |
| |
| /* Iterate over elements in the chain HEAD in order to: |
| 1. Count number of uses, storing it in *PN_USES. |
| 2. Narrow the set of registers we can use for renaming, adding |
| unavailable registers to *PUNAVAILABLE, which must be |
| initialized by the caller. |
| 3. Compute the superunion of register classes in this chain |
| and return it. */ |
| reg_class |
| regrename_find_superclass (du_head_p head, int *pn_uses, |
| HARD_REG_SET *punavailable) |
| { |
| int n_uses = 0; |
| reg_class super_class = NO_REGS; |
| for (du_chain *tmp = head->first; tmp; tmp = tmp->next_use) |
| { |
| if (DEBUG_INSN_P (tmp->insn)) |
| continue; |
| n_uses++; |
| *punavailable |= ~reg_class_contents[tmp->cl]; |
| super_class |
| = reg_class_superunion[(int) super_class][(int) tmp->cl]; |
| } |
| *pn_uses = n_uses; |
| return super_class; |
| } |
| |
| /* Perform register renaming on the current function. */ |
| static void |
| rename_chains (void) |
| { |
| HARD_REG_SET unavailable; |
| du_head_p this_head; |
| int i; |
| |
| memset (tick, 0, sizeof tick); |
| |
| CLEAR_HARD_REG_SET (unavailable); |
| /* Don't clobber traceback for noreturn functions. */ |
| if (frame_pointer_needed) |
| { |
| add_to_hard_reg_set (&unavailable, Pmode, FRAME_POINTER_REGNUM); |
| if (!HARD_FRAME_POINTER_IS_FRAME_POINTER) |
| add_to_hard_reg_set (&unavailable, Pmode, HARD_FRAME_POINTER_REGNUM); |
| } |
| |
| FOR_EACH_VEC_ELT (id_to_chain, i, this_head) |
| { |
| int best_new_reg; |
| int n_uses; |
| HARD_REG_SET this_unavailable; |
| int reg = this_head->regno; |
| |
| if (this_head->cannot_rename) |
| continue; |
| |
| if (fixed_regs[reg] || global_regs[reg] |
| || (!HARD_FRAME_POINTER_IS_FRAME_POINTER && frame_pointer_needed |
| && reg == HARD_FRAME_POINTER_REGNUM) |
| || (HARD_FRAME_POINTER_IS_FRAME_POINTER && frame_pointer_needed |
| && reg == FRAME_POINTER_REGNUM)) |
| continue; |
| |
| this_unavailable = unavailable; |
| |
| reg_class super_class = regrename_find_superclass (this_head, &n_uses, |
| &this_unavailable); |
| if (n_uses < 2) |
| continue; |
| |
| best_new_reg = find_rename_reg (this_head, super_class, |
| &this_unavailable, reg, true); |
| |
| if (dump_file) |
| { |
| fprintf (dump_file, "Register %s in insn %d", |
| reg_names[reg], INSN_UID (this_head->first->insn)); |
| if (this_head->call_abis) |
| fprintf (dump_file, " crosses a call"); |
| } |
| |
| if (best_new_reg == reg) |
| { |
| tick[reg] = ++this_tick; |
| if (dump_file) |
| fprintf (dump_file, "; no available better choice\n"); |
| continue; |
| } |
| |
| if (regrename_do_replace (this_head, best_new_reg)) |
| { |
| if (dump_file) |
| fprintf (dump_file, ", renamed as %s\n", reg_names[best_new_reg]); |
| tick[best_new_reg] = ++this_tick; |
| df_set_regs_ever_live (best_new_reg, true); |
| } |
| else |
| { |
| if (dump_file) |
| fprintf (dump_file, ", renaming as %s failed\n", |
| reg_names[best_new_reg]); |
| tick[reg] = ++this_tick; |
| } |
| } |
| } |
| |
| /* A structure to record information for each hard register at the start of |
| a basic block. */ |
| struct incoming_reg_info { |
| /* Holds the number of registers used in the chain that gave us information |
| about this register. Zero means no information known yet, while a |
| negative value is used for something that is part of, but not the first |
| register in a multi-register value. */ |
| int nregs; |
| /* Set to true if we have accesses that conflict in the number of registers |
| used. */ |
| bool unusable; |
| }; |
| |
| /* A structure recording information about each basic block. It is saved |
| and restored around basic block boundaries. |
| A pointer to such a structure is stored in each basic block's aux field |
| during regrename_analyze, except for blocks we know can't be optimized |
| (such as entry and exit blocks). */ |
| class bb_rename_info |
| { |
| public: |
| /* The basic block corresponding to this structure. */ |
| basic_block bb; |
| /* Copies of the global information. */ |
| bitmap_head open_chains_set; |
| bitmap_head incoming_open_chains_set; |
| struct incoming_reg_info incoming[FIRST_PSEUDO_REGISTER]; |
| }; |
| |
| /* Initialize a rename_info structure P for basic block BB, which starts a new |
| scan. */ |
| static void |
| init_rename_info (class bb_rename_info *p, basic_block bb) |
| { |
| int i; |
| df_ref def; |
| HARD_REG_SET start_chains_set; |
| |
| p->bb = bb; |
| bitmap_initialize (&p->open_chains_set, &bitmap_default_obstack); |
| bitmap_initialize (&p->incoming_open_chains_set, &bitmap_default_obstack); |
| |
| open_chains = NULL; |
| bitmap_clear (&open_chains_set); |
| |
| CLEAR_HARD_REG_SET (live_in_chains); |
| REG_SET_TO_HARD_REG_SET (live_hard_regs, df_get_live_in (bb)); |
| FOR_EACH_ARTIFICIAL_DEF (def, bb->index) |
| if (DF_REF_FLAGS (def) & DF_REF_AT_TOP) |
| SET_HARD_REG_BIT (live_hard_regs, DF_REF_REGNO (def)); |
| |
| /* Open chains based on information from (at least one) predecessor |
| block. This gives us a chance later on to combine chains across |
| basic block boundaries. Inconsistencies (in access sizes) will |
| be caught normally and dealt with conservatively by disabling the |
| chain for renaming, and there is no risk of losing optimization |
| opportunities by opening chains either: if we did not open the |
| chains, we'd have to track the live register as a hard reg, and |
| we'd be unable to rename it in any case. */ |
| CLEAR_HARD_REG_SET (start_chains_set); |
| for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) |
| { |
| struct incoming_reg_info *iri = p->incoming + i; |
| if (iri->nregs > 0 && !iri->unusable |
| && range_in_hard_reg_set_p (live_hard_regs, i, iri->nregs)) |
| { |
| SET_HARD_REG_BIT (start_chains_set, i); |
| remove_range_from_hard_reg_set (&live_hard_regs, i, iri->nregs); |
| } |
| } |
| for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) |
| { |
| struct incoming_reg_info *iri = p->incoming + i; |
| if (TEST_HARD_REG_BIT (start_chains_set, i)) |
| { |
| du_head_p chain; |
| if (dump_file) |
| fprintf (dump_file, "opening incoming chain\n"); |
| chain = create_new_chain (i, iri->nregs, NULL, NULL, NO_REGS); |
| bitmap_set_bit (&p->incoming_open_chains_set, chain->id); |
| } |
| } |
| } |
| |
| /* Record in RI that the block corresponding to it has an incoming |
| live value, described by CHAIN. */ |
| static void |
| set_incoming_from_chain (class bb_rename_info *ri, du_head_p chain) |
| { |
| int i; |
| int incoming_nregs = ri->incoming[chain->regno].nregs; |
| int nregs; |
| |
| /* If we've recorded the same information before, everything is fine. */ |
| if (incoming_nregs == chain->nregs) |
| { |
| if (dump_file) |
| fprintf (dump_file, "reg %d/%d already recorded\n", |
| chain->regno, chain->nregs); |
| return; |
| } |
| |
| /* If we have no information for any of the involved registers, update |
| the incoming array. */ |
| nregs = chain->nregs; |
| while (nregs-- > 0) |
| if (ri->incoming[chain->regno + nregs].nregs != 0 |
| || ri->incoming[chain->regno + nregs].unusable) |
| break; |
| if (nregs < 0) |
| { |
| nregs = chain->nregs; |
| ri->incoming[chain->regno].nregs = nregs; |
| while (nregs-- > 1) |
| ri->incoming[chain->regno + nregs].nregs = -nregs; |
| if (dump_file) |
| fprintf (dump_file, "recorded reg %d/%d\n", |
| chain->regno, chain->nregs); |
| return; |
| } |
| |
| /* There must be some kind of conflict. Prevent both the old and |
| new ranges from being used. */ |
| if (incoming_nregs < 0) |
| ri->incoming[chain->regno + incoming_nregs].unusable = true; |
| for (i = 0; i < chain->nregs; i++) |
| ri->incoming[chain->regno + i].unusable = true; |
| } |
| |
| /* Merge the two chains C1 and C2 so that all conflict information is |
| recorded and C1, and the id of C2 is changed to that of C1. */ |
| static void |
| merge_chains (du_head_p c1, du_head_p c2) |
| { |
| if (c1 == c2) |
| return; |
| |
| if (c2->first != NULL) |
| { |
| if (c1->first == NULL) |
| c1->first = c2->first; |
| else |
| c1->last->next_use = c2->first; |
| c1->last = c2->last; |
| } |
| |
| c2->first = c2->last = NULL; |
| c2->id = c1->id; |
| |
| c1->hard_conflicts |= c2->hard_conflicts; |
| bitmap_ior_into (&c1->conflicts, &c2->conflicts); |
| |
| c1->call_clobber_mask |= c2->call_clobber_mask; |
| c1->call_abis |= c2->call_abis; |
| c1->cannot_rename |= c2->cannot_rename; |
| } |
| |
| /* Analyze the current function and build chains for renaming. |
| If INCLUDE_ALL_BLOCKS_P is set to true, process all blocks, |
| ignoring BB_DISABLE_SCHEDULE. The default value is true. */ |
| |
| void |
| regrename_analyze (bitmap bb_mask, bool include_all_block_p) |
| { |
| class bb_rename_info *rename_info; |
| int i; |
| basic_block bb; |
| int n_bbs; |
| int *inverse_postorder; |
| |
| inverse_postorder = XNEWVEC (int, last_basic_block_for_fn (cfun)); |
| n_bbs = pre_and_rev_post_order_compute (NULL, inverse_postorder, false); |
| |
| /* Gather some information about the blocks in this function. */ |
| rename_info = XCNEWVEC (class bb_rename_info, n_basic_blocks_for_fn (cfun)); |
| i = 0; |
| FOR_EACH_BB_FN (bb, cfun) |
| { |
| class bb_rename_info *ri = rename_info + i; |
| ri->bb = bb; |
| if (bb_mask != NULL && !bitmap_bit_p (bb_mask, bb->index)) |
| bb->aux = NULL; |
| else |
| bb->aux = ri; |
| i++; |
| } |
| |
| current_id = 0; |
| id_to_chain.create (0); |
| bitmap_initialize (&open_chains_set, &bitmap_default_obstack); |
| |
| /* The order in which we visit blocks ensures that whenever |
| possible, we only process a block after at least one of its |
| predecessors, which provides a "seeding" effect to make the logic |
| in set_incoming_from_chain and init_rename_info useful. */ |
| |
| for (i = 0; i < n_bbs; i++) |
| { |
| basic_block bb1 = BASIC_BLOCK_FOR_FN (cfun, inverse_postorder[i]); |
| class bb_rename_info *this_info; |
| bool success; |
| edge e; |
| edge_iterator ei; |
| int old_length = id_to_chain.length (); |
| |
| this_info = (class bb_rename_info *) bb1->aux; |
| if (this_info == NULL) |
| continue; |
| |
| if (dump_file) |
| fprintf (dump_file, "\nprocessing block %d:\n", bb1->index); |
| |
| if (!include_all_block_p && (bb1->flags & BB_DISABLE_SCHEDULE) != 0) |
| { |
| if (dump_file) |
| fprintf (dump_file, "avoid disrupting the sms schedule of bb %d\n", |
| bb1->index); |
| continue; |
| } |
| |
| init_rename_info (this_info, bb1); |
| |
| success = build_def_use (bb1); |
| if (!success) |
| { |
| if (dump_file) |
| fprintf (dump_file, "failed\n"); |
| bb1->aux = NULL; |
| id_to_chain.truncate (old_length); |
| current_id = old_length; |
| bitmap_clear (&this_info->incoming_open_chains_set); |
| open_chains = NULL; |
| if (insn_rr.exists ()) |
| { |
| rtx_insn *insn; |
| FOR_BB_INSNS (bb1, insn) |
| { |
| insn_rr_info *p = &insn_rr[INSN_UID (insn)]; |
| p->op_info = NULL; |
| } |
| } |
| continue; |
| } |
| |
| if (dump_file) |
| dump_def_use_chain (old_length); |
| bitmap_copy (&this_info->open_chains_set, &open_chains_set); |
| |
| /* Add successor blocks to the worklist if necessary, and record |
| data about our own open chains at the end of this block, which |
| will be used to pre-open chains when processing the successors. */ |
| FOR_EACH_EDGE (e, ei, bb1->succs) |
| { |
| class bb_rename_info *dest_ri; |
| class du_head *chain; |
| |
| if (dump_file) |
| fprintf (dump_file, "successor block %d\n", e->dest->index); |
| |
| if (e->flags & (EDGE_EH | EDGE_ABNORMAL)) |
| continue; |
| dest_ri = (class bb_rename_info *)e->dest->aux; |
| if (dest_ri == NULL) |
| continue; |
| for (chain = open_chains; chain; chain = chain->next_chain) |
| set_incoming_from_chain (dest_ri, chain); |
| } |
| } |
| |
| free (inverse_postorder); |
| |
| /* Now, combine the chains data we have gathered across basic block |
| boundaries. |
| |
| For every basic block, there may be chains open at the start, or at the |
| end. Rather than exclude them from renaming, we look for open chains |
| with matching registers at the other side of the CFG edge. |
| |
| For a given chain using register R, open at the start of block B, we |
| must find an open chain using R on the other side of every edge leading |
| to B, if the register is live across this edge. In the code below, |
| N_PREDS_USED counts the number of edges where the register is live, and |
| N_PREDS_JOINED counts those where we found an appropriate chain for |
| joining. |
| |
| We perform the analysis for both incoming and outgoing edges, but we |
| only need to merge once (in the second part, after verifying outgoing |
| edges). */ |
| FOR_EACH_BB_FN (bb, cfun) |
| { |
| class bb_rename_info *bb_ri = (class bb_rename_info *) bb->aux; |
| unsigned j; |
| bitmap_iterator bi; |
| |
| if (bb_ri == NULL) |
| continue; |
| |
| if (dump_file) |
| fprintf (dump_file, "processing bb %d in edges\n", bb->index); |
| |
| EXECUTE_IF_SET_IN_BITMAP (&bb_ri->incoming_open_chains_set, 0, j, bi) |
| { |
| edge e; |
| edge_iterator ei; |
| class du_head *chain = regrename_chain_from_id (j); |
| int n_preds_used = 0, n_preds_joined = 0; |
| |
| FOR_EACH_EDGE (e, ei, bb->preds) |
| { |
| class bb_rename_info *src_ri; |
| unsigned k; |
| bitmap_iterator bi2; |
| HARD_REG_SET live; |
| bool success = false; |
| |
| REG_SET_TO_HARD_REG_SET (live, df_get_live_out (e->src)); |
| if (!range_overlaps_hard_reg_set_p (live, chain->regno, |
| chain->nregs)) |
| continue; |
| n_preds_used++; |
| |
| if (e->flags & (EDGE_EH | EDGE_ABNORMAL)) |
| continue; |
| |
| src_ri = (class bb_rename_info *)e->src->aux; |
| if (src_ri == NULL) |
| continue; |
| |
| EXECUTE_IF_SET_IN_BITMAP (&src_ri->open_chains_set, |
| 0, k, bi2) |
| { |
| class du_head *outgoing_chain = regrename_chain_from_id (k); |
| |
| if (outgoing_chain->regno == chain->regno |
| && outgoing_chain->nregs == chain->nregs) |
| { |
| n_preds_joined++; |
| success = true; |
| break; |
| } |
| } |
| if (!success && dump_file) |
| fprintf (dump_file, "failure to match with pred block %d\n", |
| e->src->index); |
| } |
| if (n_preds_joined < n_preds_used) |
| { |
| if (dump_file) |
| fprintf (dump_file, "cannot rename chain %d\n", j); |
| chain->cannot_rename = 1; |
| } |
| } |
| } |
| FOR_EACH_BB_FN (bb, cfun) |
| { |
| class bb_rename_info *bb_ri = (class bb_rename_info *) bb->aux; |
| unsigned j; |
| bitmap_iterator bi; |
| |
| if (bb_ri == NULL) |
| continue; |
| |
| if (dump_file) |
| fprintf (dump_file, "processing bb %d out edges\n", bb->index); |
| |
| EXECUTE_IF_SET_IN_BITMAP (&bb_ri->open_chains_set, 0, j, bi) |
| { |
| edge e; |
| edge_iterator ei; |
| class du_head *chain = regrename_chain_from_id (j); |
| int n_succs_used = 0, n_succs_joined = 0; |
| |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| { |
| bool printed = false; |
| class bb_rename_info *dest_ri; |
| unsigned k; |
| bitmap_iterator bi2; |
| HARD_REG_SET live; |
| |
| REG_SET_TO_HARD_REG_SET (live, df_get_live_in (e->dest)); |
| if (!range_overlaps_hard_reg_set_p (live, chain->regno, |
| chain->nregs)) |
| continue; |
| |
| n_succs_used++; |
| |
| dest_ri = (class bb_rename_info *)e->dest->aux; |
| if (dest_ri == NULL) |
| continue; |
| |
| EXECUTE_IF_SET_IN_BITMAP (&dest_ri->incoming_open_chains_set, |
| 0, k, bi2) |
| { |
| class du_head *incoming_chain = regrename_chain_from_id (k); |
| |
| if (incoming_chain->regno == chain->regno |
| && incoming_chain->nregs == chain->nregs) |
| { |
| if (dump_file) |
| { |
| if (!printed) |
| fprintf (dump_file, |
| "merging blocks for edge %d -> %d\n", |
| e->src->index, e->dest->index); |
| printed = true; |
| fprintf (dump_file, |
| " merging chains %d (->%d) and %d (->%d) [%s]\n", |
| k, incoming_chain->id, j, chain->id, |
| reg_names[incoming_chain->regno]); |
| } |
| |
| merge_chains (chain, incoming_chain); |
| n_succs_joined++; |
| break; |
| } |
| } |
| } |
| if (n_succs_joined < n_succs_used) |
| { |
| if (dump_file) |
| fprintf (dump_file, "cannot rename chain %d\n", |
| j); |
| chain->cannot_rename = 1; |
| } |
| } |
| } |
| |
| free (rename_info); |
| |
| FOR_EACH_BB_FN (bb, cfun) |
| bb->aux = NULL; |
| } |
| |
| /* Attempt to replace all uses of the register in the chain beginning with |
| HEAD with REG. Returns true on success and false if the replacement is |
| rejected because the insns would not validate. The latter can happen |
| e.g. if a match_parallel predicate enforces restrictions on register |
| numbering in its subpatterns. */ |
| |
| bool |
| regrename_do_replace (class du_head *head, int reg) |
| { |
| struct du_chain *chain; |
| unsigned int base_regno = head->regno; |
| machine_mode mode; |
| rtx last_reg = NULL_RTX, last_repl = NULL_RTX; |
| |
| for (chain = head->first; chain; chain = chain->next_use) |
| { |
| unsigned int regno = ORIGINAL_REGNO (*chain->loc); |
| class reg_attrs *attr = REG_ATTRS (*chain->loc); |
| int reg_ptr = REG_POINTER (*chain->loc); |
| |
| if (DEBUG_INSN_P (chain->insn) && REGNO (*chain->loc) != base_regno) |
| validate_change (chain->insn, &(INSN_VAR_LOCATION_LOC (chain->insn)), |
| gen_rtx_UNKNOWN_VAR_LOC (), true); |
| else |
| { |
| if (*chain->loc != last_reg) |
| { |
| last_repl = gen_raw_REG (GET_MODE (*chain->loc), reg); |
| if (regno >= FIRST_PSEUDO_REGISTER) |
| ORIGINAL_REGNO (last_repl) = regno; |
| REG_ATTRS (last_repl) = attr; |
| REG_POINTER (last_repl) = reg_ptr; |
| last_reg = *chain->loc; |
| } |
| validate_change (chain->insn, chain->loc, last_repl, true); |
| } |
| } |
| |
| if (!apply_change_group ()) |
| return false; |
| |
| mode = GET_MODE (*head->first->loc); |
| head->renamed = 1; |
| head->regno = reg; |
| head->nregs = hard_regno_nregs (reg, mode); |
| return true; |
| } |
| |
| |
| /* True if we found a register with a size mismatch, which means that we |
| can't track its lifetime accurately. If so, we abort the current block |
| without renaming. */ |
| static bool fail_current_block; |
| |
| /* Return true if OP is a reg for which all bits are set in PSET, false |
| if all bits are clear. |
| In other cases, set fail_current_block and return false. */ |
| |
| static bool |
| verify_reg_in_set (rtx op, HARD_REG_SET *pset) |
| { |
| unsigned regno, nregs; |
| bool all_live, all_dead; |
| if (!REG_P (op)) |
| return false; |
| |
| regno = REGNO (op); |
| nregs = REG_NREGS (op); |
| all_live = all_dead = true; |
| while (nregs-- > 0) |
| if (TEST_HARD_REG_BIT (*pset, regno + nregs)) |
| all_dead = false; |
| else |
| all_live = false; |
| if (!all_dead && !all_live) |
| { |
| fail_current_block = true; |
| return false; |
| } |
| return all_live; |
| } |
| |
| /* Return true if OP is a reg that is being tracked already in some form. |
| May set fail_current_block if it sees an unhandled case of overlap. */ |
| |
| static bool |
| verify_reg_tracked (rtx op) |
| { |
| return (verify_reg_in_set (op, &live_hard_regs) |
| || verify_reg_in_set (op, &live_in_chains)); |
| } |
| |
| /* Called through note_stores. DATA points to a rtx_code, either SET or |
| CLOBBER, which tells us which kind of rtx to look at. If we have a |
| match, record the set register in live_hard_regs and in the hard_conflicts |
| bitmap of open chains. */ |
| |
| static void |
| note_sets_clobbers (rtx x, const_rtx set, void *data) |
| { |
| enum rtx_code code = *(enum rtx_code *)data; |
| class du_head *chain; |
| |
| if (GET_CODE (x) == SUBREG) |
| x = SUBREG_REG (x); |
| if (!REG_P (x) || GET_CODE (set) != code) |
| return; |
| /* There must not be pseudos at this point. */ |
| gcc_assert (HARD_REGISTER_P (x)); |
| add_to_hard_reg_set (&live_hard_regs, GET_MODE (x), REGNO (x)); |
| for (chain = open_chains; chain; chain = chain->next_chain) |
| add_to_hard_reg_set (&chain->hard_conflicts, GET_MODE (x), REGNO (x)); |
| } |
| |
| static void |
| scan_rtx_reg (rtx_insn *insn, rtx *loc, enum reg_class cl, enum scan_actions action, |
| enum op_type type) |
| { |
| class du_head **p; |
| rtx x = *loc; |
| unsigned this_regno = REGNO (x); |
| int this_nregs = REG_NREGS (x); |
| |
| if (action == mark_write) |
| { |
| if (type == OP_OUT) |
| { |
| du_head_p c; |
| rtx pat = PATTERN (insn); |
| |
| c = create_new_chain (this_regno, this_nregs, loc, insn, cl); |
| |
| /* We try to tie chains in a move instruction for |
| a single output. */ |
| if (recog_data.n_operands == 2 |
| && GET_CODE (pat) == SET |
| && GET_CODE (SET_DEST (pat)) == REG |
| && GET_CODE (SET_SRC (pat)) == REG |
| && terminated_this_insn |
| && terminated_this_insn->nregs |
| == REG_NREGS (recog_data.operand[1])) |
| { |
| gcc_assert (terminated_this_insn->regno |
| == REGNO (recog_data.operand[1])); |
| |
| c->tied_chain = terminated_this_insn; |
| terminated_this_insn->tied_chain = c; |
| |
| if (dump_file) |
| fprintf (dump_file, "Tying chain %s (%d) with %s (%d)\n", |
| reg_names[c->regno], c->id, |
| reg_names[terminated_this_insn->regno], |
| terminated_this_insn->id); |
| } |
| } |
| |
| return; |
| } |
| |
| if ((type == OP_OUT) != (action == terminate_write || action == mark_access)) |
| return; |
| |
| for (p = &open_chains; *p;) |
| { |
| class du_head *head = *p; |
| class du_head *next = head->next_chain; |
| int exact_match = (head->regno == this_regno |
| && head->nregs == this_nregs); |
| int superset = (this_regno <= head->regno |
| && this_regno + this_nregs >= head->regno + head->nregs); |
| int subset = (this_regno >= head->regno |
| && this_regno + this_nregs <= head->regno + head->nregs); |
| |
| if (!bitmap_bit_p (&open_chains_set, head->id) |
| || head->regno + head->nregs <= this_regno |
| || this_regno + this_nregs <= head->regno) |
| { |
| p = &head->next_chain; |
| continue; |
| } |
| |
| if (action == mark_read || action == mark_access) |
| { |
| /* ??? Class NO_REGS can happen if the md file makes use of |
| EXTRA_CONSTRAINTS to match registers. Which is arguably |
| wrong, but there we are. */ |
| |
| if (cl == NO_REGS || (!exact_match && !DEBUG_INSN_P (insn))) |
| { |
| if (dump_file) |
| fprintf (dump_file, |
| "Cannot rename chain %s (%d) at insn %d (%s)\n", |
| reg_names[head->regno], head->id, INSN_UID (insn), |
| scan_actions_name[(int) action]); |
| head->cannot_rename = 1; |
| if (superset) |
| { |
| unsigned nregs = this_nregs; |
| head->regno = this_regno; |
| head->nregs = this_nregs; |
| while (nregs-- > 0) |
| SET_HARD_REG_BIT (live_in_chains, head->regno + nregs); |
| if (dump_file) |
| fprintf (dump_file, |
| "Widening register in chain %s (%d) at insn %d\n", |
| reg_names[head->regno], head->id, INSN_UID (insn)); |
| } |
| else if (!subset) |
| { |
| fail_current_block = true; |
| if (dump_file) |
| fprintf (dump_file, |
| "Failing basic block due to unhandled overlap\n"); |
| } |
| } |
| else |
| { |
| struct du_chain *this_du; |
| this_du = XOBNEW (&rename_obstack, struct du_chain); |
| this_du->next_use = 0; |
| this_du->loc = loc; |
| this_du->insn = insn; |
| this_du->cl = cl; |
| if (head->first == NULL) |
| head->first = this_du; |
| else |
| head->last->next_use = this_du; |
| record_operand_use (head, this_du); |
| head->last = this_du; |
| } |
| /* Avoid adding the same location in a DEBUG_INSN multiple times, |
| which could happen with non-exact overlap. */ |
| if (DEBUG_INSN_P (insn)) |
| return; |
| /* Otherwise, find any other chains that do not match exactly; |
| ensure they all get marked unrenamable. */ |
| p = &head->next_chain; |
| continue; |
| } |
| |
| /* Whether the terminated chain can be used for renaming |
| depends on the action and this being an exact match. |
| In either case, we remove this element from open_chains. */ |
| |
| if ((action == terminate_dead || action == terminate_write) |
| && (superset || subset)) |
| { |
| unsigned nregs; |
| |
| if (subset && !superset) |
| head->cannot_rename = 1; |
| bitmap_clear_bit (&open_chains_set, head->id); |
| |
| nregs = head->nregs; |
| while (nregs-- > 0) |
| { |
| CLEAR_HARD_REG_BIT (live_in_chains, head->regno + nregs); |
| if (subset && !superset |
| && (head->regno + nregs < this_regno |
| || head->regno + nregs >= this_regno + this_nregs)) |
| SET_HARD_REG_BIT (live_hard_regs, head->regno + nregs); |
| } |
| |
| if (action == terminate_dead) |
| terminated_this_insn = *p; |
| *p = next; |
| if (dump_file) |
| fprintf (dump_file, |
| "Closing chain %s (%d) at insn %d (%s%s)\n", |
| reg_names[head->regno], head->id, INSN_UID (insn), |
| scan_actions_name[(int) action], |
| superset ? ", superset" : subset ? ", subset" : ""); |
| } |
| else if (action == terminate_dead || action == terminate_write) |
| { |
| /* In this case, tracking liveness gets too hard. Fail the |
| entire basic block. */ |
| if (dump_file) |
| fprintf (dump_file, |
| "Failing basic block due to unhandled overlap\n"); |
| fail_current_block = true; |
| return; |
| } |
| else |
| { |
| head->cannot_rename = 1; |
| if (dump_file) |
| fprintf (dump_file, |
| "Cannot rename chain %s (%d) at insn %d (%s)\n", |
| reg_names[head->regno], head->id, INSN_UID (insn), |
| scan_actions_name[(int) action]); |
| p = &head->next_chain; |
| } |
| } |
| } |
| |
| /* A wrapper around base_reg_class which returns ALL_REGS if INSN is a |
| DEBUG_INSN. The arguments MODE, AS, CODE and INDEX_CODE are as for |
| base_reg_class. */ |
| |
| static reg_class |
| base_reg_class_for_rename (rtx_insn *insn, machine_mode mode, addr_space_t as, |
| rtx_code code, rtx_code index_code) |
| { |
| if (DEBUG_INSN_P (insn)) |
| return ALL_REGS; |
| return base_reg_class (mode, as, code, index_code); |
| } |
| |
| /* Adapted from find_reloads_address_1. CL is INDEX_REG_CLASS or |
| BASE_REG_CLASS depending on how the register is being considered. */ |
| |
| static void |
| scan_rtx_address (rtx_insn *insn, rtx *loc, enum reg_class cl, |
| enum scan_actions action, machine_mode mode, |
| addr_space_t as) |
| { |
| rtx x = *loc; |
| RTX_CODE code = GET_CODE (x); |
| const char *fmt; |
| int i, j; |
| |
| if (action == mark_write || action == mark_access) |
| return; |
| |
| switch (code) |
| { |
| case PLUS: |
| { |
| rtx orig_op0 = XEXP (x, 0); |
| rtx orig_op1 = XEXP (x, 1); |
| RTX_CODE code0 = GET_CODE (orig_op0); |
| RTX_CODE code1 = GET_CODE (orig_op1); |
| rtx op0 = orig_op0; |
| rtx op1 = orig_op1; |
| rtx *locI = NULL; |
| rtx *locB = NULL; |
| enum rtx_code index_code = SCRATCH; |
| |
| if (GET_CODE (op0) == SUBREG) |
| { |
| op0 = SUBREG_REG (op0); |
| code0 = GET_CODE (op0); |
| } |
| |
| if (GET_CODE (op1) == SUBREG) |
| { |
| op1 = SUBREG_REG (op1); |
| code1 = GET_CODE (op1); |
| } |
| |
| if (code0 == MULT || code0 == SIGN_EXTEND || code0 == TRUNCATE |
| || code0 == ZERO_EXTEND || code1 == MEM) |
| { |
| locI = &XEXP (x, 0); |
| locB = &XEXP (x, 1); |
| index_code = GET_CODE (*locI); |
| } |
| else if (code1 == MULT || code1 == SIGN_EXTEND || code1 == TRUNCATE |
| || code1 == ZERO_EXTEND || code0 == MEM) |
| { |
| locI = &XEXP (x, 1); |
| locB = &XEXP (x, 0); |
| index_code = GET_CODE (*locI); |
| } |
| else if (code0 == CONST_INT || code0 == CONST |
| || code0 == SYMBOL_REF || code0 == LABEL_REF) |
| { |
| locB = &XEXP (x, 1); |
| index_code = GET_CODE (XEXP (x, 0)); |
| } |
| else if (code1 == CONST_INT || code1 == CONST |
| || code1 == SYMBOL_REF || code1 == LABEL_REF) |
| { |
| locB = &XEXP (x, 0); |
| index_code = GET_CODE (XEXP (x, 1)); |
| } |
| else if (code0 == REG && code1 == REG) |
| { |
| int index_op; |
| unsigned regno0 = REGNO (op0), regno1 = REGNO (op1); |
| |
| if (REGNO_OK_FOR_INDEX_P (regno1) |
| && regno_ok_for_base_p (regno0, mode, as, PLUS, REG)) |
| index_op = 1; |
| else if (REGNO_OK_FOR_INDEX_P (regno0) |
| && regno_ok_for_base_p (regno1, mode, as, PLUS, REG)) |
| index_op = 0; |
| else if (regno_ok_for_base_p (regno0, mode, as, PLUS, REG) |
| || REGNO_OK_FOR_INDEX_P (regno1)) |
| index_op = 1; |
| else if (regno_ok_for_base_p (regno1, mode, as, PLUS, REG)) |
| index_op = 0; |
| else |
| index_op = 1; |
| |
| locI = &XEXP (x, index_op); |
| locB = &XEXP (x, !index_op); |
| index_code = GET_CODE (*locI); |
| } |
| else if (code0 == REG) |
| { |
| locI = &XEXP (x, 0); |
| locB = &XEXP (x, 1); |
| index_code = GET_CODE (*locI); |
| } |
| else if (code1 == REG) |
| { |
| locI = &XEXP (x, 1); |
| locB = &XEXP (x, 0); |
| index_code = GET_CODE (*locI); |
| } |
| |
| if (locI) |
| { |
| reg_class iclass = DEBUG_INSN_P (insn) ? ALL_REGS : INDEX_REG_CLASS; |
| scan_rtx_address (insn, locI, iclass, action, mode, as); |
| } |
| if (locB) |
| { |
| reg_class bclass = base_reg_class_for_rename (insn, mode, as, PLUS, |
| index_code); |
| scan_rtx_address (insn, locB, bclass, action, mode, as); |
| } |
| return; |
| } |
| |
| case POST_INC: |
| case POST_DEC: |
| case POST_MODIFY: |
| case PRE_INC: |
| case PRE_DEC: |
| case PRE_MODIFY: |
| /* If the target doesn't claim to handle autoinc, this must be |
| something special, like a stack push. Kill this chain. */ |
| if (!AUTO_INC_DEC) |
| action = mark_all_read; |
| |
| break; |
| |
| case MEM: |
| { |
| reg_class bclass = base_reg_class_for_rename (insn, GET_MODE (x), |
| MEM_ADDR_SPACE (x), |
| MEM, SCRATCH); |
| scan_rtx_address (insn, &XEXP (x, 0), bclass, action, GET_MODE (x), |
| MEM_ADDR_SPACE (x)); |
| } |
| return; |
| |
| case REG: |
| scan_rtx_reg (insn, loc, cl, action, OP_IN); |
| return; |
| |
| default: |
| break; |
| } |
| |
| fmt = GET_RTX_FORMAT (code); |
| for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--) |
| { |
| if (fmt[i] == 'e') |
| scan_rtx_address (insn, &XEXP (x, i), cl, action, mode, as); |
| else if (fmt[i] == 'E') |
| for (j = XVECLEN (x, i) - 1; j >= 0; j--) |
| scan_rtx_address (insn, &XVECEXP (x, i, j), cl, action, mode, as); |
| } |
| } |
| |
| static void |
| scan_rtx (rtx_insn *insn, rtx *loc, enum reg_class cl, enum scan_actions action, |
| enum op_type type) |
| { |
| const char *fmt; |
| rtx x = *loc; |
| int i, j; |
| |
| enum rtx_code code = GET_CODE (x); |
| switch (code) |
| { |
| case CONST: |
| CASE_CONST_ANY: |
| case SYMBOL_REF: |
| case LABEL_REF: |
| case PC: |
| return; |
| |
| case REG: |
| scan_rtx_reg (insn, loc, cl, action, type); |
| return; |
| |
| case MEM: |
| { |
| reg_class bclass = base_reg_class_for_rename (insn, GET_MODE (x), |
| MEM_ADDR_SPACE (x), |
| MEM, SCRATCH); |
| |
| scan_rtx_address (insn, &XEXP (x, 0), bclass, action, GET_MODE (x), |
| MEM_ADDR_SPACE (x)); |
| } |
| return; |
| |
| case SET: |
| scan_rtx (insn, &SET_SRC (x), cl, action, OP_IN); |
| scan_rtx (insn, &SET_DEST (x), cl, action, |
| (GET_CODE (PATTERN (insn)) == COND_EXEC |
| && verify_reg_tracked (SET_DEST (x))) ? OP_INOUT : OP_OUT); |
| return; |
| |
| case STRICT_LOW_PART: |
| scan_rtx (insn, &XEXP (x, 0), cl, action, |
| verify_reg_tracked (XEXP (x, 0)) ? OP_INOUT : OP_OUT); |
| return; |
| |
| case ZERO_EXTRACT: |
| case SIGN_EXTRACT: |
| scan_rtx (insn, &XEXP (x, 0), cl, action, |
| (type == OP_IN ? OP_IN : |
| verify_reg_tracked (XEXP (x, 0)) ? OP_INOUT : OP_OUT)); |
| scan_rtx (insn, &XEXP (x, 1), cl, action, OP_IN); |
| scan_rtx (insn, &XEXP (x, 2), cl, action, OP_IN); |
| return; |
| |
| case POST_INC: |
| case PRE_INC: |
| case POST_DEC: |
| case PRE_DEC: |
| case POST_MODIFY: |
| case PRE_MODIFY: |
| /* Should only happen inside MEM. */ |
| gcc_unreachable (); |
| |
| case CLOBBER: |
| scan_rtx (insn, &SET_DEST (x), cl, action, |
| (GET_CODE (PATTERN (insn)) == COND_EXEC |
| && verify_reg_tracked (SET_DEST (x))) ? OP_INOUT : OP_OUT); |
| return; |
| |
| case EXPR_LIST: |
| scan_rtx (insn, &XEXP (x, 0), cl, action, type); |
| if (XEXP (x, 1)) |
| scan_rtx (insn, &XEXP (x, 1), cl, action, type); |
| return; |
| |
| default: |
| break; |
| } |
| |
| fmt = GET_RTX_FORMAT (code); |
| for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--) |
| { |
| if (fmt[i] == 'e') |
| scan_rtx (insn, &XEXP (x, i), cl, action, type); |
| else if (fmt[i] == 'E') |
| for (j = XVECLEN (x, i) - 1; j >= 0; j--) |
| scan_rtx (insn, &XVECEXP (x, i, j), cl, action, type); |
| } |
| } |
| |
| /* Hide operands of the current insn (of which there are N_OPS) by |
| substituting pc for them. |
| Previous values are stored in the OLD_OPERANDS and OLD_DUPS. |
| For every bit set in DO_NOT_HIDE, we leave the operand alone. |
| If INOUT_AND_EC_ONLY is set, we only do this for OP_INOUT type operands |
| and earlyclobbers. */ |
| |
| static void |
| hide_operands (int n_ops, rtx *old_operands, rtx *old_dups, |
| unsigned HOST_WIDE_INT do_not_hide, bool inout_and_ec_only) |
| { |
| int i; |
| const operand_alternative *op_alt = which_op_alt (); |
| for (i = 0; i < n_ops; i++) |
| { |
| old_operands[i] = recog_data.operand[i]; |
| /* Don't squash match_operator or match_parallel here, since |
| we don't know that all of the contained registers are |
| reachable by proper operands. */ |
| if (recog_data.constraints[i][0] == '\0') |
| continue; |
| if (do_not_hide & (1 << i)) |
| continue; |
| if (!inout_and_ec_only || recog_data.operand_type[i] == OP_INOUT |
| || op_alt[i].earlyclobber) |
| *recog_data.operand_loc[i] = pc_rtx; |
| } |
| for (i = 0; i < recog_data.n_dups; i++) |
| { |
| int opn = recog_data.dup_num[i]; |
| old_dups[i] = *recog_data.dup_loc[i]; |
| if (do_not_hide & (1 << opn)) |
| continue; |
| if (!inout_and_ec_only || recog_data.operand_type[opn] == OP_INOUT |
| || op_alt[opn].earlyclobber) |
| *recog_data.dup_loc[i] = pc_rtx; |
| } |
| } |
| |
| /* Undo the substitution performed by hide_operands. INSN is the insn we |
| are processing; the arguments are the same as in hide_operands. */ |
| |
| static void |
| restore_operands (rtx_insn *insn, int n_ops, rtx *old_operands, rtx *old_dups) |
| { |
| int i; |
| for (i = 0; i < recog_data.n_dups; i++) |
| *recog_data.dup_loc[i] = old_dups[i]; |
| for (i = 0; i < n_ops; i++) |
| *recog_data.operand_loc[i] = old_operands[i]; |
| if (recog_data.n_dups) |
| df_insn_rescan (insn); |
| } |
| |
| /* For each output operand of INSN, call scan_rtx to create a new |
| open chain. Do this only for normal or earlyclobber outputs, |
| depending on EARLYCLOBBER. If INSN_INFO is nonnull, use it to |
| record information about the operands in the insn. */ |
| |
| static void |
| record_out_operands (rtx_insn *insn, bool earlyclobber, insn_rr_info *insn_info) |
| { |
| int n_ops = recog_data.n_operands; |
| const operand_alternative *op_alt = which_op_alt (); |
| |
| int i; |
| |
| for (i = 0; i < n_ops + recog_data.n_dups; i++) |
| { |
| int opn = i < n_ops ? i : recog_data.dup_num[i - n_ops]; |
| rtx *loc = (i < n_ops |
| ? recog_data.operand_loc[opn] |
| : recog_data.dup_loc[i - n_ops]); |
| rtx op = *loc; |
| enum reg_class cl = alternative_class (op_alt, opn); |
| |
| class du_head *prev_open; |
| |
| if (recog_data.operand_type[opn] != OP_OUT |
| || op_alt[opn].earlyclobber != earlyclobber) |
| continue; |
| |
| if (insn_info) |
| cur_operand = insn_info->op_info + i; |
| |
| prev_open = open_chains; |
| if (earlyclobber) |
| scan_rtx (insn, loc, cl, terminate_write, OP_OUT); |
| scan_rtx (insn, loc, cl, mark_write, OP_OUT); |
| |
| /* ??? Many targets have output constraints on the SET_DEST |
| of a call insn, which is stupid, since these are certainly |
| ABI defined hard registers. For these, and for asm operands |
| that originally referenced hard registers, we must record that |
| the chain cannot be renamed. */ |
| if (CALL_P (insn) |
| || (asm_noperands (PATTERN (insn)) > 0 |
| && REG_P (op) |
| && REGNO (op) == ORIGINAL_REGNO (op))) |
| { |
| if (prev_open != open_chains) |
| open_chains->cannot_rename = 1; |
| } |
| } |
| cur_operand = NULL; |
| } |
| |
| /* Build def/use chain. */ |
| |
| static bool |
| build_def_use (basic_block bb) |
| { |
| rtx_insn *insn; |
| unsigned HOST_WIDE_INT untracked_operands; |
| |
| fail_current_block = false; |
| |
| for (insn = BB_HEAD (bb); ; insn = NEXT_INSN (insn)) |
| { |
| if (NONDEBUG_INSN_P (insn)) |
| { |
| int n_ops; |
| rtx note; |
| rtx old_operands[MAX_RECOG_OPERANDS]; |
| rtx old_dups[MAX_DUP_OPERANDS]; |
| int i; |
| int predicated; |
| enum rtx_code set_code = SET; |
| enum rtx_code clobber_code = CLOBBER; |
| insn_rr_info *insn_info = NULL; |
| terminated_this_insn = NULL; |
| |
| /* Process the insn, determining its effect on the def-use |
| chains and live hard registers. We perform the following |
| steps with the register references in the insn, simulating |
| its effect: |
| (1) Deal with earlyclobber operands and CLOBBERs of non-operands |
| by creating chains and marking hard regs live. |
| (2) Any read outside an operand causes any chain it overlaps |
| with to be marked unrenamable. |
| (3) Any read inside an operand is added if there's already |
| an open chain for it. |
| (4) For any REG_DEAD note we find, close open chains that |
| overlap it. |
| (5) For any non-earlyclobber write we find, close open chains |
| that overlap it. |
| (6) For any non-earlyclobber write we find in an operand, make |
| a new chain or mark the hard register as live. |
| (7) For any REG_UNUSED, close any chains we just opened. |
| (8) For any REG_CFA_RESTORE or REG_CFA_REGISTER, kill any chain |
| containing its dest. |
| |
| We cannot deal with situations where we track a reg in one mode |
| and see a reference in another mode; these will cause the chain |
| to be marked unrenamable or even cause us to abort the entire |
| basic block. */ |
| |
| extract_constrain_insn (insn); |
| preprocess_constraints (insn); |
| const operand_alternative *op_alt = which_op_alt (); |
| n_ops = recog_data.n_operands; |
| untracked_operands = 0; |
| |
| if (insn_rr.exists ()) |
| { |
| insn_info = &insn_rr[INSN_UID (insn)]; |
| insn_info->op_info = XOBNEWVEC (&rename_obstack, operand_rr_info, |
| recog_data.n_operands); |
| memset (insn_info->op_info, 0, |
| sizeof (operand_rr_info) * recog_data.n_operands); |
| } |
| |
| /* Simplify the code below by promoting OP_OUT to OP_INOUT in |
| predicated instructions, but only for register operands |
| that are already tracked, so that we can create a chain |
| when the first SET makes a register live. */ |
| |
| predicated = GET_CODE (PATTERN (insn)) == COND_EXEC; |
| for (i = 0; i < n_ops; ++i) |
| { |
| rtx op = recog_data.operand[i]; |
| int matches = op_alt[i].matches; |
| if (matches >= 0 || op_alt[i].matched >= 0 |
| || (predicated && recog_data.operand_type[i] == OP_OUT)) |
| { |
| recog_data.operand_type[i] = OP_INOUT; |
| /* A special case to deal with instruction patterns that |
| have matching operands with different modes. If we're |
| not already tracking such a reg, we won't start here, |
| and we must instead make sure to make the operand visible |
| to the machinery that tracks hard registers. */ |
| machine_mode i_mode = recog_data.operand_mode[i]; |
| if (matches >= 0) |
| { |
| machine_mode matches_mode |
| = recog_data.operand_mode[matches]; |
| |
| if (maybe_ne (GET_MODE_SIZE (i_mode), |
| GET_MODE_SIZE (matches_mode)) |
| && !verify_reg_in_set (op, &live_in_chains)) |
| { |
| untracked_operands |= 1 << i; |
| untracked_operands |= 1 << matches; |
| } |
| } |
| } |
| #ifdef STACK_REGS |
| if (regstack_completed |
| && REG_P (op) |
| && IN_RANGE (REGNO (op), FIRST_STACK_REG, LAST_STACK_REG)) |
| untracked_operands |= 1 << i; |
| #endif |
| /* If there's an in-out operand with a register that is not |
| being tracked at all yet, open a chain. */ |
| if (recog_data.operand_type[i] == OP_INOUT |
| && !(untracked_operands & (1 << i)) |
| && REG_P (op) |
| && !verify_reg_tracked (op)) |
| create_new_chain (REGNO (op), REG_NREGS (op), NULL, NULL, |
| NO_REGS); |
| } |
| |
| if (fail_current_block) |
| break; |
| |
| /* Step 1a: Mark hard registers that are clobbered in this insn, |
| outside an operand, as live. */ |
| hide_operands (n_ops, old_operands, old_dups, untracked_operands, |
| false); |
| note_stores (insn, note_sets_clobbers, &clobber_code); |
| restore_operands (insn, n_ops, old_operands, old_dups); |
| |
| /* Step 1b: Begin new chains for earlyclobbered writes inside |
| operands. */ |
| record_out_operands (insn, true, insn_info); |
| |
| /* Step 2: Mark chains for which we have reads outside operands |
| as unrenamable. |
| We do this by munging all operands into PC, and closing |
| everything remaining. */ |
| |
| hide_operands (n_ops, old_operands, old_dups, untracked_operands, |
| false); |
| scan_rtx (insn, &PATTERN (insn), NO_REGS, mark_all_read, OP_IN); |
| restore_operands (insn, n_ops, old_operands, old_dups); |
| |
| /* Step 2B: Can't rename function call argument registers. */ |
| if (CALL_P (insn) && CALL_INSN_FUNCTION_USAGE (insn)) |
| scan_rtx (insn, &CALL_INSN_FUNCTION_USAGE (insn), |
| NO_REGS, mark_all_read, OP_IN); |
| |
| /* Step 2C: Can't rename asm operands that were originally |
| hard registers. */ |
| if (asm_noperands (PATTERN (insn)) > 0) |
| for (i = 0; i < n_ops; i++) |
| { |
| rtx *loc = recog_data.operand_loc[i]; |
| rtx op = *loc; |
| |
| if (REG_P (op) |
| && REGNO (op) == ORIGINAL_REGNO (op) |
| && (recog_data.operand_type[i] == OP_IN |
| || recog_data.operand_type[i] == OP_INOUT)) |
| scan_rtx (insn, loc, NO_REGS, mark_all_read, OP_IN); |
| } |
| |
| /* Step 3: Append to chains for reads inside operands. */ |
| for (i = 0; i < n_ops + recog_data.n_dups; i++) |
| { |
| int opn = i < n_ops ? i : recog_data.dup_num[i - n_ops]; |
| rtx *loc = (i < n_ops |
| ? recog_data.operand_loc[opn] |
| : recog_data.dup_loc[i - n_ops]); |
| enum reg_class cl = alternative_class (op_alt, opn); |
| enum op_type type = recog_data.operand_type[opn]; |
| |
| /* Don't scan match_operand here, since we've no reg class |
| information to pass down. Any operands that we could |
| substitute in will be represented elsewhere. */ |
| if (recog_data.constraints[opn][0] == '\0' |
| || untracked_operands & (1 << opn)) |
| continue; |
| |
| if (insn_info) |
| cur_operand = i == opn ? insn_info->op_info + i : NULL; |
| if (op_alt[opn].is_address) |
| scan_rtx_address (insn, loc, cl, mark_read, |
| VOIDmode, ADDR_SPACE_GENERIC); |
| else |
| scan_rtx (insn, loc, cl, mark_read, type); |
| } |
| cur_operand = NULL; |
| |
| /* Step 3B: Record updates for regs in REG_INC notes, and |
| source regs in REG_FRAME_RELATED_EXPR notes. */ |
| for (note = REG_NOTES (insn); note; note = XEXP (note, 1)) |
| if (REG_NOTE_KIND (note) == REG_INC |
| || REG_NOTE_KIND (note) == REG_FRAME_RELATED_EXPR) |
| scan_rtx (insn, &XEXP (note, 0), ALL_REGS, mark_read, |
| OP_INOUT); |
| |
| /* Step 4: Close chains for registers that die here, unless |
| the register is mentioned in a REG_UNUSED note. In that |
| case we keep the chain open until step #7 below to ensure |
| it conflicts with other output operands of this insn. |
| See PR 52573. Arguably the insn should not have both |
| notes; it has proven difficult to fix that without |
| other undesirable side effects. */ |
| for (note = REG_NOTES (insn); note; note = XEXP (note, 1)) |
| if (REG_NOTE_KIND (note) == REG_DEAD |
| && !find_regno_note (insn, REG_UNUSED, REGNO (XEXP (note, 0)))) |
| { |
| remove_from_hard_reg_set (&live_hard_regs, |
| GET_MODE (XEXP (note, 0)), |
| REGNO (XEXP (note, 0))); |
| scan_rtx (insn, &XEXP (note, 0), NO_REGS, terminate_dead, |
| OP_IN); |
| } |
| |
| /* Step 4B: If this is a call, any chain live at this point |
| requires a caller-saved reg. */ |
| if (CALL_P (insn)) |
| { |
| function_abi callee_abi = insn_callee_abi (insn); |
| class du_head *p; |
| for (p = open_chains; p; p = p->next_chain) |
| { |
| p->call_abis |= (1 << callee_abi.id ()); |
| p->call_clobber_mask |
| |= callee_abi.full_and_partial_reg_clobbers (); |
| p->hard_conflicts |= callee_abi.full_reg_clobbers (); |
| } |
| } |
| |
| /* Step 5: Close open chains that overlap writes. Similar to |
| step 2, we hide in-out operands, since we do not want to |
| close these chains. We also hide earlyclobber operands, |
| since we've opened chains for them in step 1, and earlier |
| chains they would overlap with must have been closed at |
| the previous insn at the latest, as such operands cannot |
| possibly overlap with any input operands. */ |
| |
| hide_operands (n_ops, old_operands, old_dups, untracked_operands, |
| true); |
| scan_rtx (insn, &PATTERN (insn), NO_REGS, terminate_write, OP_IN); |
| restore_operands (insn, n_ops, old_operands, old_dups); |
| |
| /* Step 6a: Mark hard registers that are set in this insn, |
| outside an operand, as live. */ |
| hide_operands (n_ops, old_operands, old_dups, untracked_operands, |
| false); |
| note_stores (insn, note_sets_clobbers, &set_code); |
| restore_operands (insn, n_ops, old_operands, old_dups); |
| |
| /* Step 6b: Begin new chains for writes inside operands. */ |
| record_out_operands (insn, false, insn_info); |
| |
| /* Step 6c: Record destination regs in REG_FRAME_RELATED_EXPR |
| notes for update. */ |
| for (note = REG_NOTES (insn); note; note = XEXP (note, 1)) |
| if (REG_NOTE_KIND (note) == REG_FRAME_RELATED_EXPR) |
| scan_rtx (insn, &XEXP (note, 0), ALL_REGS, mark_access, |
| OP_INOUT); |
| |
| /* Step 7: Close chains for registers that were never |
| really used here. */ |
| for (note = REG_NOTES (insn); note; note = XEXP (note, 1)) |
| if (REG_NOTE_KIND (note) == REG_UNUSED) |
| { |
| remove_from_hard_reg_set (&live_hard_regs, |
| GET_MODE (XEXP (note, 0)), |
| REGNO (XEXP (note, 0))); |
| scan_rtx (insn, &XEXP (note, 0), NO_REGS, terminate_dead, |
| OP_IN); |
| } |
| |
| /* Step 8: Kill the chains involving register restores. Those |
| should restore _that_ register. Similar for REG_CFA_REGISTER. */ |
| for (note = REG_NOTES (insn); note; note = XEXP (note, 1)) |
| if (REG_NOTE_KIND (note) == REG_CFA_RESTORE |
| || REG_NOTE_KIND (note) == REG_CFA_REGISTER) |
| { |
| rtx *x = &XEXP (note, 0); |
| if (!*x) |
| x = &PATTERN (insn); |
| if (GET_CODE (*x) == PARALLEL) |
| x = &XVECEXP (*x, 0, 0); |
| if (GET_CODE (*x) == SET) |
| x = &SET_DEST (*x); |
| scan_rtx (insn, x, NO_REGS, mark_all_read, OP_IN); |
| } |
| } |
| else if (DEBUG_BIND_INSN_P (insn) |
| && !VAR_LOC_UNKNOWN_P (INSN_VAR_LOCATION_LOC (insn))) |
| { |
| scan_rtx (insn, &INSN_VAR_LOCATION_LOC (insn), |
| ALL_REGS, mark_read, OP_IN); |
| } |
| if (insn == BB_END (bb)) |
| break; |
| } |
| |
| if (fail_current_block) |
| return false; |
| |
| return true; |
| } |
| |
| /* Initialize the register renamer. If INSN_INFO is true, ensure that |
| insn_rr is nonnull. */ |
| void |
| regrename_init (bool insn_info) |
| { |
| gcc_obstack_init (&rename_obstack); |
| insn_rr.create (0); |
| if (insn_info) |
| insn_rr.safe_grow_cleared (get_max_uid (), true); |
| } |
| |
| /* Free all global data used by the register renamer. */ |
| void |
| regrename_finish (void) |
| { |
| insn_rr.release (); |
| free_chain_data (); |
| obstack_free (&rename_obstack, NULL); |
| } |
| |
| /* Perform register renaming on the current function. */ |
| |
| static unsigned int |
| regrename_optimize (void) |
| { |
| df_set_flags (DF_LR_RUN_DCE); |
| df_note_add_problem (); |
| df_analyze (); |
| df_set_flags (DF_DEFER_INSN_RESCAN); |
| |
| regrename_init (false); |
| |
| regrename_analyze (NULL, false); |
| |
| rename_chains (); |
| |
| regrename_finish (); |
| |
| return 0; |
| } |
| |
| namespace { |
| |
| const pass_data pass_data_regrename = |
| { |
| RTL_PASS, /* type */ |
| "rnreg", /* name */ |
| OPTGROUP_NONE, /* optinfo_flags */ |
| TV_RENAME_REGISTERS, /* tv_id */ |
| 0, /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| TODO_df_finish, /* todo_flags_finish */ |
| }; |
| |
| class pass_regrename : public rtl_opt_pass |
| { |
| public: |
| pass_regrename (gcc::context *ctxt) |
| : rtl_opt_pass (pass_data_regrename, ctxt) |
| {} |
| |
| /* opt_pass methods: */ |
| virtual bool gate (function *) |
| { |
| return (optimize > 0 && (flag_rename_registers)); |
| } |
| |
| virtual unsigned int execute (function *) { return regrename_optimize (); } |
| |
| }; // class pass_regrename |
| |
| } // anon namespace |
| |
| rtl_opt_pass * |
| make_pass_regrename (gcc::context *ctxt) |
| { |
| return new pass_regrename (ctxt); |
| } |