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" } } */

Reply via email to