| // RTL SSA routines for changing instructions -*- C++ -*- |
| // Copyright (C) 2020-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/>. |
| |
| #define INCLUDE_ALGORITHM |
| #define INCLUDE_FUNCTIONAL |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "backend.h" |
| #include "rtl.h" |
| #include "df.h" |
| #include "rtl-ssa.h" |
| #include "rtl-ssa/internals.h" |
| #include "rtl-ssa/internals.inl" |
| #include "target.h" |
| #include "predict.h" |
| #include "memmodel.h" // Needed by emit-rtl.h |
| #include "emit-rtl.h" |
| #include "cfghooks.h" |
| #include "cfgrtl.h" |
| |
| using namespace rtl_ssa; |
| |
| // See the comment above the declaration. |
| void |
| insn_change::print (pretty_printer *pp) const |
| { |
| if (m_is_deletion) |
| { |
| pp_string (pp, "deletion of "); |
| pp_insn (pp, m_insn); |
| } |
| else |
| { |
| pp_string (pp, "change to "); |
| pp_insn (pp, m_insn); |
| pp_newline_and_indent (pp, 2); |
| pp_string (pp, "~~~~~~~"); |
| |
| pp_newline_and_indent (pp, 0); |
| pp_string (pp, "new cost: "); |
| pp_decimal_int (pp, new_cost); |
| |
| pp_newline_and_indent (pp, 0); |
| pp_string (pp, "new uses:"); |
| pp_newline_and_indent (pp, 2); |
| pp_accesses (pp, new_uses); |
| pp_indentation (pp) -= 2; |
| |
| pp_newline_and_indent (pp, 0); |
| pp_string (pp, "new defs:"); |
| pp_newline_and_indent (pp, 2); |
| pp_accesses (pp, new_defs); |
| pp_indentation (pp) -= 2; |
| |
| pp_newline_and_indent (pp, 0); |
| pp_string (pp, "first insert-after candidate: "); |
| move_range.first->print_identifier_and_location (pp); |
| |
| pp_newline_and_indent (pp, 0); |
| pp_string (pp, "last insert-after candidate: "); |
| move_range.last->print_identifier_and_location (pp); |
| } |
| } |
| |
| // Return a copy of access_array ACCESSES, allocating it on the |
| // temporary obstack. |
| access_array |
| function_info::temp_access_array (access_array accesses) |
| { |
| if (accesses.empty ()) |
| return accesses; |
| |
| gcc_assert (obstack_object_size (&m_temp_obstack) == 0); |
| obstack_grow (&m_temp_obstack, accesses.begin (), accesses.size_bytes ()); |
| return { static_cast<access_info **> (obstack_finish (&m_temp_obstack)), |
| accesses.size () }; |
| } |
| |
| // See the comment above the declaration. |
| bool |
| function_info::verify_insn_changes (array_slice<insn_change *const> changes) |
| { |
| HARD_REG_SET defined_hard_regs, clobbered_hard_regs; |
| CLEAR_HARD_REG_SET (defined_hard_regs); |
| CLEAR_HARD_REG_SET (clobbered_hard_regs); |
| |
| insn_info *min_insn = m_first_insn; |
| for (insn_change *change : changes) |
| if (!change->is_deletion ()) |
| { |
| // Make sure that the changes can be kept in their current order |
| // while honoring all of the move ranges. |
| min_insn = later_insn (min_insn, change->move_range.first); |
| while (min_insn != change->insn () && !can_insert_after (min_insn)) |
| min_insn = min_insn->next_nondebug_insn (); |
| if (*min_insn > *change->move_range.last) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "no viable insn position assignment\n"); |
| return false; |
| } |
| |
| // If recog introduced new clobbers of a register as part of |
| // the matching process, make sure that they don't conflict |
| // with any other new definitions or uses of the register. |
| // (We have already checked that they don't conflict with |
| // unchanging definitions and uses.) |
| for (use_info *use : change->new_uses) |
| { |
| unsigned int regno = use->regno (); |
| if (HARD_REGISTER_NUM_P (regno) |
| && TEST_HARD_REG_BIT (clobbered_hard_regs, regno)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "register %d would be clobbered" |
| " while it is still live\n", regno); |
| return false; |
| } |
| } |
| for (def_info *def : change->new_defs) |
| { |
| unsigned int regno = def->regno (); |
| if (HARD_REGISTER_NUM_P (regno)) |
| { |
| if (def->m_is_temp) |
| { |
| // This is a clobber introduced by recog. |
| gcc_checking_assert (is_a<clobber_info *> (def)); |
| if (TEST_HARD_REG_BIT (defined_hard_regs, regno)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "conflicting definitions of" |
| " register %d\n", regno); |
| return false; |
| } |
| SET_HARD_REG_BIT (clobbered_hard_regs, regno); |
| } |
| else if (is_a<set_info *> (def)) |
| { |
| // REGNO now has a defined value. |
| SET_HARD_REG_BIT (defined_hard_regs, regno); |
| CLEAR_HARD_REG_BIT (clobbered_hard_regs, regno); |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| // See the comment above the declaration. |
| bool |
| rtl_ssa::changes_are_worthwhile (array_slice<insn_change *const> changes, |
| bool strict_p) |
| { |
| unsigned int old_cost = 0; |
| unsigned int new_cost = 0; |
| for (insn_change *change : changes) |
| { |
| old_cost += change->old_cost (); |
| if (!change->is_deletion ()) |
| { |
| basic_block cfg_bb = change->bb ()->cfg_bb (); |
| change->new_cost = insn_cost (change->rtl (), |
| optimize_bb_for_speed_p (cfg_bb)); |
| new_cost += change->new_cost; |
| } |
| } |
| bool ok_p = (strict_p ? new_cost < old_cost : new_cost <= old_cost); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "original cost"); |
| char sep = '='; |
| for (const insn_change *change : changes) |
| { |
| fprintf (dump_file, " %c %d", sep, change->old_cost ()); |
| sep = '+'; |
| } |
| fprintf (dump_file, ", replacement cost"); |
| sep = '='; |
| for (const insn_change *change : changes) |
| if (!change->is_deletion ()) |
| { |
| fprintf (dump_file, " %c %d", sep, change->new_cost); |
| sep = '+'; |
| } |
| fprintf (dump_file, "; %s\n", |
| ok_p ? "keeping replacement" : "rejecting replacement"); |
| } |
| if (!ok_p) |
| return false; |
| |
| return true; |
| } |
| |
| // Update the REG_NOTES of INSN, whose pattern has just been changed. |
| static void |
| update_notes (rtx_insn *insn) |
| { |
| for (rtx *note_ptr = ®_NOTES (insn); *note_ptr; ) |
| { |
| rtx note = *note_ptr; |
| bool keep_p = true; |
| switch (REG_NOTE_KIND (note)) |
| { |
| case REG_EQUAL: |
| case REG_EQUIV: |
| case REG_NOALIAS: |
| keep_p = (single_set (insn) != nullptr); |
| break; |
| |
| case REG_UNUSED: |
| case REG_DEAD: |
| // These notes are stale. We'll recompute REG_UNUSED notes |
| // after the update. |
| keep_p = false; |
| break; |
| |
| default: |
| break; |
| } |
| if (keep_p) |
| note_ptr = &XEXP (*note_ptr, 1); |
| else |
| { |
| *note_ptr = XEXP (*note_ptr, 1); |
| free_EXPR_LIST_node (note); |
| } |
| } |
| } |
| |
| // Pick a location for CHANGE's instruction and return the instruction |
| // after which it should be placed. |
| static insn_info * |
| choose_insn_placement (insn_change &change) |
| { |
| gcc_checking_assert (change.move_range); |
| |
| insn_info *insn = change.insn (); |
| insn_info *first = change.move_range.first; |
| insn_info *last = change.move_range.last; |
| |
| // Quick(ish) exit if there is only one possible choice. |
| if (first == last) |
| return first; |
| if (first == insn->prev_nondebug_insn () && last == insn) |
| return insn; |
| |
| // For now just use the closest valid choice to the original instruction. |
| // If the register usage has changed significantly, it might instead be |
| // better to try to take register pressure into account. |
| insn_info *closest = change.move_range.clamp_insn_to_range (insn); |
| while (closest != insn && !can_insert_after (closest)) |
| closest = closest->next_nondebug_insn (); |
| return closest; |
| } |
| |
| // Record any changes related to CHANGE that need to be queued for later. |
| void |
| function_info::possibly_queue_changes (insn_change &change) |
| { |
| insn_info *insn = change.insn (); |
| rtx_insn *rtl = insn->rtl (); |
| |
| // If the instruction could previously throw, we eventually need to call |
| // purge_dead_edges to check whether things have changed. |
| if (find_reg_note (rtl, REG_EH_REGION, nullptr)) |
| bitmap_set_bit (m_need_to_purge_dead_edges, insn->bb ()->index ()); |
| |
| auto needs_pending_update = [&]() |
| { |
| // If an instruction became a no-op without the pass explicitly |
| // deleting it, queue the deletion for later. Removing the |
| // instruction on the fly would require an update to all instructions |
| // that use the result of the move, which would be a potential source |
| // of quadraticness. Also, definitions shouldn't disappear under |
| // the pass's feet. |
| if (INSN_CODE (rtl) == NOOP_MOVE_INSN_CODE) |
| return true; |
| |
| // If any jumps got turned into unconditional jumps or nops, we need |
| // to update the CFG accordingly. |
| if (JUMP_P (rtl) |
| && (returnjump_p (rtl) || any_uncondjump_p (rtl)) |
| && !single_succ_p (insn->bb ()->cfg_bb ())) |
| return true; |
| |
| // If a previously conditional trap now always fires, execution |
| // terminates at that point. |
| rtx pattern = PATTERN (rtl); |
| if (GET_CODE (pattern) == TRAP_IF |
| && XEXP (pattern, 0) == const1_rtx) |
| return true; |
| |
| return false; |
| }; |
| |
| if (needs_pending_update () |
| && bitmap_set_bit (m_queued_insn_update_uids, insn->uid ())) |
| { |
| gcc_assert (!change.is_deletion ()); |
| m_queued_insn_updates.safe_push (insn); |
| } |
| } |
| |
| // Remove the instruction described by CHANGE from the underlying RTL |
| // and from the insn_info list. |
| static void |
| delete_insn (insn_change &change) |
| { |
| insn_info *insn = change.insn (); |
| rtx_insn *rtl = change.rtl (); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "deleting insn %d\n", insn->uid ()); |
| set_insn_deleted (rtl); |
| } |
| |
| // Move the RTL instruction associated with CHANGE so that it comes |
| // immediately after AFTER. |
| static void |
| move_insn (insn_change &change, insn_info *after) |
| { |
| rtx_insn *rtl = change.rtl (); |
| rtx_insn *after_rtl = after->rtl (); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "moving insn %d after insn %d\n", |
| INSN_UID (rtl), INSN_UID (after_rtl)); |
| |
| // At the moment we don't support moving instructions between EBBs, |
| // but this would be worth adding if it's useful. |
| insn_info *insn = change.insn (); |
| gcc_assert (after->ebb () == insn->ebb ()); |
| bb_info *bb = after->bb (); |
| basic_block cfg_bb = bb->cfg_bb (); |
| |
| if (insn->bb () != bb) |
| // Force DF to mark the old block as dirty. |
| df_insn_delete (rtl); |
| ::remove_insn (rtl); |
| ::add_insn_after (rtl, after_rtl, cfg_bb); |
| } |
| |
| // The instruction associated with CHANGE is being changed in-place. |
| // Update the DF information for its new pattern. |
| static void |
| update_insn_in_place (insn_change &change) |
| { |
| insn_info *insn = change.insn (); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "updating insn %d in-place\n", insn->uid ()); |
| df_insn_rescan (change.rtl ()); |
| } |
| |
| // Finalize the new list of definitions and uses in CHANGE, removing |
| // any uses and definitions that are no longer needed, and converting |
| // pending clobbers into actual definitions. |
| void |
| function_info::finalize_new_accesses (insn_change &change) |
| { |
| insn_info *insn = change.insn (); |
| |
| // Get a list of all the things that the instruction now references. |
| vec_rtx_properties properties; |
| properties.add_insn (insn->rtl (), true); |
| |
| // Build up the new list of definitions. |
| for (rtx_obj_reference ref : properties.refs ()) |
| if (ref.is_write ()) |
| { |
| def_info *def = find_access (change.new_defs, ref.regno); |
| gcc_assert (def); |
| if (def->m_is_temp) |
| { |
| // At present, the only temporary instruction definitions we |
| // create are clobbers, such as those added during recog. |
| gcc_assert (is_a<clobber_info *> (def)); |
| def = allocate<clobber_info> (change.insn (), ref.regno); |
| } |
| else if (!def->m_has_been_superceded) |
| { |
| // This is a second or subsequent definition. |
| // See function_info::record_def for a discussion of when |
| // this can happen. |
| def->record_reference (ref, false); |
| continue; |
| } |
| else |
| { |
| def->m_has_been_superceded = false; |
| |
| // Clobbers can move around, so remove them from their current |
| // position and them back in their final position. |
| // |
| // At the moment, we don't allow sets to move relative to other |
| // definitions of the same resource, so we can leave those where |
| // they are. It might be useful to relax this in future. |
| // The main complication is that removing a set would potentially |
| // fuse two adjoining clobber_groups, and adding the set back |
| // would require the group to be split again. |
| if (is_a<clobber_info *> (def)) |
| remove_def (def); |
| else if (ref.is_reg ()) |
| def->set_mode (ref.mode); |
| def->set_insn (insn); |
| } |
| def->record_reference (ref, true); |
| m_temp_defs.safe_push (def); |
| } |
| |
| // Also keep any explicitly-recorded call clobbers, which are deliberately |
| // excluded from the vec_rtx_properties. Calls shouldn't move, so we can |
| // keep the definitions in their current position. |
| for (def_info *def : change.new_defs) |
| if (def->m_has_been_superceded && def->is_call_clobber ()) |
| { |
| def->m_has_been_superceded = false; |
| def->set_insn (insn); |
| m_temp_defs.safe_push (def); |
| } |
| |
| // Install the new list of definitions in CHANGE. |
| sort_accesses (m_temp_defs); |
| access_array accesses = temp_access_array (m_temp_defs); |
| change.new_defs = def_array (accesses); |
| m_temp_defs.truncate (0); |
| |
| // Create temporary copies of use_infos that are already attached to |
| // other insns, which could happen if the uses come from unchanging |
| // insns or if they have been used by earlier changes. Doing this |
| // makes it easier to detect multiple reads below. |
| auto *unshared_uses_base = XOBNEWVEC (&m_temp_obstack, access_info *, |
| change.new_uses.size ()); |
| unsigned int i = 0; |
| for (use_info *use : change.new_uses) |
| { |
| if (!use->m_has_been_superceded) |
| { |
| use = allocate_temp<use_info> (insn, use->resource (), use->def ()); |
| use->m_has_been_superceded = true; |
| use->m_is_temp = true; |
| } |
| unshared_uses_base[i++] = use; |
| } |
| auto unshared_uses = use_array (unshared_uses_base, change.new_uses.size ()); |
| |
| // Add (possibly temporary) uses to m_temp_uses for each resource. |
| // If there are multiple references to the same resource, aggregate |
| // information in the modes and flags. |
| for (rtx_obj_reference ref : properties.refs ()) |
| if (ref.is_read ()) |
| { |
| unsigned int regno = ref.regno; |
| machine_mode mode = ref.is_reg () ? ref.mode : BLKmode; |
| use_info *use = find_access (unshared_uses, ref.regno); |
| gcc_assert (use); |
| if (use->m_has_been_superceded) |
| { |
| // This is the first reference to the resource. |
| bool is_temp = use->m_is_temp; |
| *use = use_info (insn, resource_info { mode, regno }, use->def ()); |
| use->m_is_temp = is_temp; |
| use->record_reference (ref, true); |
| m_temp_uses.safe_push (use); |
| } |
| else |
| { |
| // Record the mode of the largest use. The choice is arbitrary if |
| // the instruction (unusually) references the same register in two |
| // different but equal-sized modes. |
| if (HARD_REGISTER_NUM_P (regno) |
| && partial_subreg_p (use->mode (), mode)) |
| use->set_mode (mode); |
| use->record_reference (ref, false); |
| } |
| } |
| |
| // Replace any temporary uses and definitions with real ones. |
| for (unsigned int i = 0; i < m_temp_uses.length (); ++i) |
| { |
| auto *use = as_a<use_info *> (m_temp_uses[i]); |
| if (use->m_is_temp) |
| { |
| m_temp_uses[i] = use = allocate<use_info> (*use); |
| use->m_is_temp = false; |
| set_info *def = use->def (); |
| // Handle cases in which the value was previously not used |
| // within the block. |
| if (def && def->m_is_temp) |
| { |
| phi_info *phi = as_a<phi_info *> (def); |
| gcc_assert (phi->is_degenerate ()); |
| phi = create_degenerate_phi (phi->ebb (), phi->input_value (0)); |
| use->set_def (phi); |
| } |
| } |
| } |
| |
| // Install the new list of definitions in CHANGE. |
| sort_accesses (m_temp_uses); |
| change.new_uses = use_array (temp_access_array (m_temp_uses)); |
| m_temp_uses.truncate (0); |
| |
| // Record the new instruction-wide properties. |
| insn->set_properties (properties); |
| } |
| |
| // Copy information from CHANGE to its underlying insn_info, given that |
| // the insn_info has already been placed appropriately. |
| void |
| function_info::apply_changes_to_insn (insn_change &change) |
| { |
| insn_info *insn = change.insn (); |
| if (change.is_deletion ()) |
| { |
| insn->set_accesses (nullptr, 0, 0); |
| return; |
| } |
| |
| // Copy the cost. |
| insn->set_cost (change.new_cost); |
| |
| // Add all clobbers. Sets and call clobbers never move relative to |
| // other definitions, so are OK as-is. |
| for (def_info *def : change.new_defs) |
| if (is_a<clobber_info *> (def) && !def->is_call_clobber ()) |
| add_def (def); |
| |
| // Add all uses, now that their position is final. |
| for (use_info *use : change.new_uses) |
| add_use (use); |
| |
| // Copy the uses and definitions. |
| unsigned int num_defs = change.new_defs.size (); |
| unsigned int num_uses = change.new_uses.size (); |
| if (num_defs + num_uses <= insn->num_defs () + insn->num_uses ()) |
| insn->copy_accesses (change.new_defs, change.new_uses); |
| else |
| { |
| access_array_builder builder (&m_obstack); |
| builder.reserve (num_defs + num_uses); |
| |
| for (def_info *def : change.new_defs) |
| builder.quick_push (def); |
| for (use_info *use : change.new_uses) |
| builder.quick_push (use); |
| |
| insn->set_accesses (builder.finish ().begin (), num_defs, num_uses); |
| } |
| |
| add_reg_unused_notes (insn); |
| } |
| |
| // Add a temporary placeholder instruction after AFTER. |
| insn_info * |
| function_info::add_placeholder_after (insn_info *after) |
| { |
| insn_info *insn = allocate_temp<insn_info> (after->bb (), nullptr, -1); |
| add_insn_after (insn, after); |
| return insn; |
| } |
| |
| // See the comment above the declaration. |
| void |
| function_info::change_insns (array_slice<insn_change *> changes) |
| { |
| auto watermark = temp_watermark (); |
| |
| insn_info *min_insn = m_first_insn; |
| for (insn_change *change : changes) |
| { |
| // Tentatively mark all the old uses and definitions for deletion. |
| for (use_info *use : change->old_uses ()) |
| { |
| use->m_has_been_superceded = true; |
| remove_use (use); |
| } |
| for (def_info *def : change->old_defs ()) |
| def->m_has_been_superceded = true; |
| |
| if (!change->is_deletion ()) |
| { |
| // Remove any notes that are no longer relevant. |
| update_notes (change->rtl ()); |
| |
| // Make sure that the placement of this instruction would still |
| // leave room for previous instructions. |
| change->move_range = move_later_than (change->move_range, min_insn); |
| if (!canonicalize_move_range (change->move_range, change->insn ())) |
| // verify_insn_changes is supposed to make sure that this holds. |
| gcc_unreachable (); |
| min_insn = later_insn (min_insn, change->move_range.first); |
| } |
| } |
| |
| // Walk backwards through the changes, allocating specific positions |
| // to each one. Update the underlying RTL and its associated DF |
| // information. |
| insn_info *following_insn = nullptr; |
| auto_vec<insn_info *, 16> placeholders; |
| placeholders.safe_grow_cleared (changes.size ()); |
| for (unsigned int i = changes.size (); i-- > 0;) |
| { |
| insn_change &change = *changes[i]; |
| insn_info *placeholder = nullptr; |
| possibly_queue_changes (change); |
| if (change.is_deletion ()) |
| delete_insn (change); |
| else |
| { |
| // Make sure that this instruction comes before later ones. |
| if (following_insn) |
| { |
| change.move_range = move_earlier_than (change.move_range, |
| following_insn); |
| if (!canonicalize_move_range (change.move_range, |
| change.insn ())) |
| // verify_insn_changes is supposed to make sure that this |
| // holds. |
| gcc_unreachable (); |
| } |
| |
| // Decide which instruction INSN should go after. |
| insn_info *after = choose_insn_placement (change); |
| |
| // If INSN is moving, insert a placeholder insn_info at the |
| // new location. We can't move INSN itself yet because it |
| // might still be referenced by earlier move ranges. |
| insn_info *insn = change.insn (); |
| if (after == insn || after == insn->prev_nondebug_insn ()) |
| { |
| update_insn_in_place (change); |
| following_insn = insn; |
| } |
| else |
| { |
| move_insn (change, after); |
| placeholder = add_placeholder_after (after); |
| following_insn = placeholder; |
| } |
| |
| // Finalize the new list of accesses for the change. Don't install |
| // them yet, so that we still have access to the old lists below. |
| finalize_new_accesses (change); |
| } |
| placeholders[i] = placeholder; |
| } |
| |
| // Remove all definitions that are no longer needed. After the above, |
| // such definitions should no longer have any registered users. |
| // |
| // In particular, this means that consumers must handle debug |
| // instructions before removing a set. |
| for (insn_change *change : changes) |
| for (def_info *def : change->old_defs ()) |
| if (def->m_has_been_superceded) |
| { |
| auto *set = dyn_cast<set_info *> (def); |
| gcc_assert (!set || !set->has_any_uses ()); |
| remove_def (def); |
| } |
| |
| // Move the insn_infos to their new locations. |
| for (unsigned int i = 0; i < changes.size (); ++i) |
| { |
| insn_change &change = *changes[i]; |
| insn_info *insn = change.insn (); |
| if (change.is_deletion ()) |
| remove_insn (insn); |
| else if (insn_info *placeholder = placeholders[i]) |
| { |
| // Check if earlier movements turned a move into a no-op. |
| if (placeholder->prev_nondebug_insn () == insn |
| || placeholder->next_nondebug_insn () == insn) |
| { |
| remove_insn (placeholder); |
| placeholders[i] = nullptr; |
| } |
| else |
| { |
| // Remove the placeholder first so that we have a wider range of |
| // program points when inserting INSN. |
| insn_info *after = placeholder->prev_any_insn (); |
| remove_insn (insn); |
| remove_insn (placeholder); |
| insn->set_bb (after->bb ()); |
| add_insn_after (insn, after); |
| } |
| } |
| } |
| |
| // Finally apply the changes to the underlying insn_infos. |
| for (insn_change *change : changes) |
| apply_changes_to_insn (*change); |
| } |
| |
| // See the comment above the declaration. |
| void |
| function_info::change_insn (insn_change &change) |
| { |
| insn_change *changes[] = { &change }; |
| return change_insns (changes); |
| } |
| |
| // Try to adjust CHANGE so that its pattern can include clobber rtx CLOBBER. |
| // Return true on success. |
| // |
| // ADD_REGNO_CLOBBER is a specialization of function_info::add_regno_clobber |
| // for a specific caller-provided predicate. |
| static bool |
| add_clobber (insn_change &change, add_regno_clobber_fn add_regno_clobber, |
| rtx clobber) |
| { |
| rtx pat = PATTERN (change.rtl ()); |
| gcc_assert (GET_CODE (clobber) == CLOBBER); |
| rtx dest = XEXP (clobber, 0); |
| if (GET_CODE (dest) == SCRATCH) |
| { |
| if (reload_completed) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| // ??? Maybe we could try to do some RA here? |
| fprintf (dump_file, "instruction requires a scratch" |
| " after reload:\n"); |
| print_rtl_single (dump_file, pat); |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| gcc_assert (REG_P (dest)); |
| for (unsigned int regno = REGNO (dest); regno != END_REGNO (dest); ++regno) |
| if (!add_regno_clobber (change, regno)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "cannot clobber live register %d in:\n", |
| regno); |
| print_rtl_single (dump_file, pat); |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| // Try to recognize the new form of the insn associated with CHANGE, |
| // adding any clobbers that are necessary to make the instruction match |
| // an .md pattern. Return true on success. |
| // |
| // ADD_REGNO_CLOBBER is a specialization of function_info::add_regno_clobber |
| // for a specific caller-provided predicate. |
| static bool |
| recog_level2 (insn_change &change, add_regno_clobber_fn add_regno_clobber) |
| { |
| insn_change_watermark insn_watermark; |
| rtx_insn *rtl = change.rtl (); |
| rtx pat = PATTERN (rtl); |
| int num_clobbers = 0; |
| int icode = -1; |
| bool asm_p = asm_noperands (pat) >= 0; |
| if (asm_p) |
| { |
| if (!check_asm_operands (pat)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "failed to match this asm instruction:\n"); |
| print_rtl_single (dump_file, pat); |
| } |
| return false; |
| } |
| } |
| else if (noop_move_p (rtl)) |
| { |
| INSN_CODE (rtl) = NOOP_MOVE_INSN_CODE; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "instruction becomes a no-op:\n"); |
| print_rtl_single (dump_file, pat); |
| } |
| insn_watermark.keep (); |
| return true; |
| } |
| else |
| { |
| icode = ::recog (pat, rtl, &num_clobbers); |
| if (icode < 0) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "failed to match this instruction:\n"); |
| print_rtl_single (dump_file, pat); |
| } |
| return false; |
| } |
| } |
| |
| auto prev_new_defs = change.new_defs; |
| auto prev_move_range = change.move_range; |
| if (num_clobbers > 0) |
| { |
| // ??? It would be good to have a way of recycling the rtxes on failure, |
| // but any attempt to cache old PARALLELs would at best be a half |
| // measure, since add_clobbers would still generate fresh clobbers |
| // each time. It would be better to have a more general recycling |
| // mechanism that all rtx passes can use. |
| rtvec newvec; |
| int oldlen; |
| if (GET_CODE (pat) == PARALLEL) |
| { |
| oldlen = XVECLEN (pat, 0); |
| newvec = rtvec_alloc (num_clobbers + oldlen); |
| for (int i = 0; i < oldlen; ++i) |
| RTVEC_ELT (newvec, i) = XVECEXP (pat, 0, i); |
| } |
| else |
| { |
| oldlen = 1; |
| newvec = rtvec_alloc (num_clobbers + oldlen); |
| RTVEC_ELT (newvec, 0) = pat; |
| } |
| rtx newpat = gen_rtx_PARALLEL (VOIDmode, newvec); |
| add_clobbers (newpat, icode); |
| validate_change (rtl, &PATTERN (rtl), newpat, true); |
| for (int i = 0; i < num_clobbers; ++i) |
| if (!add_clobber (change, add_regno_clobber, |
| XVECEXP (newpat, 0, oldlen + i))) |
| { |
| change.new_defs = prev_new_defs; |
| change.move_range = prev_move_range; |
| return false; |
| } |
| |
| pat = newpat; |
| } |
| |
| INSN_CODE (rtl) = icode; |
| if (reload_completed) |
| { |
| extract_insn (rtl); |
| if (!constrain_operands (1, get_preferred_alternatives (rtl))) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| if (asm_p) |
| fprintf (dump_file, "asm does not match its constraints:\n"); |
| else if (const char *name = get_insn_name (icode)) |
| fprintf (dump_file, "instruction does not match the" |
| " constraints for %s:\n", name); |
| else |
| fprintf (dump_file, "instruction does not match its" |
| " constraints:\n"); |
| print_rtl_single (dump_file, pat); |
| } |
| change.new_defs = prev_new_defs; |
| change.move_range = prev_move_range; |
| return false; |
| } |
| } |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| const char *name; |
| if (!asm_p && (name = get_insn_name (icode))) |
| fprintf (dump_file, "successfully matched this instruction " |
| "to %s:\n", name); |
| else |
| fprintf (dump_file, "successfully matched this instruction:\n"); |
| print_rtl_single (dump_file, pat); |
| } |
| |
| insn_watermark.keep (); |
| return true; |
| } |
| |
| // Try to recognize the new form of the insn associated with CHANGE, |
| // adding and removing clobbers as necessary to make the instruction |
| // match an .md pattern. Return true on success, otherwise leave |
| // CHANGE as it was on entry. |
| // |
| // ADD_REGNO_CLOBBER is a specialization of function_info::add_regno_clobber |
| // for a specific caller-provided predicate. |
| bool |
| rtl_ssa::recog_internal (insn_change &change, |
| add_regno_clobber_fn add_regno_clobber) |
| { |
| // Accept all changes to debug instructions. |
| insn_info *insn = change.insn (); |
| if (insn->is_debug_insn ()) |
| return true; |
| |
| rtx_insn *rtl = insn->rtl (); |
| rtx pat = PATTERN (rtl); |
| if (GET_CODE (pat) == PARALLEL && asm_noperands (pat) < 0) |
| { |
| // Try to remove trailing (clobber (scratch)) rtxes, since the new form |
| // of the instruction might not need those scratches. recog will add |
| // back any that are needed. |
| int len = XVECLEN (pat, 0); |
| int new_len = len; |
| while (new_len > 0 |
| && GET_CODE (XVECEXP (pat, 0, new_len - 1)) == CLOBBER |
| && GET_CODE (XEXP (XVECEXP (pat, 0, new_len - 1), 0)) == SCRATCH) |
| new_len -= 1; |
| |
| int old_num_changes = num_validated_changes (); |
| validate_change_xveclen (rtl, &PATTERN (rtl), new_len, true); |
| if (recog_level2 (change, add_regno_clobber)) |
| return true; |
| cancel_changes (old_num_changes); |
| |
| // Try to remove all trailing clobbers. For example, a pattern that |
| // used to clobber the flags might no longer need to do so. |
| int prev_len = new_len; |
| while (new_len > 0 |
| && GET_CODE (XVECEXP (pat, 0, new_len - 1)) == CLOBBER) |
| new_len -= 1; |
| if (new_len != prev_len) |
| { |
| validate_change_xveclen (rtl, &PATTERN (rtl), new_len, true); |
| if (recog_level2 (change, add_regno_clobber)) |
| return true; |
| cancel_changes (old_num_changes); |
| } |
| return false; |
| } |
| |
| return recog_level2 (change, add_regno_clobber); |
| } |
| |
| // See the comment above the declaration. |
| bool |
| function_info::perform_pending_updates () |
| { |
| bool changed_cfg = false; |
| bool changed_jumps = false; |
| for (insn_info *insn : m_queued_insn_updates) |
| { |
| rtx_insn *rtl = insn->rtl (); |
| if (JUMP_P (rtl)) |
| { |
| if (INSN_CODE (rtl) == NOOP_MOVE_INSN_CODE) |
| { |
| ::delete_insn (rtl); |
| bitmap_set_bit (m_need_to_purge_dead_edges, |
| insn->bb ()->index ()); |
| } |
| else if (returnjump_p (rtl) || any_uncondjump_p (rtl)) |
| { |
| mark_jump_label (PATTERN (rtl), rtl, 0); |
| update_cfg_for_uncondjump (rtl); |
| changed_cfg = true; |
| changed_jumps = true; |
| } |
| } |
| else if (INSN_CODE (rtl) == NOOP_MOVE_INSN_CODE) |
| ::delete_insn (rtl); |
| else |
| { |
| rtx pattern = PATTERN (rtl); |
| if (GET_CODE (pattern) == TRAP_IF |
| && XEXP (pattern, 0) == const1_rtx) |
| { |
| remove_edge (split_block (BLOCK_FOR_INSN (rtl), rtl)); |
| emit_barrier_after_bb (BLOCK_FOR_INSN (rtl)); |
| changed_cfg = true; |
| } |
| } |
| } |
| |
| unsigned int index; |
| bitmap_iterator bi; |
| EXECUTE_IF_SET_IN_BITMAP (m_need_to_purge_dead_edges, 0, index, bi) |
| if (purge_dead_edges (BASIC_BLOCK_FOR_FN (m_fn, index))) |
| changed_cfg = true; |
| |
| if (changed_jumps) |
| // This uses its own timevar internally, so we don't need to push |
| // one ourselves. |
| rebuild_jump_labels (get_insns ()); |
| |
| bitmap_clear (m_need_to_purge_dead_edges); |
| bitmap_clear (m_queued_insn_update_uids); |
| m_queued_insn_updates.truncate (0); |
| |
| if (changed_cfg) |
| { |
| free_dominance_info (CDI_DOMINATORS); |
| free_dominance_info (CDI_POST_DOMINATORS); |
| } |
| |
| return changed_cfg; |
| } |
| |
| // Print a description of CHANGE to PP. |
| void |
| rtl_ssa::pp_insn_change (pretty_printer *pp, const insn_change &change) |
| { |
| change.print (pp); |
| } |
| |
| // Print a description of CHANGE to FILE. |
| void |
| dump (FILE *file, const insn_change &change) |
| { |
| dump_using (file, pp_insn_change, change); |
| } |
| |
| // Debug interface to the dump routine above. |
| void debug (const insn_change &x) { dump (stderr, x); } |