[PATCH v2 1/4] RISC-V: Implement riscv_macro_fusion_pair_p for ARC-V RHX-100 series

Luis Silva <[email protected]> Wed, 5 Aug 2026 11:02:09 +0100
Newsgroups gmane.comp.gcc.patches
Message-ID <LO6P265MB6224C605086BBA0608FAAB29B4D32@LO6P265MB6224.GBRP265.PROD.OUTLOOK.COM>
From: Luis Silva <[email protected]>

Add the RHX-100 macro-op fusion cases to the table in riscv-fusion.cc
and enable them from the tune's fusible_ops field.  Also split BFEXT into
SRLI / SRAI.

Pairs that compete with adjacent load/store (ls-update, lui-st,
li-store) bail out when the next insn is a better adjacent memop
candidate.  Those, plus adjacent load/store themselves, wait until after
reload.

Fused_MAC only matches when the product is rs1 of the add:
  mul rd,...; add rd,rd,rs

I1->I2 means fusion only in that order; I1<->I2 means the two
instructions are interchangeable with regards to order.

RHX-100 enables:

  RISCV_FUSE_MULT_ADD       (riscv_fuse_mult_add, I1->I2)
      I1: mul rd,...; I2: add rd,rd,rs3 reading the mul result in rd
      (on RV64, mulw+addw with the result sign-extended to DImode).
  RISCV_FUSE_LI_BRANCH      (riscv_fuse_li_branch, I1->I2)
      I1: li rd; I2: conditional branch that uses rd.
  RISCV_FUSE_LI_STORE       (riscv_fuse_li_store, I1->I2)
      I1: li rt; I2: store of rt.
  RISCV_FUSE_LUI_ST         (riscv_fuse_lui_st, I1<->I2)
      I1/I2: lui and store.
  RISCV_FUSE_LUI_LD_REV     (riscv_fuse_lui_ld_reversible, I1<->I2)
      I1/I2: independent load and lui with distinct destinations.
  RISCV_FUSE_LS_UPDATE      (riscv_fuse_ls_update, I1<->I2)
      I1/I2: load or store with an addi that updates the access base.
  RISCV_FUSE_BFEXT_SRLI     (riscv_fuse_bfext_srli, I1->I2)
      I1: slli rd,rs; I2: srli rd,rd (bitfield extract).
  RISCV_FUSE_ADJACENT_LOAD  (riscv_fuse_adjacent_load, I1<->I2)
      I1/I2: two loads, same base, offsets adjacent by GET_MODE_SIZE.
  RISCV_FUSE_ADJACENT_STORE (riscv_fuse_adjacent_store, I1<->I2)
      I1/I2: two stores, same base, offsets adjacent by GET_MODE_SIZE.

Also rename RISCV_FUSE_BFEXT to RISCV_FUSE_BFEXT_SRLI and add
RISCV_FUSE_BFEXT_SRAI for other tunes.

gcc/ChangeLog:

	* config/riscv/riscv-fusion.cc (LUI_NONZERO_OPERAND): New macro.
	(riscv_fusion_enabled_p): Make non-static.
	(riscv_adjacent_memops_p): New function.
	(riscv_defer_for_adjacent_memop_p): New function.
	(riscv_ls_update_pair_p): New function.
	(riscv_fuse_lui_ld_pair_p): New function.
	(riscv_fuse_lui_ld): Use riscv_fuse_lui_ld_pair_p.
	(riscv_fuse_bfext_shift_p): New function.
	(riscv_fuse_bfext_srli): New function.
	(riscv_fuse_bfext_srai): New function.
	(riscv_fuse_mult_add): New function.
	(riscv_fuse_li_branch): New function.
	(riscv_fuse_adjacent_load): New function.
	(riscv_fuse_adjacent_store): New function.
	(riscv_fuse_ls_update): New function.
	(riscv_lui_st_pair_p): New function.
	(riscv_fuse_lui_st): New function.
	(riscv_fuse_li_store): New function.
	(riscv_fuse_lui_ld_reversible): New function.
	* config/riscv/riscv-protos.h (enum riscv_fusion_pairs): Rename
	RISCV_FUSE_BFEXT to RISCV_FUSE_BFEXT_SRLI; add the RHX-100 fusion
	bits and RISCV_FUSE_BFEXT_SRAI.
	(riscv_fusion_enabled_p): Declare.
	* config/riscv/riscv.cc (arcv_rhx100_tune_info): Set fusible_ops.
	* config/riscv/riscv.h (TARGET_ARCV_RHX100): New.

gcc/testsuite/ChangeLog:

	* gcc.target/riscv/fusion-adjacent-load.c: New test.
	* gcc.target/riscv/fusion-adjacent-store.c: New test.
	* gcc.target/riscv/fusion-bfext-srai.c: New test.
	* gcc.target/riscv/fusion-bfext.c: New test.
	* gcc.target/riscv/fusion-li-branch.c: New test.
	* gcc.target/riscv/fusion-li-store.c: New test.
	* gcc.target/riscv/fusion-ls-update-2.c: New test.
	* gcc.target/riscv/fusion-ls-update-3.c: New test.
	* gcc.target/riscv/fusion-ls-update-4.c: New test.
	* gcc.target/riscv/fusion-ls-update-5.c: New test.
	* gcc.target/riscv/fusion-ls-update.c: New test.
	* gcc.target/riscv/fusion-lui-ld.c: New test.
	* gcc.target/riscv/fusion-lui-st.c: New test.
	* gcc.target/riscv/fusion-mult-add.c: New test.

Co-authored-by: Artemiy Volkov <[email protected]>
Co-authored-by: Michiel Derhaeg <[email protected]>
Co-authored-by: Loeka Rogge <[email protected]>
Signed-off-by: Luis Silva <[email protected]>
---
 gcc/config/riscv/riscv-fusion.cc              | 582 +++++++++++++++++-
 gcc/config/riscv/riscv-protos.h               |  12 +-
 gcc/config/riscv/riscv.cc                     |   6 +-
 gcc/config/riscv/riscv.h                      |   4 +
 .../gcc.target/riscv/fusion-adjacent-load.c   |  12 +
 .../gcc.target/riscv/fusion-adjacent-store.c  |  13 +
 .../gcc.target/riscv/fusion-bfext-srai.c      |  18 +
 gcc/testsuite/gcc.target/riscv/fusion-bfext.c |  15 +
 .../gcc.target/riscv/fusion-li-branch.c       |  14 +
 .../gcc.target/riscv/fusion-li-store.c        |  12 +
 .../gcc.target/riscv/fusion-ls-update-2.c     |  17 +
 .../gcc.target/riscv/fusion-ls-update-3.c     |  81 +++
 .../gcc.target/riscv/fusion-ls-update-4.c     |  53 ++
 .../gcc.target/riscv/fusion-ls-update-5.c     |  13 +
 .../gcc.target/riscv/fusion-ls-update.c       |  16 +
 .../gcc.target/riscv/fusion-lui-ld.c          |  25 +
 .../gcc.target/riscv/fusion-lui-st.c          |  19 +
 .../gcc.target/riscv/fusion-mult-add.c        |  13 +
 18 files changed, 905 insertions(+), 20 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-adjacent-load.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-adjacent-store.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-bfext-srai.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-bfext.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-li-branch.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-li-store.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-ls-update-2.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-ls-update-3.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-ls-update-4.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-ls-update-5.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-ls-update.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-lui-ld.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-lui-st.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-mult-add.c

diff --git a/gcc/config/riscv/riscv-fusion.cc b/gcc/config/riscv/riscv-fusion.cc
index 4846dc0dfa2..98df3521029 100644
--- a/gcc/config/riscv/riscv-fusion.cc
+++ b/gcc/config/riscv/riscv-fusion.cc
@@ -32,8 +32,17 @@ along with GCC; see the file COPYING3.  If not see
 #include "memmodel.h"
 #include "emit-rtl.h"
 #include "tm_p.h"
+#include "regset.h"
+#include "basic-block.h"
+#include "insn-attr.h"
+#include "sched-int.h"
 #include "riscv-protos.h"
 
+/* A LUI_OPERAND accepts (const_int 0), but we won't emit that as LUI.
+   Reject that case explicitly.  */
+
+#define LUI_NONZERO_OPERAND(value) ((value) != 0 && LUI_OPERAND (value))
+
 /* Implement TARGET_SCHED_MACRO_FUSION_P.  Return true if target supports
    instruction fusion of some sort.  */
 
@@ -45,7 +54,7 @@ riscv_macro_fusion_p (void)
 
 /* Return true iff the instruction fusion described by OP is enabled.  */
 
-static bool
+bool
 riscv_fusion_enabled_p (enum riscv_fusion_pairs op)
 {
   return riscv_get_fusible_ops () & op;
@@ -147,6 +156,247 @@ riscv_set_is_shNadduw_p (rtx set)
 	  && REG_P (SET_DEST (set)));
 }
 
+/* Return TRUE if two memory operands can be fused based on their addresses.
+   Checks if MEM0 and MEM1 have the same base register with adjacent offsets,
+   making them suitable for fusion (e.g., adjacent load/store pairs).  */
+
+static bool
+riscv_adjacent_memops_p (rtx mem0, rtx mem1, bool is_load)
+{
+  rtx base0, base1, tmp;
+  HOST_WIDE_INT off0 = 0, off1 = 0;
+
+  if (GET_CODE (mem0) == SIGN_EXTEND || GET_CODE (mem0) == ZERO_EXTEND)
+    mem0 = XEXP (mem0, 0);
+
+  if (GET_CODE (mem1) == SIGN_EXTEND || GET_CODE (mem1) == ZERO_EXTEND)
+    mem1 = XEXP (mem1, 0);
+
+  if (!MEM_P (mem0) || !MEM_P (mem1))
+    return false;
+
+  if (GET_MODE (mem0) != GET_MODE (mem1))
+    return false;
+
+  /* Check if the mode is allowed for ARC-V fusion restrictions.
+     Loads: allow SI, HI, and QI modes.
+     Stores: allow only SI mode.  */
+  if (TARGET_ARCV_RHX100)
+    {
+      machine_mode mode = GET_MODE (mem0);
+      bool mode_allowed = ((is_load
+			    && (mode == SImode
+				|| mode == HImode
+				|| mode == QImode))
+			   || (!is_load && mode == SImode));
+
+      if (!mode_allowed)
+	return false;
+    }
+
+  rtx mem_addr0 = XEXP (mem0, 0);
+  rtx mem_addr1 = XEXP (mem1, 0);
+
+  if (GET_CODE (mem_addr0) == PLUS)
+    {
+      base0 = XEXP (mem_addr0, 0);
+      tmp = XEXP (mem_addr0, 1);
+      if (!REG_P (base0) || !CONST_INT_P (tmp))
+       return false;
+      off0 = INTVAL (tmp);
+    }
+  else if (REG_P (mem_addr0))
+    base0 = mem_addr0;
+  else
+    return false;
+
+  if (GET_CODE (mem_addr1) == PLUS)
+    {
+      base1 = XEXP (mem_addr1, 0);
+      tmp = XEXP (mem_addr1, 1);
+      if (!REG_P (base1) || !CONST_INT_P (tmp))
+       return false;
+      off1 = INTVAL (tmp);
+    }
+  else if (REG_P (mem_addr1))
+    base1 = mem_addr1;
+  else
+    return false;
+
+  /* Check if we have the same base.  */
+  if (REGNO (base0) != REGNO (base1))
+    return false;
+
+  /* Fuse adjacent aligned addresses.  */
+  if ((off0 % GET_MODE_SIZE (GET_MODE (mem0)).to_constant () == 0)
+      && (abs (off1 - off0) == GET_MODE_SIZE (GET_MODE (mem0)).to_constant ()))
+    return true;
+
+  return false;
+}
+
+/* Return true if CURR should not be fused with PREV because CURR and the
+   next fusible insn form a better adjacent load/store pair.  */
+
+static bool
+riscv_defer_for_adjacent_memop_p (rtx_insn *curr)
+{
+  rtx_insn *next = next_nonnote_nondebug_insn_bb (curr);
+  if (!next)
+    return false;
+
+  rtx curr_set = single_set (curr);
+  rtx next_set = single_set (next);
+  if (!curr_set || !next_set)
+    return false;
+
+  if (riscv_fusion_enabled_p (RISCV_FUSE_ADJACENT_LOAD)
+      && get_attr_type (curr) == TYPE_LOAD
+      && get_attr_type (next) == TYPE_LOAD
+      && riscv_adjacent_memops_p (SET_SRC (curr_set), SET_SRC (next_set), true))
+    return true;
+
+  return riscv_fusion_enabled_p (RISCV_FUSE_ADJACENT_STORE)
+	 && get_attr_type (curr) == TYPE_STORE
+	 && get_attr_type (next) == TYPE_STORE
+	 && riscv_adjacent_memops_p (SET_DEST (curr_set),
+				     SET_DEST (next_set), false);
+}
+
+/* Return true if PREV and CURR constitute an ordered load/store + op/opimm
+   pair, for the purposes of macro-op fusion.
+   This is a more general form that combines load+arith and store+arith.  */
+
+static bool
+riscv_ls_update_pair_p (rtx_insn *prev, rtx_insn *curr)
+{
+  rtx prev_set = single_set (prev);
+  rtx curr_set = single_set (curr);
+  if (!prev_set || !curr_set)
+    return false;
+
+  enum attr_type p_type = get_attr_type (prev);
+  if (!(p_type == TYPE_LOAD || p_type == TYPE_STORE))
+    return false;
+
+  enum attr_type c_type = get_attr_type (curr);
+  if (!(c_type == TYPE_ARITH
+	|| c_type == TYPE_LOGICAL
+	|| c_type == TYPE_SHIFT
+	|| c_type == TYPE_SLT
+	|| c_type == TYPE_BITMANIP
+	|| c_type == TYPE_MIN
+	|| c_type == TYPE_MAX
+	|| c_type == TYPE_MINU
+	|| c_type == TYPE_MAXU
+	|| c_type == TYPE_CLZ
+	|| c_type == TYPE_CTZ
+	|| c_type == TYPE_MOVE))
+    return false;
+
+  rtx c_src = SET_SRC (curr_set);
+  rtx c_dest = SET_DEST (curr_set);
+
+  if (CONSTANT_P (c_src))
+    return false;
+
+  int c_rs1 = INVALID_REGNUM;
+  int c_rs2 = INVALID_REGNUM;
+
+  if (REG_P (c_src))
+    c_rs1 = REGNO (c_src);
+  else
+    {
+      rtx op = c_src;
+      if (GET_CODE (op) == NOT && BINARY_P (XEXP (op, 0)))
+	op = XEXP (op, 0);
+
+      const char *fmt = GET_RTX_FORMAT (GET_CODE (op));
+      for (int i = 0; i < GET_RTX_LENGTH (GET_CODE (op)); i++)
+	{
+	  if (fmt[i] != 'e')
+	    continue;
+
+	  rtx x = XEXP (op, i);
+	  if (GET_CODE (x) == NOT)
+	    x = XEXP (x, 0);
+	  if (SUBREG_P (x))
+	    x = SUBREG_REG (x);
+	  if (!REG_P (x))
+	    continue;
+
+	  if (c_rs1 == (int) INVALID_REGNUM)
+	    c_rs1 = REGNO (x);
+	  else
+	    {
+	      c_rs2 = REGNO (x);
+	      break;
+	    }
+	}
+
+      if (c_rs1 == (int) INVALID_REGNUM)
+	return false;
+    }
+
+  switch (p_type)
+    {
+    case TYPE_LOAD:
+      {
+	if (!REG_P (c_dest))
+	  return false;
+	int c_rd = REGNO (c_dest);
+
+	rtx p_mem = SET_SRC (prev_set);
+
+	if (!MEM_P (p_mem))
+	  return false;
+
+	rtx p_dest = SET_DEST (prev_set);
+	rtx base, offset;
+	if (!extract_base_offset_in_addr (p_mem, &base, &offset)
+	    || !REG_P (p_dest))
+	  return false;
+
+	int p_rs = REGNO (base);
+	int p_rd = REGNO (p_dest);
+
+	return (p_rs == c_rs1
+		&& p_rs != p_rd
+		&& p_rd != c_rd
+		&& !reg_overlap_mentioned_p (p_dest, c_src));
+      }
+
+    case TYPE_STORE:
+      {
+	rtx p_mem = SET_DEST (prev_set);
+	if (!MEM_P (p_mem))
+	  return false;
+
+	rtx base, offset;
+	if (!extract_base_offset_in_addr (p_mem, &base, &offset))
+	  return false;
+
+	int p_rs = REGNO (base);
+
+	if (p_rs != c_rs1)
+	  return false;
+
+	if (c_rs2 == (int) INVALID_REGNUM)
+	  return true;
+
+	rtx data = SET_SRC (prev_set);
+	if (!REG_P (data))
+	  return false;
+
+	int p_rs2 = REGNO (data);
+	return p_rs2 == c_rs2;
+      }
+
+    default:
+      return false;
+    }
+}
+
 /* Check for RISCV_FUSE_ZEXTW and RISCV_FUSE_ZEXTWS fusion.
    prev (slli) == (set (reg:DI rD) (ashift:DI (reg:DI rS) (const_int 32)))
    curr (srli) == (set (reg:DI rD) (lshiftrt:DI (reg:DI rD) (const_int N)))
@@ -406,26 +656,38 @@ riscv_fuse_auipc_addi (rtx_insn *prev, rtx_insn *curr)
   return false;
 }
 
-/* Check for RISCV_FUSE_LUI_LD fusion.
-   prev (lui) == (set (reg:DI rD) (const_int UPPER_IMM_20))
-   curr (ld)  == (set (reg:DI rD) (mem:DI (plus:DI (reg:DI rD) ...)))  */
+/* Common lui+ld fusion matcher.
+   SAME_DEST_REGS: if true, require same dest (via same_dest_p); if false,
+   require different dests via REGNO (same_dest_p is always true before reload).
+   MATCH_INPUT_REG: if true, load must use the lui dest as base; if false,
+   any load after a lui is enough (Fused_LD_LUI).  */
 
 static bool
-riscv_fuse_lui_ld (rtx_insn *prev, rtx_insn *curr)
+riscv_fuse_lui_ld_pair_p (rtx_insn *prev, rtx_insn *curr,
+			  bool same_dest_regs, bool match_input_reg)
 {
   rtx prev_set = single_set (prev);
   rtx curr_set = single_set (curr);
   if (!prev_set || !curr_set || any_condjump_p (curr))
     return false;
 
-  if (!riscv_fusion_same_dest_p (prev_set, curr_set))
+  if (same_dest_regs)
+    {
+      if (!riscv_fusion_same_dest_p (prev_set, curr_set))
+	return false;
+    }
+  else if (!REG_P (SET_DEST (prev_set)) || !REG_P (SET_DEST (curr_set))
+	   || REGNO (SET_DEST (prev_set)) == REGNO (SET_DEST (curr_set)))
     return false;
 
-  /* A LUI_OPERAND accepts (const_int 0), but we won't emit that as LUI.
-     Reject that case explicitly.  */
+  if (!match_input_reg)
+    return ((GET_CODE (SET_SRC (prev_set)) == HIGH
+	     || (CONST_INT_P (SET_SRC (prev_set))
+		 && LUI_NONZERO_OPERAND (INTVAL (SET_SRC (prev_set)))))
+	    && get_attr_type (curr) == TYPE_LOAD);
+
   if (CONST_INT_P (SET_SRC (prev_set))
-      && SET_SRC (prev_set) != CONST0_RTX (GET_MODE (SET_DEST (prev_set)))
-      && LUI_OPERAND (INTVAL (SET_SRC (prev_set)))
+      && LUI_NONZERO_OPERAND (INTVAL (SET_SRC (prev_set)))
       && MEM_P (SET_SRC (curr_set))
       && SCALAR_INT_MODE_P (GET_MODE (SET_DEST (curr_set)))
       && GET_CODE (XEXP (SET_SRC (curr_set), 0)) == PLUS
@@ -457,6 +719,18 @@ riscv_fuse_lui_ld (rtx_insn *prev, rtx_insn *curr)
   return false;
 }
 
+/* Check for RISCV_FUSE_LUI_LD fusion.
+   prev (lui) == (set (reg:DI rD) (const_int UPPER_IMM_20))
+   curr (ld)  == (set (reg:DI rD) (mem:DI (plus:DI (reg:DI rD) ...)))  */
+
+static bool
+riscv_fuse_lui_ld (rtx_insn *prev, rtx_insn *curr)
+{
+  return riscv_fuse_lui_ld_pair_p (prev, curr,
+				   /* same_dest_regs */ true,
+				   /* match_input_reg */ true);
+}
+
 /* Check for RISCV_FUSE_AUIPC_LD fusion.
    prev (auipc) == (set (reg:DI rD) (unspec:DI UNSPEC_AUIPC))
    curr (ld)    == (set (reg:DI rD) (mem:DI (plus:DI (reg:DI rD) ...)))  */
@@ -597,12 +871,13 @@ riscv_fuse_aligned_std (rtx_insn *prev, rtx_insn *curr)
   return false;
 }
 
-/* Check for RISCV_FUSE_BFEXT fusion.
-   prev (slli) == (set (reg:DI rD) (ashift:DI (reg:DI rS) (const_int)))
-   curr (srli) == (set (reg:DI rD) (lshiftrt:DI (reg:DI rD) (const_int)))  */
+/* Check for RISCV_FUSE_BFEXT_SRLI / RISCV_FUSE_BFEXT_SRAI fusion.
+   prev (slli) == (set (reg rD) (ashift (reg rS) (const_int)))
+   curr (srli/srai) == (set (reg rD)
+			    (lshiftrt/ashiftrt (reg rD) (const_int)))  */
 
 static bool
-riscv_fuse_bfext (rtx_insn *prev, rtx_insn *curr)
+riscv_fuse_bfext_shift_p (rtx_insn *prev, rtx_insn *curr, enum rtx_code shift)
 {
   rtx prev_set = single_set (prev);
   rtx curr_set = single_set (curr);
@@ -613,10 +888,10 @@ riscv_fuse_bfext (rtx_insn *prev, rtx_insn *curr)
     return false;
 
   if (GET_CODE (SET_SRC (prev_set)) == ASHIFT
-      && (GET_CODE (SET_SRC (curr_set)) == LSHIFTRT
-	  || GET_CODE (SET_SRC (curr_set)) == ASHIFTRT)
+      && GET_CODE (SET_SRC (curr_set)) == shift
       && REG_P (SET_DEST (prev_set))
       && REG_P (SET_DEST (curr_set))
+      && REG_P (XEXP (SET_SRC (curr_set), 0))
       && REGNO (XEXP (SET_SRC (curr_set), 0)) == REGNO (SET_DEST (prev_set))
       && CONST_INT_P (XEXP (SET_SRC (prev_set), 1))
       && CONST_INT_P (XEXP (SET_SRC (curr_set), 1)))
@@ -625,6 +900,26 @@ riscv_fuse_bfext (rtx_insn *prev, rtx_insn *curr)
   return false;
 }
 
+/* Check for RISCV_FUSE_BFEXT_SRLI fusion.
+   prev (slli) == (set (reg rD) (ashift (reg rS) (const_int)))
+   curr (srli) == (set (reg rD) (lshiftrt (reg rD) (const_int)))  */
+
+static bool
+riscv_fuse_bfext_srli (rtx_insn *prev, rtx_insn *curr)
+{
+  return riscv_fuse_bfext_shift_p (prev, curr, LSHIFTRT);
+}
+
+/* Check for RISCV_FUSE_BFEXT_SRAI fusion.
+   prev (slli) == (set (reg rD) (ashift (reg rS) (const_int)))
+   curr (srai) == (set (reg rD) (ashiftrt (reg rD) (const_int)))  */
+
+static bool
+riscv_fuse_bfext_srai (rtx_insn *prev, rtx_insn *curr)
+{
+  return riscv_fuse_bfext_shift_p (prev, curr, ASHIFTRT);
+}
+
 /* Check for RISCV_FUSE_B_ALUI fusion.
    Covers orc.b+not, ctz+andi, sub+smax, neg+smax patterns.  */
 
@@ -691,6 +986,239 @@ riscv_fuse_b_alui (rtx_insn *prev, rtx_insn *curr)
   return false;
 }
 
+/* Check for RISCV_FUSE_MULT_ADD fusion.
+   On RV32:
+   prev (mul) == (set (reg rD) (mult:SI (reg:SI rS1) (reg:SI rS2)))
+   curr (add) == (set (reg rD) (plus:SI (reg:SI rD) (reg:SI rS3)))
+
+   Matches mulw+addw for RV64, not mul+add:
+   prev (mul) == (set (reg:DI rD) (sign_extend:DI (mult:SI ...)))
+   curr (add) == (set (reg:DI rD) (sign_extend:DI (plus:SI ...)))  */
+
+static bool
+riscv_fuse_mult_add (rtx_insn *prev, rtx_insn *curr)
+{
+  rtx prev_set = single_set (prev);
+  rtx curr_set = single_set (curr);
+  if (!prev_set || !curr_set)
+    return false;
+
+  if (!riscv_fusion_same_dest_p (prev_set, curr_set))
+    return false;
+
+  rtx prev_src = SET_SRC (prev_set);
+  rtx curr_src = SET_SRC (curr_set);
+  if (GET_CODE (prev_src) == SIGN_EXTEND
+      && GET_MODE (prev_src) == DImode)
+    prev_src = XEXP (prev_src, 0);
+  if (GET_CODE (curr_src) == SIGN_EXTEND
+      && GET_MODE (curr_src) == DImode)
+    curr_src = XEXP (curr_src, 0);
+
+  if (GET_CODE (prev_src) != MULT || GET_MODE (prev_src) != SImode
+      || GET_CODE (curr_src) != PLUS || GET_MODE (curr_src) != SImode)
+    return false;
+
+  rtx mult_dest = SET_DEST (prev_set);
+  if (!REG_P (mult_dest))
+    return false;
+  unsigned int mult_dest_regno = REGNO (mult_dest);
+
+  if (REG_P (XEXP (curr_src, 0))
+      && REGNO (XEXP (curr_src, 0)) == mult_dest_regno)
+    return true;
+
+  return false;
+}
+
+/* Check for RISCV_FUSE_LI_BRANCH fusion.
+   prev (li)     == (set (reg:DI rD) (const_int N))
+   curr (branch) == conditional branch using rD  */
+
+static bool
+riscv_fuse_li_branch (rtx_insn *prev, rtx_insn *curr)
+{
+  rtx prev_set = single_set (prev);
+  rtx curr_set = single_set (curr);
+  if (!prev_set || !curr_set)
+    return false;
+
+  if (get_attr_type (prev) != TYPE_MOVE
+      || get_attr_move_type (prev) != MOVE_TYPE_CONST)
+    return false;
+
+  if (!any_condjump_p (curr))
+    return false;
+
+  rtx prev_dest = SET_DEST (prev_set);
+  if (!REG_P (prev_dest))
+    return false;
+
+  /* Check if the loaded register is used in the branch condition.  */
+  rtx cond = XEXP (SET_SRC (curr_set), 0);
+  unsigned int prev_dest_regno = REGNO (prev_dest);
+  if ((REG_P (XEXP (cond, 0)) && REGNO (XEXP (cond, 0)) == prev_dest_regno)
+      || (REG_P (XEXP (cond, 1)) && REGNO (XEXP (cond, 1)) == prev_dest_regno))
+    return true;
+
+  return false;
+}
+
+/* Check for RISCV_FUSE_ADJACENT_LOAD fusion.
+   prev (ld) == (set (reg:SI rD1)
+		     (mem:SI (plus:DI (reg:DI rB) (const_int OFF1))))
+   curr (ld) == (set (reg:SI rD2)
+		     (mem:SI (plus:DI (reg:DI rB) (const_int OFF2))))
+   where OFF2 == OFF1 + 4 or OFF2 == OFF1 - 4  */
+
+static bool
+riscv_fuse_adjacent_load (rtx_insn *prev, rtx_insn *curr)
+{
+  /* Do not fuse loads/stores before sched2.  */
+  if (!reload_completed || sched_fusion)
+    return false;
+
+  rtx prev_set = single_set (prev);
+  rtx curr_set = single_set (curr);
+  if (!prev_set || !curr_set || any_condjump_p (curr))
+    return false;
+
+  if (get_attr_type (prev) != TYPE_LOAD
+      || get_attr_type (curr) != TYPE_LOAD)
+    return false;
+
+  return riscv_adjacent_memops_p (SET_SRC (prev_set), SET_SRC (curr_set), true);
+}
+
+/* Check for RISCV_FUSE_ADJACENT_STORE fusion.
+   prev (st) == (set (mem:SI (plus:DI (reg:DI rB) (const_int OFF1)))
+		     (reg:SI rS1))
+   curr (st) == (set (mem:SI (plus:DI (reg:DI rB) (const_int OFF2)))
+		     (reg:SI rS2))
+   where OFF2 == OFF1 + MODE_SIZE or OFF2 == OFF1 - MODE_SIZE  */
+
+static bool
+riscv_fuse_adjacent_store (rtx_insn *prev, rtx_insn *curr)
+{
+  /* Do not fuse loads/stores before sched2.  */
+  if (!reload_completed || sched_fusion)
+    return false;
+
+  rtx prev_set = single_set (prev);
+  rtx curr_set = single_set (curr);
+  if (!prev_set || !curr_set || any_condjump_p (curr))
+    return false;
+
+  if (get_attr_type (prev) != TYPE_STORE
+      || get_attr_type (curr) != TYPE_STORE)
+    return false;
+
+  return riscv_adjacent_memops_p (SET_DEST (prev_set),
+				  SET_DEST (curr_set), false);
+}
+
+/* Check for RISCV_FUSE_LS_UPDATE fusion (load/store with address update).
+   prev (ld)   == (set (reg:DI rD) (mem:DI (reg:DI rA)))
+   curr (addi) == (set (reg:DI rA) (plus:DI (reg:DI rA) (const_int)))
+   OR
+   prev (addi) == (set (reg:DI rA) (plus:DI (reg:DI rA) (const_int)))
+   curr (st)   == (set (mem:DI (reg:DI rA)) (reg:DI rS))  */
+
+static bool
+riscv_fuse_ls_update (rtx_insn *prev, rtx_insn *curr)
+{
+  if (!reload_completed || sched_fusion)
+    return false;
+
+  rtx prev_set = single_set (prev);
+  rtx curr_set = single_set (curr);
+  if (!prev_set || !curr_set || any_condjump_p (curr))
+    return false;
+
+  if (riscv_defer_for_adjacent_memop_p (curr))
+    return false;
+
+  return (riscv_ls_update_pair_p (prev, curr)
+	  || riscv_ls_update_pair_p (curr, prev));
+}
+
+static bool
+riscv_lui_st_pair_p (rtx_insn *lui, rtx_insn *store, rtx lui_set)
+{
+  return (get_attr_type (store) == TYPE_STORE
+	  && REG_P (SET_DEST (lui_set))
+	  && ((get_attr_type (lui) == TYPE_MOVE
+	       && GET_CODE (SET_SRC (lui_set)) == HIGH)
+	      || (CONST_INT_P (SET_SRC (lui_set))
+		  && LUI_NONZERO_OPERAND (INTVAL (SET_SRC (lui_set))))));
+}
+
+/* Check for RISCV_FUSE_LUI_ST fusion.
+   prev (lui) == (set (reg:DI rD) (const_int UPPER_IMM_20))
+   curr (st)  == (set (mem:DI (plus:DI (reg:DI rX) (const_int))) (reg:DI rS))
+   OR (reversed)
+   prev (st)  == (set (mem:DI (plus:DI (reg:DI rX) (const_int))) (reg:DI rS))
+   curr (lui) == (set (reg:DI rD) (const_int UPPER_IMM_20))  */
+
+static bool
+riscv_fuse_lui_st (rtx_insn *prev, rtx_insn *curr)
+{
+  if (!reload_completed || sched_fusion)
+    return false;
+
+  rtx prev_set = single_set (prev);
+  rtx curr_set = single_set (curr);
+  if (!prev_set || !curr_set || any_condjump_p (curr))
+    return false;
+
+  if (riscv_defer_for_adjacent_memop_p (curr))
+    return false;
+
+  return (riscv_lui_st_pair_p (prev, curr, prev_set)
+	  || riscv_lui_st_pair_p (curr, prev, curr_set));
+}
+
+/* Check for RISCV_FUSE_LI_STORE fusion.
+   prev (li) == (set (reg:DI rT) (const_int IMM))
+   curr (st) == (set (mem:DI (plus:DI (reg:DI rB) (const_int))) (reg:DI rT))  */
+
+static bool
+riscv_fuse_li_store (rtx_insn *prev, rtx_insn *curr)
+{
+  if (!reload_completed || sched_fusion)
+    return false;
+
+  rtx prev_set = single_set (prev);
+  rtx curr_set = single_set (curr);
+  if (!prev_set || !curr_set || any_condjump_p (curr))
+    return false;
+
+  if (riscv_defer_for_adjacent_memop_p (curr))
+    return false;
+
+  if (get_attr_type (prev) == TYPE_MOVE
+      && get_attr_move_type (prev) == MOVE_TYPE_CONST
+      && get_attr_type (curr) == TYPE_STORE
+      && REG_P (SET_SRC (curr_set)) && REG_P (SET_DEST (prev_set))
+      && REGNO (SET_SRC (curr_set)) == REGNO (SET_DEST (prev_set)))
+    return true;
+
+  return false;
+}
+
+/* Check for RISCV_FUSE_LUI_LD_REV fusion (reversible).  */
+
+static bool
+riscv_fuse_lui_ld_reversible (rtx_insn *prev, rtx_insn *curr)
+{
+  return (riscv_fuse_lui_ld_pair_p (prev, curr,
+				    /* same_dest_regs */ false,
+				    /* match_input_reg */ false)
+	  || riscv_fuse_lui_ld_pair_p (curr, prev,
+				       /* same_dest_regs */ false,
+				       /* match_input_reg */ false));
+}
+
 /* Type for a fusion checker function.  Takes the two candidate insns
    and returns true if they should be fused.  */
 
@@ -737,10 +1265,28 @@ static const struct riscv_fusion_entry riscv_fusion_table[] =
     riscv_fuse_cache_aligned_std, "RISCV_FUSE_CACHE_ALIGNED_STD" },
   { RISCV_FUSE_ALIGNED_STD,
     riscv_fuse_aligned_std, "RISCV_FUSE_ALIGNED_STD" },
-  { RISCV_FUSE_BFEXT,
-    riscv_fuse_bfext, "RISCV_FUSE_BFEXT" },
+  { RISCV_FUSE_BFEXT_SRLI,
+    riscv_fuse_bfext_srli, "RISCV_FUSE_BFEXT_SRLI" },
+  { RISCV_FUSE_BFEXT_SRAI,
+    riscv_fuse_bfext_srai, "RISCV_FUSE_BFEXT_SRAI" },
   { RISCV_FUSE_B_ALUI,
     riscv_fuse_b_alui, "RISCV_FUSE_B_ALUI" },
+  { RISCV_FUSE_MULT_ADD,
+    riscv_fuse_mult_add, "RISCV_FUSE_MULT_ADD" },
+  { RISCV_FUSE_LI_BRANCH,
+    riscv_fuse_li_branch, "RISCV_FUSE_LI_BRANCH" },
+  { RISCV_FUSE_ADJACENT_LOAD,
+    riscv_fuse_adjacent_load, "RISCV_FUSE_ADJACENT_LOAD" },
+  { RISCV_FUSE_ADJACENT_STORE,
+    riscv_fuse_adjacent_store, "RISCV_FUSE_ADJACENT_STORE" },
+  { RISCV_FUSE_LS_UPDATE,
+    riscv_fuse_ls_update, "RISCV_FUSE_LS_UPDATE" },
+  { RISCV_FUSE_LUI_ST,
+    riscv_fuse_lui_st, "RISCV_FUSE_LUI_ST" },
+  { RISCV_FUSE_LI_STORE,
+    riscv_fuse_li_store, "RISCV_FUSE_LI_STORE" },
+  { RISCV_FUSE_LUI_LD_REV,
+    riscv_fuse_lui_ld_reversible, "RISCV_FUSE_LUI_LD_REV" },
 };
 
 /* Implement TARGET_SCHED_MACRO_FUSION_PAIR_P.  Return true if PREV and CURR
diff --git a/gcc/config/riscv/riscv-protos.h b/gcc/config/riscv/riscv-protos.h
index 3843110ab6c..a94c37fa052 100644
--- a/gcc/config/riscv/riscv-protos.h
+++ b/gcc/config/riscv/riscv-protos.h
@@ -864,13 +864,23 @@ enum riscv_fusion_pairs
   RISCV_FUSE_LDPREINCREMENT = (1 << 8),
   RISCV_FUSE_ALIGNED_STD = (1 << 9),
   RISCV_FUSE_CACHE_ALIGNED_STD = (1 << 10),
-  RISCV_FUSE_BFEXT = (1 << 11),
+  RISCV_FUSE_BFEXT_SRLI = (1 << 11),
   RISCV_FUSE_EXPANDED_LD = (1 << 12),
   RISCV_FUSE_B_ALUI = (1 << 13),
+  RISCV_FUSE_MULT_ADD = (1 << 14),
+  RISCV_FUSE_LI_BRANCH = (1 << 15),
+  RISCV_FUSE_ADJACENT_LOAD = (1 << 16),
+  RISCV_FUSE_ADJACENT_STORE = (1 << 17),
+  RISCV_FUSE_LS_UPDATE = (1 << 18),
+  RISCV_FUSE_LUI_ST = (1 << 19),
+  RISCV_FUSE_LI_STORE = (1 << 20),
+  RISCV_FUSE_LUI_LD_REV = (1 << 21),
+  RISCV_FUSE_BFEXT_SRAI = (1 << 22),
 };
 
 extern bool riscv_macro_fusion_p (void);
 extern bool riscv_macro_fusion_pair_p (rtx_insn *, rtx_insn *);
+extern bool riscv_fusion_enabled_p (enum riscv_fusion_pairs);
 extern unsigned 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 e319ccaecad..aabd12aa040 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -980,7 +980,11 @@ static const struct riscv_tune_param arcv_rhx100_tune_info = {
   false,				       /* overlap_op_by_pieces */
   true,					       /* use_zero_stride_load */
   false,				       /* speculative_sched_vsetvl */
-  RISCV_FUSE_NOTHING,			       /* fusible_ops */
+  (RISCV_FUSE_MULT_ADD | RISCV_FUSE_LI_BRANCH
+   | RISCV_FUSE_ADJACENT_LOAD | RISCV_FUSE_ADJACENT_STORE
+   | RISCV_FUSE_LS_UPDATE | RISCV_FUSE_LUI_ST
+   | RISCV_FUSE_LI_STORE | RISCV_FUSE_BFEXT_SRLI
+   | RISCV_FUSE_LUI_LD_REV),  		       /* fusible_ops */
   NULL,					       /* vector cost */
   NULL,					       /* function_align */
   NULL,					       /* jump_align */
diff --git a/gcc/config/riscv/riscv.h b/gcc/config/riscv/riscv.h
index 4ba6ad260e5..b01301659e1 100644
--- a/gcc/config/riscv/riscv.h
+++ b/gcc/config/riscv/riscv.h
@@ -978,6 +978,10 @@ extern enum riscv_cc get_riscv_cc (const rtx use);
   || (riscv_microarchitecture == sifive_p600) \
   || (riscv_microarchitecture == andes_45_series))
 
+/* True if the target is ARC-V RHX100.  */
+#define TARGET_ARCV_RHX100 \
+  (riscv_microarchitecture == arcv_rhx100)
+
 /* True if the target supports misaligned vector loads and stores.  */
 #define TARGET_VECTOR_MISALIGN_SUPPORTED \
    riscv_vector_unaligned_access_p
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-adjacent-load.c b/gcc/testsuite/gcc.target/riscv/fusion-adjacent-load.c
new file mode 100644
index 00000000000..8dc2ec4b1b0
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-adjacent-load.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target rv32 } */
+/* { dg-skip-if "" { *-*-* } { "-g" "-flto" "-O0" "-O1" "-O3" "-Oz" "-Os" } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv32im -mabi=ilp32 -fdump-rtl-sched2" } */
+
+int
+fuse_adjacent_load (int *p)
+{
+  return p[0] + p[1];
+}
+
+/* { dg-final { scan-rtl-dump "RISCV_FUSE_ADJACENT_LOAD" "sched2" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-adjacent-store.c b/gcc/testsuite/gcc.target/riscv/fusion-adjacent-store.c
new file mode 100644
index 00000000000..cf86ab271ae
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-adjacent-store.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target rv32 } */
+/* { dg-skip-if "" { *-*-* } { "-g" "-flto" "-O0" "-O1" "-O3" "-Oz" "-Os" } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv32im -mabi=ilp32 -fdump-rtl-sched2" } */
+
+void
+fuse_adjacent_store (int *p, int x, int y)
+{
+  p[0] = x;
+  p[1] = y;
+}
+
+/* { dg-final { scan-rtl-dump "RISCV_FUSE_ADJACENT_STORE" "sched2" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-bfext-srai.c b/gcc/testsuite/gcc.target/riscv/fusion-bfext-srai.c
new file mode 100644
index 00000000000..df42e965926
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-bfext-srai.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target rv32 } */
+/* { dg-skip-if "" { *-*-* } { "-g" "-flto" "-O0" "-O1" "-O3" "-Oz" "-Os" } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv32im -mabi=ilp32 -fdump-rtl-sched2" } */
+
+/* RHX Fused_XBFU is slli+srli only; slli+srai must not fuse.  */
+
+int
+fusion_shift_bitextract_srai (int x, int y, int z)
+{
+  int t = x << 8;
+  int a = y + z;
+  t = t >> 4;
+  return t + a;
+}
+
+/* { dg-final { scan-rtl-dump-not "RISCV_FUSE_BFEXT_SRAI" "sched2" } } */
+/* { dg-final { scan-rtl-dump-not "RISCV_FUSE_BFEXT_SRLI" "sched2" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-bfext.c b/gcc/testsuite/gcc.target/riscv/fusion-bfext.c
new file mode 100644
index 00000000000..48f006c46eb
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-bfext.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target rv32 } */
+/* { dg-skip-if "" { *-*-* } { "-g" "-flto" "-O0" "-O1" "-O3" "-Oz" "-Os" } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv32im -mabi=ilp32 -fdump-rtl-sched2" } */
+
+unsigned int
+fusion_shift_bitextract (unsigned int x, unsigned int y, unsigned int z)
+{
+    unsigned int t = x << 8;
+    unsigned int a = y + z;
+    t = t >> 4;
+    return t + a;
+}
+
+/* { dg-final { scan-rtl-dump "RISCV_FUSE_BFEXT_SRLI" "sched2" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-li-branch.c b/gcc/testsuite/gcc.target/riscv/fusion-li-branch.c
new file mode 100644
index 00000000000..793282395bc
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-li-branch.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target rv32 } */
+/* { dg-skip-if "" { *-*-* } { "-g" "-flto" "-O0" "-O1" "-O3" "-Oz" "-Os" } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv32im -mabi=ilp32 -fdump-rtl-sched2" } */
+
+int
+fuse_li_branch (int x)
+{
+  while (x <= 3)
+    ;
+  return x;
+}
+
+/* { dg-final { scan-rtl-dump "RISCV_FUSE_LI_BRANCH" "sched2" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-li-store.c b/gcc/testsuite/gcc.target/riscv/fusion-li-store.c
new file mode 100644
index 00000000000..ea69cc5f5b5
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-li-store.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target rv32 } */
+/* { dg-skip-if "" { *-*-* } { "-g" "-flto" "-O0" "-O1" "-O3" "-Oz" "-Os" } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv32im -mabi=ilp32 -fdump-rtl-sched2" } */
+
+void
+fuse_li_store (int *p)
+{
+  *p = 42;
+}
+
+/* { dg-final { scan-rtl-dump "RISCV_FUSE_LI_STORE" "sched2" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-ls-update-2.c b/gcc/testsuite/gcc.target/riscv/fusion-ls-update-2.c
new file mode 100644
index 00000000000..5e4ba9bac21
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-ls-update-2.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target rv32 } */
+/* { dg-skip-if "" { *-*-* } { "-g" "-flto" "-O0" "-O1" "-O3" "-Oz" "-Os" } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv32im -mabi=ilp32 -fdump-rtl-sched2" } */
+
+void
+fuse_ls_update_rev (int *p, int n, int val)
+{
+  for (int i = 0; i < n; i++)
+    {
+      p++;
+      *p = val;
+    }
+}
+
+/* XFAIL until scheduling support for RHX-100 series is implemented.  */
+/* { dg-final { scan-rtl-dump "RISCV_FUSE_LS_UPDATE)" "sched2" { xfail *-*-* } } } */
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-ls-update-3.c b/gcc/testsuite/gcc.target/riscv/fusion-ls-update-3.c
new file mode 100644
index 00000000000..9731dedc4f3
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-ls-update-3.c
@@ -0,0 +1,81 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-g" "-flto" "-O0" "-O1" "-O3" "-Oz" "-Os" } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv32im_zbb -mabi=ilp32 -fdump-rtl-sched2" { target rv32 } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv64im_zbb -mabi=lp64 -fdump-rtl-sched2" { target rv64 } } */
+
+#if __SIZEOF_LONG__ == 8
+# define BSWAPL __builtin_bswap64
+#else
+# define BSWAPL __builtin_bswap32
+#endif
+
+#define ST(NAME, EXPR)					\
+  unsigned long NAME (long *p, long val)		\
+  {							\
+    *p = val;						\
+    return (EXPR);					\
+  }
+
+ST (st_addi,  ((unsigned long) p + 8))			    /* arith    */
+ST (st_andi,  ((unsigned long) p & 0xff))		    /* logical  */
+ST (st_ori,   ((unsigned long) p | 0xff))		    /* logical  */
+ST (st_xori,  ((unsigned long) p ^ 0xff))		    /* logical  */
+ST (st_slli,  ((unsigned long) p << 3))			    /* shift    */
+ST (st_srli,  ((unsigned long) p >> 3))			    /* shift    */
+ST (st_srai,  ((unsigned long) ((long) p >> 3)))	    /* shift    */
+ST (st_slti,  ((unsigned long) ((long) p < 99)))	    /* slt      */
+ST (st_sltiu, ((unsigned long) ((unsigned long) p < 99u)))  /* slt      */
+ST (st_clz,   (__builtin_clzl ((unsigned long) p)))	    /* clz      */
+ST (st_ctz,   (__builtin_ctzl ((unsigned long) p)))	    /* ctz      */
+ST (st_rev8,  (BSWAPL ((unsigned long) p)))		    /* bitmanip */
+
+#define LD(NAME, EXPR)					\
+  long NAME (long *p, unsigned long x, long *sink)	\
+  {							\
+    long v = *p;					\
+    unsigned long q = (EXPR);				\
+    *sink = v;						\
+    return (long) q;					\
+  }
+
+LD (ld_add,  ((unsigned long) p + x))					     /* arith   */
+LD (ld_sub,  ((unsigned long) p - x))					     /* arith   */
+LD (ld_and,  ((unsigned long) p & x))					     /* logical */
+LD (ld_or,   ((unsigned long) p | x))					     /* logical */
+LD (ld_xor,  ((unsigned long) p ^ x))					     /* logical */
+LD (ld_sll,  ((unsigned long) p << (x & 31)))				     /* shift   */
+LD (ld_srl,  ((unsigned long) p >> (x & 31)))				     /* shift   */
+LD (ld_sra,  ((unsigned long) ((long) p >> (x & 31))))			     /* shift   */
+LD (ld_slt,  ((unsigned long) ((long) p < (long) x)))			     /* slt     */
+LD (ld_sltu, ((unsigned long) ((unsigned long) p < x)))			     /* slt     */
+LD (ld_min,  ((unsigned long) ((long) p < (long) x ? (long) p : (long) x)))  /* min     */
+LD (ld_max,  ((unsigned long) ((long) p > (long) x ? (long) p : (long) x)))  /* max     */
+LD (ld_minu, ((unsigned long) p < x ? (unsigned long) p : x))		     /* minu    */
+LD (ld_maxu, ((unsigned long) p > x ? (unsigned long) p : x))		     /* maxu    */
+
+/* { dg-final { scan-assembler {\madd\M} } } */
+/* { dg-final { scan-assembler {\maddi\M} } } */
+/* { dg-final { scan-assembler {\msub\M} } } */
+/* { dg-final { scan-assembler {\mand\M} } } */
+/* { dg-final { scan-assembler {\mandi\M} } } */
+/* { dg-final { scan-assembler {\mor\M} } } */
+/* { dg-final { scan-assembler {\mori\M} } } */
+/* { dg-final { scan-assembler {\mxor\M} } } */
+/* { dg-final { scan-assembler {\mxori\M} } } */
+/* { dg-final { scan-assembler {\msll\M} } } */
+/* { dg-final { scan-assembler {\mslli\M} } } */
+/* { dg-final { scan-assembler {\msrl\M} } } */
+/* { dg-final { scan-assembler {\msrli\M} } } */
+/* { dg-final { scan-assembler {\msra\M} } } */
+/* { dg-final { scan-assembler {\msrai\M} } } */
+/* { dg-final { scan-assembler {\mslt\M} } } */
+/* { dg-final { scan-assembler {\mslti\M} } } */
+/* { dg-final { scan-assembler {\msltu\M} } } */
+/* { dg-final { scan-assembler {\msltiu\M} } } */
+/* { dg-final { scan-assembler {\mmin\M} } } */
+/* { dg-final { scan-assembler {\mmax\M} } } */
+/* { dg-final { scan-assembler {\mminu\M} } } */
+/* { dg-final { scan-assembler {\mmaxu\M} } } */
+/* { dg-final { scan-assembler {\mclz\M} } } */
+/* { dg-final { scan-assembler {\mctz\M} } } */
+/* { dg-final { scan-assembler {\mrev8\M} } } */
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-ls-update-4.c b/gcc/testsuite/gcc.target/riscv/fusion-ls-update-4.c
new file mode 100644
index 00000000000..49aef023dff
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-ls-update-4.c
@@ -0,0 +1,53 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-g" "-flto" "-O0" "-O1" "-O3" "-Oz" "-Os" } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv32im_zbb -mabi=ilp32 -fdump-rtl-sched2" { target rv32 } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv64im_zbb -mabi=lp64 -fdump-rtl-sched2" { target rv64 } } */
+
+long
+ld_xnor (long *p, unsigned long x, long *sink)
+{
+  long v = *p;
+  unsigned long q = ~((unsigned long) p ^ x);
+  *sink = v;
+  return (long) q;
+}
+
+long
+ld_off (long *p, unsigned long x, long *sink)
+{
+  long v = p[1];
+  unsigned long q = (unsigned long) p + x;
+  *sink = v;
+  return (long) q;
+}
+
+unsigned long
+st_off (long *p, long val)
+{
+  p[1] = val;
+  return (unsigned long) p + 8;
+}
+
+unsigned long
+st_op_add (long *p, long d)
+{
+  *p = d;
+  return (unsigned long) p + (unsigned long) d;
+}
+
+unsigned long
+st_op_sub (long *p, long d)
+{
+  *p = d;
+  return (unsigned long) p - (unsigned long) d;
+}
+
+/* Must not fuse. */
+unsigned long
+st_op_unrel (long *p, long d, unsigned long x)
+{
+  *p = d;
+  return (unsigned long) p + x;
+}
+
+/* { dg-final { scan-rtl-dump-times "RISCV_FUSE_LS_UPDATE" 5 "sched2" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-ls-update-5.c b/gcc/testsuite/gcc.target/riscv/fusion-ls-update-5.c
new file mode 100644
index 00000000000..c0d64d77d67
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-ls-update-5.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-g" "-flto" "-O0" "-O1" "-O3" "-Oz" "-Os" } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv32im -mabi=ilp32 -fdump-rtl-sched2" { target { rv32 } } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv64im -mabi=lp64 -fdump-rtl-sched2" { target { rv64 } } } */
+
+int *
+fuse_ls_update_move (int *p, int *q, int x)
+{
+  *q = x;
+  return q;
+}
+
+/* { dg-final { scan-rtl-dump "RISCV_FUSE_LS_UPDATE" "sched2" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-ls-update.c b/gcc/testsuite/gcc.target/riscv/fusion-ls-update.c
new file mode 100644
index 00000000000..b71eab0fcd9
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-ls-update.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target rv32 } */
+/* { dg-skip-if "" { *-*-* } { "-g" "-flto" "-O0" "-O1" "-O3" "-Oz" "-Os" } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv32im -mabi=ilp32 -fdump-rtl-sched2" } */
+
+int
+fuse_ls_update (int *p, int n)
+{
+  int sum = 0;
+  for (int i = 0; i < n; i++)
+    sum += *p++;
+  return sum;
+}
+
+/* XFAIL until scheduling support for RHX-100 series is implemented.  */
+/* { dg-final { scan-rtl-dump "RISCV_FUSE_LS_UPDATE" "sched2" { xfail *-*-* } } } */
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-lui-ld.c b/gcc/testsuite/gcc.target/riscv/fusion-lui-ld.c
new file mode 100644
index 00000000000..58dbe4393a1
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-lui-ld.c
@@ -0,0 +1,25 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target rv32 } */
+/* { dg-skip-if "" { *-*-* } { "-g" "-flto" "-O0" "-O1" "-O3" "-Oz" "-Os" } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv32im -mabi=ilp32 -fdump-rtl-sched2" } */
+
+extern int g1, g2;
+extern volatile int g3, g4;
+
+int
+fuse_lui_ld (void)
+{
+  return g1 + g2;
+}
+
+int
+fuse_lui_ld_rev (volatile int *p, int val)
+{
+  int a = *p;
+  int b = g3;
+  int c = p[1];
+  int d = g4;
+  return a + b + c + d + val;
+}
+
+/* { dg-final { scan-rtl-dump "RISCV_FUSE_LUI_LD_REV" "sched2" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-lui-st.c b/gcc/testsuite/gcc.target/riscv/fusion-lui-st.c
new file mode 100644
index 00000000000..54c38d7fa33
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-lui-st.c
@@ -0,0 +1,19 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target rv32 } */
+/* { dg-skip-if "" { *-*-* } { "-g" "-flto" "-O0" "-O1" "-O3" "-Oz" "-Os" } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv32im -mabi=ilp32 -fdump-rtl-sched2" } */
+
+volatile int g1, g2, g3;
+
+void
+fuse_store_lui (volatile int *p, int val)
+{
+  *p = val;
+  g1 = val;
+  p[1] = val;
+  g2 = val;
+  p[2] = val;
+  g3 = val;
+}
+
+/* { dg-final { scan-rtl-dump "RISCV_FUSE_LUI_ST" "sched2" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-mult-add.c b/gcc/testsuite/gcc.target/riscv/fusion-mult-add.c
new file mode 100644
index 00000000000..d182a65aa46
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-mult-add.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-g" "-flto" "-O0" "-O1" "-O3" "-Oz" "-Os" } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv32im -mabi=ilp32 -fdump-rtl-sched2" { target { rv32 } } } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series -march=rv64im -mabi=lp64 -fdump-rtl-sched2" { target { rv64 } } } */
+
+int
+fuse_mult_add_op0 (int a, int b, int c)
+{
+  int m = a * b;
+  return m + c;
+}
+
+/* { dg-final { scan-rtl-dump "RISCV_FUSE_MULT_ADD" "sched2" } } */
-- 
2.47.3