mirror of
https://github.com/autc04/Retro68.git
synced 2024-12-03 10:49:58 +00:00
336 lines
12 KiB
C++
336 lines
12 KiB
C++
// RTL SSA utilities relating to instruction movement -*- C++ -*-
|
|
// Copyright (C) 2020-2022 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/>.
|
|
|
|
namespace rtl_ssa {
|
|
|
|
// Restrict movement range RANGE so that the instruction is placed later
|
|
// than INSN. (The movement range is the range of instructions after which
|
|
// an instruction can be placed.)
|
|
inline insn_range_info
|
|
move_later_than (insn_range_info range, insn_info *insn)
|
|
{
|
|
return { later_insn (range.first, insn), range.last };
|
|
}
|
|
|
|
// Restrict movement range RANGE so that the instruction is placed no earlier
|
|
// than INSN. (The movement range is the range of instructions after which
|
|
// an instruction can be placed.)
|
|
inline insn_range_info
|
|
move_no_earlier_than (insn_range_info range, insn_info *insn)
|
|
{
|
|
insn_info *first = later_insn (range.first, insn->prev_nondebug_insn ());
|
|
return { first, range.last };
|
|
}
|
|
|
|
// Restrict movement range RANGE so that the instruction is placed no later
|
|
// than INSN. (The movement range is the range of instructions after which
|
|
// an instruction can be placed.)
|
|
inline insn_range_info
|
|
move_no_later_than (insn_range_info range, insn_info *insn)
|
|
{
|
|
return { range.first, earlier_insn (range.last, insn) };
|
|
}
|
|
|
|
// Restrict movement range RANGE so that the instruction is placed earlier
|
|
// than INSN. (The movement range is the range of instructions after which
|
|
// an instruction can be placed.)
|
|
inline insn_range_info
|
|
move_earlier_than (insn_range_info range, insn_info *insn)
|
|
{
|
|
insn_info *last = earlier_insn (range.last, insn->prev_nondebug_insn ());
|
|
return { range.first, last };
|
|
}
|
|
|
|
// Return true if it is possible to insert a new instruction after INSN.
|
|
inline bool
|
|
can_insert_after (insn_info *insn)
|
|
{
|
|
return insn->is_bb_head () || (insn->is_real () && !insn->is_jump ());
|
|
}
|
|
|
|
// Try to restrict move range MOVE_RANGE so that it is possible to
|
|
// insert INSN after both of the end points. Return true on success,
|
|
// otherwise leave MOVE_RANGE in an invalid state.
|
|
inline bool
|
|
canonicalize_move_range (insn_range_info &move_range, insn_info *insn)
|
|
{
|
|
while (move_range.first != insn && !can_insert_after (move_range.first))
|
|
move_range.first = move_range.first->next_nondebug_insn ();
|
|
while (move_range.last != insn && !can_insert_after (move_range.last))
|
|
move_range.last = move_range.last->prev_nondebug_insn ();
|
|
return bool (move_range);
|
|
}
|
|
|
|
// Try to restrict movement range MOVE_RANGE of INSN so that it can set
|
|
// or clobber REGNO. Assume that if:
|
|
//
|
|
// - an instruction I2 contains another access A to REGNO; and
|
|
// - IGNORE (I2) is true
|
|
//
|
|
// then either:
|
|
//
|
|
// - A will be removed; or
|
|
// - something will ensure that the new definition of REGNO does not
|
|
// interfere with A, without this having to be enforced by I1's move range.
|
|
//
|
|
// Return true on success, otherwise leave MOVE_RANGE in an invalid state.
|
|
//
|
|
// This function only works correctly for instructions that remain within
|
|
// the same extended basic block.
|
|
template<typename IgnorePredicate>
|
|
bool
|
|
restrict_movement_for_dead_range (insn_range_info &move_range,
|
|
unsigned int regno, insn_info *insn,
|
|
IgnorePredicate ignore)
|
|
{
|
|
// Find a definition at or neighboring INSN.
|
|
resource_info resource = full_register (regno);
|
|
def_lookup dl = crtl->ssa->find_def (resource, insn);
|
|
|
|
def_info *prev = dl.last_def_of_prev_group ();
|
|
ebb_info *ebb = insn->ebb ();
|
|
if (!prev || prev->ebb () != ebb)
|
|
{
|
|
// REGNO is not defined or used in EBB before INSN, but it
|
|
// might be live on entry. To keep complexity under control,
|
|
// handle only these cases:
|
|
//
|
|
// - If the register is not live on entry to EBB, the register is
|
|
// free from the start of EBB to the first definition in EBB.
|
|
//
|
|
// - Otherwise, if the register is live on entry to BB, refuse
|
|
// to allocate the register. We could in principle try to move
|
|
// the instruction to later blocks in the EBB, but it's rarely
|
|
// worth the effort, and could lead to linear complexity.
|
|
//
|
|
// - Otherwise, don't allow INSN to move earlier than its current
|
|
// block. Again, we could in principle look backwards to find where
|
|
// REGNO dies, but it's rarely worth the effort.
|
|
bb_info *bb = insn->bb ();
|
|
insn_info *limit;
|
|
if (!bitmap_bit_p (DF_LR_IN (ebb->first_bb ()->cfg_bb ()), regno))
|
|
limit = ebb->phi_insn ();
|
|
else if (bitmap_bit_p (DF_LR_IN (bb->cfg_bb ()), regno))
|
|
return false;
|
|
else
|
|
limit = bb->head_insn ();
|
|
move_range = move_later_than (move_range, limit);
|
|
}
|
|
else
|
|
{
|
|
// Stop the instruction moving beyond the previous relevant access
|
|
// to REGNO.
|
|
access_info *prev_access
|
|
= last_access_ignoring (prev, ignore_clobbers::YES, ignore);
|
|
if (prev_access)
|
|
move_range = move_later_than (move_range, access_insn (prev_access));
|
|
}
|
|
|
|
// Stop the instruction moving beyond the next relevant definition of REGNO.
|
|
def_info *next = dl.matching_set_or_first_def_of_next_group ();
|
|
next = first_def_ignoring (next, ignore_clobbers::YES, ignore);
|
|
if (next)
|
|
move_range = move_earlier_than (move_range, next->insn ());
|
|
|
|
return canonicalize_move_range (move_range, insn);
|
|
}
|
|
|
|
// Try to restrict movement range MOVE_RANGE so that it is possible for the
|
|
// instruction being moved ("instruction I1") to perform all the definitions
|
|
// in DEFS while still preserving dependencies between those definitions
|
|
// and surrounding instructions. Assume that if:
|
|
//
|
|
// - DEFS contains a definition D of resource R;
|
|
// - an instruction I2 contains another access A to R; and
|
|
// - IGNORE (I2) is true
|
|
//
|
|
// then either:
|
|
//
|
|
// - A will be removed; or
|
|
// - something will ensure that D and A maintain their current order,
|
|
// without this having to be enforced by I1's move range.
|
|
//
|
|
// Return true on success, otherwise leave MOVE_RANGE in an invalid state.
|
|
//
|
|
// This function only works correctly for instructions that remain within
|
|
// the same extended basic block.
|
|
template<typename IgnorePredicate>
|
|
bool
|
|
restrict_movement_for_defs_ignoring (insn_range_info &move_range,
|
|
def_array defs, IgnorePredicate ignore)
|
|
{
|
|
for (def_info *def : defs)
|
|
{
|
|
// If the definition is a clobber, we can move it with respect
|
|
// to other clobbers.
|
|
//
|
|
// ??? We could also do this if a definition and all its uses
|
|
// are being moved at once.
|
|
bool is_clobber = is_a<clobber_info *> (def);
|
|
|
|
// Search back for the first unfiltered use or definition of the
|
|
// same resource.
|
|
access_info *access;
|
|
access = prev_access_ignoring (def, ignore_clobbers (is_clobber),
|
|
ignore);
|
|
if (access)
|
|
move_range = move_later_than (move_range, access_insn (access));
|
|
|
|
// Search forward for the first unfiltered use of DEF,
|
|
// or the first unfiltered definition that follows DEF.
|
|
//
|
|
// We don't need to consider uses of following definitions, since
|
|
// if IGNORE (D->insn ()) is true for some definition D, the caller
|
|
// is guarantees that either
|
|
//
|
|
// - D will be removed, and thus its uses will be removed; or
|
|
// - D will occur after DEF, and thus D's uses will also occur
|
|
// after DEF.
|
|
//
|
|
// This is purely a simplification: we could also process D's uses,
|
|
// but we don't need to.
|
|
access = next_access_ignoring (def, ignore_clobbers (is_clobber),
|
|
ignore);
|
|
if (access)
|
|
move_range = move_earlier_than (move_range, access_insn (access));
|
|
|
|
// If DEF sets a hard register, take any call clobbers
|
|
// into account.
|
|
unsigned int regno = def->regno ();
|
|
if (!HARD_REGISTER_NUM_P (regno) || is_clobber)
|
|
continue;
|
|
|
|
ebb_info *ebb = def->ebb ();
|
|
for (ebb_call_clobbers_info *call_group : ebb->call_clobbers ())
|
|
{
|
|
if (!call_group->clobbers (def->resource ()))
|
|
continue;
|
|
|
|
// Exit now if we've already failed, and if the splay accesses
|
|
// below would be wasted work.
|
|
if (!move_range)
|
|
return false;
|
|
|
|
insn_info *insn;
|
|
insn = prev_call_clobbers_ignoring (*call_group, def->insn (),
|
|
ignore);
|
|
if (insn)
|
|
move_range = move_later_than (move_range, insn);
|
|
|
|
insn = next_call_clobbers_ignoring (*call_group, def->insn (),
|
|
ignore);
|
|
if (insn)
|
|
move_range = move_earlier_than (move_range, insn);
|
|
}
|
|
}
|
|
|
|
// Make sure that we don't move stores between basic blocks, since we
|
|
// don't have enough information to tell whether it's safe.
|
|
if (def_info *def = memory_access (defs))
|
|
{
|
|
move_range = move_later_than (move_range, def->bb ()->head_insn ());
|
|
move_range = move_earlier_than (move_range, def->bb ()->end_insn ());
|
|
}
|
|
|
|
return bool (move_range);
|
|
}
|
|
|
|
// Like restrict_movement_for_defs_ignoring, but for the uses in USES.
|
|
template<typename IgnorePredicate>
|
|
bool
|
|
restrict_movement_for_uses_ignoring (insn_range_info &move_range,
|
|
use_array uses, IgnorePredicate ignore)
|
|
{
|
|
for (const use_info *use : uses)
|
|
{
|
|
// Ignore uses of undefined values.
|
|
set_info *set = use->def ();
|
|
if (!set)
|
|
continue;
|
|
|
|
// Ignore uses by debug instructions. Debug instructions are
|
|
// never supposed to move, and uses by debug instructions are
|
|
// never supposed to be transferred elsewhere, so we know that
|
|
// the caller must be changing the uses on the debug instruction
|
|
// and checking whether all new uses are available at the debug
|
|
// instruction's original location.
|
|
if (use->is_in_debug_insn ())
|
|
continue;
|
|
|
|
// If the used value is defined by an instruction I2 for which
|
|
// IGNORE (I2) is true, the caller guarantees that I2 will occur
|
|
// before change.insn (). Otherwise, make sure that the use occurs
|
|
// after the definition.
|
|
insn_info *insn = set->insn ();
|
|
if (!ignore (insn))
|
|
move_range = move_later_than (move_range, insn);
|
|
|
|
// Search forward for the first unfiltered definition that follows SET.
|
|
//
|
|
// We don't need to consider the uses of these definitions, since
|
|
// if IGNORE (D->insn ()) is true for some definition D, the caller
|
|
// is guarantees that either
|
|
//
|
|
// - D will be removed, and thus its uses will be removed; or
|
|
// - D will occur after USE, and thus D's uses will also occur
|
|
// after USE.
|
|
//
|
|
// This is purely a simplification: we could also process D's uses,
|
|
// but we don't need to.
|
|
def_info *def;
|
|
def = first_def_ignoring (set->next_def (), ignore_clobbers::NO,
|
|
ignore);
|
|
if (def)
|
|
move_range = move_earlier_than (move_range, def->insn ());
|
|
|
|
// If USE uses a hard register, take any call clobbers into account too.
|
|
// SET will necessarily occur after any previous call clobber, so we
|
|
// only need to check for later clobbers.
|
|
unsigned int regno = use->regno ();
|
|
if (!HARD_REGISTER_NUM_P (regno))
|
|
continue;
|
|
|
|
ebb_info *ebb = use->ebb ();
|
|
for (ebb_call_clobbers_info *call_group : ebb->call_clobbers ())
|
|
{
|
|
if (!call_group->clobbers (use->resource ()))
|
|
continue;
|
|
|
|
if (!move_range)
|
|
return false;
|
|
|
|
insn_info *insn = next_call_clobbers_ignoring (*call_group,
|
|
use->insn (), ignore);
|
|
if (insn)
|
|
move_range = move_earlier_than (move_range, insn);
|
|
}
|
|
}
|
|
|
|
// Make sure that we don't move loads into an earlier basic block.
|
|
//
|
|
// ??? It would be good to relax this for loads that can be safely
|
|
// speculated.
|
|
if (use_info *use = memory_access (uses))
|
|
move_range = move_later_than (move_range, use->bb ()->head_insn ());
|
|
|
|
return bool (move_range);
|
|
}
|
|
|
|
}
|