Implement TARGET_CALLEE_SAVE_COST and TARGET_FRAME_ALLOCATION_COST so
IRA weighs callee saves against spills at their marginal execution
cost: reusing an already-required save is free, completing an odd
save count is discounted by half the memory cost (the fusion-aware
scheduling keeps the pair adjacent), and the first spill of a
frameless leaf is charged the separate SP adjustment.
This is a first, deliberately simple attempt, posted as a draft to
collect suggestions on the approach. The model is unweighted and
knows nothing about emission; with more precise modeling (call and
spill frequency weighting, awareness of the final save layout) the
fusion characteristics of the target should translate into more
reliable gains.
Assisted-by: qwen3.8-max
gcc/ChangeLog:
* config/riscv/riscv.cc (riscv_ira_save_mask): New.
(riscv_frame_allocation_cost): New.
(riscv_callee_save_cost): New.
(TARGET_FRAME_ALLOCATION_COST, TARGET_CALLEE_SAVE_COST): Define.
gcc/testsuite/ChangeLog:
* gcc.target/riscv/callee-save-cost-mempair.c: New test.
* gcc.target/riscv/callee-save-cost-fusion.c: New test.
---
gcc/config/riscv/riscv.cc | 96 +++++++++++++++++++
.../riscv/callee-save-cost-fusion.c | 21 ++++
.../riscv/callee-save-cost-mempair.c | 22 +++++
3 files changed, 139 insertions(+)
create mode 100644 gcc/testsuite/gcc.target/riscv/callee-save-cost-fusion.c
create mode 100644 gcc/testsuite/gcc.target/riscv/callee-save-cost-mempair.c
diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index 0deedcc4beb..72e5ac3a31a 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -11864,6 +11864,98 @@ riscv_memory_move_cost (machine_mode mode, reg_class_t
rclass, bool in)
+ memory_move_secondary_cost (mode, rclass, in));
}
+/* Return the save mask for registers FIRST..FIRST+31, combining IRA's
+ ALLOCATED set with the saves the function already requires. */
+
+static unsigned int
+riscv_ira_save_mask (const HARD_REG_SET &allocated, unsigned int first)
+{
+ unsigned int mask = 0;
+ for (unsigned int i = 0; i < 32; ++i)
+ {
+ unsigned int regno = first + i;
+ /* Use known RA requirements instead of scanning RTL for jumps. */
+ if (TEST_HARD_REG_BIT (allocated, regno)
+ || (regno == RETURN_ADDR_REGNUM
+ ? !crtl->is_leaf || df_regs_ever_live_p (regno)
+ || cfun->machine->far_jump_used
+ : riscv_save_reg_p (regno)))
+ mask |= 1U << i;
+ }
+ return mask;
+}
+
+/* Implement TARGET_FRAME_ALLOCATION_COST. Charge the SP adjustment the
+ first spill of a frameless function needs; an existing frame absorbs
+ the spill. FIXME: Account for push/pop and save-libcall frames. */
+
+static int
+riscv_frame_allocation_cost (frame_cost_type, const HARD_REG_SET &allocated)
+{
+ /* An existing frame, a frame pointer, local variables, outgoing
+ arguments or any already-committed callee save absorbs the spill:
+ no separate SP adjustment is needed. Naked functions have no
+ generated frame at all. */
+ if (cfun->machine->naked_p || !crtl->is_leaf || frame_pointer_needed
+ || cfun->calls_alloca || crtl->calls_eh_return
+ || cfun->machine->varargs_size || crtl->args.pretend_args_size
+ || maybe_ne (get_frame_size () + crtl->outgoing_args_size, 0)
+ || !hard_reg_set_empty_p (allocated)
+ || riscv_ira_save_mask (allocated, GP_REG_FIRST)
+ || (TARGET_HARD_FLOAT && riscv_ira_save_mask (allocated, FP_REG_FIRST)))
+ return 0;
+ /* Model the entry/exit addi pair as one GPR move. */
+ return riscv_register_move_cost (word_mode, GR_REGS, GR_REGS);
+}
+
+/* Implement TARGET_CALLEE_SAVE_COST. Return the marginal cost of one
+ scalar save or restore. */
+
+static int
+riscv_callee_save_cost (spill_cost_type type, unsigned int regno,
+ machine_mode mode, unsigned int nregs, int mem_cost,
+ const HARD_REG_SET &allocated, bool existing_spills_p)
+{
+ /* Charge a new frame only while IRA has not committed to spilling. */
+ int frame_cost = (existing_spills_p ? 0 : riscv_frame_allocation_cost
+ (type == spill_cost_type::SAVE ? frame_cost_type::ALLOCATION
+ :
frame_cost_type::DEALLOCATION,
+ allocated));
+ bool gp = GP_REG_P (regno);
+ /* Keep special frames and multi-register candidates on the fallback.
+ Shrink-wrapping only moves the prologue; the number and adjacency
+ of the emitted saves are unchanged. */
+ if (cfun->machine->naked_p || cfun->machine->interrupt_handler_p
+ || crtl->calls_eh_return || crtl->abi->id () != RISCV_CC_BASE
+ || TARGET_SAVE_RESTORE
+ || (!gp && !FP_REG_P (regno)) || nregs != 1
+ || hard_regno_nregs (regno, mode) != 1)
+ return mem_cost + frame_cost;
+
+ unsigned int first = gp ? GP_REG_FIRST : FP_REG_FIRST;
+ unsigned int mask = riscv_ira_save_mask (allocated, first);
+ unsigned int bit = 1U << (regno - first);
+ /* An already-required save costs nothing: the save and restore run
+ anyway, so the value parked there displaces spill code for free. */
+ if (mask & bit)
+ return 0;
+
+ /* An odd save count leaves a partner at a descending offset, and the
+ scheduler positions same-base consecutive-offset pairs adjacently,
+ so completing a pair halves its issue cost. The two saves still
+ issue as two instructions, hence only half the memory cost is
+ discounted. FIXME: Refine XTheadMemPair reach and overlapping
+ fusion estimates. */
+ unsigned HOST_WIDE_INT fusion
+ = (optimize_function_for_size_p (cfun) ? 0 : riscv_get_fusible_ops ());
+ if ((popcount_hwi (mask) & 1)
+ && ((gp && TARGET_XTHEADMEMPAIR)
+ || (fusion & (gp ? RISCV_FUSE_LDST_PAIR_DEC
+ : RISCV_FUSE_FLDFST_PAIR_DEC))))
+ mem_cost /= 2;
+ return mem_cost + frame_cost;
+}
+
/* Return the number of instructions that can be issued per cycle. */
static int
@@ -17353,6 +17445,10 @@ riscv_memtag_tag_bitsize ()
#undef TARGET_CONDITIONAL_REGISTER_USAGE
#define TARGET_CONDITIONAL_REGISTER_USAGE riscv_conditional_register_usage
+#undef TARGET_FRAME_ALLOCATION_COST
+#define TARGET_FRAME_ALLOCATION_COST riscv_frame_allocation_cost
+#undef TARGET_CALLEE_SAVE_COST
+#define TARGET_CALLEE_SAVE_COST riscv_callee_save_cost
#undef TARGET_CLASS_MAX_NREGS
#define TARGET_CLASS_MAX_NREGS riscv_class_max_nregs
diff --git a/gcc/testsuite/gcc.target/riscv/callee-save-cost-fusion.c
b/gcc/testsuite/gcc.target/riscv/callee-save-cost-fusion.c
new file mode 100644
index 00000000000..773dbff4a52
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/callee-save-cost-fusion.c
@@ -0,0 +1,21 @@
+/* { dg-do compile { target rv64 } } */
+/* { dg-skip-if "" { *-*-* } { "-O0" "-O1" "-O[sgz]" "-flto" } } */
+/* { dg-options "-O2 -march=rv64gc -mabi=lp64d -mtune=xt-c9501fdvt" } */
+
+/* Verify the pair-completion discount: with an odd required save count
+ (ra), completing the pair with s0 costs half the memory cost, so IRA
+ keeps x in a callee-saved register instead of spilling it around the
+ call. */
+
+extern void call (void);
+volatile int sink;
+
+void
+reuse_pair (int x)
+{
+ call ();
+ sink = x;
+}
+
+/* { dg-final { scan-assembler-times {\tsd\tra,} 1 } } */
+/* { dg-final { scan-assembler-times {\tsd\ts0,} 1 } } */
diff --git a/gcc/testsuite/gcc.target/riscv/callee-save-cost-mempair.c
b/gcc/testsuite/gcc.target/riscv/callee-save-cost-mempair.c
new file mode 100644
index 00000000000..57178938576
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/callee-save-cost-mempair.c
@@ -0,0 +1,22 @@
+/* { dg-do compile { target rv64 } } */
+/* { dg-skip-if "" { *-*-* } { "-O0" "-O1" "-O3" "-O[sgz]" "-flto" } } */
+/* { dg-options "-O2 -march=rv64imac_xtheadmempair -mabi=lp64 -mtune=rocket" }
*/
+/* { dg-additional-options "-fomit-frame-pointer -fno-shrink-wrap" } */
+
+/* Verify the pair-completion discount with XTheadMemPair: with an odd
+ required save count (ra), completing the pair with s0 is cheap, so
+ IRA saves both in one th.sdd instead of spilling x around the
+ call. */
+
+extern void call (void);
+volatile float sink;
+
+void
+reuse_pair (float x)
+{
+ call ();
+ sink = x;
+}
+
+/* { dg-final { scan-assembler-times {th.sdd\s+} 1 } } */
+/* { dg-final { scan-assembler-times {th.ldd\s+} 1 } } */
--
2.52.0