https://gcc.gnu.org/g:93352420faab917cc4b6fcf1127b9242fc9d25b0
commit r17-2511-g93352420faab917cc4b6fcf1127b9242fc9d25b0 Author: Matt Turner <[email protected]> Date: Sat Jul 18 10:32:39 2026 -0600 [PATCH] lra: mark the hard frame pointer live when LRA decides it is needed [PR117184] ira_setup_eliminable_regset calls df_set_regs_ever_live for the hard frame pointer when it decides frame_pointer_needed. LRA can reach that decision later instead, in setup_can_eliminate, when it finds the frame pointer to stack pointer elimination is not possible after all, but it does not mark the register live there. A target whose prologue decides which registers to save from df_regs_ever_live_p then sets up the frame pointer without saving the caller's value. On alpha this miscompiles pge while bootstrapping the Modula-2 front end (PR117184): alpha_compute_frame_layout leaves $15 out of the save mask, so alpha_expand_prologue emits the "mov $30,$15" that clobbers it but no matching store, while alpha_expand_epilogue restores the register whenever frame_pointer_needed, from an fp_offset that stayed 0 -- the return address slot. The caller gets its call-saved $15 back as a code address, which shows up much later as a NULL dereference, and the Modula-2 runtime turns the resulting SIGSEGV into an unhandled exception: terminate called after throwing an instance of 'unsigned int' The testcase needs the VLA to reach the caller by inlining: a caller with its own VLA has cfun->calls_alloca set, so IRA already knows a frame pointer is needed and marks $15 live itself. Do what IRA does, so the two paths agree. PR target/117184 gcc/ * lra-eliminations.cc (setup_can_eliminate): Mark the hard frame pointer live when setting frame_pointer_needed. gcc/testsuite/ * gcc.target/alpha/frame-pointer-save-1.c: New test. Diff: --- gcc/lra-eliminations.cc | 12 ++++++- .../gcc.target/alpha/frame-pointer-save-1.c | 37 ++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/gcc/lra-eliminations.cc b/gcc/lra-eliminations.cc index 40f055635667..2aa83e2aa427 100644 --- a/gcc/lra-eliminations.cc +++ b/gcc/lra-eliminations.cc @@ -147,7 +147,17 @@ setup_can_eliminate (class lra_elim_table *ep, bool value) ep->can_eliminate = ep->prev_can_eliminate = value; if (! value && ep->from == FRAME_POINTER_REGNUM && ep->to == STACK_POINTER_REGNUM) - frame_pointer_needed = 1; + { + frame_pointer_needed = 1; + /* ira_setup_eliminable_regset marks the hard frame pointer live when + it decides a frame pointer is needed. When we make that decision + here instead, we have to do the same, otherwise a target whose + prologue keys the register save off df_regs_ever_live_p would + clobber the caller's hard frame pointer without saving it. */ + int fp_reg_count = hard_regno_nregs (HARD_FRAME_POINTER_REGNUM, Pmode); + for (int i = 0; i < fp_reg_count; i++) + df_set_regs_ever_live (HARD_FRAME_POINTER_REGNUM + i, true); + } if (!frame_pointer_needed) REGNO_POINTER_ALIGN (HARD_FRAME_POINTER_REGNUM) = 0; } diff --git a/gcc/testsuite/gcc.target/alpha/frame-pointer-save-1.c b/gcc/testsuite/gcc.target/alpha/frame-pointer-save-1.c new file mode 100644 index 000000000000..97fa7c474e6a --- /dev/null +++ b/gcc/testsuite/gcc.target/alpha/frame-pointer-save-1.c @@ -0,0 +1,37 @@ +/* $15 is call-saved, and a function that needs a frame pointer clobbers it. + The VLA lives in a static helper that is inlined into the caller, so the + caller acquires dynamic stack allocation without frame_pointer_needed + being known when IRA runs; LRA only decides a frame pointer is needed + later, while marking the elimination not possible. The prologue must + still save $15 -- the epilogue restores it unconditionally, and used to + read it back from the return address slot. */ +/* { dg-do compile } */ +/* { dg-options "-O2" } */ + +#include <string.h> + +extern void use (const char *, unsigned); +extern void sink (char); +extern unsigned Indent; + +static void +ind (const char *s_, unsigned n) +{ + char a[n + 1]; + + memcpy (a, s_, n + 1); + for (unsigned i = 0; i < Indent; i++) + sink (' '); + use (a, n); +} + +void +caller (unsigned name) +{ + ind ("", 0); + use ("x", name); +} + +/* If the frame pointer is set up from the stack pointer, $15 must have been + saved first. */ +/* { dg-final { scan-assembler "stq\[ \t\]+\\\$15" } } */
