Recognize adjacent 4-byte or 8-byte integer loads or stores whose
addresses share a base and increase by one access size.  Factor common
pair parsing into a helper reused by ALIGNED_STD, and parameterize the
direction and register-class differences for the remaining pair rules.

Leave the new fusion disabled by default and XFAIL its positive dump
checks until a CPU enables it.

gcc/ChangeLog:

        * config/riscv/riscv-fusion.cc (riscv_fuse_mem_pair_p): New
        function.
        (riscv_fuse_ldst_pair_p): Likewise.
        (riscv_fuse_aligned_std): Use riscv_fuse_mem_pair_p.
        (riscv_fuse_ldst_pair_inc): New function.
        (riscv_fusion_table): Add RISCV_FUSE_LDST_PAIR_INC.
        * config/riscv/riscv-protos.h (enum riscv_fusion_pairs): Add
        RISCV_FUSE_LDST_PAIR_INC.

gcc/testsuite/ChangeLog:

        * gcc.target/riscv/fusion-ldst-pair-inc.c: New test.
        * gcc.target/riscv/fusion-memory-rtl-shapes-ldst-pair.c: Likewise.

Signed-off-by: Jin Ma <[email protected]>
---
 gcc/config/riscv/riscv-fusion.cc              | 109 +++++++++++++++---
 gcc/config/riscv/riscv-protos.h               |   1 +
 .../gcc.target/riscv/fusion-ldst-pair-inc.c   |  33 ++++++
 .../fusion-memory-rtl-shapes-ldst-pair.c      |  33 ++++++
 4 files changed, 162 insertions(+), 14 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-ldst-pair-inc.c
 create mode 100644 
gcc/testsuite/gcc.target/riscv/fusion-memory-rtl-shapes-ldst-pair.c

diff --git a/gcc/config/riscv/riscv-fusion.cc b/gcc/config/riscv/riscv-fusion.cc
index 530c24482b2..419628a5038 100644
--- a/gcc/config/riscv/riscv-fusion.cc
+++ b/gcc/config/riscv/riscv-fusion.cc
@@ -839,6 +839,74 @@ 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.  */
+
+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))
+    return false;
+
+  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))
+    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.  */
+
+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)
+    return false;
+
+  HOST_WIDE_INT access_size = GET_MODE_SIZE (prev_mem.mode).to_constant ();
+  if (access_size != 4 && access_size != 8)
+    return false;
+
+  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;
+    }
+
+  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;
+}
+
 /* Check the common RTL for ZEXTW, ZEXTWS and ZEXTH fusion.  */
 
 static bool
@@ -1422,22 +1490,13 @@ riscv_fuse_auipc_ld (rtx_insn *prev, rtx_insn *curr)
 static bool
 riscv_fuse_aligned_std (rtx_insn *prev, rtx_insn *curr)
 {
-  if (!riscv_fuse_sets_p (prev, curr))
-    return false;
-
   struct riscv_fusion_mem_info prev_mem, curr_mem;
-
-  if (!riscv_fuse_mem_p (prev, &prev_mem)
-      || !riscv_fuse_mem_p (curr, &curr_mem)
-      || prev_mem.type != SCHED_FUSION_ST
-      || curr_mem.type != SCHED_FUSION_ST
+  bool load_p;
+  if (!riscv_fuse_mem_pair_p (prev, curr, &prev_mem, &curr_mem,
+                               &load_p)
+      || load_p
       || prev_mem.fp_p
-      || curr_mem.fp_p
-      || !SCALAR_INT_MODE_P (prev_mem.mode)
-      || prev_mem.mode != curr_mem.mode
-      || prev_mem.addr.type != ADDRESS_REG
-      || curr_mem.addr.type != ADDRESS_REG
-      || !riscv_fuse_same_reg_p (prev_mem.addr.reg, curr_mem.addr.reg))
+      || !SCALAR_INT_MODE_P (prev_mem.mode))
     return false;
 
   unsigned int mode_size
@@ -1454,6 +1513,26 @@ riscv_fuse_aligned_std (rtx_insn *prev, rtx_insn *curr)
   return false;
 }
 
+/* Check for RISCV_FUSE_LDST_PAIR_INC fusion.
+   prev/curr (one of the following pairs):
+     prev (lw/ld) == (set (reg rd1) (mem (rs1, offset1)))
+     curr (lw/ld) == (set (reg rd2) (mem (rs1, offset2)))
+
+     prev (sw/sd) == (set (mem (rs1, offset1)) (reg rs2))
+     curr (sw/sd) == (set (mem (rs1, offset2)) (reg rs3))
+
+   Constraints:
+     access size is 4 or 8 bytes
+     offset2 - offset1 equals 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);
+}
+
 /* Check for RISCV_FUSE_BFEXT fusion.
    prev (slli) == (set (reg rd1)
                       (ashift (reg rs1) (const_int shamt1)))
@@ -1797,6 +1876,8 @@ static const struct riscv_fusion_entry 
riscv_fusion_table[] =
     riscv_fuse_auipc_ld, "RISCV_FUSE_AUIPC_LD" },
   { RISCV_FUSE_ALIGNED_STD,
     riscv_fuse_aligned_std, "RISCV_FUSE_ALIGNED_STD" },
+  { RISCV_FUSE_LDST_PAIR_INC,
+    riscv_fuse_ldst_pair_inc, "RISCV_FUSE_LDST_PAIR_INC" },
   { RISCV_FUSE_BFEXT,
     riscv_fuse_bfext, "RISCV_FUSE_BFEXT" },
   { RISCV_FUSE_SLLI_SRLI,
diff --git a/gcc/config/riscv/riscv-protos.h b/gcc/config/riscv/riscv-protos.h
index 445d1f7f257..de4476a2d66 100644
--- a/gcc/config/riscv/riscv-protos.h
+++ b/gcc/config/riscv/riscv-protos.h
@@ -876,6 +876,7 @@ enum riscv_fusion_pairs
   RISCV_FUSE_PREINDEX_ST = HOST_WIDE_INT_1U << 20,
   RISCV_FUSE_POSTINDEX_LD = HOST_WIDE_INT_1U << 21,
   RISCV_FUSE_POSTINDEX_ST = HOST_WIDE_INT_1U << 22,
+  RISCV_FUSE_LDST_PAIR_INC = HOST_WIDE_INT_1U << 23,
 };
 
 extern bool riscv_macro_fusion_p (void);
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-ldst-pair-inc.c 
b/gcc/testsuite/gcc.target/riscv/fusion-ldst-pair-inc.c
new file mode 100644
index 00000000000..44ad262bf17
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-ldst-pair-inc.c
@@ -0,0 +1,33 @@
+/* { dg-do compile { target { rv64 } } } */
+/* { dg-skip-if "" { *-*-* } { "-O0" "-O1" "-O3" "-O[sgz]" "-flto" } } */
+/* { dg-options "-march=rv64gc -mabi=lp64d -mtune=xt-c9501fdvt -O2 
-fdump-rtl-sched2-details" } */
+/* { dg-final { scan-rtl-dump-times "RISCV_FUSE_LDST_PAIR_INC" 4 "sched2" { 
xfail *-*-* } } } */
+
+typedef int int32_t;
+typedef long int64_t;
+
+int64_t
+test_ld_pair_inc (int64_t *p)
+{
+  return p[0] + p[1];
+}
+
+int64_t
+test_lw_pair_inc (int32_t *p)
+{
+  return (int64_t) p[0] + p[1];
+}
+
+void
+test_sd_pair_inc (int64_t *p, int64_t a, int64_t b)
+{
+  p[0] = a;
+  p[1] = b;
+}
+
+void
+test_sw_pair_inc (int32_t *p, int32_t a, int32_t b)
+{
+  p[0] = a;
+  p[1] = b;
+}
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
new file mode 100644
index 00000000000..2a19c6f3d97
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-memory-rtl-shapes-ldst-pair.c
@@ -0,0 +1,33 @@
+/* { dg-do compile { target { rv64 } } } */
+/* { dg-skip-if "" { *-*-* } { "-O0" "-O1" "-O3" "-O[sgz]" "-flto" } } */
+/* { 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.  */
+long __RTL (startwith ("sched2"))
+test_load_pair_inc_second_destination_is_base (void)
+{
+(function "test_load_pair_inc_second_destination_is_base"
+  (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 a0)
+                    (mem:DI
+                      (plus:DI (reg:DI a0)
+                               (const_int 8)) [0 S8 A64])))
+      (cinsn 5 (use (reg:DI a1)))
+      (cinsn 6 (use (reg:DI a0)))
+      (cjump_insn 7 (simple_return))
+      (edge-to exit)
+    ) ;; block 2
+    (cbarrier 8)
+  ) ;; insn-chain
+  (crtl
+    (return_rtx (reg/i:DI a0))
+  ) ;; crtl
+) ;; function "test_load_pair_inc_second_destination_is_base"
+}
-- 
2.52.0

Reply via email to