From: Ajit Agarwal <aagarwa1@linux.ibm.com>
To: Alex Coplan <alex.coplan@arm.com>
Cc: "Kewen.Lin" <linkw@linux.ibm.com>,
Segher Boessenkool <segher@kernel.crashing.org>,
David Edelsohn <dje.gcc@gmail.com>,
Peter Bergner <bergner@linux.ibm.com>,
Michael Meissner <meissner@linux.ibm.com>,
Richard Sandiford <richard.sandiford@arm.com>,
gcc-patches <gcc-patches@gcc.gnu.org>
Subject: Re: [PATCH V2] rs6000: New pass for replacement of adjacent loads fusion (lxv).
Date: Wed, 14 Feb 2024 19:11:27 +0530 [thread overview]
Message-ID: <1043f073-45b7-4e8d-8dac-bba0aa3269c4@linux.ibm.com> (raw)
In-Reply-To: <ZbE+FNc7TzrzADIG@arm.com>
Hello Alex:
On 24/01/24 10:13 pm, Alex Coplan wrote:
> Hi Ajit,
>
> On 21/01/2024 19:57, Ajit Agarwal wrote:
>>
>> Hello All:
>>
>> New pass to replace adjacent memory addresses lxv with lxvp.
>> Added common infrastructure for load store fusion for
>> different targets.
>
> Thanks for this, it would be nice to see the load/store pair pass
> generalized to multiple targets.
>
> I assume you are targeting GCC 15 for this, as we are in stage 4 at
> the moment?
>
>>
>> Common routines are refactored in fusion-common.h.
>>
>> AARCH64 load/store fusion pass is not changed with the
>> common infrastructure.
>
> I think any patch to generalize the load/store pair fusion pass should
> update the aarch64 code at the same time to use the generic
> infrastructure, instead of duplicating the code.
>
> As a general comment, I think we should move as much of the code as
> possible to target-independent code, with only the bits that are truly
> target-specific (e.g. deciding which modes to allow for a load/store
> pair operand) in target code.
>
> In terms of structuring the interface between generic code and target
> code, I think it would be pragmatic to use a class with (in some cases,
> pure) virtual functions that can be overriden by targets to implement
> any target-specific behaviour.
>
> IMO the generic class should be implemented in its own .cc instead of
> using a header-only approach. The target code would then define a
> derived class which overrides the virtual functions (where necessary)
> declared in the generic class, and then instantiate the derived class to
> create a target-customized instance of the pass.
Incorporated the above comments in the recent patch sent.
>
> A more traditional GCC approach would be to use optabs and target hooks
> to customize the behaviour of the pass to handle target-specific
> aspects, but:
> - Target hooks are quite heavyweight, and we'd potentially have to add
> quite a few hooks just for one pass that (at least initially) will
> only be used by a couple of targets.
> - Using classes allows both sides to easily maintain their own state
> and share that state where appropriate.
>
> Nit on naming: I understand you want to move away from ldp_fusion, but
> how about pair_fusion or mem_pair_fusion instead of just "fusion" as a
> base name? IMO just "fusion" isn't very clear as to what the pass is
> trying to achieve.
>
I have made it pair_fusion.
> In general the code could do with a lot more commentary to explain the
> rationale for various things / explain the high-level intent of the
> code.
>
> Unfortunately I'm not familiar with the DF framework (I've only really
> worked with RTL-SSA for the aarch64 pass), so I haven't commented on the
> use of that framework, but it would be nice if what you're trying to do
> could be done using RTL-SSA instead of using DF directly.
>
I have used rtl-ssa DEF-USE at many places in the recent patch.
But DF framework is useful as it populates a pointer rtx through
DF_REF_LOC and then we can easily modify. This is missing in
rtl-ssa pass and wherever LOC is required to change I have
used DF framework in the recent patch.
> Hopefully Richard S can chime in on those aspects.
>
> My main concerns with the patch at the moment (apart from the code
> duplication) is that it looks like:
>
> - The patch removes alias analysis from try_fuse_pair, which is unsafe.
> - The patch tries to make its own RTL changes inside
> rs6000_gen_load_pair, but it should let fuse_pair make those changes
> using RTL-SSA instead.
>
My mistake that I have remove alias analysis from try_fuse_pair.
In recent patch I kept all the code in the aarch64-ldp-fusion
intact except organizing the generic and target dependent code
through pure virtual functions.
> I've left some more specific (but still mostly high-level) comments below.
>
>>
>> For AARCH64 architectures just include "fusion-common.h"
>> and target dependent code can be added to that.
>>
>>
>> Alex/Richard:
>>
>> If you would like me to add for AARCH64 I can do that for AARCH64.
>>
>> If you would like to do that is fine with me.
>>
>> Bootstrapped and regtested with powerpc64-linux-gnu.
>>
>> Improvement in performance is seen with Spec 2017 spec FP benchmarks.
>>
>> Thanks & Regards
>> Ajit
>>
>> rs6000: New pass for replacement of adjacent lxv with lxvp.
>
> Are you looking to handle stores eventually, out of interest? Looking
> at rs6000-vecload-opt.cc:fusion_bb it looks like you're just handling
> loads at the moment.
>
>>
I have included store fusion also in the recent patch.
>> New pass to replace adjacent memory addresses lxv with lxvp.
>> Added common infrastructure for load store fusion for
>> different targets.
>>
>> Common routines are refactored in fusion-common.h.
>
> I've just done a very quick scan through this file as it mostly just
> looks to be idential to existing code in aarch64-ldp-fusion.cc.
>
>>
>> 2024-01-21 Ajit Kumar Agarwal <aagarwa1@linux.ibm.com>
>>
>> gcc/ChangeLog:
>>
>> * config/rs6000/rs6000-passes.def: New vecload pass
>> before pass_early_remat.
>> * config/rs6000/rs6000-vecload-opt.cc: Add new pass.
>> * config.gcc: Add new executable.
>> * config/rs6000/rs6000-protos.h: Add new prototype for vecload
>> pass.
>> * config/rs6000/rs6000.cc: Add new prototype for vecload pass.
>> * config/rs6000/t-rs6000: Add new rule.
>> * fusion-common.h: Add common infrastructure for load store
>> fusion that can be shared across different architectures.
>> * emit-rtl.cc: Modify assert code.
>>
>> gcc/testsuite/ChangeLog:
>>
>> * g++.target/powerpc/vecload.C: New test.
>> * g++.target/powerpc/vecload1.C: New test.
>> * gcc.target/powerpc/mma-builtin-1.c: Modify test.
>> ---
>> gcc/config.gcc | 4 +-
>> gcc/config/rs6000/rs6000-passes.def | 3 +
>> gcc/config/rs6000/rs6000-protos.h | 1 +
>> gcc/config/rs6000/rs6000-vecload-opt.cc | 1186 ++++++++++++++++
>> gcc/config/rs6000/rs6000.cc | 1 +
>> gcc/config/rs6000/t-rs6000 | 5 +
>> gcc/emit-rtl.cc | 14 +-
>> gcc/fusion-common.h | 1195 +++++++++++++++++
>> gcc/testsuite/g++.target/powerpc/vecload.C | 15 +
>> gcc/testsuite/g++.target/powerpc/vecload1.C | 22 +
>> .../gcc.target/powerpc/mma-builtin-1.c | 4 +-
>> 11 files changed, 2433 insertions(+), 17 deletions(-)
>> create mode 100644 gcc/config/rs6000/rs6000-vecload-opt.cc
>> create mode 100644 gcc/fusion-common.h
>> create mode 100644 gcc/testsuite/g++.target/powerpc/vecload.C
>> create mode 100644 gcc/testsuite/g++.target/powerpc/vecload1.C
>>
>> diff --git a/gcc/config.gcc b/gcc/config.gcc
>> index 00355509c92..9bff42cf830 100644
>> --- a/gcc/config.gcc
>> +++ b/gcc/config.gcc
>> @@ -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"
>> diff --git a/gcc/config/rs6000/rs6000-passes.def b/gcc/config/rs6000/rs6000-passes.def
>> index 46a0d0b8c56..eb4a65ebe10 100644
>> --- a/gcc/config/rs6000/rs6000-passes.def
>> +++ b/gcc/config/rs6000/rs6000-passes.def
>> @@ -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
>> diff --git a/gcc/config/rs6000/rs6000-protos.h b/gcc/config/rs6000/rs6000-protos.h
>> index 09a57a806fa..f0a9f36602e 100644
>> --- a/gcc/config/rs6000/rs6000-protos.h
>> +++ b/gcc/config/rs6000/rs6000-protos.h
>> @@ -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);
>> diff --git a/gcc/config/rs6000/rs6000-vecload-opt.cc b/gcc/config/rs6000/rs6000-vecload-opt.cc
>> new file mode 100644
>> index 00000000000..cebadad97d8
>> --- /dev/null
>> +++ b/gcc/config/rs6000/rs6000-vecload-opt.cc
>> @@ -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)
>
> Can you explain the rationale for this function?
> I'm not really sure what it's trying to achieve.
>
>> +{
>> + 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;
>> +}
>> +
I have removed this check from the recent patch.
>> +/* 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)
>
> This condition is always true due to the early return above.
>
I have made this change in recent patch.
>> + {
>> + 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,
>
> Please can you explain the rationale for this condition?
>
> Do I understand correctly that you want to bail out if a use of a
> register loaded by either insn occurs in a vec_select rtx? If so, why?
>
I have removed vec_select check from load fusion only check I have
provided in target of load in UNSPEC this where we require load
vector pair to be generated.
> AIUI, since we shouldn't change register dataflow in the pass, I wonder
> if you could check this condition once in track_access, instead of
> checking it for every pair formation attempt?
>
I have added the check in the wrapper code where we call track_access.
>> +
>> + - machine mode of unspec is not same as machine mode
>> + of lxv instruction.
>> +
>> + - dependent instruction is not unspec.
>
> Likewise, please explain the rationale for this.
>
>> +
>> + - Source operand of unspec is eq instruction. */
>
> Same here.
>
>> +
>> +rtx
>> +rs6000_gen_load_pair (rtx_insn *insn1, rtx_insn *insn2)
>> +{
>
> I think this function is named in a rather misleading way, and I think
> it is trying to do too much at once.
>
> Based on the name of the function, I would expect it to just produce the
> appropriate RTL pattern for a load pair given the two candidate loads.
> But it actually tries to do a bunch of analysis to determine if the pair
> can be formed, and then inserts a new insn and deletes the original
> candidate insns.
>
I have made changes in the recent patch as you have mentioned above.
> Any changing of the RTL should be done by RTL-SSA in fuse_pair.
>
>> + 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;
>
> IIUC this should hold from generic RTL rules, so shouldn't be needed.
>
>> +
>> + if (GET_MODE (dest_exp) == TImode)
>> + return NULL_RTX;
>> +
>> + if (!ALTIVEC_OR_VSX_VECTOR_MODE (GET_MODE (dest_exp)))
>> + return NULL_RTX;
>
> Validation of the modes should have happened much earlier (in
> track_access), so shouldn't be necessary at this point.
>
I did that.
>> +
>> + if (!is_feasible (insn1))
>> + return NULL_RTX;
>> +
>> + if (!is_feasible (insn2))
>> + return NULL_RTX;
>
> I can't really comment on this as I don't understand the intent behind
> is_feasible, but AFAICT it seems to do some dataflow analysis (looking
> at uses/defs). That should ideally have been done at the latest in
> try_fuse_pair (before we get to fuse_pair), and shouldn't be done when
> we're trying to generate the final RTL pattern for the pair.
>
> Given that these seem to be properties of the indiviudal insns it might
> even be possible to check this much earlier (when first considering the
> individual accesses in track_access).
>
Most of the check that were used in this patch are removed and moved
some in earlier part of code in the recent patch.
>> +
>> + 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;
>
> Can you explain why you're punting if the candidate insns have these
> notes? Did you see a performance improvement with that change? It
> might be something we want to consider doing for aarch64 too if running
> before RA (in which case we could do it in the generic code).
>
Yes we found that it is not useful in spec 2017 benchmarks.
>> +
>> + 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)
>
> As above, I think this condition is redundant due to the early return
> immediately above.
>
I did that.
>> + {
>> + 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);
>
> I don't think you should be updating the original RTL directly like
> this, but instead return a new pattern which will ultimately get passed
> to validate_unshare_change in fuse_pair (i.e. let fuse_pair update the
> RTL).
>
I did that.
>> + lxv = gen_movoo (dest_exp, src_exp);
>> + rtx_insn *new_insn = emit_insn_before (lxv, insn1);
>
> Likewise, instead of emitting a new insn, this should let fuse_pair
> perform the change.
>
>> + 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 ();
>
> Likewise, I don't think you should be deleting the original insns here,
> but you should let RTL-SSA do this in the generic code instead (i.e. in
> fuse_pair).
>
I did that in recent patch.
>> + 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)
>> +{
>
> This function should be in the generic code, not in target code.
> Was there a reason you moved it to target code?
>
Yes I have moved in generic code.
> When moving code from aarch64-ldp-fusion.cc, I think it would be better
> if you left the code style unchanged and only made changes where there
> is a need for a functional change. For example ...
>
>> + 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;
>
> ... here you introduce new left and right locals, but AFAICT there isn't
> a need for this (no functional change).
>
>> +
>> + 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)
>> +{
>
> This function should also be in generic code, not in target code.
> I can see that you want to customize a target-specific aspect ...
>
>> + // 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;
>
> ... here. But I think this should be done either using a target hook
> or ideally using a virtual function which is overriden by target code.
>
Yes I did in pure virtual function in target code.
> For rs6000, would it be enough to validate the mode of the mem instead
> of looking at the mode of reg_op? Then we could have a single function,
> e.g.:
>
> bool pair_fusion::pair_operand_mode_ok_p (machine_mode mode);
>
> which for rs6000 would return:
>
> ALTIVEC_OR_VSX_VECTOR_MODE (mode);
>
> and for aarch64 would return:
>
> ldp_operand_mode_ok_p (mode);
>
> and in track_access (from generic code), you would do:
>
> if (!pair_operand_mode_ok_p (mem_mode))
> return;
>
Yes I did that.
> note that, at least for aarch64, the reason that we validate the mem mode
> instead of the register mode is that the register operand may be a modeless
> const_int in the store case.
>
>> +
>> + const bool fpsimd_op_p = (ALTIVEC_OR_VSX_VECTOR_MODE (GET_MODE (reg_op)));
>
> IIUC, this boolean is always true due to the above check. So it is
> better to just set it to a constant (doesn't matter which) if you only
> want to handle vector modes for rs6000. As above, this should be done
> through a virtual function, where for aarch64 we return the condition in
> aarch64-ldp-fusion.cc:
>
> const bool fpsimd_op_p
> = reload_completed
> ? (REG_P (reg_op) && FP_REGNUM_P (REGNO (reg_op)))
> : (GET_MODE_CLASS (mem_mode) != MODE_INT
> && (load_p || !aarch64_const_zero_rtx_p (reg_op)));
>
> and for rs6000 you can just return false (assuming you don't want to segregate
> accesses using this bit).
>
>> +
>> + 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)
>
> Likewise, I think this function should be in generic code, not target code.
>
>> +{
>> + 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",
>
> I understand that you might not want to use "ldp" in the generic code,
> but how about "punting on load pair" instead?
>
>> + 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] = {};
>
> It looks like you've just commented out / removed the alias analysis (I don't
> see a call to do_alias_analysis). How is that supposed to work?
>
Yes my mistake I kept that in recent patch.
>> +
>> + // 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)
>> +{
>
> Again, I think this function should be in generic code, not in target
> code.
Yes I moved it to generic code.
>
>> + 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;
>
> I can see you've dropped the call to find_trailing_add here.
>
> If you don't want to handle writeback pairs, that's fine, but I think
> that should be controlled with a target-controlled virtual function.
>
> Effectively you can just replace uses of the param aarch64_ldp_writeback with a
> call to such a function, then the target can control which writeback
> opportunities we handle, if any. For aarch64 this would just return
> the result of the param for the time being.
>
Yes I did that.
>> +
>> + 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);
>
> Can you explain why you changed this to unconditionally cancel the
> changes instead of letting RTL-SSA perform them?
>
>> +
>> + 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)
>> +{
>
> Likewise, I think this function should be in generic code, not in target
> code. Note for aarch64 we have:
>
> const bool track_loads
> = aarch64_tune_params.ldp_policy_model != AARCH64_LDP_STP_POLICY_NEVER;
> const bool track_stores
> = aarch64_tune_params.stp_policy_model != AARCH64_LDP_STP_POLICY_NEVER;
>
> I think these can just be hidden behind target-controlled virtual
> functions, so you'd have something like:
>
> bool pair_fusion::should_fuse_stores ();
> bool pair_fusion::should_fuse_loads ();
>
I kept transform_for_base in target code with pure virtual
function and we dont need to use these in generic code.
As this is different in rs6000 and aarch64 target.
> (or a single function that returns multiple bitfields if you like).
>
> then for aarch64 you would return:
>
> aarch64_tune_params.ldp_policy_model != AARCH64_LDP_STP_POLICY_NEVER;
>
> for the load case, and for rs6000 you would return true (just based on
> what's below). Presumably since you don't handle stores below, you'd
> want to return false in the store case for rs6000 code (at least
> initially).
>
> As I mentioned above, the aarch64 version of this function also tries
> to promote existing pairs to writeback pairs, again that would be
> controlled by the same function call that replaces uses of
> the param aarch64_ldp_writeback.
>
Yes I did that.
>> + 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);
>
> Is there a reason you can't use RTL-SSA for the def-use information?
> IMO it would be cleaner to use RTL-SSA as the existing aarch64 pass does
> (and the refactored generic code presumably will do).
>
Yes I have used rtl-ssa at most of the places in rs6000 target.
But where require to modify rtx * loc then DF_REF_LOC while
generation subreg in order to generate sequential registers
for pairs.
>> + 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)
>
> Again, I think this function should be in generic code.
>
Yes I moved it to generic code.
>> +{
>> + 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);
>> +}
>> +
>> diff --git a/gcc/config/rs6000/rs6000.cc b/gcc/config/rs6000/rs6000.cc
>> index 5d975dab921..e36946c44ea 100644
>> --- a/gcc/config/rs6000/rs6000.cc
>> +++ b/gcc/config/rs6000/rs6000.cc
>> @@ -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. */
>>
>> diff --git a/gcc/config/rs6000/t-rs6000 b/gcc/config/rs6000/t-rs6000
>> index b3ce09d523b..7664830f8ab 100644
>> --- a/gcc/config/rs6000/t-rs6000
>> +++ b/gcc/config/rs6000/t-rs6000
>> @@ -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)
>> diff --git a/gcc/emit-rtl.cc b/gcc/emit-rtl.cc
>> index 1856fa4884f..ffc47a6eaa0 100644
>> --- a/gcc/emit-rtl.cc
>> +++ b/gcc/emit-rtl.cc
>> @@ -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;
>
> Can you explain why this change is needed?
>
This is required in rs6000 target where we generate the subreg
with offset 16 from OO mode (256 bit) to 128 bit vector modes.
Otherwise it segfaults.
>>
>> 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));
>> -
>
I have removed this modification to assert in recent patch.
> I think the need to make this change is likely indicative of a problem
> in the pass, and the pass should be fixed instead of changing
> verify_rtx_sharing.
>
>> RTX_FLAG (x, used) = 1;
>>
Yes I did that.
>> /* Now scan the subexpressions recursively. */
>> diff --git a/gcc/fusion-common.h b/gcc/fusion-common.h
>> new file mode 100644
>> index 00000000000..82f1d6ef032
>> --- /dev/null
>> +++ b/gcc/fusion-common.h
>> @@ -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;
>
> Note we would not want these aarch64-specific constants in generic code,
> they would need to be target-configurable (in fact we might just want a
> virtual function / target hook to validate offsets instead).
>
>> +
>> +// 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
>
> Perhaps better to spell out "load pair or store pair" instead of listing
> the various instruction mnenomics for different targets.
>
Yes I did that.
>> +// 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)
>
> Nit: naming. How about pair_fusion_strip_offset instead?
>
Yes I did that.
>> +{
>> + 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 ();
>
> Why the .to_constant () comment here?
>
Yes I have uncommented that.
Please review the recent patch as I have incorporated all the
above comments in recent patch that I have sent.
Thanks & Regards
Ajit
> I hope these comments are useful. Please feel free to reach out if you have any
> questions / get stuck with anything. I'm often around on IRC.
>
> Thanks,
> Alex
>
>> +
>> + // 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);
>> +}
>> diff --git a/gcc/testsuite/g++.target/powerpc/vecload.C b/gcc/testsuite/g++.target/powerpc/vecload.C
>> new file mode 100644
>> index 00000000000..c523572cf3c
>> --- /dev/null
>> +++ b/gcc/testsuite/g++.target/powerpc/vecload.C
>> @@ -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} } } */
>> diff --git a/gcc/testsuite/g++.target/powerpc/vecload1.C b/gcc/testsuite/g++.target/powerpc/vecload1.C
>> new file mode 100644
>> index 00000000000..d10ff0cdf36
>> --- /dev/null
>> +++ b/gcc/testsuite/g++.target/powerpc/vecload1.C
>> @@ -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} } } */
>> diff --git a/gcc/testsuite/gcc.target/powerpc/mma-builtin-1.c b/gcc/testsuite/gcc.target/powerpc/mma-builtin-1.c
>> index 69ee826e1be..ae29127f954 100644
>> --- a/gcc/testsuite/gcc.target/powerpc/mma-builtin-1.c
>> +++ b/gcc/testsuite/gcc.target/powerpc/mma-builtin-1.c
>> @@ -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 } } */
>> --
>> 2.39.3
>>
next prev parent reply other threads:[~2024-02-14 13:41 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <cfda8399-c9d3-4d06-b521-ebca431d8981@linux.ibm.com>
2024-01-21 14:24 ` Fwd: " Ajit Agarwal
2024-01-21 14:27 ` Ajit Agarwal
2024-01-24 16:43 ` Alex Coplan
2024-01-31 10:12 ` Ajit Agarwal
2024-02-14 13:41 ` Ajit Agarwal [this message]
2024-02-14 17:15 ` Richard Sandiford
2024-02-14 19:44 ` Ajit Agarwal
2024-02-14 20:51 ` Richard Sandiford
2024-02-14 21:16 ` Ajit Agarwal
2024-01-30 9:45 ` Michael Meissner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1043f073-45b7-4e8d-8dac-bba0aa3269c4@linux.ibm.com \
--to=aagarwa1@linux.ibm.com \
--cc=alex.coplan@arm.com \
--cc=bergner@linux.ibm.com \
--cc=dje.gcc@gmail.com \
--cc=gcc-patches@gcc.gnu.org \
--cc=linkw@linux.ibm.com \
--cc=meissner@linux.ibm.com \
--cc=richard.sandiford@arm.com \
--cc=segher@kernel.crashing.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).