@@ -518,7 +518,7 @@ or1k*-*-*)
;;
powerpc*-*-*)
cpu_type=rs6000
- extra_objs="rs6000-string.o rs6000-p8swap.o rs6000-logue.o"
+ extra_objs="rs6000-string.o rs6000-p8swap.o rs6000-logue.o rs6000-vecload-opt.o"
extra_objs="${extra_objs} rs6000-call.o rs6000-pcrel-opt.o"
extra_objs="${extra_objs} rs6000-builtins.o rs6000-builtin.o"
extra_headers="ppc-asm.h altivec.h htmintrin.h htmxlintrin.h"
@@ -555,7 +555,7 @@ riscv*)
;;
rs6000*-*-*)
extra_options="${extra_options} g.opt fused-madd.opt rs6000/rs6000-tables.opt"
- extra_objs="rs6000-string.o rs6000-p8swap.o rs6000-logue.o"
+ extra_objs="rs6000-string.o rs6000-p8swap.o rs6000-logue.o rs6000-vecload-opt.o"
extra_objs="${extra_objs} rs6000-call.o rs6000-pcrel-opt.o"
target_gtfiles="$target_gtfiles \$(srcdir)/config/rs6000/rs6000-logue.cc \$(srcdir)/config/rs6000/rs6000-call.cc"
target_gtfiles="$target_gtfiles \$(srcdir)/config/rs6000/rs6000-pcrel-opt.cc"
@@ -28,6 +28,9 @@ along with GCC; see the file COPYING3. If not see
The power8 does not have instructions that automaticaly do the byte swaps
for loads and stores. */
INSERT_PASS_BEFORE (pass_cse, 1, pass_analyze_swaps);
+ /* Pass to replace adjacent memory addresses lxv instruction with lxvp
+ instruction. */
+ INSERT_PASS_BEFORE (pass_early_remat, 1, pass_analyze_vecload);
/* Pass to do the PCREL_OPT optimization that combines the load of an
external symbol's address along with a single load or store using that
@@ -343,6 +343,7 @@ namespace gcc { class context; }
class rtl_opt_pass;
extern rtl_opt_pass *make_pass_analyze_swaps (gcc::context *);
+extern rtl_opt_pass *make_pass_analyze_vecload (gcc::context *);
extern rtl_opt_pass *make_pass_pcrel_opt (gcc::context *);
extern bool rs6000_sum_of_two_registers_p (const_rtx expr);
extern bool rs6000_quadword_masked_address_p (const_rtx exp);
new file mode 100644
@@ -0,0 +1,1186 @@
+/* Subroutines used to replace lxv with lxvp
+ for TARGET_POWER10 and TARGET_VSX,
+
+ Copyright (C) 2020-2023 Free Software Foundation, Inc.
+ Contributed by Ajit Kumar Agarwal <aagarwa1@linux.ibm.com>.
+
+ 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 IN_TARGET_CODE 1
+#define INCLUDE_ALGORITHM
+#define INCLUDE_FUNCTIONAL
+#define INCLUDE_LIST
+#define INCLUDE_TYPE_TRAITS
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "target.h"
+#include "rtl.h"
+#include "memmodel.h"
+#include "emit-rtl.h"
+#include "tree-pass.h"
+#include "df.h"
+#include "dumpfile.h"
+#include "rs6000-internal.h"
+#include "rs6000-protos.h"
+#include "fusion-common.h"
+
+/* Return false if dependent rtx LOC is SUBREG. */
+static bool
+is_feasible (rtx_insn *insn)
+{
+ df_ref use;
+ df_insn_info *insn_info = DF_INSN_INFO_GET (insn);
+ FOR_EACH_INSN_INFO_DEF (use, insn_info)
+ {
+ struct df_link *def_link = DF_REF_CHAIN (use);
+ if (!def_link || !def_link->ref || DF_REF_IS_ARTIFICIAL (def_link->ref))
+ continue;
+ while (def_link && def_link->ref)
+ {
+ rtx *loc = DF_REF_LOC (def_link->ref);
+ if (!loc || *loc == NULL_RTX)
+ return false;
+ if (GET_CODE (*loc) == SUBREG)
+ return false;
+ def_link = def_link->next;
+ }
+ }
+ return true;
+}
+
+/* df_scan_rescan the unspec instruction where operands
+ are reversed. */
+void set_rescan_for_unspec (rtx_insn *insn)
+{
+ df_ref use;
+ df_insn_info *insn_info = DF_INSN_INFO_GET (insn);
+ rtx_insn *select_insn2;
+ FOR_EACH_INSN_INFO_DEF (use, insn_info)
+ {
+ struct df_link *def_link = DF_REF_CHAIN (use);
+ while (def_link && def_link->ref)
+ {
+ select_insn2 = DF_REF_INSN (def_link->ref);
+ rtx set = single_set (select_insn2);
+
+ if (set == NULL_RTX)
+ return;
+
+ if (set != NULL_RTX)
+ {
+ rtx op0 = SET_SRC (set);
+ if (GET_CODE (op0) != UNSPEC)
+ return;
+
+ if (GET_CODE (op0) == VEC_SELECT
+ && GET_CODE (XEXP (op0, 1)) == PARALLEL)
+ return;
+
+ if (GET_CODE (op0) == UNSPEC)
+ df_insn_rescan (select_insn2);
+ }
+ def_link = def_link->next;
+ }
+ }
+}
+
+/* Return dependent UNSPEC instruction. */
+rtx_insn *get_rtx_UNSPEC (rtx_insn *insn)
+{
+ df_ref use;
+ df_insn_info *insn_info = DF_INSN_INFO_GET (insn);
+ rtx_insn *select_insn2;
+ FOR_EACH_INSN_INFO_DEF (use, insn_info)
+ {
+ struct df_link *def_link = DF_REF_CHAIN (use);
+ while (def_link && def_link->ref)
+ {
+ select_insn2 = DF_REF_INSN (def_link->ref);
+ rtx set = single_set (select_insn2);
+
+ if (set == NULL_RTX)
+ return 0;
+
+ if (set != NULL_RTX)
+ {
+ rtx op0 = SET_SRC (set);
+
+ if (GET_CODE (op0) == UNSPEC)
+ return select_insn2;
+ }
+ def_link = def_link->next;
+ }
+ }
+ return 0;
+}
+
+/* Replace identified lxv with lxvp.
+ Bail out if following condition are true:
+
+ - dependent instruction of load is vec_select instruction,
+
+ - machine mode of unspec is not same as machine mode
+ of lxv instruction.
+
+ - dependent instruction is not unspec.
+
+ - Source operand of unspec is eq instruction. */
+
+rtx
+rs6000_gen_load_pair (rtx_insn *insn1, rtx_insn *insn2)
+{
+ rtx body = PATTERN (insn1);
+ rtx src_exp = SET_SRC (body);
+ rtx dest_exp = SET_DEST (body);
+ rtx lxv;
+ rtx insn2_body = PATTERN (insn2);
+
+ rtx insn2_src_exp = SET_SRC (insn2_body);
+
+ if (GET_MODE (src_exp) != GET_MODE (SET_SRC (insn2_body)))
+ return NULL_RTX;
+
+ if (GET_MODE (dest_exp) == TImode)
+ return NULL_RTX;
+
+ if (!ALTIVEC_OR_VSX_VECTOR_MODE (GET_MODE (dest_exp)))
+ return NULL_RTX;
+
+ if (!is_feasible (insn1))
+ return NULL_RTX;
+
+ if (!is_feasible (insn2))
+ return NULL_RTX;
+
+ for (rtx note = REG_NOTES (insn1); note; note = XEXP (note, 1))
+ if (REG_NOTE_KIND (note) == REG_EQUAL
+ || REG_NOTE_KIND (note) == REG_EQUIV)
+ return NULL_RTX;
+
+ int no_dep = 0;
+ df_ref use;
+ df_insn_info *insn_info = DF_INSN_INFO_GET (insn1);
+ rtx_insn *select_insn2;
+
+ FOR_EACH_INSN_INFO_DEF (use, insn_info)
+ {
+ struct df_link *def_link = DF_REF_CHAIN (use);
+ while (def_link && def_link->ref)
+ {
+ select_insn2 = DF_REF_INSN (def_link->ref);
+ rtx set = single_set (select_insn2);
+
+ if (set == NULL_RTX)
+ return NULL_RTX;
+
+ if (set != NULL_RTX)
+ {
+ rtx op0 = SET_SRC (set);
+
+ if (GET_CODE (op0) != UNSPEC)
+ return NULL_RTX;
+
+ if (GET_CODE (op0) == VEC_SELECT
+ && GET_CODE (XEXP (op0, 1)) == PARALLEL)
+ return NULL_RTX;
+
+ if (GET_CODE (op0) == UNSPEC)
+ {
+ if (GET_MODE (op0) != XOmode
+ && GET_MODE (op0) != GET_MODE (dest_exp))
+ return NULL_RTX;
+
+ int nvecs = XVECLEN (op0, 0);
+ for (int i = 0; i < nvecs; i++)
+ {
+ rtx op;
+ op = XVECEXP (op0, 0, i);
+
+ if (GET_MODE (op) == OOmode)
+ return NULL_RTX;
+ if (GET_CODE (op) == EQ)
+ return NULL_RTX;
+ }
+ }
+ ++no_dep;
+ }
+ def_link = def_link->next;
+ }
+ }
+
+ rtx_insn *insn = get_rtx_UNSPEC (insn1);
+
+ if (insn && insn == get_rtx_UNSPEC (insn2) && no_dep == 1)
+ return NULL_RTX;
+
+ int regoff;
+ rtx src;
+ rtx addr = XEXP (src_exp, 0);
+
+ if (GET_CODE (addr) == PLUS
+ && XEXP (addr, 1) && CONST_INT_P (XEXP (addr, 1)))
+ {
+ regoff = 0;
+ src = simplify_gen_subreg (GET_MODE (dest_exp),
+ dest_exp, GET_MODE (dest_exp),
+ regoff);
+ }
+ else
+ {
+ regoff = INTVAL (CONST0_RTX (SImode)) *
+ GET_MODE_SIZE (GET_MODE (dest_exp));
+ src = simplify_gen_subreg (GET_MODE (dest_exp),
+ dest_exp, GET_MODE (dest_exp),
+ regoff);
+ }
+ insn_info = DF_INSN_INFO_GET (insn2);
+ FOR_EACH_INSN_INFO_DEF (use, insn_info)
+ {
+ struct df_link *def_link = DF_REF_CHAIN (use);
+ if (!def_link || !def_link->ref || DF_REF_IS_ARTIFICIAL (def_link->ref))
+ continue;
+ while (def_link && def_link->ref)
+ {
+ rtx *loc = DF_REF_LOC (def_link->ref);
+ *loc = src;
+ def_link = def_link->next;
+ }
+ }
+
+ int regoff1;
+ rtx src1;
+ addr = XEXP (insn2_src_exp, 0);
+
+ if (GET_CODE (addr) == PLUS
+ && XEXP (addr, 1)
+ && CONST_INT_P (XEXP(addr, 1)))
+ {
+ regoff1 = 16;
+ src1 = simplify_gen_subreg (GET_MODE (dest_exp),
+ dest_exp, GET_MODE (dest_exp),
+ regoff1);
+ }
+ else
+ {
+ regoff1 = INTVAL (CONST0_RTX (SImode)) * GET_MODE_SIZE (GET_MODE (dest_exp));//V16QImode);
+ src1 = simplify_gen_subreg (GET_MODE (dest_exp),
+ dest_exp, GET_MODE (dest_exp),
+ regoff1);
+ }
+
+ insn_info = DF_INSN_INFO_GET (insn1);
+ FOR_EACH_INSN_INFO_DEF (use, insn_info)
+ {
+ struct df_link *def_link = DF_REF_CHAIN (use);
+ if (!def_link || !def_link->ref || DF_REF_IS_ARTIFICIAL (def_link->ref))
+ continue;
+ while (def_link && def_link->ref)
+ {
+ rtx *loc = DF_REF_LOC (def_link->ref);
+ PUT_MODE_RAW (*loc, OOmode);
+ *loc = src1;
+ def_link = def_link->next;
+ }
+ }
+
+ set_rescan_for_unspec (insn1);
+ set_rescan_for_unspec (insn2);
+ df_insn_rescan (insn1);
+ df_insn_rescan (insn2);
+
+ PUT_MODE_RAW (src_exp, OOmode);
+ PUT_MODE_RAW (dest_exp, OOmode);
+ lxv = gen_movoo (dest_exp, src_exp);
+ rtx_insn *new_insn = emit_insn_before (lxv, insn1);
+ set_block_for_insn (new_insn, BLOCK_FOR_INSN (insn1));
+ df_insn_rescan (new_insn);
+
+ if (dump_file)
+ {
+ unsigned int new_uid = INSN_UID (new_insn);
+ fprintf (dump_file, "Replacing lxv %d with lxvp %d\n",
+ INSN_UID (insn1), new_uid);
+ print_rtl_single (dump_file, new_insn);
+ print_rtl_single (dump_file, insn1);
+ print_rtl_single (dump_file, insn2);
+
+ }
+
+ df_insn_delete (insn1);
+ remove_insn (insn1);
+ df_insn_delete (insn2);
+ remove_insn (insn2);
+ insn1->set_deleted ();
+ insn2->set_deleted ();
+ return lxv;
+}
+
+// LEFT_LIST and RIGHT_LIST are lists of candidate instructions where all insns
+// in LEFT_LIST are known to be adjacent to those in RIGHT_LIST.
+//
+// This function traverses the resulting 2D matrix of possible pair candidates
+// and attempts to merge them into pairs.
+//
+// The algorithm is straightforward: if we consider a combined list of
+// candidates X obtained by merging LEFT_LIST and RIGHT_LIST in program order,
+// then we advance through X until we reach a crossing point (where X[i] and
+// X[i+1] come from different source lists).
+//
+// At this point we know X[i] and X[i+1] are adjacent accesses, and we try to
+// fuse them into a pair. If this succeeds, we remove X[i] and X[i+1] from
+// their original lists and continue as above.
+//
+// In the failure case, we advance through the source list containing X[i] and
+// continue as above (proceeding to the next crossing point).
+//
+// The rationale for skipping over groups of consecutive candidates from the
+// same source list is as follows:
+//
+// In the store case, the insns in the group can't be re-ordered over each
+// other as they are guaranteed to store to the same location, so we're
+// guaranteed not to lose opportunities by doing this.
+//
+// In the load case, subsequent loads from the same location are either
+// redundant (in which case they should have been cleaned up by an earlier
+// optimization pass) or there is an intervening aliasing hazard, in which case
+// we can't re-order them anyway, so provided earlier passes have cleaned up
+// redundant loads, we shouldn't miss opportunities by doing this.
+void
+fusion_bb_info::merge_pairs (insn_list_t &left_list,
+ insn_list_t &right_list,
+ bool load_p,
+ unsigned access_size)
+{
+ if (dump_file)
+ {
+ fprintf (dump_file, "merge_pairs [L=%d], cand vecs ", load_p);
+ dump_insn_list (dump_file, left_list);
+ fprintf (dump_file, " x ");
+ dump_insn_list (dump_file, right_list);
+ fprintf (dump_file, "\n");
+ }
+
+ auto iter_l = left_list.begin ();
+ auto iter_r = right_list.begin ();
+ while (iter_l != left_list.end () && iter_r != right_list.end ())
+ {
+ auto left = *iter_l;
+ auto right = *iter_r;
+
+ auto next_l = std::next (iter_l);
+ auto next_r = std::next (iter_r);
+
+ if ((*left) < (*right)
+ && next_l != (left_list.end ())
+ && **next_l < *right)
+ iter_l = next_l;
+ else if ((*right) < (*left)
+ && next_r != (right_list.end ())
+ && **next_r < *left)
+ iter_r = next_r;
+ else if (try_fuse_pair (load_p, access_size, *iter_l, *iter_r)) //left, right))
+ {
+ left_list.erase (iter_l);
+ iter_l = next_l;
+ right_list.erase (iter_r);
+ iter_r = next_r;
+ }
+ else if (*left < *right)
+ iter_l = next_l;
+ else
+ iter_r = next_r;
+ }
+
+}
+
+
+
+// Main function to begin pair discovery. Given a memory access INSN,
+// determine whether it could be a candidate for fusing into an lxvp/stvxp,
+// and if so, track it in the appropriate data structure for this basic
+// block. LOAD_P is true if the access is a load, and MEM is the mem
+// rtx that occurs in INSN.
+void
+fusion_bb_info::track_access (insn_info *insn, bool load_p, rtx mem)
+{
+ // We can't combine volatile MEMs, so punt on these.
+ if (MEM_VOLATILE_P (mem))
+ return;
+
+ // Ignore writeback accesses if the param says to do so.
+ if (GET_RTX_CLASS (GET_CODE (XEXP (mem, 0))) == RTX_AUTOINC)
+ return;
+
+ const machine_mode mem_mode = GET_MODE (mem);
+
+ rtx reg_op = XEXP (PATTERN (insn->rtl ()), !load_p);
+
+ if (!ALTIVEC_OR_VSX_VECTOR_MODE (GET_MODE (reg_op)))
+ return;
+
+ const bool fpsimd_op_p = (ALTIVEC_OR_VSX_VECTOR_MODE (GET_MODE (reg_op)));
+
+ const HOST_WIDE_INT mem_size = GET_MODE_SIZE (mem_mode);
+ const lfs_fields lfs = { load_p, fpsimd_op_p, mem_size };
+
+ if (track_via_mem_expr (insn, mem, lfs))
+ return;
+
+ poly_int64 mem_off;
+ rtx addr = XEXP (mem, 0);
+ const bool autoinc_p = GET_RTX_CLASS (GET_CODE (addr)) == RTX_AUTOINC;
+ rtx base = load_strip_offset (mem, &mem_off);
+ if (!REG_P (base))
+ return;
+
+ // Need to calculate two (possibly different) offsets:
+ // - Offset at which the access occurs.
+ // - Offset of the new base def.
+ poly_int64 access_off;
+ if (autoinc_p && any_post_modify_p (addr))
+ access_off = 0;
+ else
+ access_off = mem_off;
+
+ poly_int64 new_def_off = mem_off;
+
+ // Punt on accesses relative to eliminable regs. Since we don't know the
+ // elimination offset pre-RA, we should postpone forming pairs on such
+ // accesses until after RA.
+ //
+ // As it stands, addresses with offsets in range for LDR but not
+ // in range for LDP/STP are currently reloaded inefficiently,
+ // ending up with a separate base register for each pair.
+ //
+ // In theory LRA should make use of
+ // targetm.legitimize_address_displacement to promote sharing of
+ // bases among multiple (nearby) address reloads, but the current
+ // LRA code returns early from process_address_1 for operands that
+ // satisfy "m", even if they don't satisfy the real (relaxed) address
+ // constraint; this early return means we never get to the code
+ // that calls targetm.legitimize_address_displacement.
+ //
+ // So for now, it's better to punt when we can't be sure that the
+ // offset is in range for LDP/STP. Out-of-range cases can then be
+ // handled after RA by the out-of-range LDP/STP peepholes. Eventually, it
+ // would be nice to handle known out-of-range opportunities in the
+ // pass itself (for stack accesses, this would be in the post-RA pass).
+ if (!reload_completed
+ && (REGNO (base) == FRAME_POINTER_REGNUM
+ || REGNO (base) == ARG_POINTER_REGNUM))
+ return;
+
+ // Now need to find def of base register.
+ use_info *base_use = find_access (insn->uses (), REGNO (base));
+ gcc_assert (base_use);
+ def_info *base_def = base_use->def ();
+ if (!base_def)
+ {
+ if (dump_file)
+ fprintf (dump_file,
+ "base register (regno %d) of insn %d is undefined",
+ REGNO (base), insn->uid ());
+ return;
+ }
+
+ alt_base *canon_base = canon_base_map.get (base_def);
+ if (canon_base)
+ {
+ // Express this as the combined offset from the canonical base.
+ base_def = canon_base->base;
+ new_def_off += canon_base->offset;
+ access_off += canon_base->offset;
+ }
+
+ if (autoinc_p)
+ {
+ auto def = find_access (insn->defs (), REGNO (base));
+ gcc_assert (def);
+
+ // Record that DEF = BASE_DEF + MEM_OFF.
+ if (dump_file)
+ {
+ pretty_printer pp;
+ pp_access (&pp, def, 0);
+ pp_string (&pp, " = ");
+ pp_access (&pp, base_def, 0);
+ fprintf (dump_file, "[bb %u] recording %s + ",
+ m_bb->index (), pp_formatted_text (&pp));
+ print_dec (new_def_off, dump_file);
+ fprintf (dump_file, "\n");
+ }
+
+ alt_base base_rec { base_def, new_def_off };
+ if (canon_base_map.put (def, base_rec))
+ gcc_unreachable (); // Base defs should be unique.
+ }
+
+ // Punt on misaligned offsets. LDP/STP require offsets to be a multiple of
+ // the access size.
+ if (!multiple_p (mem_off, mem_size))
+ return;
+
+ const auto key = std::make_pair (base_def, encode_lfs (lfs));
+ access_group &group = def_map.get_or_insert (key, NULL);
+ auto alloc = [&](access_record *access) { return node_alloc (access); };
+ group.track (alloc, access_off, insn);
+ if (dump_file)
+ {
+ pretty_printer pp;
+ pp_access (&pp, base_def, 0);
+
+ fprintf (dump_file, "[bb %u] tracking insn %d via %s",
+ m_bb->index (), insn->uid (), pp_formatted_text (&pp));
+ fprintf (dump_file,
+ " [L=%d, WB=%d, FP=%d, %smode, off=",
+ lfs.load_p, autoinc_p, lfs.fpsimd_p, mode_name[mem_mode]);
+ print_dec (access_off, dump_file);
+ fprintf (dump_file, "]\n");
+ }
+}
+
+
+// Given two adjacent memory accesses of the same size, I1 and I2, try
+// and see if we can merge them into a lxvp or stvxp.
+//
+// ACCESS_SIZE gives the (common) size of a single access, LOAD_P is true
+// if the accesses are both loads, otherwise they are both stores.
+bool
+fusion_bb_info::try_fuse_pair (bool load_p, unsigned access_size,
+ insn_info *i1, insn_info *i2)
+{
+ if (dump_file)
+ fprintf (dump_file, "analyzing pair (load=%d): (%d,%d)\n",
+ load_p, i1->uid (), i2->uid ());
+
+ insn_info *insns[2];
+ bool reversed = false;
+ if (*i1 < *i2)
+ {
+ insns[0] = i1;
+ insns[1] = i2;
+ }
+ else
+ {
+ insns[0] = i2;
+ insns[1] = i1;
+ reversed = true;
+ }
+
+ rtx cand_mems[2];
+ rtx reg_ops[2];
+ rtx pats[2];
+ for (int i = 0; i < 2; i++)
+ {
+ pats[i] = PATTERN (insns[i]->rtl ());
+ cand_mems[i] = XEXP (pats[i], load_p);
+ reg_ops[i] = XEXP (pats[i], !load_p);
+ }
+
+ if (load_p && reg_overlap_mentioned_p (reg_ops[0], reg_ops[1]))
+ {
+ if (dump_file)
+ fprintf (dump_file,
+ "punting on lxvp due to reg conflcits (%d,%d)\n",
+ insns[0]->uid (), insns[1]->uid ());
+ return false;
+ }
+
+ if (cfun->can_throw_non_call_exceptions
+ && find_reg_note (insns[0]->rtl (), REG_EH_REGION, NULL_RTX)
+ && find_reg_note (insns[1]->rtl (), REG_EH_REGION, NULL_RTX))
+ {
+ if (dump_file)
+ fprintf (dump_file,
+ "can't combine insns with EH side effects (%d,%d)\n",
+ insns[0]->uid (), insns[1]->uid ());
+ return false;
+ }
+
+ auto_vec<base_cand, 2> base_cands (2);
+
+ int writeback = get_viable_bases (insns, base_cands, cand_mems,
+ access_size, reversed);
+ if (base_cands.is_empty ())
+ {
+ if (dump_file)
+ fprintf (dump_file, "no viable base for pair (%d,%d)\n",
+ insns[0]->uid (), insns[1]->uid ());
+ return false;
+ }
+
+ // Punt on frame-related insns with writeback. We probably won't see
+ // these in practice, but this is conservative and ensures we don't
+ // have to worry about these later on.
+ if (writeback && (RTX_FRAME_RELATED_P (i1->rtl ())
+ || RTX_FRAME_RELATED_P (i2->rtl ())))
+ {
+ if (dump_file)
+ fprintf (dump_file,
+ "rejecting pair (%d,%d): frame-related insn with writeback\n",
+ i1->uid (), i2->uid ());
+ return false;
+ }
+
+ rtx *ignore = &XEXP (pats[1], load_p);
+ for (auto use : insns[1]->uses ())
+ if (!use->is_mem ()
+ && refers_to_regno_p (use->regno (), use->regno () + 1, pats[1], ignore)
+ && use->def () && use->def ()->insn () == insns[0])
+ {
+ // N.B. we allow a true dependence on the base address, as this
+ // happens in the case of auto-inc accesses. Consider a post-increment
+ // load followed by a regular indexed load, for example.
+ if (dump_file)
+ fprintf (dump_file,
+ "%d has non-address true dependence on %d, rejecting pair\n",
+ insns[1]->uid (), insns[0]->uid ());
+ return false;
+ }
+
+ unsigned i = 0;
+ while (i < base_cands.length ())
+ {
+ base_cand &cand = base_cands[i];
+
+ rtx *ignore[2] = {};
+ for (int j = 0; j < 2; j++)
+ if (cand.from_insn == !j)
+ ignore[j] = &XEXP (cand_mems[j], 0);
+
+ insn_info *h = first_hazard_after (insns[0], ignore[0]);
+ if (h && *h <= *insns[1])
+ cand.hazards[0] = h;
+
+ h = latest_hazard_before (insns[1], ignore[1]);
+ if (h && *h >= *insns[0])
+ cand.hazards[1] = h;
+
+ if (!cand.viable ())
+ {
+ if (dump_file)
+ fprintf (dump_file,
+ "pair (%d,%d): rejecting base %d due to dataflow "
+ "hazards (%d,%d)\n",
+ insns[0]->uid (),
+ insns[1]->uid (),
+ cand.def->regno (),
+ cand.hazards[0]->uid (),
+ cand.hazards[1]->uid ());
+
+ base_cands.ordered_remove (i);
+ }
+ else
+ i++;
+ }
+
+ if (base_cands.is_empty ())
+ {
+ if (dump_file)
+ fprintf (dump_file,
+ "can't form pair (%d,%d) due to dataflow hazards\n",
+ insns[0]->uid (), insns[1]->uid ());
+ return false;
+ }
+
+ //insn_info *alias_hazards[4] = {};
+
+ // First def of memory after the first insn, and last def of memory
+ // before the second insn, respectively.
+ def_info *mem_defs[2] = {};
+ if (load_p)
+ {
+ if (!MEM_READONLY_P (cand_mems[0]))
+ {
+ mem_defs[0] = memory_access (insns[0]->uses ())->def ();
+ gcc_checking_assert (mem_defs[0]);
+ mem_defs[0] = mem_defs[0]->next_def ();
+ }
+ if (!MEM_READONLY_P (cand_mems[1]))
+ {
+ mem_defs[1] = memory_access (insns[1]->uses ())->def ();
+ gcc_checking_assert (mem_defs[1]);
+ }
+ }
+ else
+ {
+ mem_defs[0] = memory_access (insns[0]->defs ())->next_def ();
+ mem_defs[1] = memory_access (insns[1]->defs ())->prev_def ();
+ gcc_checking_assert (mem_defs[0]);
+ gcc_checking_assert (mem_defs[1]);
+ }
+
+ //auto tombstone_p = [&](insn_info *insn) -> bool {
+ // return m_emitted_tombstone
+// && bitmap_bit_p (&m_tombstone_bitmap, insn->uid ());
+ // };
+
+ if (base_cands.is_empty ())
+ {
+ if (dump_file)
+ fprintf (dump_file,
+ "cannot form pair (%d,%d) due to alias/dataflow hazards",
+ insns[0]->uid (), insns[1]->uid ());
+
+ return false;
+ }
+
+ base_cand *base = &base_cands[0];
+ insn_range_info range (insns[0], insns[1]);
+ // If the second insn can throw, narrow the move range to exactly that insn.
+ // This prevents us trying to move the second insn from the end of the BB.
+ if (cfun->can_throw_non_call_exceptions
+ && find_reg_note (insns[1]->rtl (), REG_EH_REGION, NULL_RTX))
+ {
+ gcc_assert (range.includes (insns[1]));
+ range = insn_range_info (insns[1]);
+ }
+
+ // Placement strategy: push loads down and pull stores up, this should
+ // help register pressure by reducing live ranges.
+ if (load_p)
+ range.first = range.last;
+ else
+ range.last = range.first;
+ return fuse_pair (load_p, access_size, writeback,
+ i1, i2, *base, range);
+}
+// Try and actually fuse the pair given by insns I1 and I2.
+//
+// Here we've done enough analysis to know this is safe, we only
+// reject the pair at this stage if either the tuning policy says to,
+// or recog fails on the final pair insn.
+//
+// LOAD_P is true for loads, ACCESS_SIZE gives the access size of each
+// candidate insn. Bit i of WRITEBACK is set if the ith insn (in program
+// order) uses writeback.
+//
+// BASE gives the chosen base candidate for the pair and MOVE_RANGE is
+// a singleton range which says where to place the pair.
+bool
+fusion_bb_info::fuse_pair (bool load_p,
+ unsigned access_size,
+ int writeback,
+ insn_info *i1, insn_info *i2,
+ base_cand &base,
+ const insn_range_info &move_range)
+{
+ auto attempt = crtl->ssa->new_change_attempt ();
+
+ auto make_change = [&attempt](insn_info *insn)
+ {
+ return crtl->ssa->change_alloc<insn_change> (attempt, insn);
+ };
+ auto make_delete = [&attempt](insn_info *insn)
+ {
+ return crtl->ssa->change_alloc<insn_change> (attempt,
+ insn,
+ insn_change::DELETE);
+ };
+
+ if (*i1 > *i2)
+ return false;
+
+ insn_info *first = (*i1 < *i2) ? i1 : i2;
+ insn_info *second = (first == i1) ? i2 : i1;
+
+ insn_info *insns[2] = { first, second };
+
+ auto_vec<insn_change *, 4> changes (4);
+ auto_vec<int, 2> tombstone_uids (2);
+
+ rtx pats[2] = {
+ PATTERN (first->rtl ()),
+ PATTERN (second->rtl ())
+ };
+
+ use_array input_uses[2] = { first->uses (), second->uses () };
+ def_array input_defs[2] = { first->defs (), second->defs () };
+
+ int changed_insn = -1;
+ if (base.from_insn != -1)
+ {
+ // If we're not already using a shared base, we need
+ // to re-write one of the accesses to use the base from
+ // the other insn.
+ gcc_checking_assert (base.from_insn == 0 || base.from_insn == 1);
+ changed_insn = !base.from_insn;
+
+ rtx base_pat = pats[base.from_insn];
+ rtx change_pat = pats[changed_insn];
+ rtx base_mem = XEXP (base_pat, load_p);
+ rtx change_mem = XEXP (change_pat, load_p);
+
+ const bool lower_base_p = (insns[base.from_insn] == i1);
+ HOST_WIDE_INT adjust_amt = access_size;
+ if (!lower_base_p)
+ adjust_amt *= -1;
+
+ rtx change_reg = XEXP (change_pat, !load_p);
+ machine_mode mode_for_mem = GET_MODE (change_mem);
+ rtx effective_base = drop_writeback (base_mem);
+ rtx new_mem = adjust_address_nv (effective_base,
+ mode_for_mem,
+ adjust_amt);
+ rtx new_set = load_p
+ ? gen_rtx_SET (change_reg, new_mem)
+ : gen_rtx_SET (new_mem, change_reg);
+
+ pats[changed_insn] = new_set;
+
+ auto keep_use = [&](use_info *u)
+ {
+ return refers_to_regno_p (u->regno (), u->regno () + 1,
+ change_pat, &XEXP (change_pat, load_p));
+ };
+
+ // Drop any uses that only occur in the old address.
+ input_uses[changed_insn] = filter_accesses (attempt,
+ input_uses[changed_insn],
+ keep_use);
+ }
+
+ rtx writeback_effect = NULL_RTX;
+ if (writeback)
+ writeback_effect = extract_writebacks (load_p, pats, changed_insn);
+
+ const auto base_regno = base.def->regno ();
+
+ if (base.from_insn == -1 && (writeback & 1))
+ {
+ // If the first of the candidate insns had a writeback form, we'll need to
+ // drop the use of the updated base register from the second insn's uses.
+ //
+ // N.B. we needn't worry about the base register occurring as a store
+ // operand, as we checked that there was no non-address true dependence
+ // between the insns in try_fuse_pair.
+ gcc_checking_assert (find_access (input_uses[1], base_regno));
+ input_uses[1] = check_remove_regno_access (attempt,
+ input_uses[1],
+ base_regno);
+ }
+
+ // Go through and drop uses that only occur in register notes,
+ // as we won't be preserving those.
+ for (int i = 0; i < 2; i++)
+ {
+ auto rti = insns[i]->rtl ();
+ if (!REG_NOTES (rti))
+ continue;
+
+ input_uses[i] = remove_note_accesses (attempt, input_uses[i]);
+ }
+
+ // So far the patterns have been in instruction order,
+ // now we want them in offset order.
+ if (i1 != first)
+ std::swap (pats[0], pats[1]);
+
+ poly_int64 offsets[2];
+ for (int i = 0; i < 2; i++)
+ {
+ rtx mem = XEXP (pats[i], load_p);
+ gcc_checking_assert (MEM_P (mem));
+ rtx base = strip_offset (XEXP (mem, 0), offsets + i);
+ gcc_checking_assert (REG_P (base));
+ gcc_checking_assert (base_regno == REGNO (base));
+ }
+
+ // If either of the original insns had writeback, but the resulting pair insn
+ // does not (can happen e.g. in the lxvp edge case above, or if the writeback
+ // effects cancel out), then drop the def(s) of the base register as
+ // appropriate.
+ //
+ // Also drop the first def in the case that both of the original insns had
+ // writeback. The second def could well have uses, but the first def should
+ // only be used by the second insn (and we dropped that use above).
+ for (int i = 0; i < 2; i++)
+ if ((!writeback_effect && (writeback & (1 << i)))
+ || (i == 0 && writeback == 3))
+ input_defs[i] = check_remove_regno_access (attempt,
+ input_defs[i],
+ base_regno);
+
+ // If we don't currently have a writeback pair, and we don't have
+ // a load that clobbers the base register, look for a trailing destructive
+ // update of the base register and try and fold it in to make this into a
+ // writeback pair.
+ insn_info *trailing_add = nullptr;
+
+ rtx reg_notes = combine_reg_notes (first, second, load_p);
+
+ rtx pair_pat = NULL_RTX;
+
+ if (load_p) {
+ pair_pat = rs6000_gen_load_pair ((first->rtl ()), (second->rtl ()));
+ if (pair_pat == NULL_RTX)
+ return false;
+ }
+ insn_change *pair_change = nullptr;
+ auto set_pair_pat = [pair_pat,reg_notes](insn_change *change) {
+ rtx_insn *rti = change->insn ()->rtl ();
+ validate_unshare_change (rti, &PATTERN (rti), pair_pat, true);
+ validate_change (rti, ®_NOTES (rti), reg_notes, true);
+ };
+
+ if (load_p)
+ {
+ changes.quick_push (make_delete (first));
+ pair_change = make_change (second);
+ changes.quick_push (pair_change);
+
+ pair_change->move_range = move_range;
+ pair_change->new_defs = merge_access_arrays (attempt,
+ input_defs[0],
+ input_defs[1]);
+ gcc_assert (pair_change->new_defs.is_valid ());
+
+ pair_change->new_uses
+ = merge_access_arrays (attempt,
+ drop_memory_access (input_uses[0]),
+ drop_memory_access (input_uses[1]));
+ gcc_assert (pair_change->new_uses.is_valid ());
+ set_pair_pat (pair_change);
+ }
+ else
+ {
+ insn_info *store_to_change = decide_store_strategy (first, second,
+ move_range);
+
+ if (store_to_change && dump_file)
+ fprintf (dump_file, " stvx: re-purposing store %d\n",
+ store_to_change->uid ());
+
+ insn_change *change;
+ for (int i = 0; i < 2; i++)
+ {
+ change = make_change (insns[i]);
+ if (insns[i] == store_to_change)
+ {
+ set_pair_pat (change);
+ change->new_uses = merge_access_arrays (attempt,
+ input_uses[0],
+ input_uses[1]);
+ auto d1 = drop_memory_access (input_defs[0]);
+ auto d2 = drop_memory_access (input_defs[1]);
+ change->new_defs = merge_access_arrays (attempt, d1, d2);
+ gcc_assert (change->new_defs.is_valid ());
+ def_info *stxvp_def = memory_access (store_to_change->defs ());
+ change->new_defs = insert_access (attempt,
+ stxvp_def,
+ change->new_defs);
+ gcc_assert (change->new_defs.is_valid ());
+ change->move_range = move_range;
+ pair_change = change;
+ }
+ else
+ {
+ // Note that we are turning this insn into a tombstone,
+ // we need to keep track of these if we go ahead with the
+ // change.
+ tombstone_uids.quick_push (insns[i]->uid ());
+ rtx_insn *rti = insns[i]->rtl ();
+ validate_change (rti, &PATTERN (rti), gen_tombstone (), true);
+ validate_change (rti, ®_NOTES (rti), NULL_RTX, true);
+ change->new_uses = use_array (nullptr, 0);
+ }
+ gcc_assert (change->new_uses.is_valid ());
+ changes.quick_push (change);
+ }
+
+ if (!store_to_change)
+ {
+ // Tricky case. Cannot re-purpose existing insns for stp.
+ // Need to insert new insn.
+ if (dump_file)
+ fprintf (dump_file,
+ " stp fusion: cannot re-purpose candidate stores\n");
+
+ auto new_insn = crtl->ssa->create_insn (attempt, INSN, pair_pat);
+ change = make_change (new_insn);
+ change->move_range = move_range;
+ change->new_uses = merge_access_arrays (attempt,
+ input_uses[0],
+ input_uses[1]);
+ gcc_assert (change->new_uses.is_valid ());
+
+ auto d1 = drop_memory_access (input_defs[0]);
+ auto d2 = drop_memory_access (input_defs[1]);
+ change->new_defs = merge_access_arrays (attempt, d1, d2);
+ gcc_assert (change->new_defs.is_valid ());
+
+ auto new_set = crtl->ssa->create_set (attempt, new_insn, memory);
+ change->new_defs = insert_access (attempt, new_set,
+ change->new_defs);
+ gcc_assert (change->new_defs.is_valid ());
+ changes.safe_insert (1, change);
+ pair_change = change;
+ }
+ }
+
+ if (trailing_add)
+ changes.quick_push (make_delete (trailing_add));
+
+ auto n_changes = changes.length ();
+ gcc_checking_assert (n_changes >= 2 && n_changes <= 4);
+
+ gcc_assert (crtl->ssa->verify_insn_changes (changes));
+
+ cancel_changes (0);
+
+ confirm_change_group ();
+
+ gcc_checking_assert (tombstone_uids.length () <= 2);
+ for (auto uid : tombstone_uids)
+ track_tombstone (uid);
+
+ return true;
+}
+
+void fusion_bb (bb_info *bb)
+{
+ fusion_bb_info bb_state (bb);
+
+ for (auto insn : bb->nondebug_insns ())
+ {
+ rtx_insn *rti = insn->rtl ();
+
+ if (!rti || !INSN_P (rti))
+ continue;
+
+ rtx pat = PATTERN (rti);
+
+ if (GET_CODE (pat) != SET)
+ continue;
+
+ if (MEM_P (XEXP (pat, 1)))
+ bb_state.track_access (insn, true, XEXP (pat, 1));
+ }
+
+ bb_state.transform ();
+ bb_state.cleanup_tombstones ();
+}
+
+static void
+fusion_init ()
+{
+ df_set_flags (DF_RD_PRUNE_DEAD_DEFS);
+ df_chain_add_problem (DF_DU_CHAIN | DF_UD_CHAIN);
+ df_analyze ();
+ df_set_flags (DF_DEFER_INSN_RESCAN);
+ /* Rebuild ud- and du-chains. */
+ df_remove_problem (df_chain);
+ df_process_deferred_rescans ();
+ df_set_flags (DF_RD_PRUNE_DEAD_DEFS);
+ df_chain_add_problem (DF_DU_CHAIN | DF_UD_CHAIN);
+ df_analyze ();
+ df_set_flags (DF_DEFER_INSN_RESCAN);
+ calculate_dominance_info (CDI_DOMINATORS);
+ crtl->ssa = new rtl_ssa::function_info (cfun);
+}
+
+static void
+fusion_destroy ()
+{
+ if (crtl->ssa->perform_pending_updates ())
+ cleanup_cfg (0);
+
+ free_dominance_info (CDI_DOMINATORS);
+ delete crtl->ssa;
+ crtl->ssa = nullptr;
+}
+
+// Iterate over the accesses in GROUP, looking for adjacent sets
+// of accesses. If we find two sets of adjacent accesses, call
+// merge_pairs.
+void
+fusion_bb_info::transform_for_base (int encoded_lfs,
+ access_group &group)
+{
+ const auto lfs = decode_lfs (encoded_lfs);
+ const unsigned access_size = lfs.size;
+
+ bool skip_next = true;
+ access_record *prev_access = nullptr;
+
+ for (auto &access : group.list)
+ {
+ if (skip_next)
+ skip_next = false;
+ else if (known_eq (access.offset, prev_access->offset + access_size))
+ {
+ merge_pairs (prev_access->cand_insns,
+ access.cand_insns,
+ lfs.load_p,
+ access_size);
+ skip_next = true;
+ }
+ prev_access = &access;
+ }
+}
+
+void rs6000_analyze_vecload ()
+{
+ fusion_init ();
+
+ for (auto bb : crtl->ssa->bbs ())
+ fusion_bb (bb);
+
+ fusion_destroy ();
+}
+
+const pass_data pass_data_analyze_vecload =
+{
+ RTL_PASS, /* type */
+ "vecload", /* name */
+ OPTGROUP_NONE, /* optinfo_flags */
+ TV_NONE, /* tv_id */
+ 0, /* properties_required */
+ 0, /* properties_provided */
+ 0, /* properties_destroyed */
+ 0, /* todo_flags_start */
+ TODO_df_finish, /* todo_flags_finish */
+};
+
+class pass_analyze_vecload : public rtl_opt_pass
+{
+public:
+ pass_analyze_vecload(gcc::context *ctxt)
+ : rtl_opt_pass(pass_data_analyze_vecload, ctxt)
+ {}
+
+ /* opt_pass methods: */
+ bool gate (function *)
+ {
+ return (optimize > 0 && TARGET_VSX && TARGET_POWER10);
+ }
+
+ unsigned int execute (function *) final override
+ {
+ rs6000_analyze_vecload ();
+ return 0;
+ }
+}; // class pass_analyze_vecload
+
+rtl_opt_pass *
+make_pass_analyze_vecload (gcc::context *ctxt)
+{
+ return new pass_analyze_vecload (ctxt);
+}
+
@@ -1178,6 +1178,7 @@ static bool rs6000_secondary_reload_move (enum rs6000_reg_type,
secondary_reload_info *,
bool);
rtl_opt_pass *make_pass_analyze_swaps (gcc::context*);
+rtl_opt_pass *make_pass_analyze_vecload (gcc::context*);
/* Hash table stuff for keeping track of TOC entries. */
@@ -35,6 +35,11 @@ rs6000-p8swap.o: $(srcdir)/config/rs6000/rs6000-p8swap.cc
$(COMPILE) $<
$(POSTCOMPILE)
+rs6000-vecload-opt.o: $(srcdir)/config/rs6000/rs6000-vecload-opt.cc
+ $(COMPILE) $<
+ $(POSTCOMPILE)
+
+
rs6000-d.o: $(srcdir)/config/rs6000/rs6000-d.cc
$(COMPILE) $<
$(POSTCOMPILE)
@@ -921,7 +921,7 @@ validate_subreg (machine_mode omode, machine_mode imode,
return false;
/* The subreg offset cannot be outside the inner object. */
- if (maybe_ge (offset, isize))
+ if (maybe_gt (offset, isize))
return false;
poly_uint64 regsize = REGMODE_NATURAL_SIZE (imode);
@@ -3035,18 +3035,6 @@ verify_rtx_sharing (rtx orig, rtx insn)
break;
}
- /* This rtx may not be shared. If it has already been seen,
- replace it with a copy of itself. */
- if (flag_checking && RTX_FLAG (x, used))
- {
- error ("invalid rtl sharing found in the insn");
- debug_rtx (insn);
- error ("shared rtx");
- debug_rtx (x);
- internal_error ("internal consistency failure");
- }
- gcc_assert (!RTX_FLAG (x, used));
-
RTX_FLAG (x, used) = 1;
/* Now scan the subexpressions recursively. */
new file mode 100644
@@ -0,0 +1,1195 @@
+// Common infrastructure for Load/Store Pair fusion optimization pass.
+// Copyright (C) 2023-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
+// <http://www.gnu.org/licenses/>.
+
+#define INCLUDE_ALGORITHM
+#define INCLUDE_FUNCTIONAL
+#define INCLUDE_LIST
+#define INCLUDE_TYPE_TRAITS
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "rtl.h"
+#include "df.h"
+#include "rtl-iter.h"
+#include "rtl-ssa.h"
+#include "cfgcleanup.h"
+#include "tree-pass.h"
+#include "ordered-hash-map.h"
+#include "tree-dfa.h"
+#include "fold-const.h"
+#include "tree-hash-traits.h"
+#include "print-tree.h"
+#include "insn-attr.h"
+
+using namespace rtl_ssa;
+
+static constexpr HOST_WIDE_INT LDP_IMM_BITS = 7;
+static constexpr HOST_WIDE_INT LDP_IMM_SIGN_BIT = (1 << (LDP_IMM_BITS - 1));
+static constexpr HOST_WIDE_INT LDP_MAX_IMM = LDP_IMM_SIGN_BIT - 1;
+static constexpr HOST_WIDE_INT LDP_MIN_IMM = -LDP_MAX_IMM - 1;
+
+// We pack these fields (load_p, fpsimd_p, and size) into an integer
+// (LFS) which we use as part of the key into the main hash tables.
+//
+// The idea is that we group candidates together only if they agree on
+// the fields below. Candidates that disagree on any of these
+// properties shouldn't be merged together.
+struct lfs_fields
+{
+ bool load_p;
+ bool fpsimd_p;
+ unsigned size;
+};
+
+using insn_list_t = std::list<insn_info *>;
+using insn_iter_t = insn_list_t::iterator;
+
+// Information about the accesses at a given offset from a particular
+// base. Stored in an access_group, see below.
+struct access_record
+{
+ poly_int64 offset;
+ std::list<insn_info *> cand_insns;
+ std::list<access_record>::iterator place;
+
+ access_record (poly_int64 off) : offset (off) {}
+};
+
+// A group of accesses where adjacent accesses could be ldp/stp
+// or lxvp/stxvp candidates. The splay tree supports efficient
+// insertion, while the list supports efficient iteration.
+struct access_group
+{
+ splay_tree<access_record *> tree;
+ std::list<access_record> list;
+
+ template<typename Alloc>
+ inline void track (Alloc node_alloc, poly_int64 offset, insn_info *insn);
+};
+
+// Information about a potential base candidate, used in try_fuse_pair.
+// There may be zero, one, or two viable RTL bases for a given pair.
+struct base_cand
+{
+ // DEF is the def of the base register to be used by the pair.
+ def_info *def;
+
+ // FROM_INSN is -1 if the base candidate is already shared by both
+ // candidate insns. Otherwise it holds the index of the insn from
+ // which the base originated.
+ //
+ // In the case that the base is shared, either DEF is already used
+ // by both candidate accesses, or both accesses see different versions
+ // of the same regno, in which case DEF is the def consumed by the
+ // first candidate access.
+ int from_insn;
+
+ // To form a pair, we do so by moving the first access down and the second
+ // access up. To determine where to form the pair, and whether or not
+ // it is safe to form the pair, we track instructions which cannot be
+ // re-ordered past due to either dataflow or alias hazards.
+ //
+ // Since we allow changing the base used by an access, the choice of
+ // base can change which instructions act as re-ordering hazards for
+ // this pair (due to different dataflow). We store the initial
+ // dataflow hazards for this choice of base candidate in HAZARDS.
+ //
+ // These hazards act as re-ordering barriers to each candidate insn
+ // respectively, in program order.
+ //
+ // Later on, when we take alias analysis into account, we narrow
+ // HAZARDS accordingly.
+ insn_info *hazards[2];
+
+ base_cand (def_info *def, int insn)
+ : def (def), from_insn (insn), hazards {nullptr, nullptr} {}
+
+ base_cand (def_info *def) : base_cand (def, -1) {}
+
+ // Test if this base candidate is viable according to HAZARDS.
+ bool viable () const
+ {
+ return !hazards[0] || !hazards[1] || (*hazards[0] > *hazards[1]);
+ }
+};
+
+// Information about an alternate base. For a def_info D, it may
+// instead be expressed as D = BASE + OFFSET.
+struct alt_base
+{
+ def_info *base;
+ poly_int64 offset;
+};
+
+// State used by the pass for a given basic block.
+struct fusion_bb_info
+{
+ using def_hash = nofree_ptr_hash<def_info>;
+ using expr_key_t = pair_hash<tree_operand_hash, int_hash<int, -1, -2>>;
+ using def_key_t = pair_hash<def_hash, int_hash<int, -1, -2>>;
+
+ // Map of <tree base, LFS> -> access_group.
+ ordered_hash_map<expr_key_t, access_group> expr_map;
+
+ // Map of <RTL-SSA def_info *, LFS> -> access_group.
+ ordered_hash_map<def_key_t, access_group> def_map;
+
+ // Given the def_info for an RTL base register, express it as an offset from
+ // some canonical base instead.
+ //
+ // Canonicalizing bases in this way allows us to identify adjacent accesses
+ // even if they see different base register defs.
+ hash_map<def_hash, alt_base> canon_base_map;
+
+ static const size_t obstack_alignment = sizeof (void *);
+ bb_info *m_bb;
+
+ fusion_bb_info (bb_info *bb) : m_bb (bb), m_emitted_tombstone (false)
+ {
+ obstack_specify_allocation (&m_obstack, OBSTACK_CHUNK_SIZE,
+ obstack_alignment, obstack_chunk_alloc,
+ obstack_chunk_free);
+ }
+ ~fusion_bb_info ()
+ {
+ obstack_free (&m_obstack, nullptr);
+
+ if (m_emitted_tombstone)
+ {
+ bitmap_release (&m_tombstone_bitmap);
+ bitmap_obstack_release (&m_bitmap_obstack);
+ }
+ }
+
+ inline void track_access (insn_info *, bool load, rtx mem);
+ inline void transform ();
+ inline void cleanup_tombstones ();
+
+private:
+ obstack m_obstack;
+
+ // State for keeping track of tombstone insns emitted for this BB.
+ bitmap_obstack m_bitmap_obstack;
+ bitmap_head m_tombstone_bitmap;
+ bool m_emitted_tombstone;
+
+ inline splay_tree_node<access_record *> *node_alloc (access_record *);
+
+ template<typename Map>
+ inline void traverse_base_map (Map &map);
+ inline void transform_for_base (int load_size, access_group &group);
+
+ void merge_pairs (insn_list_t &, insn_list_t &,
+ bool load_p, unsigned access_size);
+
+ bool try_fuse_pair (bool load_p, unsigned access_size,
+ insn_info *i1, insn_info *i2);
+
+ bool fuse_pair (bool load_p, unsigned access_size,
+ int writeback,
+ insn_info *i1, insn_info *i2,
+ base_cand &base,
+ const insn_range_info &move_range);
+
+ inline void track_tombstone (int uid);
+
+ inline bool track_via_mem_expr (insn_info *, rtx mem, lfs_fields lfs);
+};
+
+splay_tree_node<access_record *> *fusion_bb_info::node_alloc (access_record *access)
+{
+ using T = splay_tree_node<access_record *>;
+ void *addr = obstack_alloc (&m_obstack, sizeof (T));
+ return new (addr) T (access);
+}
+
+// Given a mem MEM, if the address has side effects, return a MEM that accesses
+// the same address but without the side effects. Otherwise, return
+// MEM unchanged.
+static rtx
+drop_writeback (rtx mem)
+{
+ rtx addr = XEXP (mem, 0);
+
+ if (!side_effects_p (addr))
+ return mem;
+
+ switch (GET_CODE (addr))
+ {
+ case PRE_MODIFY:
+ addr = XEXP (addr, 1);
+ break;
+ case POST_MODIFY:
+ case POST_INC:
+ case POST_DEC:
+ addr = XEXP (addr, 0);
+ break;
+ case PRE_INC:
+ case PRE_DEC:
+ {
+ poly_int64 adjustment = GET_MODE_SIZE (GET_MODE (mem));
+ if (GET_CODE (addr) == PRE_DEC)
+ adjustment *= -1;
+ addr = plus_constant (GET_MODE (addr), XEXP (addr, 0), adjustment);
+ break;
+ }
+ default:
+ gcc_unreachable ();
+ }
+
+ return change_address (mem, GET_MODE (mem), addr);
+}
+
+// Convenience wrapper around strip_offset that can also look through
+// RTX_AUTOINC addresses. The interface is like strip_offset except we take a
+// MEM so that we know the mode of the access.
+static rtx
+load_strip_offset (rtx mem, poly_int64 *offset)
+{
+ rtx addr = XEXP (mem, 0);
+
+ switch (GET_CODE (addr))
+ {
+ case PRE_MODIFY:
+ case POST_MODIFY:
+ addr = strip_offset (XEXP (addr, 1), offset);
+ gcc_checking_assert (REG_P (addr));
+ gcc_checking_assert (rtx_equal_p (XEXP (XEXP (mem, 0), 0), addr));
+ break;
+ case PRE_INC:
+ case POST_INC:
+ addr = XEXP (addr, 0);
+ *offset = GET_MODE_SIZE (GET_MODE (mem));
+ gcc_checking_assert (REG_P (addr));
+ break;
+ case PRE_DEC:
+ case POST_DEC:
+ addr = XEXP (addr, 0);
+ *offset = -GET_MODE_SIZE (GET_MODE (mem));
+ gcc_checking_assert (REG_P (addr));
+ break;
+
+ default:
+ addr = strip_offset (addr, offset);
+ }
+
+ return addr;
+}
+
+// Return true if X is a PRE_{INC,DEC,MODIFY} rtx.
+static bool
+any_pre_modify_p (rtx x)
+{
+ const auto code = GET_CODE (x);
+ return code == PRE_INC || code == PRE_DEC || code == PRE_MODIFY;
+}
+
+// Return true if X is a POST_{INC,DEC,MODIFY} rtx.
+static bool
+any_post_modify_p (rtx x)
+{
+ const auto code = GET_CODE (x);
+ return code == POST_INC || code == POST_DEC || code == POST_MODIFY;
+}
+// Given LFS (load_p, fpsimd_p, size) fields in FIELDS, encode these
+// into an integer for use as a hash table key.
+static int
+encode_lfs (lfs_fields fields)
+{
+ int size_log2 = exact_log2 (fields.size);
+ //gcc_checking_assert (size_log2 >= 2 && size_log2 <= 4);
+ return ((int)fields.load_p << 3)
+ | ((int)fields.fpsimd_p << 2)
+ | (size_log2 - 2);
+}
+
+// Inverse of encode_lfs.
+static lfs_fields
+decode_lfs (int lfs)
+{
+ bool load_p = (lfs & (1 << 3));
+ bool fpsimd_p = (lfs & (1 << 2));
+ unsigned size = 1U << ((lfs & 3) + 2);
+ return { load_p, fpsimd_p, size };
+}
+// Track the access INSN at offset OFFSET in this access group.
+// ALLOC_NODE is used to allocate splay tree nodes.
+template<typename Alloc>
+void
+access_group::track (Alloc alloc_node, poly_int64 offset, insn_info *insn)
+{
+ auto insert_before = [&](std::list<access_record>::iterator after)
+ {
+ auto it = list.emplace (after, offset);
+ it->cand_insns.push_back (insn);
+ it->place = it;
+ return &*it;
+ };
+
+ if (!list.size ())
+ {
+ auto access = insert_before (list.end ());
+ tree.insert_max_node (alloc_node (access));
+ return;
+ }
+
+ auto compare = [&](splay_tree_node<access_record *> *node)
+ {
+ return compare_sizes_for_sort (offset, node->value ()->offset);
+ };
+ auto result = tree.lookup (compare);
+ splay_tree_node<access_record *> *node = tree.root ();
+ if (result == 0)
+ node->value ()->cand_insns.push_back (insn);
+ else
+ {
+ auto it = node->value ()->place;
+ auto after = (result > 0) ? std::next (it) : it;
+ auto access = insert_before (after);
+ tree.insert_child (node, result > 0, alloc_node (access));
+ }
+}
+// Given a candidate access INSN (with mem MEM), see if it has a suitable
+// MEM_EXPR base (i.e. a tree decl) relative to which we can track the access.
+// LFS is used as part of the key to the hash table, see track_access.
+bool
+fusion_bb_info::track_via_mem_expr (insn_info *insn, rtx mem, lfs_fields lfs)
+{
+ if (!MEM_EXPR (mem) || !MEM_OFFSET_KNOWN_P (mem))
+ return false;
+
+ poly_int64 offset;
+ tree base_expr = get_addr_base_and_unit_offset (MEM_EXPR (mem),
+ &offset);
+ if (!base_expr || !DECL_P (base_expr))
+ return false;
+
+ offset += MEM_OFFSET (mem);
+
+ const machine_mode mem_mode = GET_MODE (mem);
+ const HOST_WIDE_INT mem_size = GET_MODE_SIZE (mem_mode);//.to_constant ();
+
+ // Punt on misaligned offsets. LDP/STP instructions require offsets to be a
+ // multiple of the access size, and we believe that misaligned offsets on
+ // MEM_EXPR bases are likely to lead to misaligned offsets w.r.t. RTL bases.
+ if (!multiple_p (offset, mem_size))
+ return false;
+
+ const auto key = std::make_pair (base_expr, encode_lfs (lfs));
+ access_group &group = expr_map.get_or_insert (key, NULL);
+ auto alloc = [&](access_record *access) { return node_alloc (access); };
+ group.track (alloc, offset, insn);
+
+ if (dump_file)
+ {
+ fprintf (dump_file, "[bb %u] tracking insn %d via ",
+ m_bb->index (), insn->uid ());
+ print_node_brief (dump_file, "mem expr", base_expr, 0);
+ fprintf (dump_file, " [L=%d FP=%d, %smode, off=",
+ lfs.load_p, lfs.fpsimd_p, mode_name[mem_mode]);
+ print_dec (offset, dump_file);
+ fprintf (dump_file, "]\n");
+ }
+
+ return true;
+}
+
+// Dummy predicate that never ignores any insns.
+static bool no_ignore (insn_info *) { return false; }
+
+// Return the latest dataflow hazard before INSN.
+//
+// If IGNORE is non-NULL, this points to a sub-rtx which we should ignore for
+// dataflow purposes. This is needed when considering changing the RTL base of
+// an access discovered through a MEM_EXPR base.
+//
+// If IGNORE_INSN is non-NULL, we should further ignore any hazards arising
+// from that insn.
+//
+// N.B. we ignore any defs/uses of memory here as we deal with that separately,
+// making use of alias disambiguation.
+static insn_info *
+latest_hazard_before (insn_info *insn, rtx *ignore,
+ insn_info *ignore_insn = nullptr)
+{
+ insn_info *result = nullptr;
+
+ // If the insn can throw then it is at the end of a BB and we can't
+ // move it, model this by recording a hazard in the previous insn
+ // which will prevent moving the insn up.
+ if (cfun->can_throw_non_call_exceptions
+ && find_reg_note (insn->rtl (), REG_EH_REGION, NULL_RTX))
+ return insn->prev_nondebug_insn ();
+
+ // Return true if we registered the hazard.
+ auto hazard = [&](insn_info *h) -> bool
+ {
+ gcc_checking_assert (*h < *insn);
+ if (h == ignore_insn)
+ return false;
+
+ if (!result || *h > *result)
+ result = h;
+
+ return true;
+ };
+
+ rtx pat = PATTERN (insn->rtl ());
+ auto ignore_use = [&](use_info *u)
+ {
+ if (u->is_mem ())
+ return true;
+
+ return !refers_to_regno_p (u->regno (), u->regno () + 1, pat, ignore);
+ };
+
+ // Find defs of uses in INSN (RaW).
+ for (auto use : insn->uses ())
+ if (!ignore_use (use) && use->def ())
+ hazard (use->def ()->insn ());
+
+ // Find previous defs (WaW) or previous uses (WaR) of defs in INSN.
+ for (auto def : insn->defs ())
+ {
+ if (def->is_mem ())
+ continue;
+
+ if (def->prev_def ())
+ {
+ hazard (def->prev_def ()->insn ()); // WaW
+
+ auto set = dyn_cast<set_info *> (def->prev_def ());
+ if (set && set->has_nondebug_insn_uses ())
+ for (auto use : set->reverse_nondebug_insn_uses ())
+ if (use->insn () != insn && hazard (use->insn ())) // WaR
+ break;
+ }
+
+ if (!HARD_REGISTER_NUM_P (def->regno ()))
+ continue;
+
+ // Also need to check backwards for call clobbers (WaW).
+ for (auto call_group : def->ebb ()->call_clobbers ())
+ {
+ if (!call_group->clobbers (def->resource ()))
+ continue;
+
+ auto clobber_insn = prev_call_clobbers_ignoring (*call_group,
+ def->insn (),
+ no_ignore);
+ if (clobber_insn)
+ hazard (clobber_insn);
+ }
+
+ }
+
+ return result;
+}
+
+// Return the first dataflow hazard after INSN.
+//
+// If IGNORE is non-NULL, this points to a sub-rtx which we should ignore for
+// dataflow purposes. This is needed when considering changing the RTL base of
+// an access discovered through a MEM_EXPR base.
+//
+// N.B. we ignore any defs/uses of memory here as we deal with that separately,
+// making use of alias disambiguation.
+static insn_info *
+first_hazard_after (insn_info *insn, rtx *ignore)
+{
+ insn_info *result = nullptr;
+ auto hazard = [insn, &result](insn_info *h)
+ {
+ gcc_checking_assert (*h > *insn);
+ if (!result || *h < *result)
+ result = h;
+ };
+
+ rtx pat = PATTERN (insn->rtl ());
+ auto ignore_use = [&](use_info *u)
+ {
+ if (u->is_mem ())
+ return true;
+
+ return !refers_to_regno_p (u->regno (), u->regno () + 1, pat, ignore);
+ };
+
+ for (auto def : insn->defs ())
+ {
+ if (def->is_mem ())
+ continue;
+
+ if (def->next_def ())
+ hazard (def->next_def ()->insn ()); // WaW
+
+ auto set = dyn_cast<set_info *> (def);
+ if (set && set->has_nondebug_insn_uses ())
+ hazard (set->first_nondebug_insn_use ()->insn ()); // RaW
+
+ if (!HARD_REGISTER_NUM_P (def->regno ()))
+ continue;
+
+ // Also check for call clobbers of this def (WaW).
+ for (auto call_group : def->ebb ()->call_clobbers ())
+ {
+ if (!call_group->clobbers (def->resource ()))
+ continue;
+
+ auto clobber_insn = next_call_clobbers_ignoring (*call_group,
+ def->insn (),
+ no_ignore);
+ if (clobber_insn)
+ hazard (clobber_insn);
+ }
+ }
+
+ // Find any subsequent defs of uses in INSN (WaR).
+ for (auto use : insn->uses ())
+ {
+ if (ignore_use (use))
+ continue;
+
+ if (use->def ())
+ {
+ auto def = use->def ()->next_def ();
+ if (def && def->insn () == insn)
+ def = def->next_def ();
+
+ if (def)
+ hazard (def->insn ());
+ }
+
+ if (!HARD_REGISTER_NUM_P (use->regno ()))
+ continue;
+
+ // Also need to handle call clobbers of our uses (again WaR).
+ //
+ // See restrict_movement_for_uses_ignoring for why we don't
+ // need to check backwards for call clobbers.
+ for (auto call_group : use->ebb ()->call_clobbers ())
+ {
+ if (!call_group->clobbers (use->resource ()))
+ continue;
+
+ auto clobber_insn = next_call_clobbers_ignoring (*call_group,
+ use->insn (),
+ no_ignore);
+ if (clobber_insn)
+ hazard (clobber_insn);
+ }
+ }
+
+ return result;
+}
+
+// Return true iff R1 and R2 overlap.
+static bool
+ranges_overlap_p (const insn_range_info &r1, const insn_range_info &r2)
+{
+ // If either range is empty, then their intersection is empty.
+ if (!r1 || !r2)
+ return false;
+
+ // When do they not overlap? When one range finishes before the other
+ // starts, i.e. (*r1.last < *r2.first || *r2.last < *r1.first).
+ // Inverting this, we get the below.
+ return *r1.last >= *r2.first && *r2.last >= *r1.first;
+}
+
+// Get the range of insns that def feeds.
+static insn_range_info get_def_range (def_info *def)
+{
+ insn_info *last = def->next_def ()->insn ()->prev_nondebug_insn ();
+ return { def->insn (), last };
+}
+
+// Given a def (of memory), return the downwards range within which we
+// can safely move this def.
+static insn_range_info
+def_downwards_move_range (def_info *def)
+{
+ auto range = get_def_range (def);
+
+ auto set = dyn_cast<set_info *> (def);
+ if (!set || !set->has_any_uses ())
+ return range;
+
+ auto use = set->first_nondebug_insn_use ();
+ if (use)
+ range = move_earlier_than (range, use->insn ());
+
+ return range;
+}
+
+// Given a def (of memory), return the upwards range within which we can
+// safely move this def.
+static insn_range_info
+def_upwards_move_range (def_info *def)
+{
+ def_info *prev = def->prev_def ();
+ insn_range_info range { prev->insn (), def->insn () };
+
+ auto set = dyn_cast<set_info *> (prev);
+ if (!set || !set->has_any_uses ())
+ return range;
+
+ auto use = set->last_nondebug_insn_use ();
+ if (use)
+ range = move_later_than (range, use->insn ());
+
+ return range;
+}
+
+// Given candidate store insns FIRST and SECOND, see if we can re-purpose one
+// of them (together with its def of memory) for the stp/stxvp insn. If so,
+// return that insn. Otherwise, return null.
+static insn_info *
+decide_store_strategy (insn_info *first,
+ insn_info *second,
+ const insn_range_info &move_range)
+{
+ def_info * const defs[2] = {
+ memory_access (first->defs ()),
+ memory_access (second->defs ())
+ };
+
+ if (move_range.includes (first)
+ || ranges_overlap_p (move_range, def_downwards_move_range (defs[0])))
+ return first;
+
+ if (move_range.includes (second)
+ || ranges_overlap_p (move_range, def_upwards_move_range (defs[1])))
+ return second;
+
+ return nullptr;
+}
+
+// Generate the RTL pattern for a "tombstone"; used temporarily during this pass
+// to replace stores that are marked for deletion where we can't immediately
+// delete the store (since there are uses of mem hanging off the store).
+//
+// These are deleted at the end of the pass and uses re-parented appropriately
+// at this point.
+static rtx
+gen_tombstone (void)
+{
+ return gen_rtx_CLOBBER (VOIDmode,
+ gen_rtx_MEM (BLKmode, gen_rtx_SCRATCH (Pmode)));
+}
+// Go through the reg notes rooted at NOTE, dropping those that we should drop,
+// and preserving those that we want to keep by prepending them to (and
+// returning) RESULT. EH_REGION is used to make sure we have at most one
+// REG_EH_REGION note in the resulting list. FR_EXPR is used to return any
+// REG_FRAME_RELATED_EXPR note we find, as these can need special handling in
+// combine_reg_notes.
+static rtx
+filter_notes (rtx note, rtx result, bool *eh_region, rtx *fr_expr)
+{
+ for (; note; note = XEXP (note, 1))
+ {
+ switch (REG_NOTE_KIND (note))
+ {
+ case REG_DEAD:
+ // REG_DEAD notes aren't required to be maintained.
+ case REG_EQUAL:
+ case REG_EQUIV:
+ case REG_UNUSED:
+ case REG_NOALIAS:
+ // These can all be dropped. For REG_EQU{AL,IV} they cannot apply to
+ // non-single_set insns, and REG_UNUSED is re-computed by RTl-SSA, see
+ // rtl-ssa/changes.cc:update_notes.
+ //
+ // Similarly, REG_NOALIAS cannot apply to a parallel.
+ case REG_INC:
+ // When we form the pair insn, the reg update is implemented
+ // as just another SET in the parallel, so isn't really an
+ // auto-increment in the RTL sense, hence we drop the note.
+ break;
+ case REG_EH_REGION:
+ gcc_assert (!*eh_region);
+ *eh_region = true;
+ result = alloc_reg_note (REG_EH_REGION, XEXP (note, 0), result);
+ break;
+ case REG_CFA_DEF_CFA:
+ case REG_CFA_OFFSET:
+ case REG_CFA_RESTORE:
+ result = alloc_reg_note (REG_NOTE_KIND (note),
+ copy_rtx (XEXP (note, 0)),
+ result);
+ break;
+ case REG_FRAME_RELATED_EXPR:
+ gcc_assert (!*fr_expr);
+ *fr_expr = copy_rtx (XEXP (note, 0));
+ break;
+ default:
+ // Unexpected REG_NOTE kind.
+ gcc_unreachable ();
+ }
+ }
+
+ return result;
+}
+
+// Return the notes that should be attached to a combination of I1 and I2, where
+// *I1 < *I2. LOAD_P is true for loads.
+static rtx
+combine_reg_notes (insn_info *i1, insn_info *i2, bool load_p)
+{
+ // Temporary storage for REG_FRAME_RELATED_EXPR notes.
+ rtx fr_expr[2] = {};
+
+ bool found_eh_region = false;
+ rtx result = NULL_RTX;
+ result = filter_notes (REG_NOTES (i2->rtl ()), result,
+ &found_eh_region, fr_expr);
+ result = filter_notes (REG_NOTES (i1->rtl ()), result,
+ &found_eh_region, fr_expr + 1);
+
+ if (!load_p)
+ {
+ // Simple frame-related sp-relative saves don't need CFI notes, but when
+ // we combine them into an stp/stxvp we will need a CFI note as dwarf2cfi
+ // can't interpret the unspec pair representation directly.
+ if (RTX_FRAME_RELATED_P (i1->rtl ()) && !fr_expr[0])
+ fr_expr[0] = copy_rtx (PATTERN (i1->rtl ()));
+ if (RTX_FRAME_RELATED_P (i2->rtl ()) && !fr_expr[1])
+ fr_expr[1] = copy_rtx (PATTERN (i2->rtl ()));
+ }
+
+ rtx fr_pat = NULL_RTX;
+ if (fr_expr[0] && fr_expr[1])
+ {
+ // Combining two frame-related insns, need to construct
+ // a REG_FRAME_RELATED_EXPR note which represents the combined
+ // operation.
+ RTX_FRAME_RELATED_P (fr_expr[1]) = 1;
+ fr_pat = gen_rtx_PARALLEL (VOIDmode,
+ gen_rtvec (2, fr_expr[0], fr_expr[1]));
+ }
+ else
+ fr_pat = fr_expr[0] ? fr_expr[0] : fr_expr[1];
+
+ if (fr_pat)
+ result = alloc_reg_note (REG_FRAME_RELATED_EXPR,
+ fr_pat, result);
+
+ return result;
+}
+
+// Given two memory accesses in PATS, at least one of which is of a
+// writeback form, extract two non-writeback memory accesses addressed
+// relative to the initial value of the base register, and output these
+// in PATS. Return an rtx that represents the overall change to the
+// base register.
+static rtx
+extract_writebacks (bool load_p, rtx pats[2], int changed)
+{
+ rtx base_reg = NULL_RTX;
+ poly_int64 current_offset = 0;
+
+ poly_int64 offsets[2];
+
+ for (int i = 0; i < 2; i++)
+ {
+ rtx mem = XEXP (pats[i], load_p);
+ rtx reg = XEXP (pats[i], !load_p);
+
+ rtx addr = XEXP (mem, 0);
+ const bool autoinc_p = GET_RTX_CLASS (GET_CODE (addr)) == RTX_AUTOINC;
+
+ poly_int64 offset;
+ rtx this_base = load_strip_offset (mem, &offset);
+ gcc_assert (REG_P (this_base));
+ if (base_reg)
+ gcc_assert (rtx_equal_p (base_reg, this_base));
+ else
+ base_reg = this_base;
+
+ // If we changed base for the current insn, then we already
+ // derived the correct mem for this insn from the effective
+ // address of the other access.
+ if (i == changed)
+ {
+ gcc_checking_assert (!autoinc_p);
+ offsets[i] = offset;
+ continue;
+ }
+
+ if (autoinc_p && any_pre_modify_p (addr))
+ current_offset += offset;
+
+ poly_int64 this_off = current_offset;
+ if (!autoinc_p)
+ this_off += offset;
+
+ offsets[i] = this_off;
+ rtx new_mem = change_address (mem, GET_MODE (mem),
+ plus_constant (GET_MODE (base_reg),
+ base_reg, this_off));
+ pats[i] = load_p
+ ? gen_rtx_SET (reg, new_mem)
+ : gen_rtx_SET (new_mem, reg);
+
+ if (autoinc_p && any_post_modify_p (addr))
+ current_offset += offset;
+ }
+
+ if (known_eq (current_offset, 0))
+ return NULL_RTX;
+
+ return gen_rtx_SET (base_reg, plus_constant (GET_MODE (base_reg),
+ base_reg, current_offset));
+}
+
+// We just emitted a tombstone with uid UID, track it in a bitmap for
+// this BB so we can easily identify it later when cleaning up tombstones.
+void
+fusion_bb_info::track_tombstone (int uid)
+{
+ if (!m_emitted_tombstone)
+ {
+ // Lazily initialize the bitmap for tracking tombstone insns.
+ bitmap_obstack_initialize (&m_bitmap_obstack);
+ bitmap_initialize (&m_tombstone_bitmap, &m_bitmap_obstack);
+ m_emitted_tombstone = true;
+ }
+
+ if (!bitmap_set_bit (&m_tombstone_bitmap, uid))
+ gcc_unreachable (); // Bit should have changed.
+}
+
+// Virtual base class for load/store walkers used in alias analysis.
+struct alias_walker
+{
+ virtual bool conflict_p (int &budget) const = 0;
+ virtual insn_info *insn () const = 0;
+ virtual bool valid () const = 0;
+ virtual void advance () = 0;
+};
+
+// Implement some common functionality used by both store_walker
+// and load_walker.
+template<bool reverse>
+class def_walker : public alias_walker
+{
+protected:
+ using def_iter_t = typename std::conditional<reverse,
+ reverse_def_iterator, def_iterator>::type;
+
+ static use_info *start_use_chain (def_iter_t &def_iter)
+ {
+ set_info *set = nullptr;
+ for (; *def_iter; def_iter++)
+ {
+ set = dyn_cast<set_info *> (*def_iter);
+ if (!set)
+ continue;
+
+ use_info *use = reverse
+ ? set->last_nondebug_insn_use ()
+ : set->first_nondebug_insn_use ();
+
+ if (use)
+ return use;
+ }
+
+ return nullptr;
+ }
+
+ def_iter_t def_iter;
+ insn_info *limit;
+ def_walker (def_info *def, insn_info *limit) :
+ def_iter (def), limit (limit) {}
+
+ virtual bool iter_valid () const { return *def_iter; }
+
+public:
+ insn_info *insn () const override { return (*def_iter)->insn (); }
+ void advance () override { def_iter++; }
+ bool valid () const override final
+ {
+ if (!iter_valid ())
+ return false;
+
+ if (reverse)
+ return *(insn ()) > *limit;
+ else
+ return *(insn ()) < *limit;
+ }
+};
+
+// alias_walker that iterates over stores.
+template<bool reverse, typename InsnPredicate>
+class store_walker : public def_walker<reverse>
+{
+ rtx cand_mem;
+ InsnPredicate tombstone_p;
+
+public:
+ store_walker (def_info *mem_def, rtx mem, insn_info *limit_insn,
+ InsnPredicate tombstone_fn) :
+ def_walker<reverse> (mem_def, limit_insn),
+ cand_mem (mem), tombstone_p (tombstone_fn) {}
+
+ bool conflict_p (int &budget) const override final
+ {
+ if (tombstone_p (this->insn ()))
+ return false;
+
+ return store_modifies_mem_p (cand_mem, this->insn (), budget);
+ }
+};
+
+// alias_walker that iterates over loads.
+template<bool reverse>
+class load_walker : public def_walker<reverse>
+{
+ using Base = def_walker<reverse>;
+ using use_iter_t = typename std::conditional<reverse,
+ reverse_use_iterator, nondebug_insn_use_iterator>::type;
+
+ use_iter_t use_iter;
+ insn_info *cand_store;
+
+ bool iter_valid () const override final { return *use_iter; }
+
+public:
+ void advance () override final
+ {
+ use_iter++;
+ if (*use_iter)
+ return;
+ this->def_iter++;
+ use_iter = Base::start_use_chain (this->def_iter);
+ }
+
+ insn_info *insn () const override final
+ {
+ return (*use_iter)->insn ();
+ }
+
+ bool conflict_p (int &budget) const override final
+ {
+ return load_modified_by_store_p (insn (), cand_store, budget);
+ }
+
+ load_walker (def_info *def, insn_info *store, insn_info *limit_insn)
+ : Base (def, limit_insn),
+ use_iter (Base::start_use_chain (this->def_iter)),
+ cand_store (store) {}
+};
+
+// Given INSNS (in program order) which are known to be adjacent, look
+// to see if either insn has a suitable RTL (register) base that we can
+// use to form a pair. Push these to BASE_CANDS if we find any. CAND_MEMs
+// gives the relevant mems from the candidate insns, ACCESS_SIZE gives the
+// size of a single candidate access, and REVERSED says whether the accesses
+// are inverted in offset order.
+//
+// Returns an integer where bit (1 << i) is set if INSNS[i] uses writeback
+// addressing.
+static int
+get_viable_bases (insn_info *insns[2],
+ vec<base_cand> &base_cands,
+ rtx cand_mems[2],
+ unsigned access_size,
+ bool reversed)
+{
+ // We discovered this pair through a common base. Need to ensure that
+ // we have a common base register that is live at both locations.
+ def_info *base_defs[2] = {};
+ int writeback = 0;
+ for (int i = 0; i < 2; i++)
+ {
+ const bool is_lower = (i == reversed);
+ poly_int64 poly_off;
+ rtx base = load_strip_offset (cand_mems[i], &poly_off);
+ if (GET_RTX_CLASS (GET_CODE (XEXP (cand_mems[i], 0))) == RTX_AUTOINC)
+ writeback |= (1 << i);
+
+ if (!REG_P (base) || !poly_off.is_constant ())
+ continue;
+
+ // Punt on accesses relative to eliminable regs. See the comment in
+ // fusion_bb_info::track_access for a detailed explanation of this.
+ if (!reload_completed
+ && (REGNO (base) == FRAME_POINTER_REGNUM
+ || REGNO (base) == ARG_POINTER_REGNUM))
+ continue;
+
+ HOST_WIDE_INT base_off = poly_off.to_constant ();
+
+ // It should be unlikely that we ever punt here, since MEM_EXPR offset
+ // alignment should be a good proxy for register offset alignment.
+ if (base_off % access_size != 0)
+ {
+ if (dump_file)
+ fprintf (dump_file,
+ "base not viable, offset misaligned (insn %d)\n",
+ insns[i]->uid ());
+ continue;
+ }
+
+ base_off /= access_size;
+
+ if (!is_lower)
+ base_off--;
+
+ if (base_off < LDP_MIN_IMM || base_off > LDP_MAX_IMM)
+ continue;
+
+ use_info *use = find_access (insns[i]->uses (), REGNO (base));
+ gcc_assert (use);
+ base_defs[i] = use->def ();
+ }
+
+ if (!base_defs[0] && !base_defs[1])
+ {
+ if (dump_file)
+ fprintf (dump_file, "no viable base register for pair (%d,%d)\n",
+ insns[0]->uid (), insns[1]->uid ());
+ return writeback;
+ }
+
+ for (int i = 0; i < 2; i++)
+ if ((writeback & (1 << i)) && !base_defs[i])
+ {
+ if (dump_file)
+ fprintf (dump_file, "insn %d has writeback but base isn't viable\n",
+ insns[i]->uid ());
+ return writeback;
+ }
+
+ if (writeback == 3
+ && base_defs[0]->regno () != base_defs[1]->regno ())
+ {
+ if (dump_file)
+ fprintf (dump_file,
+ "pair (%d,%d): double writeback with distinct regs (%d,%d): "
+ "punting\n",
+ insns[0]->uid (), insns[1]->uid (),
+ base_defs[0]->regno (), base_defs[1]->regno ());
+ return writeback;
+ }
+
+ if (base_defs[0] && base_defs[1]
+ && base_defs[0]->regno () == base_defs[1]->regno ())
+ {
+ // Easy case: insns already share the same base reg.
+ base_cands.quick_push (base_defs[0]);
+ return writeback;
+ }
+
+ // Otherwise, we know that one of the bases must change.
+ //
+ // Note that if there is writeback we must use the writeback base
+ // (we know now there is exactly one).
+ for (int i = 0; i < 2; i++)
+ if (base_defs[i] && (!writeback || (writeback & (1 << i))))
+ base_cands.quick_push (base_cand { base_defs[i], i });
+
+ return writeback;
+}
+
+static void
+dump_insn_list (FILE *f, const insn_list_t &l)
+{
+ fprintf (f, "(");
+
+ auto i = l.begin ();
+ auto end = l.end ();
+
+ if (i != end)
+ fprintf (f, "%d", (*i)->uid ());
+ i++;
+
+ for (; i != end; i++)
+ fprintf (f, ", %d", (*i)->uid ());
+
+ fprintf (f, ")");
+}
+
+DEBUG_FUNCTION void
+debug (const insn_list_t &l)
+{
+ dump_insn_list (stderr, l);
+ fprintf (stderr, "\n");
+}
+
+// If we emitted tombstone insns for this BB, iterate through the BB
+// and remove all the tombstone insns, being sure to reparent any uses
+// of mem to previous defs when we do this.
+void
+fusion_bb_info::cleanup_tombstones ()
+{
+ // No need to do anything if we didn't emit a tombstone insn for this BB.
+ if (!m_emitted_tombstone)
+ return;
+
+ insn_info *insn = m_bb->head_insn ();
+ while (insn)
+ {
+ insn_info *next = insn->next_nondebug_insn ();
+ if (!insn->is_real ()
+ || !bitmap_bit_p (&m_tombstone_bitmap, insn->uid ()))
+ {
+ insn = next;
+ continue;
+ }
+
+ auto def = memory_access (insn->defs ());
+ auto set = dyn_cast<set_info *> (def);
+ if (set && set->has_any_uses ())
+ {
+ def_info *prev_def = def->prev_def ();
+ auto prev_set = dyn_cast<set_info *> (prev_def);
+ if (!prev_set)
+ gcc_unreachable ();
+
+ while (set->first_use ())
+ crtl->ssa->reparent_use (set->first_use (), prev_set);
+ }
+
+ // Now set has no uses, we can delete it.
+ insn_change change (insn, insn_change::DELETE);
+ //df_insn_rescan (insn->rtl());
+ crtl->ssa->change_insn (change);
+ insn = next;
+ }
+}
+
+template<typename Map>
+void
+fusion_bb_info::traverse_base_map (Map &map)
+{
+ for (auto kv : map)
+ {
+ const auto &key = kv.first;
+ auto &value = kv.second;
+ transform_for_base (key.second, value);
+ }
+}
+
+void
+fusion_bb_info::transform ()
+{
+ traverse_base_map (expr_map);
+ traverse_base_map (def_map);
+}
new file mode 100644
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target power10_ok } */
+/* { dg-options "-mdejagnu-cpu=power10 -O2" } */
+
+#include <altivec.h>
+
+void
+foo (__vector_quad *dst, vector unsigned char *ptr, vector unsigned char src)
+{
+ __vector_quad acc;
+ __builtin_mma_xvf32ger(&acc, src, ptr[0]);
+ __builtin_mma_xvf32gerpp(&acc, src, ptr[1]);
+ *dst = acc;
+}
+/* { dg-final { scan-assembler {\mlxvp\M} } } */
new file mode 100644
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target power10_ok } */
+/* { dg-options "-mdejagnu-cpu=power10 -O2" } */
+
+#include <altivec.h>
+
+void
+foo2 ()
+{
+ __vector_quad *dst1;
+ __vector_quad *dst2;
+ vector unsigned char src;
+ __vector_quad acc;
+ vector unsigned char *ptr;
+ __builtin_mma_xvf32ger(&acc, src, ptr[0]);
+ __builtin_mma_xvf32gerpp(&acc, src, ptr[1]);
+ *dst1 = acc;
+ __builtin_mma_xvf32ger(&acc, src, ptr[2]);
+ __builtin_mma_xvf32gerpp(&acc, src, ptr[3]);
+ *dst2 = acc;
+}
+/* { dg-final { scan-assembler {\mlxvp\M} } } */
@@ -258,8 +258,8 @@ foo13b (__vector_quad *dst, __vector_quad *src, vec_t *vec)
dst[13] = acc;
}
-/* { dg-final { scan-assembler-times {\mlxv\M} 40 } } */
-/* { dg-final { scan-assembler-times {\mlxvp\M} 12 } } */
+/* { dg-final { scan-assembler-times {\mlxv\M} 0 } } */
+/* { dg-final { scan-assembler-times {\mlxvp\M} 32 } } */
/* { dg-final { scan-assembler-times {\mstxvp\M} 40 } } */
/* { dg-final { scan-assembler-times {\mxxmfacc\M} 20 } } */
/* { dg-final { scan-assembler-times {\mxxmtacc\M} 6 } } */