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.

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.
---
 gcc/lra-eliminations.cc                       | 12 +++++-
 .../gcc.target/alpha/frame-pointer-save-1.c   | 37 +++++++++++++++++++
 2 files changed, 48 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/gcc.target/alpha/frame-pointer-save-1.c

diff --git ./gcc/lra-eliminations.cc ./gcc/lra-eliminations.cc
index 40f05563566..2aa83e2aa42 100644
--- ./gcc/lra-eliminations.cc
+++ ./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 ./gcc/testsuite/gcc.target/alpha/frame-pointer-save-1.c 
./gcc/testsuite/gcc.target/alpha/frame-pointer-save-1.c
new file mode 100644
index 00000000000..97fa7c474e6
--- /dev/null
+++ ./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" } } */
-- 
2.54.0

Reply via email to