https://gcc.gnu.org/g:440ade62c96692a25c4b0d68bd47f1c04d6834fb
commit r17-4538-g440ade62c96692a25c4b0d68bd47f1c04d6834fb Author: Ancheng.Qiao <[email protected]> Date: Mon Sep 21 12:48:43 2026 -0600 [PATCH] RISC-V: fix below-SP FPR saves (use-before-allocate) with Zcmp With Zcmp (cm.push) and -Os, the prologue folds the callee-saved FPRs (fs0..fs11, i.e. f8/f9/f18..f31) into the stack region that cm.push reserves using get_multi_push_fpr_mask (multi_push_additional / UNITS_PER_WORD). That region is measured in bytes, but a double is 8 bytes (UNITS_PER_FP_REG), so dividing by the word size (4 bytes) lets the mask fold up to twice as many FPRs as the reserved space can actually hold. The surplus FPRs (typically fs6..fs11, i.e. f22..f27) therefore end up stored at negative offsets of the current SP, with the stack allocation that should cover them deferred to a trailing "addi sp,sp,-N" that comes after the stores. This is a use-the-stack-before-allocating-it defect: callee-saved FP state lives in the not-yet-allocated region below SP, which an asynchronous interrupt running on the interrupted SP (e.g. a nested CLIC/Zcswl handler) can overwrite before the frame is claimed, silently corrupting f22..f27. gcc/ChangeLog: * config/riscv/riscv.cc (riscv_expand_prologue): Fold into the cm.push reserve only as many callee-saved FPRs as it can hold by dividing the additional reserve by UNITS_PER_FP_REG. (riscv_expand_epilogue): Likewise. gcc/testsuite/ChangeLog: * gcc.target/riscv/zcmp_fpr_save_below_sp.c: New test. Compile an FP-register-heavy function at -Os with Zcmp and check that no callee-saved FPR is saved at a negative offset of SP. Signed-off-by: Ancheng.Qiao <[email protected]> Diff: --- gcc/config/riscv/riscv.cc | 4 +- .../gcc.target/riscv/zcmp_fpr_save_below_sp.c | 52 ++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc index 9fca2f679a07..b676292ca566 100644 --- a/gcc/config/riscv/riscv.cc +++ b/gcc/config/riscv/riscv.cc @@ -10563,7 +10563,7 @@ riscv_expand_prologue (void) if (fmask) { unsigned mask_fprs_push - = get_multi_push_fpr_mask (multi_push_additional / UNITS_PER_WORD); + = get_multi_push_fpr_mask (multi_push_additional / UNITS_PER_FP_REG); frame->fmask &= mask_fprs_push; riscv_for_each_saved_reg (remaining_size, riscv_save_reg, false, false, false); @@ -10969,7 +10969,7 @@ riscv_expand_epilogue (int style) if (fmask) { mask_fprs_push = get_multi_push_fpr_mask (frame->multi_push_adj_addi - / UNITS_PER_WORD); + / UNITS_PER_FP_REG); frame->fmask &= ~mask_fprs_push; /* FPRs not saved by cm.push */ } } diff --git a/gcc/testsuite/gcc.target/riscv/zcmp_fpr_save_below_sp.c b/gcc/testsuite/gcc.target/riscv/zcmp_fpr_save_below_sp.c new file mode 100644 index 000000000000..0f60b7e755ed --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/zcmp_fpr_save_below_sp.c @@ -0,0 +1,52 @@ +/* { dg-do compile { target rv32 } } */ +/* { dg-options "-Os -march=rv32imafd_zca_zcmp -mabi=ilp32d -mcmodel=medlow" } */ +/* { dg-skip-if "" { *-*-* } { "-O0" "-O1" "-O2" "-Og" "-O3" "-Oz" "-flto" } } */ +/* { dg-final { scan-assembler "cm\\.push" } } */ +/* { dg-final { scan-assembler-not "fsd\\s+fs\[0-9\]+,-" } } */ +/* + When Zcmp (cm.push) is enabled, GCC may allocate only the GPR save area + with cm.push and then save the callee-saved FPRs (fs0..fs11) at *negative* + offsets of the current SP, deferring the rest of the frame allocation to a + trailing "addi sp,sp,-N". Those below-SP slots are treated as scratch by + the interrupt/preemption machinery (e.g. nested interrupts that run on the + interrupted SP), so they can be silently clobbered, corrupting the + callee-saved FP registers. The prologue must grow the frame before + emitting any callee-saved FPR store, so no "fsd fsN,-...". */ + +double gmem[40]; + +__attribute__((noinline)) double +helper (double a, double b) +{ + return a * b + gmem[0]; +} + +__attribute__((noinline)) double +sink (double x) +{ + gmem[3] = x; + return x; +} + +/* Keep many callee-saved FPRs live across calls so that GCC must spill + fs2..fs11; this is what triggers the below-SP saves with cm.push. */ +double +test_zcmp_fpr_save (double a, double b, double c, double d, double e, + double q, double w, double r, double t, double y, + double u, double i) +{ + double x0 = a, x1 = b, x2 = c, x3 = d, x4 = e; + double x5 = q, x6 = w, x7 = r, x8 = t, x9 = y; + + double acc = helper (x0, x1) + helper (x2, x3) + helper (x4, x5) + + helper (x6, x7) + helper (x8, x9); + double acc2 = helper (x0, x2) + helper (x4, x6) + helper (x8, x9) + + helper (w, t) + helper (r, y) + helper (u, i); + double acc3 = helper (x1, x3) + helper (x5, x7) + helper (x9, y) + + helper (q, w) + helper (t, r) + helper (x0, x6); + + gmem[1] = acc; + gmem[2] = acc2; + gmem[4] = acc3; + return sink (acc + acc2 + acc3); +}
