// Implementation of public inline member functions for RTL SSA -*- C++ -*- // Copyright (C) 2020-2024 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 // . // This file contains inline implementations of public member functions that // are too large to be written in the class definition. It also contains // some non-inline template definitions of public member functions. // See the comments above the function declarations for details. // // The file also contains the bare minimum of private and protected inline // member functions that are needed to make the public functions compile. namespace rtl_ssa { inline void access_array_builder::reserve (unsigned int num_accesses) { obstack_make_room (m_obstack, num_accesses * sizeof (access_info *)); } inline void access_array_builder::quick_push (access_info *access) { obstack_ptr_grow_fast (m_obstack, access); } inline array_slice access_array_builder::finish () { auto num_accesses = obstack_object_size (m_obstack) / sizeof (access_info *); if (num_accesses == 0) return {}; auto **base = static_cast (obstack_finish (m_obstack)); keep (); return { base, num_accesses }; } inline bool access_info::is_set_with_nondebug_insn_uses () const { return m_is_set_with_nondebug_insn_uses; } inline bool use_info::is_in_debug_insn () const { return m_insn_or_phi.is_first () && m_is_in_debug_insn_or_phi; } inline bb_info * use_info::bb () const { if (m_insn_or_phi.is_first ()) return m_insn_or_phi.known_first ()->bb (); return m_insn_or_phi.known_second ()->bb (); } inline ebb_info * use_info::ebb () const { return bb ()->ebb (); } inline use_info * use_info::prev_use () const { return m_last_use_or_prev_use.second_or_null (); } inline use_info * use_info::next_use () const { return m_last_nondebug_insn_use_or_next_use.second_or_null (); } inline bool use_info::is_first_use () const { return m_last_use_or_prev_use.is_first (); } inline bool use_info::is_last_use () const { return m_last_nondebug_insn_use_or_next_use.is_first (); } inline use_info * use_info::next_nondebug_insn_use () const { if (m_is_last_nondebug_insn_use) return nullptr; return m_last_nondebug_insn_use_or_next_use.known_second (); } inline use_info * use_info::next_any_insn_use () const { // This is used less often than next_nondebug_insn_use, so it doesn't // seem worth having an m_is_last_nondebug_insn_use-style end marker. if (use_info *use = next_use ()) if (use->is_in_any_insn ()) return use; return nullptr; } inline use_info * use_info::next_debug_insn_use () const { if (auto use = next_use ()) if (use->is_in_debug_insn ()) return use; return nullptr; } inline use_info * use_info::prev_phi_use () const { // This is used less often than next_nondebug_insn_use, so it doesn't // seem worth having an m_is_last_nondebug_insn_use-style end marker. if (use_info *use = prev_use ()) if (use->is_in_phi ()) return use; return nullptr; } // Return the last use of any kind in the list. Only valid when is_first () // is true. inline use_info * use_info::last_use () const { return m_last_use_or_prev_use.known_first (); } // Return the last nondebug insn use in the list, or null if none. Only valid // when is_last_use () is true. inline use_info * use_info::last_nondebug_insn_use () const { return m_last_nondebug_insn_use_or_next_use.known_first (); } inline def_info * def_info::prev_def () const { return m_last_def_or_prev_def.second_or_null (); } inline def_info * def_info::next_def () const { return m_splay_root_or_next_def.second_or_null (); } inline bool def_info::is_first_def () const { return m_last_def_or_prev_def.is_first (); } inline bool def_info::is_last_def () const { return m_splay_root_or_next_def.is_first (); } inline bb_info * def_info::bb () const { return m_insn->bb (); } inline ebb_info * def_info::ebb () const { return m_insn->ebb (); } inline clobber_group * clobber_info::group () const { if (!m_group || !m_group->has_been_superceded ()) return m_group; return const_cast (this)->recompute_group (); } inline use_info * set_info::last_use () const { return m_first_use ? m_first_use->last_use () : nullptr; } inline use_info * set_info::first_nondebug_insn_use () const { if (m_is_set_with_nondebug_insn_uses) return m_first_use; return nullptr; } inline use_info * set_info::last_nondebug_insn_use () const { if (m_is_set_with_nondebug_insn_uses) return m_first_use->last_use ()->last_nondebug_insn_use (); return nullptr; } inline use_info * set_info::first_debug_insn_use () const { use_info *use; if (has_nondebug_insn_uses ()) use = last_nondebug_insn_use ()->next_use (); else use = first_use (); if (use && use->is_in_debug_insn ()) return use; return nullptr; } inline use_info * set_info::first_any_insn_use () const { if (m_first_use && m_first_use->is_in_any_insn ()) return m_first_use; return nullptr; } inline use_info * set_info::last_phi_use () const { if (m_first_use) { use_info *last = m_first_use->last_use (); if (last->is_in_phi ()) return last; } return nullptr; } inline bool set_info::has_nondebug_uses () const { return has_nondebug_insn_uses () || has_phi_uses (); } inline bool set_info::has_nondebug_insn_uses () const { return m_is_set_with_nondebug_insn_uses; } inline bool set_info::has_phi_uses () const { return m_first_use && m_first_use->last_use ()->is_in_phi (); } inline use_info * set_info::single_nondebug_use () const { if (!has_phi_uses ()) return single_nondebug_insn_use (); if (!has_nondebug_insn_uses ()) return single_phi_use (); return nullptr; } inline use_info * set_info::single_nondebug_insn_use () const { use_info *first = first_nondebug_insn_use (); if (first && !first->next_nondebug_insn_use ()) return first; return nullptr; } inline use_info * set_info::single_phi_use () const { use_info *last = last_phi_use (); if (last && !last->prev_phi_use ()) return last; return nullptr; } inline bool set_info::is_local_to_ebb () const { if (!m_first_use) return true; use_info *last = m_first_use->last_use (); if (last->is_in_phi ()) return false; last = last->last_nondebug_insn_use (); return !last || last->ebb () == ebb (); } inline iterator_range set_info::all_uses () const { return { m_first_use, nullptr }; } inline iterator_range set_info::reverse_all_uses () const { return { last_use (), nullptr }; } inline iterator_range set_info::nondebug_insn_uses () const { return { first_nondebug_insn_use (), nullptr }; } inline iterator_range set_info::debug_insn_uses () const { return { first_debug_insn_use (), nullptr }; } inline iterator_range set_info::reverse_nondebug_insn_uses () const { return { last_nondebug_insn_use (), nullptr }; } inline iterator_range set_info::all_insn_uses () const { return { first_any_insn_use (), nullptr }; } inline iterator_range set_info::phi_uses () const { return { last_phi_use (), nullptr }; } inline use_array phi_info::inputs () const { if (m_num_inputs == 1) return use_array (&m_single_input, 1); return use_array (m_inputs, m_num_inputs); } inline use_info * phi_info::input_use (unsigned int i) const { if (m_num_inputs == 1) return as_a (m_single_input); return as_a (m_inputs[i]); } inline set_info * phi_info::input_value (unsigned int i) const { return input_use (i)->def (); } inline def_info * def_node::first_def () const { // This should get optimized into an AND with -2. if (m_clobber_or_set.is_first ()) return m_clobber_or_set.known_first (); return m_clobber_or_set.known_second (); } inline clobber_info * clobber_group::first_clobber () const { return m_clobber_or_set.known_first (); } inline iterator_range clobber_group::clobbers () const { return { first_clobber (), m_last_clobber->next_def () }; } inline def_info * def_mux::first_def () const { if (is_first ()) return known_first (); return known_second ()->first_def (); } inline def_info * def_mux::last_def () const { if (is_first ()) return known_first (); def_node *node = known_second (); if (auto *clobber = ::dyn_cast (node)) return clobber->last_clobber (); return node->first_def (); } inline set_info * def_mux::set () const { if (is_first ()) return ::safe_dyn_cast (known_first ()); return ::dyn_cast (known_second ()->first_def ()); } inline def_info * def_lookup::last_def_of_prev_group () const { if (!mux) return nullptr; if (comparison > 0) return mux.last_def (); return mux.first_def ()->prev_def (); } inline def_info * def_lookup::first_def_of_next_group () const { if (!mux) return nullptr; if (comparison < 0) return mux.first_def (); return mux.last_def ()->next_def (); } inline set_info * def_lookup::matching_set () const { if (comparison == 0) return mux.set (); return nullptr; } inline def_info * def_lookup::matching_set_or_last_def_of_prev_group () const { if (set_info *set = matching_set ()) return set; return last_def_of_prev_group (); } inline def_info * def_lookup::matching_set_or_first_def_of_next_group () const { if (set_info *set = matching_set ()) return set; return first_def_of_next_group (); } inline insn_note::insn_note (insn_note_kind kind) : m_next_note (nullptr), m_kind (kind), m_data8 (0), m_data16 (0), m_data32 (0) { } template inline T insn_note::as_a () { using deref_type = decltype (*std::declval ()); using derived = typename std::remove_reference::type; gcc_checking_assert (m_kind == derived::kind); return static_cast (this); } template inline T insn_note::dyn_cast () { using deref_type = decltype (*std::declval ()); using derived = typename std::remove_reference::type; if (m_kind == derived::kind) return static_cast (this); return nullptr; } inline bool insn_info::operator< (const insn_info &other) const { if (this == &other) return false; if (LIKELY (m_point != other.m_point)) return m_point < other.m_point; return slow_compare_with (other) < 0; } inline bool insn_info::operator> (const insn_info &other) const { return other < *this; } inline bool insn_info::operator<= (const insn_info &other) const { return !(other < *this); } inline bool insn_info::operator>= (const insn_info &other) const { return !(*this < other); } inline int insn_info::compare_with (const insn_info *other) const { if (this == other) return 0; if (LIKELY (m_point != other->m_point)) // Assume that points remain in [0, INT_MAX]. return m_point - other->m_point; return slow_compare_with (*other); } inline insn_info * insn_info::prev_nondebug_insn () const { gcc_checking_assert (!is_debug_insn ()); return m_prev_insn_or_last_debug_insn.known_first (); } inline insn_info * insn_info::next_nondebug_insn () const { gcc_checking_assert (!is_debug_insn ()); const insn_info *from = this; if (insn_info *first_debug = m_next_nondebug_or_debug_insn.second_or_null ()) from = first_debug->last_debug_insn (); return from->m_next_nondebug_or_debug_insn.known_first (); } inline insn_info * insn_info::prev_any_insn () const { const insn_info *from = this; if (insn_info *last_debug = m_prev_insn_or_last_debug_insn.second_or_null ()) // This instruction is the first in a subsequence of debug instructions. // Move to the following nondebug instruction. from = last_debug->m_next_nondebug_or_debug_insn.known_first (); return from->m_prev_insn_or_last_debug_insn.known_first (); } inline insn_info * insn_info::next_any_insn () const { // This should get optimized into an AND with -2. if (m_next_nondebug_or_debug_insn.is_first ()) return m_next_nondebug_or_debug_insn.known_first (); return m_next_nondebug_or_debug_insn.known_second (); } inline bool insn_info::is_phi () const { return this == ebb ()->phi_insn (); } inline bool insn_info::is_bb_head () const { return this == m_bb->head_insn (); } inline bool insn_info::is_bb_end () const { return this == m_bb->end_insn (); } inline ebb_info * insn_info::ebb () const { return m_bb->ebb (); } inline int insn_info::uid () const { return m_cost_or_uid < 0 ? m_cost_or_uid : INSN_UID (m_rtl); } inline use_array insn_info::uses () const { return use_array (m_accesses + m_num_defs, m_num_uses); } inline bool insn_info::has_call_clobbers () const { return find_note (); } inline def_array insn_info::defs () const { return def_array (m_accesses, m_num_defs); } inline unsigned int insn_info::cost () const { if (m_cost_or_uid < 0) return 0; if (m_cost_or_uid == UNKNOWN_COST) calculate_cost (); return m_cost_or_uid; } template inline const T * insn_info::find_note () const { // We could break if the note kind is > T::kind, but since the number // of notes should be very small, the check is unlikely to pay for itself. for (const insn_note *note = first_note (); note; note = note->next_note ()) if (note->kind () == T::kind) return static_cast (note); return nullptr; } // Only valid for debug instructions that come after a nondebug instruction, // and so start a subsequence of debug instructions. Return the last debug // instruction in the subsequence. inline insn_info * insn_info::last_debug_insn () const { return m_prev_insn_or_last_debug_insn.known_second (); } inline insn_range_info::insn_range_info (insn_info *first, insn_info *last) : first (first), last (last) { } inline bool insn_range_info::operator== (const insn_range_info &other) const { return first == other.first && last == other.last; } inline bool insn_range_info::operator!= (const insn_range_info &other) const { return first != other.first || last != other.last; } inline insn_info * insn_range_info::singleton () const { return first == last ? last : nullptr; } inline bool insn_range_info::includes (insn_info *insn) const { return *insn >= *first && *insn <= *last; } inline insn_info * insn_range_info::clamp_insn_to_range (insn_info *insn) const { if (*first > *insn) return first; if (*last < *insn) return last; return insn; } inline bool insn_range_info::is_subrange_of (const insn_range_info &other) const { return *first >= *other.first && *last <= *other.last; } inline iterator_range bb_info::all_insns () const { return { m_head_insn, m_end_insn->next_any_insn () }; } inline iterator_range bb_info::reverse_all_insns () const { return { m_end_insn, m_head_insn->prev_any_insn () }; } inline iterator_range bb_info::nondebug_insns () const { return { m_head_insn, m_end_insn->next_nondebug_insn () }; } inline iterator_range bb_info::reverse_nondebug_insns () const { return { m_end_insn, m_head_insn->prev_nondebug_insn () }; } inline iterator_range bb_info::real_insns () const { return { m_head_insn->next_any_insn (), m_end_insn }; } inline iterator_range bb_info::reverse_real_insns () const { return { m_end_insn->prev_any_insn (), m_head_insn }; } inline iterator_range bb_info::real_nondebug_insns () const { return { m_head_insn->next_nondebug_insn (), m_end_insn }; } inline iterator_range bb_info::reverse_real_nondebug_insns () const { return { m_end_insn->prev_nondebug_insn (), m_head_insn }; } inline bool ebb_call_clobbers_info::clobbers (resource_info resource) const { // Only register clobbers are tracked this way. Other clobbers are // recorded explicitly. return (resource.is_reg () && m_abi->clobbers_reg_p (resource.mode, resource.regno)); } inline ebb_info * ebb_info::prev_ebb () const { if (bb_info *prev_bb = m_first_bb->prev_bb ()) return prev_bb->ebb (); return nullptr; } inline ebb_info * ebb_info::next_ebb () const { if (bb_info *next_bb = m_last_bb->next_bb ()) return next_bb->ebb (); return nullptr; } inline iterator_range ebb_info::phis () const { return { m_first_phi, nullptr }; } inline iterator_range ebb_info::bbs () const { return { m_first_bb, m_last_bb->next_bb () }; } inline iterator_range ebb_info::reverse_bbs () const { return { m_last_bb, m_first_bb->prev_bb () }; } inline iterator_range ebb_info::all_insns () const { return { m_phi_insn, m_last_bb->end_insn ()->next_any_insn () }; } inline iterator_range ebb_info::reverse_all_insns () const { return { m_last_bb->end_insn (), m_phi_insn->prev_any_insn () }; } inline iterator_range ebb_info::nondebug_insns () const { return { m_phi_insn, m_last_bb->end_insn ()->next_nondebug_insn () }; } inline iterator_range ebb_info::reverse_nondebug_insns () const { return { m_last_bb->end_insn (), m_phi_insn->prev_nondebug_insn () }; } inline insn_range_info ebb_info::insn_range () const { return { m_phi_insn, m_last_bb->end_insn () }; } inline void ebb_info::set_first_call_clobbers (ebb_call_clobbers_info *call_clobbers) { m_first_call_clobbers = call_clobbers; } inline ebb_call_clobbers_info * ebb_info::first_call_clobbers () const { return m_first_call_clobbers; } inline iterator_range ebb_info::call_clobbers () const { return { m_first_call_clobbers, nullptr }; } inline insn_change::insn_change (insn_info *insn) : m_insn (insn), new_defs (insn->defs ()), new_uses (insn->uses ()), move_range (insn), new_cost (UNKNOWN_COST), m_is_deletion (false) { } inline insn_change::insn_change (insn_info *insn, delete_action) : m_insn (insn), new_defs (), new_uses (), move_range (insn), new_cost (0), m_is_deletion (true) { } inline insn_is_changing_closure:: insn_is_changing_closure (array_slice changes) : m_changes (changes) { } inline bool insn_is_changing_closure::operator() (const insn_info *insn) const { for (const insn_change *change : m_changes) if (change->insn () == insn) return true; return false; } inline iterator_range function_info::bbs () const { return { m_first_bb, nullptr }; } inline iterator_range function_info::reverse_bbs () const { return { m_last_bb, nullptr }; } inline iterator_range function_info::ebbs () const { return { m_first_bb->ebb (), nullptr }; } inline iterator_range function_info::reverse_ebbs () const { return { m_last_bb->ebb (), nullptr }; } inline iterator_range function_info::all_insns () const { return { m_first_insn, nullptr }; } inline iterator_range function_info::reverse_all_insns () const { return { m_last_insn, nullptr }; } inline iterator_range function_info::nondebug_insns () const { return { m_first_insn, nullptr }; } inline iterator_range function_info::reverse_nondebug_insns () const { return { m_last_insn, nullptr }; } inline iterator_range function_info::mem_defs () const { return { m_defs[0], nullptr }; } inline iterator_range function_info::reg_defs (unsigned int regno) const { return { m_defs[regno + 1], nullptr }; } inline bool function_info::is_single_dominating_def (const set_info *set) const { return (set->is_first_def () && set->is_last_def () && (!HARD_REGISTER_NUM_P (set->regno ()) || !TEST_HARD_REG_BIT (m_clobbered_by_calls, set->regno ()))); } inline set_info * function_info::single_dominating_def (unsigned int regno) const { if (set_info *set = safe_dyn_cast (m_defs[regno + 1])) if (is_single_dominating_def (set)) return set; return nullptr; } template bool function_info::add_regno_clobber (obstack_watermark &watermark, insn_change &change, unsigned int regno, IgnorePredicate ignore) { // Check whether CHANGE already clobbers REGNO. if (find_access (change.new_defs, regno)) return true; // Get the closest position to INSN at which the new instruction // could be placed. insn_info *insn = change.move_range.clamp_insn_to_range (change.insn ()); def_array new_defs = insert_temp_clobber (watermark, insn, regno, change.new_defs); if (!new_defs.is_valid ()) return false; // Find a definition at or neighboring INSN. insn_range_info move_range = change.move_range; if (!restrict_movement_for_dead_range (move_range, regno, insn, ignore)) return false; change.new_defs = new_defs; change.move_range = move_range; return true; } template inline T * function_info::change_alloc (obstack_watermark &wm, Ts... args) { static_assert (std::is_trivially_destructible::value, "destructor won't be called"); static_assert (alignof (T) <= obstack_alignment, "too much alignment required"); void *addr = XOBNEW (wm, T); return new (addr) T (std::forward (args)...); } }