On Sun, Aug 16, 2026 at 11:21 PM Jin Ma <[email protected]> wrote:
>
> The sched_fusion pass can bring independent memory accesses together
> before sched2.  Add a priority hook that groups scalar load/store-pair
> candidates by register file, access kind, mode and base register, then
> orders each group by offset.  Prefer decreasing offsets when both
> directions are available, matching frame save/restore memory order.
>
> Enable scheduling fusion by default for load/store-pair tunes while
> preserving an explicit -f[no-]schedule-fusion option.  Keep the positive
> test as XFAIL until the tuning patch enables the pairs.
>
> Static sched2 macro-fusion counts for SPEC CPU 2017 Integer built with
> -O3 -flto, measured independently of ready-list reordering against a
> baseline with xt-c9501fdvt fusion pairs enabled:
>
> Benchmark             Baseline  With patch  Change
> 500.perlbench_r           15302       17077  +11.6%
> 502.gcc_r                 72806       78135   +7.3%
> 505.mcf_r                    63          85  +34.9%
> 520.omnetpp_r             20479       22084   +7.8%
> 523.xalancbmk_r           42442       44651   +5.2%
> 525.x264_r                 6232        7033  +12.9%
> 531.deepsjeng_r             299         328   +9.7%
> 541.leela_r                 794         851   +7.2%
> 548.exchange2_r             353         426  +20.7%
> 557.xz_r                   1217        1321   +8.5%
> SUM                      159987      171991   +7.5%


I have not looked into this fully but there is a secondary pass which
needs to be extended for riscv load/store fusion called pair-fusion.cc
It is different from the scheduling passes (sched_fusion and
pass_dep_fusion) in that it has its own mechanisms to do the fusion
and it uses aliasing info.  It is currently used for aarch64 for
load/store pair formation (and there were patches to do a similar
thing for power).  The pass was split out into a target independent
part and a target specific part when the pass was extended for power.
Maybe this could be used for this case?

Thanks,
Andrea

>
> gcc/ChangeLog:
>
>         * config/riscv/riscv-fusion.cc (riscv_sched_fusion_priority): New
>         function.
>         * config/riscv/riscv-protos.h (riscv_sched_fusion_priority):
>         Declare.
>         * config/riscv/riscv.cc (riscv_override_options_internal): Set the
>         scheduling-fusion default for the current tune.
>         (TARGET_SCHED_FUSION_PRIORITY): Define.
>
> gcc/testsuite/ChangeLog:
>
>         * gcc.target/riscv/sched-fusion-priority.c: New test.
>
> Signed-off-by: Jin Ma <[email protected]>
> ---
>  gcc/config/riscv/riscv-fusion.cc              | 68 +++++++++++++++++++
>  gcc/config/riscv/riscv-protos.h               |  1 +
>  gcc/config/riscv/riscv.cc                     | 10 +++
>  .../gcc.target/riscv/sched-fusion-priority.c  | 49 +++++++++++++
>  4 files changed, 128 insertions(+)
>  create mode 100644 gcc/testsuite/gcc.target/riscv/sched-fusion-priority.c
>
> diff --git a/gcc/config/riscv/riscv-fusion.cc 
> b/gcc/config/riscv/riscv-fusion.cc
> index 332f64bd7cc..06cdab5694d 100644
> --- a/gcc/config/riscv/riscv-fusion.cc
> +++ b/gcc/config/riscv/riscv-fusion.cc
> @@ -907,6 +907,74 @@ riscv_fuse_ldst_pair_p (rtx_insn *prev, rtx_insn *curr,
>    return diff == access_size;
>  }
>
> +/* Implement TARGET_SCHED_FUSION_PRIORITY.  Group load/store pair candidates
> +   by register file, access kind, mode and base register, then by offset.  */
> +
> +void
> +riscv_sched_fusion_priority (rtx_insn *insn, int max_pri,
> +                            int *fusion_pri, int *pri)
> +{
> +  struct riscv_fusion_mem_info mem;
> +  unsigned HOST_WIDE_INT fusible_ops;
> +  enum riscv_fusion_pairs inc_op, dec_op;
> +  HOST_WIDE_INT access_size;
> +  unsigned int base_regno;
> +  bool isload_p, fp_p, inc_p;
> +  int fusion_type, tmp;
> +
> +  gcc_assert (INSN_P (insn));
> +
> +  tmp = max_pri - 1;
> +  *fusion_pri = tmp;
> +  *pri = tmp;
> +
> +  if (!riscv_fuse_mem_p (insn, &mem)
> +      || mem.type == SCHED_FUSION_LD_ZERO_EXTEND
> +      || mem.addr.type != ADDRESS_REG
> +      || !CONST_INT_P (mem.addr.offset))
> +    return;
> +
> +  base_regno = riscv_regno (mem.addr.reg);
> +  if (base_regno >= FIRST_PSEUDO_REGISTER)
> +    return;
> +
> +  isload_p = mem.type != SCHED_FUSION_ST;
> +  fp_p = mem.fp_p;
> +  access_size = GET_MODE_SIZE (mem.mode).to_constant ();
> +  if (access_size != 4 && access_size != 8)
> +    return;
> +
> +  inc_op = fp_p ? RISCV_FUSE_FLDFST_PAIR_INC : RISCV_FUSE_LDST_PAIR_INC;
> +  dec_op = fp_p ? RISCV_FUSE_FLDFST_PAIR_DEC : RISCV_FUSE_LDST_PAIR_DEC;
> +  fusible_ops = riscv_get_fusible_ops ();
> +  if (!(fusible_ops & (inc_op | dec_op)))
> +    return;
> +
> +  /* Prefer decreasing offsets to match frame save/restore order.  */
> +  inc_p = !(fusible_ops & dec_op);
> +
> +  /* Give each load/store class and base register a distinct priority below
> +     that of unrelated instructions.  */
> +  fusion_type = (fp_p ? 4 : 0) + (isload_p ? 0 : 2);
> +  fusion_type += access_size == 8;
> +  fusion_type++;
> +  *fusion_pri -= (fusion_type * FIRST_PSEUDO_REGISTER
> +                 + (int) base_regno);
> +
> +  tmp /= 2;
> +  HOST_WIDE_INT off_val = INTVAL (mem.addr.offset);
> +  unsigned HOST_WIDE_INT magnitude = off_val < 0
> +                                      ? -(unsigned HOST_WIDE_INT) off_val
> +                                      : off_val;
> +  int offset_pri = magnitude & 0xfffff;
> +
> +  /* Order offsets in the preferred pair direction.  */
> +  if (inc_p == (off_val >= 0))
> +    *pri = tmp - offset_pri;
> +  else
> +    *pri = tmp + offset_pri;
> +}
> +
>  /* Check the common RTL for ZEXTW, ZEXTWS and ZEXTH fusion.  */
>
>  static bool
> diff --git a/gcc/config/riscv/riscv-protos.h b/gcc/config/riscv/riscv-protos.h
> index cf36efccf75..0921b3a7618 100644
> --- a/gcc/config/riscv/riscv-protos.h
> +++ b/gcc/config/riscv/riscv-protos.h
> @@ -884,6 +884,7 @@ enum riscv_fusion_pairs
>
>  extern bool riscv_macro_fusion_p (void);
>  extern bool riscv_macro_fusion_pair_p (rtx_insn *, rtx_insn *);
> +extern void riscv_sched_fusion_priority (rtx_insn *, int, int *, int *);
>  extern unsigned HOST_WIDE_INT riscv_get_fusible_ops (void);
>
>  /* Routines implemented in thead.cc.  */
> diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
> index a2a51c019ec..9f0c1d704c2 100644
> --- a/gcc/config/riscv/riscv.cc
> +++ b/gcc/config/riscv/riscv.cc
> @@ -12121,6 +12121,14 @@ riscv_override_options_internal (struct gcc_options 
> *opts)
>                  ? &optimize_size_tune_info
>                  : cpu->tune_param;
>
> +  /* Enable scheduling fusion for load/store-pair tunes.  */
> +  if (!OPTION_SET_P (flag_schedule_fusion))
> +    opts->x_flag_schedule_fusion
> +      = (riscv_get_fusible_ops ()
> +        & (RISCV_FUSE_LDST_PAIR_INC | RISCV_FUSE_LDST_PAIR_DEC
> +           | RISCV_FUSE_FLDFST_PAIR_INC | RISCV_FUSE_FLDFST_PAIR_DEC))
> +       != RISCV_FUSE_NOTHING;
> +
>    /* If not optimizing for size, set the default
>        alignment to what the target wants.  */
>    if (!opts->x_optimize_size)
> @@ -16617,6 +16625,8 @@ riscv_memtag_tag_bitsize ()
>  #define TARGET_SCHED_MACRO_FUSION_P riscv_macro_fusion_p
>  #undef TARGET_SCHED_MACRO_FUSION_PAIR_P
>  #define TARGET_SCHED_MACRO_FUSION_PAIR_P riscv_macro_fusion_pair_p
> +#undef TARGET_SCHED_FUSION_PRIORITY
> +#define TARGET_SCHED_FUSION_PRIORITY riscv_sched_fusion_priority
>
>  #undef TARGET_SCHED_INIT
>  #define TARGET_SCHED_INIT riscv_sched_init
> diff --git a/gcc/testsuite/gcc.target/riscv/sched-fusion-priority.c 
> b/gcc/testsuite/gcc.target/riscv/sched-fusion-priority.c
> new file mode 100644
> index 00000000000..3d2c100a408
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/riscv/sched-fusion-priority.c
> @@ -0,0 +1,49 @@
> +/* { dg-do compile { target { rv64 } } } */
> +/* { dg-skip-if "" { *-*-* } { "-O0" "-O1" "-O3" "-O[sgz]" "-flto" } } */
> +/* { dg-options "-march=rv64gc -mabi=lp64d -mtune=xt-c9501fdvt -O2" } */
> +/* { dg-additional-options "-fno-schedule-insns2" } */
> +/* { dg-final { check-function-bodies "**" "" } } */
> +
> +/* Start before sched_fusion and disable sched2 to isolate the priority
> +   hook.  */
> +
> +/*
> +**sched_fusion_load_pair: { xfail *-*-* }
> +**     ...
> +**     ld      [a-z][0-9]+,8\(a0\)
> +**     ld      [a-z][0-9]+,0\(a0\)
> +**     ...
> +**     ret
> +*/
> +long __RTL (startwith ("compgotos"))
> +sched_fusion_load_pair (void)
> +{
> +(function "sched_fusion_load_pair"
> +  (insn-chain
> +    (block 2
> +      (edge-from entry (flags "FALLTHRU"))
> +      (cnote 1 [bb 2] NOTE_INSN_BASIC_BLOCK)
> +      (cnote 2 NOTE_INSN_FUNCTION_BEG)
> +      (cinsn 3 (set (reg:DI a1)
> +                    (mem:DI (reg:DI a0) [0 S8 A64])))
> +      (cinsn 4 (set (reg:DI a3)
> +                    (plus:DI (reg:DI a4) (reg:DI a5))))
> +      (cinsn 5 (set (reg:DI a2)
> +                    (mem:DI
> +                      (plus:DI (reg:DI a0)
> +                               (const_int 8)) [0 S8 A64])))
> +      (cinsn 6 (use (reg:DI a1)))
> +      (cinsn 7 (use (reg:DI a2)))
> +      (cinsn 8 (use (reg:DI a3)))
> +      (cjump_insn 9 (simple_return))
> +      (edge-to exit)
> +    ) ;; block 2
> +    (cbarrier 10)
> +  ) ;; insn-chain
> +  (crtl
> +    (return_rtx
> +      (reg/i:DI a0)
> +    ) ;; return_rtx
> +  ) ;; crtl
> +) ;; function "sched_fusion_load_pair"
> +}
> --
> 2.52.0
>

Reply via email to