The regrename pass runs after prologue/epilogue generation and may
rename temporaries to callee-saved registers that the prologue did
not save.  This corrupts the caller's register values on return or
exception unwind.

The IRA callee-save cost hooks added by r15-8547-gb191e8bdecf changed
the cost model so that IRA leaves more callee-saved registers unused.
The epilogue's stack_tie references s0 (hard frame pointer), which
makes df_regs_ever_live_p(s0) true after the prologue is generated.
regrename's check_new_reg_p relies on df_regs_ever_live_p as a proxy
for "saved by prologue", so it treats s0 as available even though it
was not saved.

Fix by checking frame.mask/frame.fmask/frame.vmask in
HARD_REGNO_RENAME_OK instead.

gcc/ChangeLog:

        * config/riscv/riscv.cc (riscv_hard_regno_rename_ok): Reject
        callee-saved registers not present in frame.mask, frame.fmask,
        or frame.vmask.

gcc/testsuite/ChangeLog:

        * g++.target/riscv/regrename-unwind.C: New test.
---
 gcc/config/riscv/riscv.cc                     | 19 +++++++-
 .../g++.target/riscv/regrename-unwind.C       | 45 +++++++++++++++++++
 2 files changed, 63 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/g++.target/riscv/regrename-unwind.C

diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index 873defc5a5b..2ba24559aa3 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -12952,7 +12952,24 @@ riscv_hard_regno_rename_ok (unsigned from_regno 
ATTRIBUTE_UNUSED,
   /* Interrupt functions can only use registers that have already been
      saved by the prologue, even if they would normally be
      call-clobbered.  */
-  return !cfun->machine->interrupt_handler_p || df_regs_ever_live_p (to_regno);
+  if (cfun->machine->interrupt_handler_p)
+    return df_regs_ever_live_p (to_regno);
+
+  /* Registers not saved by the prologue must not be renamed into.
+     Check the frame save masks directly, since df_regs_ever_live_p
+     can look live merely because of a stack_tie in the epilogue,
+     without the register having actually been saved.  */
+  if (crtl->abi->clobbers_full_reg_p (to_regno))
+    return true;
+
+  if (GP_REG_P (to_regno))
+    return (cfun->machine->frame.mask >> to_regno) & 1;
+  if (FP_REG_P (to_regno))
+    return (cfun->machine->frame.fmask >> (to_regno - FP_REG_FIRST)) & 1;
+  if (V_REG_P (to_regno))
+    return (cfun->machine->frame.vmask >> (to_regno - V_REG_FIRST)) & 1;
+
+  return true;
 }
 
 /* Implement TARGET_NEW_ADDRESS_PROFITABLE_P.  */
diff --git a/gcc/testsuite/g++.target/riscv/regrename-unwind.C 
b/gcc/testsuite/g++.target/riscv/regrename-unwind.C
new file mode 100644
index 00000000000..0b133d72640
--- /dev/null
+++ b/gcc/testsuite/g++.target/riscv/regrename-unwind.C
@@ -0,0 +1,45 @@
+// { dg-do run }
+// { dg-options "-O2 -frename-registers -fprofile-generate" }
+// { dg-require-profiling "-fprofile-generate" }
+// { dg-require-effective-target exceptions_enabled }
+
+/* Verify that regrename does not rename temporaries to callee-saved
+   registers that were not saved by the prologue.  With
+   -fprofile-generate the gcov instrumentation in the throw path
+   provides enough temporaries for regrename to pick s0, which the
+   prologue did not save.  Exception unwinding then fails to restore
+   s0 and the caller sees a corrupted value.  */
+
+extern "C" void abort (void);
+
+struct MyException {};
+struct Data {
+    int nr;
+    Data() : nr(66) {}
+};
+
+Data __attribute__((noinline,noclone))
+getData (int i)
+{
+  if (i)
+    throw MyException ();
+  Data data;
+  data.nr = i;
+  return data;
+}
+
+int
+main (int, char **)
+{
+  Data data;
+  try
+    {
+      data = getData (1);
+    }
+  catch (MyException &)
+    {
+      if (data.nr != 66)
+       abort ();
+    }
+  return 0;
+}
-- 
2.52.0

Reply via email to