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

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

This patch implements instruction scheduling support for the dual-issue
ARC-V RHX-100 processor by adding scheduler hooks and state tracking for
the two execution pipes.

These hooks address microarchitectural details not covered by the
pipeline description.

The riscv_sched_variable_issue () and riscv_sched_reorder2 () hooks work
together to make sure that:
  - the critical path and the instruction priorities are respected;
  - both pipes are filled (taking advantage of parallel dispatch within
    the microarchitectural constraints);
  - there is as much fusion going on as possible;
  - the existing fusion pairs are not broken up.

riscv_sched_adjust_priority () slightly bumps the priority of load/store
pairs.  As a result it becomes easier for riscv_sched_reorder2 () to
schedule instructions in the memory pipe.

TARGET_SCHED_REORDER2 is defined for all RISC-V tunes, so
riscv_sched_reorder2 must preserve the remaining issue count for non-RHX
(via cached_can_issue_more from riscv_sched_variable_issue).

gcc/ChangeLog:

	* config/riscv/arcv.cc (struct arcv_sched_state): New.
	(arcv_sched_init): New function.
	(arcv_sched_reorder2): New function.
	(arcv_sched_adjust_priority): New function.
	(arcv_sched_adjust_cost): New function.
	(arcv_can_issue_more_p): New function.
	(arcv_sched_variable_issue): New function.
	* config/riscv/riscv-protos.h (arcv_sched_init): Declare.
	(arcv_sched_reorder2): Declare.
	(arcv_sched_adjust_priority): Declare.
	(arcv_sched_adjust_cost): Declare.
	(arcv_can_issue_more_p): Declare.
	(arcv_sched_variable_issue): Declare.
	* config/riscv/riscv.cc (cached_can_issue_more): New.
	(riscv_sched_init): Call arcv_sched_init.
	(riscv_sched_variable_issue): Handle RHX-100; cache the returned
	issue count.
	(riscv_sched_adjust_cost): Handle RHX-100.
	(riscv_sched_adjust_priority): New function.
	(riscv_sched_reorder2): New function; return cached_can_issue_more
	for non-RHX.
	(TARGET_SCHED_ADJUST_PRIORITY): Define.
	(TARGET_SCHED_REORDER2): Define.

gcc/testsuite/ChangeLog:

	* gcc.target/riscv/fusion-ls-update-2.c: Remove XFAIL, and fix the
	scan pattern.
	* gcc.target/riscv/fusion-ls-update.c: Remove XFAIL.

Co-authored-by: Artemiy Volkov <[email protected]>
Co-authored-by: Michiel Derhaeg <[email protected]>
Co-authored-by: Alex Turjan <[email protected]>
Co-authored-by: Loeka Rogge <[email protected]>
Signed-off-by: Luis Silva <[email protected]>
---
 gcc/config/riscv/arcv.cc                      | 247 ++++++++++++++++++
 gcc/config/riscv/riscv-protos.h               |   6 +
 gcc/config/riscv/riscv.cc                     |  82 +++++-
 .../gcc.target/riscv/fusion-ls-update-2.c     |   3 +-
 .../gcc.target/riscv/fusion-ls-update.c       |   3 +-
 5 files changed, 329 insertions(+), 12 deletions(-)

diff --git a/gcc/config/riscv/arcv.cc b/gcc/config/riscv/arcv.cc
index e29712ca2be..050c91401c4 100644
--- a/gcc/config/riscv/arcv.cc
+++ b/gcc/config/riscv/arcv.cc
@@ -49,6 +49,32 @@ along with GCC; see the file COPYING3.  If not see
 #include "sched-int.h"
 #include "tm-constrs.h"
 
+/* Scheduler state tracking for dual-pipe ARCV architectures.  */
+
+struct arcv_sched_state {
+  /* True if the ALU pipe has been scheduled for the current cycle.
+     The ALU pipe handles arithmetic, logical, and other computational
+     instructions.  */
+  int alu_pipe_scheduled_p;
+
+  /* True if pipe B has been scheduled for the current cycle.
+     Pipe B is the second execution pipe, typically used for memory
+     operations (loads/stores) but can also handle other instructions.  */
+  int pipeB_scheduled_p;
+
+  /* The last instruction that was scheduled.  Used to detect fusion
+     opportunities by looking ahead at the next instruction to be
+     scheduled.  */
+  rtx_insn *last_scheduled_insn;
+
+  /* Cached value of how many more instructions can be issued in the
+     current cycle.  Updated as instructions are scheduled and pipes
+     become occupied.  */
+  short cached_can_issue_more;
+};
+
+static struct arcv_sched_state sched_state;
+
 /* If INSN is a load or store of address in the form of [base+offset],
    extract the two parts and set to BASE and OFFSET.  IS_LOAD is set
    to TRUE if it's a load.  Return TRUE if INSN is such an instruction,
@@ -160,3 +186,224 @@ arcv_sched_fusion_priority (rtx_insn *insn, int max_pri, int *fusion_pri,
 
   return true;
 }
+
+/* Initialize ARCV scheduler state at the beginning of scheduling.  */
+
+void
+arcv_sched_init (void)
+{
+  sched_state.last_scheduled_insn = 0;
+}
+
+/* Try to reorder ready queue to promote ARCV fusion opportunities.
+   Returns the number of instructions that can be issued this cycle.  */
+
+int
+arcv_sched_reorder2 (rtx_insn **ready, int *n_readyp)
+{
+  if (sched_fusion)
+    return sched_state.cached_can_issue_more;
+
+  if (!sched_state.cached_can_issue_more)
+    return 0;
+
+  /* Fuse double load/store instances missed by sched_fusion.  */
+  if (!sched_state.pipeB_scheduled_p && sched_state.last_scheduled_insn
+      && ready && *n_readyp > 0
+      && !SCHED_GROUP_P (sched_state.last_scheduled_insn)
+      && (get_attr_type (sched_state.last_scheduled_insn) == TYPE_LOAD
+	  || get_attr_type (sched_state.last_scheduled_insn) == TYPE_STORE))
+    {
+      for (int i = 1; i <= *n_readyp; i++)
+       {
+	 rtx_insn *next_insn
+	   = next_nonnote_nondebug_insn_bb (ready[*n_readyp - i]);
+	 /* Try to fuse the last_scheduled_insn with.  */
+	 /* Fuse only with nondebug insn.  */
+	 if (NONDEBUG_INSN_P (ready[*n_readyp - i])
+	     /* Which have not been already fused.  */
+	     && !SCHED_GROUP_P (ready[*n_readyp - i])
+	     && (!next_insn || !NONDEBUG_INSN_P (next_insn)
+		 || !SCHED_GROUP_P (next_insn))
+	     && riscv_macro_fusion_pair_p (sched_state.last_scheduled_insn,
+					   ready[*n_readyp - i]))
+	   {
+	     std::swap (ready[*n_readyp - 1], ready[*n_readyp - i]);
+	     SCHED_GROUP_P (ready[*n_readyp - 1]) = 1;
+	     sched_state.pipeB_scheduled_p = 1;
+	     return sched_state.cached_can_issue_more;
+	  }
+       }
+      sched_state.pipeB_scheduled_p = 1;
+    }
+
+  /* Try to fuse a non-memory last_scheduled_insn.  */
+  if ((!sched_state.alu_pipe_scheduled_p || !sched_state.pipeB_scheduled_p)
+      && sched_state.last_scheduled_insn && ready && *n_readyp > 0
+      && !SCHED_GROUP_P (sched_state.last_scheduled_insn)
+      && (get_attr_type (sched_state.last_scheduled_insn) != TYPE_LOAD
+	  && get_attr_type (sched_state.last_scheduled_insn) != TYPE_STORE))
+    {
+      for (int i = 1; i <= *n_readyp; i++)
+       {
+	 rtx_insn* next_insn
+	   = next_nonnote_nondebug_insn_bb (ready[*n_readyp - i]);
+	 if (NONDEBUG_INSN_P (ready[*n_readyp - i])
+	     && !SCHED_GROUP_P (ready[*n_readyp - i])
+	     && active_insn_p (ready[*n_readyp - i])
+	     && (!next_insn || !NONDEBUG_INSN_P (next_insn)
+		 || !SCHED_GROUP_P (next_insn))
+	     && riscv_macro_fusion_pair_p (sched_state.last_scheduled_insn,
+					   ready[*n_readyp - i]))
+	   {
+	     if (get_attr_type (ready[*n_readyp - i]) == TYPE_LOAD
+		 || get_attr_type (ready[*n_readyp - i]) == TYPE_STORE)
+	     {
+	       if (sched_state.pipeB_scheduled_p)
+		 continue;
+	       else
+		 sched_state.pipeB_scheduled_p = 1;
+	     }
+	     else if (!sched_state.alu_pipe_scheduled_p)
+	       sched_state.alu_pipe_scheduled_p = 1;
+	     else
+	       sched_state.pipeB_scheduled_p = 1;
+
+	     std::swap (ready[*n_readyp - 1], ready[*n_readyp - i]);
+	     SCHED_GROUP_P (ready[*n_readyp - 1]) = 1;
+	     return sched_state.cached_can_issue_more;
+	   }
+       }
+      sched_state.alu_pipe_scheduled_p = 1;
+    }
+  /* When pipe B is scheduled, we can have no more memops this cycle.  */
+  if (sched_state.pipeB_scheduled_p && *n_readyp > 0
+      && NONDEBUG_INSN_P (ready[*n_readyp - 1])
+      && recog_memoized (ready[*n_readyp - 1]) >= 0
+      && !SCHED_GROUP_P (ready[*n_readyp - 1]))
+  {
+    rtx_insn *head_next = next_nonnote_nondebug_insn_bb (ready[*n_readyp - 1]);
+    if (get_attr_type (ready[*n_readyp - 1]) == TYPE_LOAD
+	|| get_attr_type (ready[*n_readyp - 1]) == TYPE_STORE
+	|| (head_next && NONDEBUG_INSN_P (head_next)
+	    && SCHED_GROUP_P (head_next) && recog_memoized (head_next) >= 0
+	    && (get_attr_type (head_next) == TYPE_LOAD
+		|| get_attr_type (head_next) == TYPE_STORE)))
+    {
+      if (sched_state.alu_pipe_scheduled_p)
+	return 0;
+
+      for (int i = 2; i <= *n_readyp; i++)
+	{
+	 rtx_insn* next_insn
+	   = next_nonnote_nondebug_insn_bb (ready[*n_readyp - i]);
+	 if ((NONDEBUG_INSN_P (ready[*n_readyp - i])
+	      && recog_memoized (ready[*n_readyp - i]) >= 0
+	      && get_attr_type (ready[*n_readyp - i]) != TYPE_LOAD
+	      && get_attr_type (ready[*n_readyp - i]) != TYPE_STORE
+	      && !SCHED_GROUP_P (ready[*n_readyp - i])
+	      && (!next_insn || !NONDEBUG_INSN_P (next_insn)
+		  || !SCHED_GROUP_P (next_insn)))
+	     || (next_insn && NONDEBUG_INSN_P (next_insn)
+		 && recog_memoized (next_insn) >= 0
+		 && get_attr_type (next_insn) != TYPE_LOAD
+		 && get_attr_type (next_insn) != TYPE_STORE))
+	   {
+	     std::swap (ready[*n_readyp - 1], ready[*n_readyp - i]);
+	     sched_state.alu_pipe_scheduled_p = 1;
+	     sched_state.cached_can_issue_more = 1;
+	     return 1;
+	   }
+	}
+      return 0;
+    }
+  }
+
+  /* If all else fails, schedule a single (fused) instruction.  */
+  if (ready && *n_readyp > 0
+      && NONDEBUG_INSN_P (ready[*n_readyp - 1])
+      && recog_memoized (ready[*n_readyp - 1]) >= 0)
+  {
+    rtx_insn *next_insn = next_nonnote_nondebug_insn_bb (ready[*n_readyp - 1]);
+
+    sched_state.cached_can_issue_more
+      = next_insn && NONDEBUG_INSN_P (next_insn) && SCHED_GROUP_P (next_insn)
+	? 2 : 1;
+  }
+
+  return sched_state.cached_can_issue_more;
+}
+
+int
+arcv_sched_adjust_priority (rtx_insn *insn, int priority)
+{
+  /* Bump the priority of fused load-store pairs for easier
+     scheduling of the memory pipe.  The specific increase
+     value is determined empirically.  */
+  rtx_insn *next = next_nonnote_nondebug_insn_bb (insn);
+  if (next && single_set (insn) && single_set (next)
+      && SCHED_GROUP_P (next)
+      && ((get_attr_type (insn) == TYPE_STORE
+	   && get_attr_type (next) == TYPE_STORE)
+	  || (get_attr_type (insn) == TYPE_LOAD
+	      && get_attr_type (next) == TYPE_LOAD)))
+    return priority + 1;
+
+  return priority;
+}
+
+/* Adjust scheduling cost for ARCV fusion.  */
+
+int
+arcv_sched_adjust_cost (rtx_insn *insn, int dep_type, int cost)
+{
+  if (dep_type == REG_DEP_ANTI && !SCHED_GROUP_P (insn))
+    return cost + 1;
+
+  return cost;
+}
+
+bool
+arcv_can_issue_more_p (int issue_rate, int more, rtx_insn *insn)
+{
+  /* Beginning of cycle - reset variables.  */
+  if (more == issue_rate)
+    {
+      sched_state.alu_pipe_scheduled_p = 0;
+      sched_state.pipeB_scheduled_p = 0;
+    }
+
+  if (!(insn && NONDEBUG_INSN_P (insn) && SCHED_GROUP_P (insn))
+      && sched_state.alu_pipe_scheduled_p && sched_state.pipeB_scheduled_p)
+    {
+      sched_state.cached_can_issue_more = 0;
+      return false;
+    }
+
+  sched_state.cached_can_issue_more = more;
+
+  return true;
+}
+
+int
+arcv_sched_variable_issue (rtx_insn *insn, int more)
+{
+  rtx_insn *next = next_nonnote_nondebug_insn_bb (insn);
+  if (next && NONDEBUG_INSN_P (next) && SCHED_GROUP_P (next)
+      && single_set (insn) && single_set (next))
+    {
+      if (get_attr_type (insn) == TYPE_LOAD
+	  || get_attr_type (insn) == TYPE_STORE
+	  || get_attr_type (next) == TYPE_LOAD
+	  || get_attr_type (next) == TYPE_STORE
+	  || sched_state.alu_pipe_scheduled_p)
+	sched_state.pipeB_scheduled_p = 1;
+      else
+	sched_state.alu_pipe_scheduled_p = 1;
+    }
+
+  sched_state.last_scheduled_insn = insn;
+  sched_state.cached_can_issue_more = more - 1;
+
+  return sched_state.cached_can_issue_more;
+}
diff --git a/gcc/config/riscv/riscv-protos.h b/gcc/config/riscv/riscv-protos.h
index 4158af2fd1a..8a39e7f59f3 100644
--- a/gcc/config/riscv/riscv-protos.h
+++ b/gcc/config/riscv/riscv-protos.h
@@ -917,6 +917,12 @@ extern bool arcv_mpy_10c_bypass_p (rtx_insn *, rtx_insn *);
 /* Routines implemented in arcv.cc.  */
 extern bool arcv_pair_fusion_mode_allowed_p (machine_mode, bool);
 extern bool arcv_sched_fusion_priority (rtx_insn *, int, int *, int *);
+extern void arcv_sched_init (void);
+extern int arcv_sched_reorder2 (rtx_insn **, int *);
+extern int arcv_sched_adjust_priority (rtx_insn *, int);
+extern int arcv_sched_adjust_cost (rtx_insn *, int, int);
+extern bool arcv_can_issue_more_p (int, int, rtx_insn *);
+extern int arcv_sched_variable_issue (rtx_insn *, int);
 
 extern bool strided_load_broadcast_p (void);
 extern bool riscv_prefer_agnostic_p (void);
diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index d08a4539536..dd80da2d55a 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -11455,6 +11455,10 @@ struct last_vconfig
   rtx avl;
 } last_vconfig;
 
+/* Cached value of can_issue_more.  Set by riscv_sched_variable_issue and
+   returned from riscv_sched_reorder2.  */
+static int cached_can_issue_more;
+
 /* Clear LAST_VCONFIG so we have no known state.  */
 static void
 clear_vconfig (void)
@@ -11507,23 +11511,42 @@ static void
 riscv_sched_init (FILE *, int, int)
 {
   clear_vconfig ();
+
+  if (TARGET_ARCV_RHX100)
+    arcv_sched_init ();
 }
 
 /* Implement TARGET_SCHED_VARIABLE_ISSUE.  */
 static int
 riscv_sched_variable_issue (FILE *, int, rtx_insn *insn, int more)
 {
+  if (TARGET_ARCV_RHX100)
+    if (!arcv_can_issue_more_p (riscv_issue_rate (), more, insn))
+      {
+	cached_can_issue_more = 0;
+	return 0;
+      }
+
   if (DEBUG_INSN_P (insn))
-    return more;
+    {
+      cached_can_issue_more = more;
+      return more;
+    }
 
   rtx_code code = GET_CODE (PATTERN (insn));
   if (code == USE || code == CLOBBER)
-    return more;
+    {
+      cached_can_issue_more = more;
+      return more;
+    }
 
   /* GHOST insns are used for blockage and similar cases which
      effectively end a cycle.  */
   if (get_attr_type (insn) == TYPE_GHOST)
-    return 0;
+    {
+      cached_can_issue_more = 0;
+      return 0;
+    }
 
   /* If we ever encounter an insn with an unknown type, trip
      an assert so we can find and fix this problem.  */
@@ -11558,6 +11581,14 @@ riscv_sched_variable_issue (FILE *, int, rtx_insn *insn, int more)
 	}
     }
 
+  if (TARGET_ARCV_RHX100)
+    {
+      more = arcv_sched_variable_issue (insn, more);
+      cached_can_issue_more = more;
+      return more;
+    }
+
+  cached_can_issue_more = more - 1;
   return more - 1;
 }
 
@@ -11631,9 +11662,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,
-			 unsigned int)
+riscv_sched_adjust_cost (rtx_insn *insn, int dep_type, rtx_insn *dep_insn,
+			 int cost, unsigned int)
 {
+  /* Use ARCV-specific cost adjustment for RHX-100.  */
+  if (TARGET_ARCV_RHX100)
+    return arcv_sched_adjust_cost (insn, dep_type, cost);
 
   /* Only do adjustments for the generic out-of-order and spacemit_x60
      scheduling model.  */
@@ -11642,10 +11676,10 @@ riscv_sched_adjust_cost (rtx_insn *, int, rtx_insn *insn, int cost,
 	  && riscv_microarchitecture != spacemit_x60))
     return cost;
 
-  if (recog_memoized (insn) < 0)
+  if (recog_memoized (dep_insn) < 0)
     return cost;
 
-  enum attr_type type = get_attr_type (insn);
+  enum attr_type type = get_attr_type (dep_insn);
 
   if (type == TYPE_VFREDO || type == TYPE_VFWREDO)
     {
@@ -11663,7 +11697,7 @@ riscv_sched_adjust_cost (rtx_insn *, int, rtx_insn *insn, int cost,
     return cost;
 
   enum riscv_vector::vlmul_type lmul =
-    (riscv_vector::vlmul_type)get_attr_vlmul (insn);
+    (riscv_vector::vlmul_type)get_attr_vlmul (dep_insn);
 
   double factor = 1;
   switch (lmul)
@@ -11696,6 +11730,32 @@ riscv_sched_adjust_cost (rtx_insn *, int, rtx_insn *insn, int cost,
   return new_cost;
 }
 
+/* Implement TARGET_SCHED_ADJUST_PRIORITY hook.  */
+
+static int
+riscv_sched_adjust_priority (rtx_insn *insn, int priority)
+{
+  if (TARGET_ARCV_RHX100)
+    return arcv_sched_adjust_priority (insn, priority);
+
+  return priority;
+}
+
+/* Implement TARGET_SCHED_REORDER2 hook.  */
+
+static int
+riscv_sched_reorder2 (FILE *file ATTRIBUTE_UNUSED,
+		      int verbose ATTRIBUTE_UNUSED,
+		      rtx_insn **ready,
+		      int *n_readyp,
+		      int clock ATTRIBUTE_UNUSED)
+{
+  if (TARGET_ARCV_RHX100)
+    return arcv_sched_reorder2 (ready, n_readyp);
+
+  return cached_can_issue_more;
+}
+
 /* Implement TARGET_SCHED_CAN_SPECULATE_INSN hook.  Return true if insn
    can be scheduled for speculative execution.  Reject vsetvl instructions to
    prevent the scheduler from hoisting them out of basic blocks without
@@ -16605,6 +16665,12 @@ riscv_memtag_tag_bitsize ()
 #undef  TARGET_SCHED_ADJUST_COST
 #define TARGET_SCHED_ADJUST_COST riscv_sched_adjust_cost
 
+#undef  TARGET_SCHED_ADJUST_PRIORITY
+#define TARGET_SCHED_ADJUST_PRIORITY riscv_sched_adjust_priority
+
+#undef  TARGET_SCHED_REORDER2
+#define TARGET_SCHED_REORDER2 riscv_sched_reorder2
+
 #undef TARGET_SCHED_CAN_SPECULATE_INSN
 #define TARGET_SCHED_CAN_SPECULATE_INSN riscv_sched_can_speculate_insn
 
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-ls-update-2.c b/gcc/testsuite/gcc.target/riscv/fusion-ls-update-2.c
index 5e4ba9bac21..8d3045cbf4d 100644
--- a/gcc/testsuite/gcc.target/riscv/fusion-ls-update-2.c
+++ b/gcc/testsuite/gcc.target/riscv/fusion-ls-update-2.c
@@ -13,5 +13,4 @@ fuse_ls_update_rev (int *p, int n, int val)
     }
 }
 
-/* XFAIL until scheduling support for RHX-100 series is implemented.  */
-/* { dg-final { scan-rtl-dump "RISCV_FUSE_LS_UPDATE)" "sched2" { xfail *-*-* } } } */
+/* { 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
index b71eab0fcd9..5939fdd0de9 100644
--- a/gcc/testsuite/gcc.target/riscv/fusion-ls-update.c
+++ b/gcc/testsuite/gcc.target/riscv/fusion-ls-update.c
@@ -12,5 +12,4 @@ fuse_ls_update (int *p, int n)
   return sum;
 }
 
-/* XFAIL until scheduling support for RHX-100 series is implemented.  */
-/* { dg-final { scan-rtl-dump "RISCV_FUSE_LS_UPDATE" "sched2" { xfail *-*-* } } } */
+/* { dg-final { scan-rtl-dump "RISCV_FUSE_LS_UPDATE" "sched2" } } */
-- 
2.47.3