[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