From: Loeka Rogge <[email protected]>

RPX-100 allows a mulh(s/u) followed by a mul operation to be bonded,
saving 3 cycles in the multiplier and allowing the mul's result to be
used 1 cycle earlier than expected.

- Added RISCV_FUSE_BONDED_MUL fusion
- riscv_sched_adjust_cost reflects the mul result being available 1
  cycle sooner in this case

Regtested on rv32gc, rv64gc and with rhx100 config and mtune.

gcc/ChangeLog:

        * config/riscv/riscv-cores.def (RISCV_TUNE): Make rpx100 a
        separate tune instead of reusing rhx100 tune
        * config/riscv/riscv-fusion.cc (riscv_set_is_multiply_high_pattern_p):
        Check if a set is a mulh(s/u) pattern.
        (riscv_get_mulh_operands): Get operands from a mulh(s/u)
        pattern.
        (riscv_fuse_bonded_mul): Check for bonded mul pairs to fuse.
        (riscv_depends_on_lowpart_bonded_mul_p): check if dependency is
        on low part of a bonded mul operation.
        * config/riscv/riscv-protos.h (enum riscv_fusion_pairs): Add
        RISCV_FUSE_BONDED_MUL fusion
        (riscv_depends_on_lowpart_bonded_mul_p): Add function header
        for use in riscv.cc .
        * config/riscv/riscv.cc (riscv_sched_adjust_cost): Adjust cost
        for rpx100 bonded mul.

gcc/testsuite/ChangeLog:

        * gcc.target/riscv/fusion-bonded-mul-rtl.c: New test.

Signed-off-by: Loeka Rogge <[email protected]>
---
Changes since v1:
- riscv_depends_on_lowpart_bonded_mul_p now checks SCHED_GROUP_P (prev_insn)
- Removed changes to RTL patterns

 gcc/config/riscv/riscv-cores.def              |   2 +-
 gcc/config/riscv/riscv-fusion.cc              | 143 +++++++++++++++
 gcc/config/riscv/riscv-protos.h               |   2 +
 gcc/config/riscv/riscv.cc                     |  29 ++-
 .../gcc.target/riscv/fusion-bonded-mul-rtl.c  | 165 ++++++++++++++++++
 5 files changed, 339 insertions(+), 2 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-bonded-mul-rtl.c

diff --git a/gcc/config/riscv/riscv-cores.def b/gcc/config/riscv/riscv-cores.def
index f330c790de7..05994333ef6 100644
--- a/gcc/config/riscv/riscv-cores.def
+++ b/gcc/config/riscv/riscv-cores.def
@@ -58,7 +58,7 @@ RISCV_TUNE("spacemit-x60", spacemit_x60, 
spacemit_x60_tune_info)
 RISCV_TUNE("spacemit-x100", generic_ooo, spacemit_x100_tune_info)
 RISCV_TUNE("spacemit-a100", spacemit_x60, spacemit_x60_tune_info)
 RISCV_TUNE("arc-v-rhx-100-series", arcv_rhx100, arcv_rhx100_tune_info)
-RISCV_TUNE("arc-v-rpx-100-series", arcv_rpx100, arcv_rhx100_tune_info)
+RISCV_TUNE("arc-v-rpx-100-series", arcv_rpx100, arcv_rpx100_tune_info)
 RISCV_TUNE("generic-ooo", generic_ooo, generic_ooo_tune_info)
 RISCV_TUNE("size", generic, optimize_size_tune_info)
 RISCV_TUNE("mips-p8700", mips_p8700, mips_p8700_tune_info)
diff --git a/gcc/config/riscv/riscv-fusion.cc b/gcc/config/riscv/riscv-fusion.cc
index f04dfe52e20..7caae64c5b4 100644
--- a/gcc/config/riscv/riscv-fusion.cc
+++ b/gcc/config/riscv/riscv-fusion.cc
@@ -147,6 +147,38 @@ riscv_set_is_shNadduw_p (rtx set)
          && REG_P (SET_DEST (set)));
 }
 
+/* Check if RTX matches multiply-high pattern, where X is the word mode:
+   (set (reg:X DEST)
+    (truncate:X (lshiftrt:2X (mult:2X (extend:2X (OP0))
+                                     (extend:2X (OP1)))
+                            (const_int BITS_PER_WORD)))  */
+
+static bool
+riscv_set_is_multiply_high_pattern_p (rtx src)
+{
+  if (GET_CODE (src) != SET)
+    return false;
+  rtx truncate = SET_SRC (src);
+
+  if (GET_CODE (truncate) != TRUNCATE)
+    return false;
+
+  rtx lshiftrt = XEXP (truncate, 0);
+  if (GET_CODE (lshiftrt) != LSHIFTRT)
+    return false;
+
+  rtx shift_amount = XEXP (lshiftrt, 1);
+  rtx mult = XEXP (lshiftrt, 0);
+
+  if (!CONST_INT_P (shift_amount) || INTVAL (shift_amount) != BITS_PER_WORD)
+    return false;
+
+  if (GET_CODE (mult) != MULT)
+    return false;
+
+  return true;
+}
+
 /* Check the common RTL for ZEXTW, ZEXTWS and ZEXTH fusion.  */
 
 static bool
@@ -744,6 +776,115 @@ riscv_fuse_b_alui (rtx_insn *prev, rtx_insn *curr)
   return false;
 }
 
+/* Extract register operands and destination from multiply high-part pattern.
+   Operands are returned in the order of the assembly instruction.  The usmul
+   pattern emits the operands in the reverse order.
+   Returns true if successful.  */
+
+static bool
+riscv_get_mulh_operands (rtx set_rtx, rtx *op0, rtx *op1, rtx *dest)
+{
+  if (!riscv_set_is_multiply_high_pattern_p (set_rtx))
+    return false;
+
+  rtx mulh_pattern = SET_SRC (set_rtx);
+
+  rtx lshiftrt = XEXP (mulh_pattern, 0);
+  rtx mult = XEXP (lshiftrt, 0);
+
+  rtx operand0 = XEXP (mult, 0);
+  rtx operand1 = XEXP (mult, 1);
+
+  /* Swap operands for usmul pattern.  */
+  if (GET_CODE (operand0) == ZERO_EXTEND
+      && GET_CODE (operand1) == SIGN_EXTEND)
+    std::swap (operand0, operand1);
+
+  /* Strip zero_extend/sign_extend.  */
+  if (GET_CODE (operand0) == ZERO_EXTEND || GET_CODE (operand0) == SIGN_EXTEND)
+    operand0 = XEXP (operand0, 0);
+  if (GET_CODE (operand1) == ZERO_EXTEND || GET_CODE (operand1) == SIGN_EXTEND)
+    operand1 = XEXP (operand1, 0);
+
+  /* Operands must be low part of reg:TI.  */
+  if (!subreg_lowpart_p (operand0) || !subreg_lowpart_p (operand1))
+    return false;
+
+  /* Strip subreg.  */
+  if (GET_CODE (operand0) == SUBREG)
+    operand0 = SUBREG_REG (operand0);
+  if (GET_CODE (operand1) == SUBREG)
+    operand1 = SUBREG_REG (operand1);
+
+  if (!REG_P (operand0) || !REG_P (operand1))
+    return false;
+
+  rtx dest_reg = SET_DEST (set_rtx);
+  if (!REG_P (dest_reg))
+    return false;
+
+  *op0 = operand0;
+  *op1 = operand1;
+  *dest = dest_reg;
+  return true;
+}
+
+/* Check for RISCV_FUSE_BONDED_MUL fusion.
+   prev (mulh/mulhu/mulhsu) and curr (mul) of the same inputs, with the
+   high-part destination distinct from both inputs.  */
+
+static bool
+riscv_fuse_bonded_mul (rtx_insn *prev, rtx_insn *curr)
+{
+  rtx prev_set = single_set (prev);
+  rtx curr_set = single_set (curr);
+
+  /* Bond mul hi part and mul low part pair:
+     prev: (set rd_hi (mulh(u/s) rs1 rs2))
+     curr: (set rd_lo (mul rs1 rs2))
+     where rd_hi != rs1 && rd_hi != rs2.  */
+  if (prev_set && curr_set
+      && GET_CODE (SET_SRC (curr_set)) == MULT)
+    {
+      rtx prev_op0, prev_op1, prev_dest;
+      if (riscv_get_mulh_operands (prev_set, &prev_op0, &prev_op1, &prev_dest))
+       {
+         rtx curr_src = SET_SRC (curr_set);
+
+         rtx curr_op0 = XEXP (curr_src, 0);
+         rtx curr_op1 = XEXP (curr_src, 1);
+
+         /* Check that the operands are the same and in the same order
+            and check the prev destination is distinct from the operands.  */
+         if (REG_P (prev_op0) && REG_P (prev_op1)
+             && REG_P (curr_op0) && REG_P (curr_op1)
+             && REGNO (prev_op0) == REGNO (curr_op0)
+             && REGNO (prev_op1) == REGNO (curr_op1)
+             && REGNO (prev_dest) != REGNO (prev_op0)
+             && REGNO (prev_dest) != REGNO (prev_op1))
+           return true;
+       }
+    }
+
+  return false;
+}
+
+bool
+riscv_depends_on_lowpart_bonded_mul_p (int dep_type, rtx_insn *dep_insn)
+{
+  if (dep_type != REG_DEP_TRUE)
+    return false;
+
+  rtx_insn *prev_insn = prev_nonnote_nondebug_insn (dep_insn);
+  if (!prev_insn || !INSN_P (prev_insn) || SCHED_GROUP_P (prev_insn))
+    return false;
+
+  if (!riscv_fuse_bonded_mul (prev_insn, dep_insn))
+    return false;
+
+  return true;
+}
+
 /* Type for a fusion checker function.  Takes the two candidate insns
    and returns true if they should be fused.  */
 
@@ -793,6 +934,8 @@ static const struct riscv_fusion_entry riscv_fusion_table[] 
=
     riscv_fuse_bfext, "RISCV_FUSE_BFEXT" },
   { RISCV_FUSE_B_ALUI,
     riscv_fuse_b_alui, "RISCV_FUSE_B_ALUI" },
+  { RISCV_FUSE_BONDED_MUL,
+    riscv_fuse_bonded_mul, "RISCV_FUSE_BONDED_MUL" },
 };
 
 /* Return the name of fusion operation OP.  */
diff --git a/gcc/config/riscv/riscv-protos.h b/gcc/config/riscv/riscv-protos.h
index 07a1da94605..648cc97e005 100644
--- a/gcc/config/riscv/riscv-protos.h
+++ b/gcc/config/riscv/riscv-protos.h
@@ -869,6 +869,7 @@ enum riscv_fusion_pairs
   RISCV_FUSE_BFEXT = HOST_WIDE_INT_1U << 10,
   RISCV_FUSE_EXPANDED_LD = HOST_WIDE_INT_1U << 11,
   RISCV_FUSE_B_ALUI = HOST_WIDE_INT_1U << 12,
+  RISCV_FUSE_BONDED_MUL = HOST_WIDE_INT_1U << 13,
 };
 
 extern bool riscv_macro_fusion_p (void);
@@ -903,6 +904,7 @@ extern const char *th_output_move (rtx, rtx);
 extern bool th_print_operand_address (FILE *, machine_mode, rtx);
 #endif
 
+extern bool riscv_depends_on_lowpart_bonded_mul_p (int, rtx_insn *);
 extern bool arcv_mpy_1c_bypass_p (rtx_insn *, rtx_insn *);
 extern bool arcv_mpy_2c_bypass_p (rtx_insn *, rtx_insn *);
 extern bool arcv_mpy_10c_bypass_p (rtx_insn *, rtx_insn *);
diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index d83e26351c9..c8e7a533312 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -1087,6 +1087,30 @@ static const struct riscv_tune_param 
arcv_rhx100_tune_info = {
   true,                                               /* prefer-agnostic.  */
 };
 
+/* Same pipeline costs as RHX-100.  Fuse bonded mulh/mul pairs.  */
+static const struct riscv_tune_param arcv_rpx100_tune_info = {
+  {COSTS_N_INSNS (4), COSTS_N_INSNS (5)},      /* fp_add */
+  {COSTS_N_INSNS (4), COSTS_N_INSNS (5)},      /* fp_mul */
+  {COSTS_N_INSNS (20), COSTS_N_INSNS (20)},    /* fp_div */
+  {COSTS_N_INSNS (4), COSTS_N_INSNS (4)},      /* int_mul */
+  {COSTS_N_INSNS (27), COSTS_N_INSNS (43)},    /* int_div */
+  4,                                           /* issue_rate */
+  9,                                           /* branch_cost */
+  2,                                           /* memory_cost */
+  8,                                           /* fmv_cost */
+  false,                                       /* slow_unaligned_access */
+  false,                                       /* vector_unaligned_access */
+  true,                                                /* use_divmod_expansion 
*/
+  false,                                       /* overlap_op_by_pieces */
+  true,                                                /* use_zero_stride_load 
*/
+  false,                                       /* speculative_sched_vsetvl */
+  RISCV_FUSE_BONDED_MUL,                       /* fusible_ops */
+  NULL,                                                /* vector cost */
+  NULL,                                                /* function_align */
+  NULL,                                                /* jump_align */
+  NULL,                                                /* loop_align */
+  true,                                                /* prefer-agnostic.  */
+};
 
 /* Costs to use when optimizing for Synopsys RMX-100.  */
 static const struct riscv_tune_param arcv_rmx100_tune_info = {
@@ -12088,9 +12112,12 @@ riscv_get_fusible_ops (void)
    we currently only perform the adjustment when -madjust-lmul-cost is given.
    */
 static int
-riscv_sched_adjust_cost (rtx_insn *, int, rtx_insn *insn, int cost,
+riscv_sched_adjust_cost (rtx_insn *, int dep_type, rtx_insn *insn, int cost,
                         unsigned int)
 {
+  if (riscv_microarchitecture == arcv_rpx100
+      && riscv_depends_on_lowpart_bonded_mul_p (dep_type, insn))
+    return cost - 1;
 
   /* Only do adjustments for the generic out-of-order and spacemit_x60
      scheduling model.  */
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-bonded-mul-rtl.c 
b/gcc/testsuite/gcc.target/riscv/fusion-bonded-mul-rtl.c
new file mode 100644
index 00000000000..27de0cb60f1
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-bonded-mul-rtl.c
@@ -0,0 +1,165 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target rv64 } */
+/* { dg-skip-if "" { *-*-* } { "-g" "-flto" "-O0" "-O1" "-Oz" "-Os" } } */
+/* { dg-options "-mtune=arc-v-rpx-100-series -march=rv64im_zbs -mabi=lp64 
-fdump-rtl-sched2" } */
+
+__int128 __RTL (startwith ("sched2"))
+foo (void)
+{
+(function "foo"
+  (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 a4)
+                    (truncate:DI
+                      (lshiftrt:TI
+                        (mult:TI (sign_extend:TI (reg:DI a0))
+                                 (sign_extend:TI (reg:DI a1)))
+                        (const_int 64)))))
+      (cinsn 4 (set (reg:DI a5)
+                    (mult:DI (reg:DI a0)
+                             (reg:DI a1))))
+      (cinsn 5 (set (reg:DI a0)
+                    (plus:DI (reg:DI a5)
+                             (const_int 1))))
+      (cinsn 6 (set (reg:DI t0)
+                    (ltu:DI (reg:DI a0)
+                            (reg:DI a5))))
+      (cinsn 7 (set (reg:DI a1)
+                    (plus:DI (reg:DI t0)
+                             (reg:DI a4))))
+      (cinsn 8 (use (reg/i:TI a0)))
+      (cjump_insn 9 (simple_return))
+      (edge-to exit)
+    ) ;; block 2
+    (cbarrier 10)
+  ) ;; insn-chain
+  (crtl
+    (return_rtx (reg/i:TI a0))
+  ) ;; crtl
+) ;; function "foo"
+}
+
+__int128 __RTL (startwith ("sched2"))
+bar (void)
+{
+(function "bar"
+  (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 a4)
+                    (truncate:DI
+                      (lshiftrt:TI
+                        (mult:TI (zero_extend:TI (reg:DI a1))
+                                 (sign_extend:TI (reg:DI a0)))
+                        (const_int 64)))))
+      (cinsn 4 (set (reg:DI a5)
+                    (mult:DI (reg:DI a0)
+                             (reg:DI a1))))
+      (cinsn 5 (set (reg:DI a0)
+                    (plus:DI (reg:DI a5)
+                             (const_int 1))))
+      (cinsn 6 (set (reg:DI t0)
+                    (ltu:DI (reg:DI a0)
+                            (reg:DI a5))))
+      (cinsn 7 (set (reg:DI a1)
+                    (plus:DI (reg:DI t0)
+                             (reg:DI a4))))
+      (cinsn 8 (use (reg/i:TI a0)))
+      (cjump_insn 9 (simple_return))
+      (edge-to exit)
+    ) ;; block 2
+    (cbarrier 10)
+  ) ;; insn-chain
+  (crtl
+    (return_rtx (reg/i:TI a0))
+  ) ;; crtl
+) ;; function "bar"
+}
+
+__int128 __RTL (startwith ("sched2"))
+baz (void)
+{
+(function "baz"
+  (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 a4)
+                    (truncate:DI
+                      (lshiftrt:TI
+                        (mult:TI (zero_extend:TI (reg:DI a0))
+                                 (zero_extend:TI (reg:DI a1)))
+                        (const_int 64)))))
+      (cinsn 4 (set (reg:DI a5)
+                    (mult:DI (reg:DI a0)
+                             (reg:DI a1))))
+      (cinsn 5 (set (reg:DI a0)
+                    (plus:DI (reg:DI a5)
+                             (const_int 1))))
+      (cinsn 6 (set (reg:DI t0)
+                    (ltu:DI (reg:DI a0)
+                            (reg:DI a5))))
+      (cinsn 7 (set (reg:DI a1)
+                    (plus:DI (reg:DI t0)
+                             (reg:DI a4))))
+      (cinsn 8 (use (reg/i:TI a0)))
+      (cjump_insn 9 (simple_return))
+      (edge-to exit)
+    ) ;; block 2
+    (cbarrier 10)
+  ) ;; insn-chain
+  (crtl
+    (return_rtx (reg/i:TI a0))
+  ) ;; crtl
+) ;; function "baz"
+}
+
+__int128 __RTL (startwith ("sched2"))
+quux (void)
+{
+(function "quux"
+  (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 a4)
+                    (truncate:DI
+                      (lshiftrt:TI
+                        (mult:TI (zero_extend:TI (reg:DI a0))
+                                 (sign_extend:TI (reg:DI a1)))
+                        (const_int 64)))))
+      (cinsn 4 (set (reg:DI a5)
+                    (mult:DI (reg:DI a1)
+                             (reg:DI a0))))
+      (cinsn 5 (set (reg:DI a0)
+                    (plus:DI (reg:DI a5)
+                             (const_int 1))))
+      (cinsn 6 (set (reg:DI t0)
+                    (ltu:DI (reg:DI a0)
+                            (reg:DI a5))))
+      (cinsn 7 (set (reg:DI a1)
+                    (plus:DI (reg:DI t0)
+                             (reg:DI a4))))
+      (cinsn 8 (use (reg/i:TI a0)))
+      (cjump_insn 9 (simple_return))
+      (edge-to exit)
+    ) ;; block 2
+    (cbarrier 10)
+  ) ;; insn-chain
+  (crtl
+    (return_rtx (reg/i:TI a0))
+  ) ;; crtl
+) ;; function "quux"
+}
+
+/* { dg-final { scan-rtl-dump "(?n)0-->.*a4=trn\\(sxn\\(a0\\)\\*sxn\\(a1\\) 
0>>0x40\\).*\n.*4-->.*a5=a0\\*a1.*\n.*9-->.*a0=a5\\+0x1.*" "sched2" } } */
+/* { dg-final { scan-rtl-dump "(?n)0-->.*a4=trn\\(zxn\\(a1\\)\\*sxn\\(a0\\) 
0>>0x40\\).*\n.*4-->.*a5=a0\\*a1.*\n.*9-->.*a0=a5\\+0x1.*" "sched2" } } */
+/* { dg-final { scan-rtl-dump "(?n)0-->.*a4=trn\\(zxn\\(a0\\)\\*zxn\\(a1\\) 
0>>0x40\\).*\n.*4-->.*a5=a0\\*a1.*\n.*9-->.*a0=a5\\+0x1.*" "sched2" } } */
+/* { dg-final { scan-rtl-dump "(?n)0-->.*a4=trn\\(zxn\\(a0\\)\\*sxn\\(a1\\) 
0>>0x40\\).*\n.*4-->.*a5=a1\\*a0.*\n.*9-->.*a0=a5\\+0x1.*" "sched2" } } */
-- 
2.47.3

Reply via email to