diff --git a/gcc/config.gcc b/gcc/config.gcc
index 40cbc52dc994..ac02d1835372 100644
--- a/gcc/config.gcc
+++ b/gcc/config.gcc
@@ -520,7 +520,7 @@ pru-*-*)
 	;;
 riscv*)
 	cpu_type=riscv
-	extra_objs="riscv-builtins.o riscv-c.o"
+	extra_objs="riscv-builtins.o riscv-c.o riscv-sr.o"
 	d_target_objs="riscv-d.o"
 	;;
 rs6000*-*-*)
diff --git a/gcc/config/riscv/riscv-sr.c b/gcc/config/riscv/riscv-sr.c
new file mode 100644
index 000000000000..7d12c0d82111
--- /dev/null
+++ b/gcc/config/riscv/riscv-sr.c
@@ -0,0 +1,375 @@
+/* This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+/* This file contains code aimed at optimising function generated with the
+   use of '-msave-restore.  The goal is to identify cases where the call
+   out to the save/restore routines are sub-optimal, and remove the calls
+   in this case.
+
+   As GCC currently makes the choice between using or not using
+   save/restore early on (during the gimple expand pass) once we have
+   selected to use save/restore we are stuck with it.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "rtl.h"
+#include "function.h"
+#include "memmodel.h"
+#include "emit-rtl.h"
+#include "target.h"
+#include "basic-block.h"
+#include "bitmap.h"
+#include "df.h"
+#include "tree.h"
+#include "expr.h"
+
+/* Look in the function prologue for a call to the save stub.  Ensure that
+   the instruction is as we expect (see detail below) and if the
+   instruction matches return a pointer to it.  Otherwise, return NULL.
+
+   We expect the function prologue to look like this:
+
+   (note NOTE_INSN_BASIC_BLOCK)
+   (insn (parallel [
+	          (unspec_volatile [
+	                  (const_int 2 [0x2])
+	              ] UNSPECV_GPR_SAVE)
+	          (clobber (reg:SI 5 t0))
+	          (clobber (reg:SI 6 t1))])
+   (note NOTE_INSN_PROLOGUE_END)
+
+   Between the NOTE_INSN_BASIC_BLOCK and the GPR_SAVE insn we might find
+   other notes of type NOTE_INSN_DELETED and/or NOTE_INSN_FUNCTION_BEG.  */
+
+static rtx_insn *
+riscv_sr_match_prologue (void)
+{
+  rtx_insn *insn, *bb_note;
+  bool seen_first_insn = false;
+
+  /* Find the prologue end note.  */
+  for (insn = get_insns (); insn != NULL; insn = NEXT_INSN (insn))
+    if (NOTE_P (insn) && NOTE_KIND (insn) == NOTE_INSN_PROLOGUE_END)
+      break;
+
+  /* If we don't have the prologue end note and at least one instruction
+     before it, then this function doesn't have the structure we expect.  */
+  if (insn == NULL
+      || PREV_INSN (insn) == NULL)
+    return NULL;
+
+  /* The INSN is the end of prologue note, before this we expect to find
+     one real instruction which makes the prologue, and before that we
+     expect to find sone number of notes for deleted instructions, the
+     beginning of the function, and finally a basicblock beginning.  The
+     following loop checks that this assumption is true.  */
+  for (bb_note = PREV_INSN (PREV_INSN (insn));
+       bb_note != NULL;
+       bb_note = PREV_INSN (bb_note))
+    {
+      if (!NOTE_P (bb_note))
+	return NULL;
+      if (NOTE_KIND (bb_note) == NOTE_INSN_BASIC_BLOCK)
+	break;
+      if (NOTE_KIND (bb_note) != NOTE_INSN_DELETED
+	  && NOTE_KIND (bb_note) != NOTE_INSN_FUNCTION_BEG)
+	return NULL;
+    }
+  if (bb_note == NULL)
+    return NULL;
+
+  /* Set INSN to point to the actual interesting prologue instruction.  */
+  insn = PREV_INSN (insn);
+  if (INSN_P (insn)
+      && INSN_CODE (insn) == CODE_FOR_gpr_save
+      /* Check this is a call to _riscv_save_0.  */
+      && GET_CODE (PATTERN (insn)) == PARALLEL
+      && GET_CODE (XVECEXP (PATTERN (insn), 0, 0)) == UNSPEC_VOLATILE
+      && (GET_CODE (XVECEXP (XVECEXP (PATTERN (insn), 0, 0), 0, 0))
+	  == CONST_INT)
+      && XINT (XVECEXP (XVECEXP (PATTERN (insn), 0, 0), 0, 0), 0) == 2)
+    return insn;
+
+  return NULL;
+}
+
+/* Find the first instruction in the epilogue of the current function, and
+   return a pointer to that instruction if, and only if, the epilogue has
+   the correct structure that would allow us to optimise out the call to
+   _riscv_restore_0.  */
+
+static rtx_insn *
+riscv_sr_match_epilogue (void)
+{
+  /* Find the first instruction in the epilogue.  */
+  rtx_insn *insn, *start;
+  for (insn = get_insns (); insn != NULL; insn = NEXT_INSN (insn))
+    if (NOTE_P (insn) && NOTE_KIND (insn) == NOTE_INSN_EPILOGUE_BEG)
+      {
+	insn = NEXT_INSN (insn);
+	break;
+      }
+  if (insn == NULL)
+    return NULL;
+
+  /* At this point INSN is the first instruction in the epilogue.  A
+     standard epilogue (of the form we expect to handle) consists of the
+     following instructions:
+
+     1. A stack_tiesi or stack_tiedi (for RV32 and RV64 respectively),
+
+     2. An optional use instruction for the register holding the return
+        value.  This will be missing in functions with no return value,
+
+     3. A gpr_restore instruction, and
+
+     4. A jump instruction of type gpr_restore_return.  */
+  start = insn;
+  if (INSN_CODE (insn) != CODE_FOR_stack_tiesi
+      && INSN_CODE (insn) != CODE_FOR_stack_tiedi)
+    return NULL;
+
+  insn = NEXT_INSN (insn);
+  if (INSN_P (insn) && GET_CODE (PATTERN (insn)) == USE)
+    insn = NEXT_INSN (insn);
+
+  if (!INSN_P (insn) || INSN_CODE (insn) != CODE_FOR_gpr_restore)
+    return NULL;
+
+  insn = NEXT_INSN (insn);
+  if (!INSN_P (insn) || INSN_CODE (insn) != CODE_FOR_gpr_restore_return)
+    return NULL;
+
+  return start;
+}
+
+/* Entry point called from riscv_reorg to remove some unneeded calls to
+   the save and restore stubs.  This should only be called when
+   -msave-restore is in use.
+
+   We identify some simple cases where the function looks like this:
+
+   call t0,__riscv_save_0
+   <other-code>
+   call foo
+   tail __riscv_restore_0
+
+   And transform it into something like this:
+
+   <other-code>
+   tail foo
+
+   In the above examples, what can appear in <other-code> is pretty
+   restricted; only caller saved registers can be touched, this prevents
+   any additional calls (as they would write to 'ra').  */
+
+void
+riscv_remove_unneeded_save_restore_calls (void)
+{
+  /* Should only be called with -msave-restore is in use.  */
+  gcc_assert (TARGET_SAVE_RESTORE);
+
+  /* Match the expected prologue and epilogue patterns.  If either of these
+     fail to match then we abandon our attempt to optimise this function.  */
+  rtx_insn *prologue_matched = riscv_sr_match_prologue();
+  if (prologue_matched == NULL)
+    return;
+
+  rtx_insn *epilogue_matched = riscv_sr_match_epilogue ();
+  if (epilogue_matched == NULL)
+    return;
+
+  if (dump_file)
+    fprintf (dump_file,
+	     "Could be a candidate for save/restore removal\n");
+
+  /* We want to check which registers this function uses.  */
+  df_analyze ();
+
+  /* This finds the prologue end note.  */
+  rtx_insn *body;
+  for (body = get_insns (); body != NULL; body = NEXT_INSN (body))
+    if (NOTE_P (body) && NOTE_KIND (body) == NOTE_INSN_PROLOGUE_END)
+      {
+	body = NEXT_INSN (body);
+	break;
+      }
+
+  int call_count = 0;
+  bool good_use = true;
+  df_ref use;
+
+  /* Now examine all of the instructions up to the epilogue begin note.  */
+  rtx_insn *insn;
+  for (insn = body;
+       !NOTE_P (insn) || NOTE_KIND (insn) != NOTE_INSN_EPILOGUE_BEG;
+       insn = NEXT_INSN (insn))
+    {
+      if (!INSN_P (insn))
+	continue;
+
+      if (CALL_P (insn))
+	++call_count;
+      else
+	{
+	  FOR_EACH_INSN_USE (use, insn)
+	    {
+	      /* If the function makes use of any registers that are callee
+		 saved then we should be saving them in this function,
+		 which would suggest that a call to the save and restore
+		 functions is required.  This would seem to indicate that
+		 something has gone wrong above, as we should only get here
+		 if we are saving zero registers.
+
+		 The one exception to this rule is the return address
+		 register used within a call instruction.  We can optimise
+		 a single call within a function (by making it a tail
+		 call), so we skip call instructions here.  */
+	      if (CALLEE_SAVED_REG_P (DF_REF_REGNO (use)))
+		{
+		  good_use = false;
+		  break;
+		}
+	    }
+	  if (!good_use)
+	    break;
+	}
+    }
+
+  /* If we used any registers that would indicate a need for a call to a
+     save/restore stub then don't optimise.  */
+  if (!good_use)
+    {
+      if (dump_file)
+	fprintf (dump_file,
+		 "Found unsupported use of callee saved register\n");
+      return;
+    }
+
+  /* We can only optimise functions containing a single call, any more
+     would require us to add instructions to store the return address on
+     the stack (and restore it before we return).  We could do this in the
+     future, but for now we don't.  A single call can be transformed into
+     a tail call reasonably easily.  */
+  if (call_count > 1)
+    {
+      if (dump_file)
+	fprintf (dump_file,
+		 "Found too many call instructions\n");
+      return;
+    }
+
+  rtx_insn *epilogue_begin_note = insn;
+  gcc_assert (NOTE_P (epilogue_begin_note)
+	      && NOTE_KIND (epilogue_begin_note) == NOTE_INSN_EPILOGUE_BEG);
+
+  df_finish_pass (false);
+
+  /* Find the first instruction before the function epilogue.  */
+  rtx_insn *insn_before_epilogue;
+  for (insn_before_epilogue = PREV_INSN (epilogue_begin_note);
+       NOTE_P (insn_before_epilogue);
+       insn_before_epilogue = PREV_INSN (insn_before_epilogue))
+    ;
+
+  /* Leaf functions will not generate calls to the save/restore stubs, so
+     there's no need for this optimisation there.  We know this function
+     has no more than 1 call (checked above).  To convert this single call
+     into a tail call we rely on the call being the last thing before the
+     epilogue.  */
+  if (GET_CODE (insn_before_epilogue) != CALL_INSN)
+    return;
+
+  /* The last instruction in this block, just before the epilogue is a
+     call.  We can potentially change this call into a tail call.  */
+  rtx_insn *call = insn_before_epilogue;
+
+  /* Transform call in insn to a sibcall, this will only be done if the
+     last thing in the function is a call.  */
+  rtx callpat = PATTERN (call);
+  gcc_assert (GET_CODE (callpat) == PARALLEL);
+
+  /* Extract from CALLPAT the information we need to build the sibcall.  */
+  rtx target_call = NULL;
+  rtx tmp_rtx = XVECEXP (callpat, 0, 0);
+  rtx set_target = NULL;
+  switch (GET_CODE (tmp_rtx))
+    {
+    case CALL:
+      target_call = tmp_rtx;
+      break;
+
+    case SET:
+      {
+	set_target = XEXP (tmp_rtx, 0);
+	tmp_rtx = XEXP (tmp_rtx, 1);
+	if (GET_CODE (tmp_rtx) != CALL)
+	  return;
+	target_call = tmp_rtx;
+	break;
+      }
+
+    default:
+      return;
+    }
+
+  rtx target_mem = XEXP (target_call, 0);
+  if (GET_CODE (target_mem) != MEM)
+    return;
+
+  rtx target = XEXP (target_mem, 0);
+  if (GET_CODE (target) != SYMBOL_REF && GET_CODE (target) != REG)
+    return;
+
+  /* The sibcall instructions can only use a specific subset of
+     registers, we're about to (possibly) move a call through a
+     register from the function body and make it a sibcall.  If we're
+     not using an appropriate register then we can't make this change.
+
+     Maybe in some future iteration we could actually scan the
+     function, find a suitable sibcall register, and switch over the
+     registers.  But we don't do that yet.  */
+  if (GET_CODE (target) == REG
+      && !SIBCALL_REG_P (REGNO (target)))
+    return;
+
+  rtx sibcall = NULL;
+  if (set_target != NULL)
+    sibcall
+      = gen_sibcall_value_internal (set_target, target, const0_rtx);
+  else
+    sibcall = gen_sibcall_internal (target, const0_rtx);
+
+  rtx_insn *before_call = PREV_INSN (call);
+  remove_insn (call);
+  insn = emit_call_insn_after (sibcall, before_call);
+  REG_NOTES (insn) = REG_NOTES (call);
+  SIBLING_CALL_P (insn) = 1;
+
+  /* Now update the prologue and epilogue to take account of the
+     changes within the function body.  */
+  remove_insn (prologue_matched);
+  remove_insn (NEXT_INSN (NEXT_INSN (NEXT_INSN (epilogue_matched))));
+  remove_insn (NEXT_INSN (NEXT_INSN (epilogue_matched)));
+  remove_insn (NEXT_INSN (epilogue_matched));
+  remove_insn (epilogue_matched);
+
+  if (dump_file)
+    fprintf (dump_file,
+	     "Save/restore successfully removed\n");
+}
diff --git a/gcc/config/riscv/riscv.c b/gcc/config/riscv/riscv.c
index c12b26f0dc40..daa2648ebb09 100644
--- a/gcc/config/riscv/riscv.c
+++ b/gcc/config/riscv/riscv.c
@@ -4996,6 +4996,16 @@ riscv_promote_function_mode (const_tree type ATTRIBUTE_UNUSED,
   return mode;
 }
 
+/* Implement TARGET_MACHINE_DEPENDENT_REORG.  */
+
+static void
+riscv_reorg (void)
+{
+  /* Do nothing unless we have -msave-restore */
+  if (TARGET_SAVE_RESTORE)
+    riscv_remove_unneeded_save_restore_calls ();
+}
+
 /* Initialize the GCC target structure.  */
 #undef TARGET_ASM_ALIGNED_HI_OP
 #define TARGET_ASM_ALIGNED_HI_OP "\t.half\t"
@@ -5170,6 +5180,9 @@ riscv_promote_function_mode (const_tree type ATTRIBUTE_UNUSED,
 #undef TARGET_CUSTOM_FUNCTION_DESCRIPTORS
 #define TARGET_CUSTOM_FUNCTION_DESCRIPTORS 1
 
+#undef TARGET_MACHINE_DEPENDENT_REORG
+#define TARGET_MACHINE_DEPENDENT_REORG riscv_reorg
+
 struct gcc_target targetm = TARGET_INITIALIZER;
 
 #include "gt-riscv.h"
diff --git a/gcc/config/riscv/riscv.h b/gcc/config/riscv/riscv.h
index bb8240bb849a..54e10954e3fe 100644
--- a/gcc/config/riscv/riscv.h
+++ b/gcc/config/riscv/riscv.h
@@ -289,6 +289,22 @@ along with GCC; see the file COPYING3.  If not see
 #define FP_REG_P(REGNO)  \
   ((unsigned int) ((int) (REGNO) - FP_REG_FIRST) < FP_REG_NUM)
 
+/* True when REGNO is in SIBCALL_REGS set.  */
+#define SIBCALL_REG_P(REGNO)					\
+  (GP_REG_P(REGNO) && (((REGNO) >= 5 && (REGNO) <= 7)		\
+		       || ((REGNO) >= 10 && (REGNO) <= 17)	\
+		       || ((REGNO) >= 28 && (REGNO) <= 31)))
+
+/* True if REGNO is a register saved by the callee.  */
+#define CALLEE_SAVED_REG_P(REGNO)			\
+  ((GP_REG_P (REGNO)					\
+    && ((REGNO) == 2					\
+	|| ((REGNO) >= 8 && (REGNO) <= 9)		\
+	|| ((REGNO) >= 18 && (REGNO) <= 27)))		\
+   || (FP_REG_P (REGNO)					\
+       && (((REGNO) >= 40 && (REGNO) <= 41)		\
+	   || ((REGNO) >= 50 && (REGNO) <= 59))))
+
 #define FP_REG_RTX_P(X) (REG_P (X) && FP_REG_P (REGNO (X)))
 
 /* Use s0 as the frame pointer if it is so requested.  */
@@ -918,4 +934,8 @@ extern unsigned riscv_stack_boundary;
 #define SWSP_REACH (4LL << C_SxSP_BITS)
 #define SDSP_REACH (8LL << C_SxSP_BITS)
 
+/* Called from RISCV_REORG, this is defined in riscv-sr.c.  */
+
+extern void riscv_remove_unneeded_save_restore_calls (void);
+
 #endif /* ! GCC_RISCV_H */
diff --git a/gcc/config/riscv/t-riscv b/gcc/config/riscv/t-riscv
index ece3a75d512e..5ecb3c160a61 100644
--- a/gcc/config/riscv/t-riscv
+++ b/gcc/config/riscv/t-riscv
@@ -5,6 +5,11 @@ riscv-builtins.o: $(srcdir)/config/riscv/riscv-builtins.c $(CONFIG_H) \
 	$(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) $(INCLUDES) \
 		$(srcdir)/config/riscv/riscv-builtins.c
 
+riscv-sr.o: $(srcdir)/config/riscv/riscv-sr.c $(CONFIG_H) \
+  $(SYSTEM_H)
+	$(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) $(INCLUDES) \
+		$(srcdir)/config/riscv/riscv-sr.c
+
 riscv-c.o: $(srcdir)/config/riscv/riscv-c.c $(CONFIG_H) $(SYSTEM_H) \
     coretypes.h $(TM_H) $(TREE_H) output.h $(C_COMMON_H) $(TARGET_H)
 	$(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) $(INCLUDES) \
diff --git a/gcc/testsuite/gcc.target/riscv/save-restore-2.c b/gcc/testsuite/gcc.target/riscv/save-restore-2.c
new file mode 100644
index 000000000000..204bf67b66e5
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/save-restore-2.c
@@ -0,0 +1,22 @@
+/* { dg-options "-Os -msave-restore" } */
+
+/* With -msave-restore in use it should not be possible to remove the calls
+   to the save and restore stubs in this case (in current GCC).  */
+
+extern void fn2 ();
+
+volatile int a = 0;
+
+int
+fn1 ()
+{
+  fn2 ();
+
+  while (a)
+    ;
+
+  return 0;
+}
+
+/* { dg-final { scan-assembler "call\[ \t\]*t0,__riscv_save_0" } } */
+/* { dg-final { scan-assembler "tail\[ \t\]*__riscv_restore_0" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/save-restore-3.c b/gcc/testsuite/gcc.target/riscv/save-restore-3.c
new file mode 100644
index 000000000000..6bf9fb014d6b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/save-restore-3.c
@@ -0,0 +1,16 @@
+/* { dg-options "-Os -msave-restore" } */
+
+/* With -msave-restore in use GCC should be able to remove the calls to the
+   save and restore stubs in this case, replacing them with a tail call to
+   foo.  */
+
+extern int foo ();
+
+int bar ()
+{
+  return foo ();
+}
+
+/* { dg-final { scan-assembler-not "call\[ \t\]*t0,__riscv_save_0" } } */
+/* { dg-final { scan-assembler-not "tail\[ \t\]*__riscv_restore_0" } } */
+/* { dg-final { scan-assembler "tail\[ \t\]*foo" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/save-restore-4.c b/gcc/testsuite/gcc.target/riscv/save-restore-4.c
new file mode 100644
index 000000000000..c1c10c604a1b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/save-restore-4.c
@@ -0,0 +1,27 @@
+/* { dg-options "-Os -msave-restore" } */
+
+/* This test covers a case where we can't (currently) remove the calls to
+   the save/restore stubs.  The cast of the return value from BAR requires
+   a sign extension between the call to BAR, and the return from FOO, this
+   currently prevents the removal of the save/restore calls.  */
+
+typedef enum SomeType
+  {
+   A = 0,
+   B = 1,
+   C = 2,
+   D = 3
+  }
+  SomeType;
+
+typedef unsigned int u_32;
+
+extern u_32 bar (u_32 arg);
+
+SomeType foo (u_32 arg)
+{
+  return (SomeType) bar (arg);
+}
+
+/* { dg-final { scan-assembler "call\[ \t\]*t0,__riscv_save_0" } } */
+/* { dg-final { scan-assembler "tail\[ \t\]*__riscv_restore_0" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/save-restore-5.c b/gcc/testsuite/gcc.target/riscv/save-restore-5.c
new file mode 100644
index 000000000000..fe0ffdcd5044
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/save-restore-5.c
@@ -0,0 +1,9 @@
+typedef int (*FPTR) (void);
+FPTR a;
+
+int
+func ()
+{
+  int b = a ();
+  return b;
+}
diff --git a/gcc/testsuite/gcc.target/riscv/save-restore-6.c b/gcc/testsuite/gcc.target/riscv/save-restore-6.c
new file mode 100644
index 000000000000..c5d25ad006fe
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/save-restore-6.c
@@ -0,0 +1,16 @@
+/* { dg-options "-Os -msave-restore" } */
+
+/* With -msave-restore in use GCC should be able to remove the calls to the
+   save and restore stubs in this case, replacing them with a tail call to
+   foo.  */
+
+extern void other_func ();
+
+void func ()
+{
+  other_func ();
+}
+
+/* { dg-final { scan-assembler-not "call\[ \t\]*t0,__riscv_save_0" } } */
+/* { dg-final { scan-assembler-not "tail\[ \t\]*__riscv_restore_0" } } */
+/* { dg-final { scan-assembler "tail\[ \t\]*other_func" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/save-restore-7.c b/gcc/testsuite/gcc.target/riscv/save-restore-7.c
new file mode 100644
index 000000000000..06719c4e4138
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/save-restore-7.c
@@ -0,0 +1,30 @@
+/* { dg-options "-Os -msave-restore" } */
+
+/* With -msave-restore in use it should not be possible to remove the calls
+   to the save and restore stubs in this case (in current GCC).  */
+
+enum
+  {
+   VAL_A,
+   VAL_B,
+   VAL_C,
+   VAL_D
+  } a;
+
+extern void other_1 ();
+extern void other_2 ();
+
+void func ()
+{
+  switch (a)
+    {
+    case VAL_B:
+    case VAL_C:
+      other_1 ();
+    case VAL_D:
+      other_2 ();
+    }
+}
+
+/* { dg-final { scan-assembler "call\[ \t\]*t0,__riscv_save_0" } } */
+/* { dg-final { scan-assembler "tail\[ \t\]*__riscv_restore_0" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/save-restore-8.c b/gcc/testsuite/gcc.target/riscv/save-restore-8.c
new file mode 100644
index 000000000000..8880cd288eea
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/save-restore-8.c
@@ -0,0 +1,12 @@
+/* { dg-options "-Os -msave-restore" } */
+
+/* As a leaf function this should never have the calls to the save and
+   restore stubs added, but lets check anyway.  */
+
+int func ()
+{
+  return 3;
+}
+
+/* { dg-final { scan-assembler-not "call\[ \t\]*t0,__riscv_save_0" } } */
+/* { dg-final { scan-assembler-not "tail\[ \t\]*__riscv_restore_0" } } */
