aboutsummaryrefslogtreecommitdiff
path: root/gcc/rtl-ssa/changes.cc
diff options
context:
space:
mode:
authorThomas Koenig <tkoenig@gcc.gnu.org>2021-01-03 21:40:04 +0100
committerThomas Koenig <tkoenig@gcc.gnu.org>2021-01-03 21:40:04 +0100
commitafae4a55ccaa0de95ea11e5f634084db6ab2f444 (patch)
treed632cc867d10410ba9fb750523be790b86846ac4 /gcc/rtl-ssa/changes.cc
parent9d9a82ec8478ff52c7a9d61f58cd2a7b6295b5f9 (diff)
parentd2eb616a0f7bea78164912aa438c29fe1ef5774a (diff)
downloadgcc-afae4a55ccaa0de95ea11e5f634084db6ab2f444.zip
gcc-afae4a55ccaa0de95ea11e5f634084db6ab2f444.tar.gz
gcc-afae4a55ccaa0de95ea11e5f634084db6ab2f444.tar.bz2
Merge branch 'master' into devel/coarray_native
Diffstat (limited to 'gcc/rtl-ssa/changes.cc')
-rw-r--r--gcc/rtl-ssa/changes.cc1025
1 files changed, 1025 insertions, 0 deletions
diff --git a/gcc/rtl-ssa/changes.cc b/gcc/rtl-ssa/changes.cc
new file mode 100644
index 0000000..1885a80
--- /dev/null
+++ b/gcc/rtl-ssa/changes.cc
@@ -0,0 +1,1025 @@
+// RTL SSA routines for changing instructions -*- C++ -*-
+// Copyright (C) 2020 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.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 = &REG_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.
+ 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 never moved relative to other definitions,
+ // so are OK as-is.
+ for (def_info *def : change.new_defs)
+ if (is_a<clobber_info *> (def))
+ 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); }