Group scalar load/store-pair candidates before sched2 and order them by
offset, preferring decreasing offsets when both INC and DEC are eligible.

Share eligibility, direction and alignment constraints through a rule
table used by the priority hook and the final pair matcher.  Require
offsets aligned to the access size for integer and floating-point pairs.
ALIGNED_STD requires the lower offset aligned to twice the access size.

Enable scheduling fusion by default for supported tunes, including after
restoring per-function optimization options, while preserving explicit
-f[no-]schedule-fusion options.  Keep positive tests as XFAIL until the
tuning patch enables the pairs.

Reference 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.  These
measurements have not been repeated for this patch:

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%

gcc/ChangeLog:

        * config/riscv/riscv-fusion.cc (sched_fusion_type): Use bitmasks.
        (riscv_fusion_mem_info): Add reg.
        (riscv_fuse_mem_p): Record the register operand.
        (riscv_fusion_mem_rule): New struct.
        (riscv_fusion_mem_rules): New table.
        (riscv_fuse_mem_candidate_p): New function.
        (riscv_fuse_mem_direction): Likewise.
        (riscv_fuse_mem_pair_p): Check complete pairs using an operation
        parameter and the shared rule table and eligibility check.
        (riscv_fuse_ldst_pair_p): Remove.
        (riscv_sched_fusion_priority): New function.
        (riscv_fuse_aligned_std): Use riscv_fuse_mem_pair_p.
        (riscv_fuse_ldst_pair_inc): Update call.
        (riscv_fuse_ldst_pair_dec): Likewise.
        (riscv_fuse_fldfst_pair_inc): Likewise.
        (riscv_fuse_fldfst_pair_dec): Likewise.
        * config/riscv/riscv-protos.h (riscv_fusion_direction): New enum.
        (riscv_sched_fusion_priority): Declare.
        (riscv_fuse_mem_direction): Likewise.
        * config/riscv/riscv.cc (riscv_override_options_internal): Enable
        scheduling fusion by default using riscv_fuse_mem_direction.
        (riscv_override_options_after_change): Refresh the scheduling-fusion
        default.
        (TARGET_SCHED_FUSION_PRIORITY): Define.

gcc/testsuite/ChangeLog:

        * gcc.target/riscv/fusion-memory-rtl-shapes-ldst-pair.c: Check
        access-size alignment.
        * gcc.target/riscv/sched-fusion-priority.c: New test.

Signed-off-by: Jin Ma <[email protected]>
---
 gcc/config/riscv/riscv-fusion.cc              | 301 +++++++++++++-----
 gcc/config/riscv/riscv-protos.h               |  11 +
 gcc/config/riscv/riscv.cc                     |  13 +
 .../fusion-memory-rtl-shapes-ldst-pair.c      |  70 +++-
 .../gcc.target/riscv/sched-fusion-priority.c  |  92 ++++++
 5 files changed, 402 insertions(+), 85 deletions(-)
 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 3e1e466dd8e..608cd2e7a38 100644
--- a/gcc/config/riscv/riscv-fusion.cc
+++ b/gcc/config/riscv/riscv-fusion.cc
@@ -633,13 +633,13 @@ riscv_fuse_shift_pair_p (rtx_insn *prev, rtx_insn *curr,
   return true;
 }
 
-/* Load/store classes used by fusion checks.  */
+/* Load/store classes used by fusion checks and rule masks.  */
 enum sched_fusion_type
 {
-  SCHED_FUSION_LD_SIGN_EXTEND = 0,
-  SCHED_FUSION_LD_ZERO_EXTEND,
-  SCHED_FUSION_LD,
-  SCHED_FUSION_ST
+  SCHED_FUSION_LD_SIGN_EXTEND = 1 << 0,
+  SCHED_FUSION_LD_ZERO_EXTEND = 1 << 1,
+  SCHED_FUSION_LD = 1 << 2,
+  SCHED_FUSION_ST = 1 << 3
 };
 
 /* Fusion-relevant information about a scalar load or store.  */
@@ -648,6 +648,8 @@ struct riscv_fusion_mem_info
 {
   enum sched_fusion_type type;
   struct riscv_address_info addr;
+  /* The register operand, or const0_rtx for a zero store.  */
+  rtx reg;
   machine_mode mode;
   bool fp_p;
 };
@@ -768,9 +770,111 @@ riscv_fuse_mem_p (rtx_insn *insn, struct 
riscv_fusion_mem_info *info)
   else
     return false;
 
+  /* Retain the register operand for pair dependency checks.  */
+  info->reg = isload_p ? dest : src;
   return true;
 }
 
+/* Constraints shared by memory-pair classification and matching.  */
+
+struct riscv_fusion_mem_rule
+{
+  enum riscv_fusion_pairs op;
+  /* Bit N permits an N-byte access.  */
+  unsigned HOST_WIDE_INT access_sizes;
+  /* Mask of sched_fusion_type values.  */
+  unsigned int types;
+  /* Whether the access uses the floating-point register file.  */
+  bool fp_p;
+  /* Whether the memory mode must be a scalar integer mode.  */
+  bool scalar_int_p;
+  /* Supported address order, independent of scheduling preferences.  */
+  enum riscv_fusion_direction direction;
+  /* Positive multiple of the access size used to align the lower offset.  */
+  unsigned int alignment_factor;
+};
+
+static const struct riscv_fusion_mem_rule riscv_fusion_mem_rules[] =
+{
+  { RISCV_FUSE_LDST_PAIR_INC, (1U << 4) | (1U << 8),
+    SCHED_FUSION_LD | SCHED_FUSION_LD_SIGN_EXTEND | SCHED_FUSION_ST,
+    false, false, RISCV_FUSION_INC, 1 },
+  { RISCV_FUSE_LDST_PAIR_DEC, (1U << 4) | (1U << 8),
+    SCHED_FUSION_LD | SCHED_FUSION_LD_SIGN_EXTEND | SCHED_FUSION_ST,
+    false, false, RISCV_FUSION_DEC, 1 },
+  { RISCV_FUSE_FLDFST_PAIR_INC, (1U << 4) | (1U << 8),
+    SCHED_FUSION_LD | SCHED_FUSION_LD_SIGN_EXTEND
+    | SCHED_FUSION_LD_ZERO_EXTEND | SCHED_FUSION_ST,
+    true, false, RISCV_FUSION_INC, 1 },
+  { RISCV_FUSE_FLDFST_PAIR_DEC, (1U << 4) | (1U << 8),
+    SCHED_FUSION_LD | SCHED_FUSION_LD_SIGN_EXTEND
+    | SCHED_FUSION_LD_ZERO_EXTEND | SCHED_FUSION_ST,
+    true, false, RISCV_FUSION_DEC, 1 },
+  /* Allow 1-, 2-, 4- and 8-byte stores.  */
+  { RISCV_FUSE_ALIGNED_STD, (1U << 1) | (1U << 2) | (1U << 4) | (1U << 8),
+    SCHED_FUSION_ST, false, true, RISCV_FUSION_ANY, 2 }
+};
+
+/* Return true if MEM satisfies RULE's single-instruction constraints.
+   Leave scheduling phases and pair constraints to the operation checker.  */
+
+static bool
+riscv_fuse_mem_candidate_p (const struct riscv_fusion_mem_info *mem,
+                           const struct riscv_fusion_mem_rule &rule)
+{
+  /* Check the memory mode independently of the register file.  */
+  if (mem->addr.type != ADDRESS_REG
+      || !CONST_INT_P (mem->addr.offset)
+      || mem->fp_p != rule.fp_p
+      || !(rule.types & mem->type)
+      || (rule.scalar_int_p && !SCALAR_INT_MODE_P (mem->mode)))
+    return false;
+
+  /* Bound the shift used to test the access-size mask.  */
+  unsigned int access_size = GET_MODE_SIZE (mem->mode).to_constant ();
+  if (access_size >= HOST_BITS_PER_WIDE_INT
+      || !(rule.access_sizes & (HOST_WIDE_INT_1U << access_size)))
+    return false;
+
+  /* Both members of an aligned pair have access-size-aligned offsets.  */
+  return INTVAL (mem->addr.offset) % access_size == 0;
+}
+
+/* Return a memory-pair scheduling direction for INSN under the current tune.
+   A null INSN queries support without checking a specific instruction.  */
+
+enum riscv_fusion_direction
+riscv_fuse_mem_direction (rtx_insn *insn)
+{
+  struct riscv_fusion_mem_info mem;
+  if (insn && !riscv_fuse_mem_p (insn, &mem))
+    return RISCV_FUSION_NONE;
+
+  unsigned HOST_WIDE_INT fusible_ops = riscv_get_fusible_ops ();
+  bool inc_p = false, dec_p = false, any_p = false;
+  /* Collect directions from all enabled rules that accept this access.  */
+  for (const riscv_fusion_mem_rule &rule : riscv_fusion_mem_rules)
+    {
+      if (!(fusible_ops & rule.op)
+         || (insn && !riscv_fuse_mem_candidate_p (&mem, rule)))
+       continue;
+
+      inc_p |= rule.direction == RISCV_FUSION_INC;
+      dec_p |= rule.direction == RISCV_FUSION_DEC;
+      any_p |= rule.direction == RISCV_FUSION_ANY;
+    }
+
+  /* Prefer directional rules over bidirectional rules to preserve their
+     scheduling order when both are eligible.  */
+  if (inc_p && dec_p)
+    return RISCV_FUSION_ANY;
+  if (inc_p)
+    return RISCV_FUSION_INC;
+  if (dec_p)
+    return RISCV_FUSION_DEC;
+  return any_p ? RISCV_FUSION_ANY : RISCV_FUSION_NONE;
+}
+
 /* Extract an add-type instruction followed by an integer load or store that
    uses the add result as an undisplaced address.  */
 
@@ -839,72 +943,120 @@ riscv_fuse_indexed_mem_p (rtx_insn *prev, rtx_insn *curr,
   return !riscv_fuse_same_reg_p (SET_SRC (mem_set), update_dest);
 }
 
-/* Extract a pair of scalar loads or stores with the same mode and base
-   register.  Store whether they are loads in *LOAD_P.  */
+/* Check scalar load/store pair operation OP, sharing candidate eligibility
+   with the priority hook.  Leave scheduling phase checks to the caller.  */
 
 static bool
 riscv_fuse_mem_pair_p (rtx_insn *prev, rtx_insn *curr,
-                        struct riscv_fusion_mem_info *prev_mem,
-                        struct riscv_fusion_mem_info *curr_mem,
-                        bool *load_p)
-{
-  if (!riscv_fuse_sets_p (prev, curr)
-      || !riscv_fuse_mem_p (prev, prev_mem)
-      || !riscv_fuse_mem_p (curr, curr_mem))
+                      enum riscv_fusion_pairs op)
+{
+  /* Match the caller's operation, regardless of other enabled rules.  */
+  const riscv_fusion_mem_rule *rule = nullptr;
+  for (const riscv_fusion_mem_rule &candidate : riscv_fusion_mem_rules)
+    if (candidate.op == op)
+      {
+       rule = &candidate;
+       break;
+      }
+  gcc_assert (rule);
+
+  struct riscv_fusion_mem_info prev_mem, curr_mem;
+  if (!riscv_fuse_mem_p (prev, &prev_mem)
+      || !riscv_fuse_mem_candidate_p (&prev_mem, *rule)
+      || !riscv_fuse_mem_p (curr, &curr_mem)
+      || !riscv_fuse_mem_candidate_p (&curr_mem, *rule))
     return false;
 
-  bool prev_load_p = prev_mem->type != SCHED_FUSION_ST;
-  bool curr_load_p = curr_mem->type != SCHED_FUSION_ST;
+  /* Check the relationship between the two eligible accesses.  */
+  bool prev_load_p = prev_mem.type != SCHED_FUSION_ST;
+  bool curr_load_p = curr_mem.type != SCHED_FUSION_ST;
   if (prev_load_p != curr_load_p
-      || prev_mem->fp_p != curr_mem->fp_p
-      || prev_mem->mode != curr_mem->mode
-      || prev_mem->addr.type != ADDRESS_REG
-      || curr_mem->addr.type != ADDRESS_REG
-      || !CONST_INT_P (prev_mem->addr.offset)
-      || !CONST_INT_P (curr_mem->addr.offset)
-      || !riscv_fuse_same_reg_p (prev_mem->addr.reg,
-                                  curr_mem->addr.reg))
+      || prev_mem.fp_p != curr_mem.fp_p
+      || prev_mem.mode != curr_mem.mode
+      || !riscv_fuse_same_reg_p (prev_mem.addr.reg, curr_mem.addr.reg))
     return false;
 
-  *load_p = prev_load_p;
-  return true;
-}
-
-/* Check common adjacent load/store-pair constraints.  INC_P selects ascending
-   offsets and FP_P selects floating-point rather than integer accesses.  */
+  /* Loads need distinct results, and the first must preserve the base.  */
+  if (prev_load_p
+      && (riscv_fuse_same_reg_p (prev_mem.reg, curr_mem.reg)
+         || riscv_fuse_same_reg_p (prev_mem.addr.reg, prev_mem.reg)))
+    return false;
 
-static bool
-riscv_fuse_ldst_pair_p (rtx_insn *prev, rtx_insn *curr,
-                         bool inc_p, bool fp_p)
-{
-  struct riscv_fusion_mem_info prev_mem, curr_mem;
-  bool load_p;
-  if (!riscv_fuse_mem_pair_p (prev, curr, &prev_mem, &curr_mem,
-                               &load_p)
-      || prev_mem.fp_p != fp_p
-      || prev_mem.type == SCHED_FUSION_LD_ZERO_EXTEND
-      || curr_mem.type == SCHED_FUSION_LD_ZERO_EXTEND)
+  HOST_WIDE_INT prev_offset = INTVAL (prev_mem.addr.offset);
+  HOST_WIDE_INT curr_offset = INTVAL (curr_mem.addr.offset);
+  bool inc_p = prev_offset < curr_offset;
+  if (rule->direction != RISCV_FUSION_ANY
+      && inc_p != (rule->direction == RISCV_FUSION_INC))
     return false;
 
+  /* Use the lower offset for the pair alignment check.  */
+  if (!inc_p)
+    std::swap (prev_offset, curr_offset);
+
+  /* Use unsigned subtraction to avoid overflow for opposite-signed offsets.  
*/
+  unsigned HOST_WIDE_INT diff = ((unsigned HOST_WIDE_INT) curr_offset
+                               - (unsigned HOST_WIDE_INT) prev_offset);
   HOST_WIDE_INT access_size = GET_MODE_SIZE (prev_mem.mode).to_constant ();
-  if (access_size != 4 && access_size != 8)
-    return false;
+  HOST_WIDE_INT alignment = rule->alignment_factor * access_size;
+  return (diff == (unsigned HOST_WIDE_INT) access_size
+         && prev_offset % alignment == 0);
+}
 
-  if (load_p)
-    {
-      rtx prev_dest = SET_DEST (single_set (prev));
-      rtx curr_dest = SET_DEST (single_set (curr));
-      if (riscv_fuse_same_reg_p (prev_dest, curr_dest)
-         || riscv_fuse_same_reg_p (prev_mem.addr.reg, prev_dest))
-       return false;
-    }
+/* Implement TARGET_SCHED_FUSION_PRIORITY.  Group load/store pair candidates
+   by register file, access kind, mode and base register, then by offset.  */
 
-  HOST_WIDE_INT diff = inc_p
-                      ? INTVAL (curr_mem.addr.offset)
-                        - INTVAL (prev_mem.addr.offset)
-                      : INTVAL (prev_mem.addr.offset)
-                        - INTVAL (curr_mem.addr.offset);
-  return diff == access_size;
+void
+riscv_sched_fusion_priority (rtx_insn *insn, int max_pri,
+                            int *fusion_pri, int *pri)
+{
+  struct riscv_fusion_mem_info mem;
+  unsigned int base_regno;
+  bool isload_p, fp_p, inc_p;
+  int fusion_type, tmp;
+
+  gcc_assert (INSN_P (insn));
+
+  /* Keep default priorities unless this instruction is a pair candidate.  */
+  tmp = max_pri - 1;
+  *fusion_pri = tmp;
+  *pri = tmp;
+
+  enum riscv_fusion_direction direction
+    = riscv_fuse_mem_direction (insn);
+  if (direction == RISCV_FUSION_NONE || !riscv_fuse_mem_p (insn, &mem))
+    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;
+  /* Prefer decreasing offsets by default when either direction is possible,
+     matching frame save/restore order.  */
+  inc_p = direction == RISCV_FUSION_INC;
+
+  /* Give each load/store class and base register a distinct priority below
+     that of unrelated instructions.  */
+  fusion_type = (fp_p ? 2 : 0) + (isload_p ? 0 : 1);
+  fusion_type *= NUM_MACHINE_MODES;
+  fusion_type += (int) mem.mode + 1;
+  *fusion_pri -= (fusion_type * FIRST_PSEUDO_REGISTER
+                 + (int) base_regno);
+
+  tmp /= 2;
+  HOST_WIDE_INT off_val = INTVAL (mem.addr.offset);
+  /* Use unsigned arithmetic to handle the most negative 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.  */
@@ -1488,6 +1640,7 @@ riscv_fuse_auipc_ld (rtx_insn *prev, rtx_insn *curr)
    curr (store) == (set (mem (rs1, offset2)) (reg rs3))
 
    Constraints:
+     access size is 1, 2, 4 or 8 bytes
      both stores use the same scalar integer mode
      min (offset1, offset2) is aligned to twice the access size
      abs (offset1 - offset2) equals the access size.  */
@@ -1495,27 +1648,7 @@ riscv_fuse_auipc_ld (rtx_insn *prev, rtx_insn *curr)
 static bool
 riscv_fuse_aligned_std (rtx_insn *prev, rtx_insn *curr)
 {
-  struct riscv_fusion_mem_info prev_mem, curr_mem;
-  bool load_p;
-  if (!riscv_fuse_mem_pair_p (prev, curr, &prev_mem, &curr_mem,
-                               &load_p)
-      || load_p
-      || prev_mem.fp_p
-      || !SCALAR_INT_MODE_P (prev_mem.mode))
-    return false;
-
-  unsigned int mode_size
-    = estimated_poly_value (GET_MODE_SIZE (curr_mem.mode));
-  HOST_WIDE_INT prev_offset = INTVAL (prev_mem.addr.offset);
-  HOST_WIDE_INT curr_offset = INTVAL (curr_mem.addr.offset);
-  if (prev_offset > curr_offset)
-    std::swap (prev_offset, curr_offset);
-
-  if (prev_offset % (2 * mode_size) == 0
-      && prev_offset + mode_size == curr_offset)
-    return true;
-
-  return false;
+  return riscv_fuse_mem_pair_p (prev, curr, RISCV_FUSE_ALIGNED_STD);
 }
 
 /* Check for RISCV_FUSE_LDST_PAIR_INC fusion.
@@ -1529,13 +1662,14 @@ riscv_fuse_aligned_std (rtx_insn *prev, rtx_insn *curr)
    Constraints:
      access size is 4 or 8 bytes
      offset2 - offset1 equals the access size
+     offsets are aligned to the access size
      loads are not zero-extending
      for loads, rd1 != rd2 and rd1 != rs1.  */
 
 static bool
 riscv_fuse_ldst_pair_inc (rtx_insn *prev, rtx_insn *curr)
 {
-  return riscv_fuse_ldst_pair_p (prev, curr, true, false);
+  return riscv_fuse_mem_pair_p (prev, curr, RISCV_FUSE_LDST_PAIR_INC);
 }
 
 /* Check for RISCV_FUSE_LDST_PAIR_DEC fusion.
@@ -1549,13 +1683,14 @@ riscv_fuse_ldst_pair_inc (rtx_insn *prev, rtx_insn 
*curr)
    Constraints:
      access size is 4 or 8 bytes
      offset1 - offset2 equals the access size
+     offsets are aligned to the access size
      loads are not zero-extending
      for loads, rd1 != rd2 and rd1 != rs1.  */
 
 static bool
 riscv_fuse_ldst_pair_dec (rtx_insn *prev, rtx_insn *curr)
 {
-  return riscv_fuse_ldst_pair_p (prev, curr, false, false);
+  return riscv_fuse_mem_pair_p (prev, curr, RISCV_FUSE_LDST_PAIR_DEC);
 }
 
 /* Check for RISCV_FUSE_FLDFST_PAIR_INC fusion.
@@ -1569,12 +1704,13 @@ riscv_fuse_ldst_pair_dec (rtx_insn *prev, rtx_insn 
*curr)
    Constraints:
      access size is 4 or 8 bytes
      offset2 - offset1 equals the access size
+     offsets are aligned to the access size
      for loads, frd1 != frd2.  */
 
 static bool
 riscv_fuse_fldfst_pair_inc (rtx_insn *prev, rtx_insn *curr)
 {
-  return riscv_fuse_ldst_pair_p (prev, curr, true, true);
+  return riscv_fuse_mem_pair_p (prev, curr, RISCV_FUSE_FLDFST_PAIR_INC);
 }
 
 /* Check for RISCV_FUSE_FLDFST_PAIR_DEC fusion.
@@ -1588,12 +1724,13 @@ riscv_fuse_fldfst_pair_inc (rtx_insn *prev, rtx_insn 
*curr)
    Constraints:
      access size is 4 or 8 bytes
      offset1 - offset2 equals the access size
+     offsets are aligned to the access size
      for loads, frd1 != frd2.  */
 
 static bool
 riscv_fuse_fldfst_pair_dec (rtx_insn *prev, rtx_insn *curr)
 {
-  return riscv_fuse_ldst_pair_p (prev, curr, false, true);
+  return riscv_fuse_mem_pair_p (prev, curr, RISCV_FUSE_FLDFST_PAIR_DEC);
 }
 
 /* Check for RISCV_FUSE_BFEXT fusion.
diff --git a/gcc/config/riscv/riscv-protos.h b/gcc/config/riscv/riscv-protos.h
index f9e9ad51ab5..0a0ba6a0442 100644
--- a/gcc/config/riscv/riscv-protos.h
+++ b/gcc/config/riscv/riscv-protos.h
@@ -883,8 +883,19 @@ enum riscv_fusion_pairs
   RISCV_FUSE_FLDFST_PAIR_DEC = HOST_WIDE_INT_1U << 26,
 };
 
+/* Possible directions for a memory-pair candidate.  */
+enum riscv_fusion_direction
+{
+  RISCV_FUSION_NONE,
+  RISCV_FUSION_INC,
+  RISCV_FUSION_DEC,
+  RISCV_FUSION_ANY
+};
+
 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 enum riscv_fusion_direction riscv_fuse_mem_direction (rtx_insn *);
 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 f8b54fc4ae6..281080f282c 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -12319,6 +12319,11 @@ 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_fuse_mem_direction (nullptr) != RISCV_FUSION_NONE;
+
   /* If not optimizing for size, set the default
       alignment to what the target wants.  */
   if (!opts->x_optimize_size)
@@ -12427,6 +12432,12 @@ riscv_override_options_after_change (void)
     }
   else if (!OPTION_SET_P (flag_cunroll_grow_size))
     flag_cunroll_grow_size = flag_peel_loops || optimize >= 3;
+
+  /* Restore the tune's fusion default after optimization option changes.
+     The initial option override sets tune_param later.  */
+  if (tune_param && !OPTION_SET_P (flag_schedule_fusion))
+    flag_schedule_fusion
+      = riscv_fuse_mem_direction (nullptr) != RISCV_FUSION_NONE;
 }
 
 /* Implement TARGET_OPTION_OVERRIDE.  */
@@ -16835,6 +16846,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/fusion-memory-rtl-shapes-ldst-pair.c 
b/gcc/testsuite/gcc.target/riscv/fusion-memory-rtl-shapes-ldst-pair.c
index 2a19c6f3d97..ff59079fcc9 100644
--- a/gcc/testsuite/gcc.target/riscv/fusion-memory-rtl-shapes-ldst-pair.c
+++ b/gcc/testsuite/gcc.target/riscv/fusion-memory-rtl-shapes-ldst-pair.c
@@ -3,7 +3,8 @@
 /* { dg-options "-march=rv64gc -mabi=lp64d -O2 -mtune=xt-c9501fdvt 
-fdump-rtl-sched2-details" } */
 /* { dg-final { scan-rtl-dump-times "RISCV_FUSE_LDST_PAIR_INC" 1 "sched2" { 
xfail *-*-* } } } */
 
-/* The second load destination may be the shared base register.  */
+/* The second load destination may be the shared base register.  Offsets
+   aligned to the access size need not be aligned to twice that size.  */
 long __RTL (startwith ("sched2"))
 test_load_pair_inc_second_destination_is_base (void)
 {
@@ -14,11 +15,13 @@ test_load_pair_inc_second_destination_is_base (void)
       (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 a0)
                     (mem:DI
                       (plus:DI (reg:DI a0)
                                (const_int 8)) [0 S8 A64])))
+      (cinsn 4 (set (reg:DI a0)
+                    (mem:DI
+                      (plus:DI (reg:DI a0)
+                               (const_int 16)) [0 S8 A64])))
       (cinsn 5 (use (reg:DI a1)))
       (cinsn 6 (use (reg:DI a0)))
       (cjump_insn 7 (simple_return))
@@ -31,3 +34,64 @@ test_load_pair_inc_second_destination_is_base (void)
   ) ;; crtl
 ) ;; function "test_load_pair_inc_second_destination_is_base"
 }
+
+/* Reject unaligned loads and stores, covering both widths and directions.  */
+long __RTL (startwith ("sched2"))
+test_unaligned_load_store_pairs (void)
+{
+(function "test_unaligned_load_store_pairs"
+  (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)
+                    (sign_extend:DI
+                      (mem:SI
+                        (plus:DI (reg:DI a0)
+                                 (const_int 2)) [0 S4 A16]))))
+      (cinsn 4 (set (reg:DI a2)
+                    (sign_extend:DI
+                      (mem:SI
+                        (plus:DI (reg:DI a0)
+                                 (const_int 6)) [0 S4 A16]))))
+      (cinsn 5 (set (reg:DI a3)
+                    (mem:DI
+                      (plus:DI (reg:DI a0)
+                               (const_int 12)) [0 S8 A32])))
+      (cinsn 6 (set (reg:DI a4)
+                    (mem:DI
+                      (plus:DI (reg:DI a0)
+                               (const_int 4)) [0 S8 A32])))
+      (cinsn 7 (set (mem:SI
+                     (plus:DI (reg:DI a0)
+                              (const_int 18)) [0 S4 A16])
+                    (reg:SI a5)))
+      (cinsn 8 (set (mem:SI
+                     (plus:DI (reg:DI a0)
+                              (const_int 22)) [0 S4 A16])
+                    (reg:SI a6)))
+      (cinsn 9 (set (mem:DI
+                     (plus:DI (reg:DI a0)
+                              (const_int 44)) [0 S8 A32])
+                    (reg:DI a5)))
+      (cinsn 10 (set (mem:DI
+                      (plus:DI (reg:DI a0)
+                               (const_int 36)) [0 S8 A32])
+                     (reg:DI a6)))
+      (cinsn 11 (use (reg:DI a1)))
+      (cinsn 12 (use (reg:DI a2)))
+      (cinsn 13 (use (reg:DI a3)))
+      (cinsn 14 (use (reg:DI a4)))
+      (cjump_insn 15 (simple_return))
+      (edge-to exit)
+    ) ;; block 2
+    (cbarrier 16)
+  ) ;; insn-chain
+  (crtl
+    (return_rtx (reg/i:DI a0))
+  ) ;; crtl
+) ;; function "test_unaligned_load_store_pairs"
+}
+
+/* { dg-final { scan-rtl-dump-not "RISCV_FUSE_LDST_PAIR_DEC" "sched2" } } */
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..f153e45e9aa
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/sched-fusion-priority.c
@@ -0,0 +1,92 @@
+/* { 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"
+}
+
+/* Unaligned offsets must not receive memory-pair priorities.  */
+/*
+**sched_fusion_unaligned_loads:
+**     ...
+**     ld      [a-z][0-9]+,4\(a0\)
+**     add     [a-z][0-9]+,[a-z][0-9]+,[a-z][0-9]+
+**     ld      [a-z][0-9]+,12\(a0\)
+**     ...
+**     ret
+*/
+long __RTL (startwith ("compgotos"))
+sched_fusion_unaligned_loads (void)
+{
+(function "sched_fusion_unaligned_loads"
+  (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
+                      (plus:DI (reg:DI a0)
+                               (const_int 4)) [0 S8 A32])))
+      (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 12)) [0 S8 A32])))
+      (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))
+  ) ;; crtl
+) ;; function "sched_fusion_unaligned_loads"
+}
-- 
2.52.0

Reply via email to