[gcc r17-2511] [PATCH] lra: mark the hard frame pointer live when LRA decides it is needed [PR117184]

Jeff Law via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
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" } } */
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.