diff mbox series

[1/2] x86: Add no_callee_saved_registers function attribute

Message ID 20240120142752.1387725-2-hjl.tools@gmail.com
State New
Headers show
Series x86: Don't save callee-saved registers if not needed | expand

Commit Message

H.J. Lu Jan. 20, 2024, 2:27 p.m. UTC
When an interrupt handler is implemented by an assembly stub which does:

1. Save all registers.
2. Call a C function.
3. Restore all registers.
4. Return from interrupt.

it is completely unnecessary to save and restore any registers in the C
function called by the assembly stub, even if they would normally be
callee-saved.

Add no_callee_saved_registers function attribute, which is complementary
to no_caller_saved_registers function attribute, to mark a function which
doesn't have any callee-saved registers.  Such a function won't save and
restore any registers.  Classify function call-saved register handling
type with:

1. Default call-saved registers.
2. No caller-saved registers with no_caller_saved_registers attribute.
3. No callee-saved registers with no_callee_saved_registers attribute.

Disallow sibcall if callee is a no_callee_saved_registers function
and caller isn't a no_callee_saved_registers function.  Otherwise,
callee-saved registers won't be preserved.

After a no_callee_saved_registers function is called, all registers may
be clobbered.  If the calling function isn't a no_callee_saved_registers
function, we need to preserve all registers which aren't used by function
calls.

gcc/

	PR target/103503
	PR target/113312
	* config/i386/i386-expand.cc (ix86_expand_call): Set
	call_no_callee_saved_registers to true when calling function
	with no_callee_saved_registers attribute.  Replace
	no_caller_saved_registers check with call_saved_registers check.
	* config/i386/i386-options.cc (ix86_set_func_type): Set
	call_saved_registers to TYPE_NO_CALLEE_SAVED_REGISTERS for
	noreturn function.  Disallow no_callee_saved_registers with
	interrupt or no_caller_saved_registers attributes together.
	(ix86_set_current_function): Replace no_caller_saved_registers
	check with call_saved_registers check.
	(ix86_handle_no_caller_saved_registers_attribute): Renamed to ...
	(ix86_handle_call_saved_registers_attribute): This.
	(ix86_gnu_attributes): Add
	ix86_handle_call_saved_registers_attribute.
	* config/i386/i386.cc (ix86_conditional_register_usage): Replace
	no_caller_saved_registers check with call_saved_registers check.
	(ix86_function_ok_for_sibcall): Don't allow callee with
	no_callee_saved_registers attribute when the calling function
	has callee-saved registers.
	(ix86_comp_type_attributes): Also check
	no_callee_saved_registers.
	(ix86_epilogue_uses): Replace no_caller_saved_registers check
	with call_saved_registers check.
	(ix86_hard_regno_scratch_ok): Likewise.
	(ix86_save_reg): Replace no_caller_saved_registers check with
	call_saved_registers check.  Don't save any registers for
	TYPE_NO_CALLEE_SAVED_REGISTERS.  Save all registers with
	TYPE_DEFAULT_CALL_SAVED_REGISTERS if function with
	no_callee_saved_registers attribute is called.
	(find_drap_reg): Replace no_caller_saved_registers check with
	call_saved_registers check.
	* config/i386/i386.h (call_saved_registers_type): New enum.
	(machine_function): Replace no_caller_saved_registers with
	call_saved_registers.  Add call_no_callee_saved_registers.
	* doc/extend.texi: Document no_callee_saved_registers attribute.

gcc/testsuite/

	PR target/103503
	PR target/113312
	* gcc.dg/torture/no-callee-saved-run-1a.c: New file.
	* gcc.dg/torture/no-callee-saved-run-1b.c: Likewise.
	* gcc.target/i386/no-callee-saved-1.c: Likewise.
	* gcc.target/i386/no-callee-saved-2.c: Likewise.
	* gcc.target/i386/no-callee-saved-3.c: Likewise.
	* gcc.target/i386/no-callee-saved-4.c: Likewise.
	* gcc.target/i386/no-callee-saved-5.c: Likewise.
	* gcc.target/i386/no-callee-saved-6.c: Likewise.
	* gcc.target/i386/no-callee-saved-7.c: Likewise.
	* gcc.target/i386/no-callee-saved-8.c: Likewise.
	* gcc.target/i386/no-callee-saved-9.c: Likewise.
	* gcc.target/i386/no-callee-saved-10.c: Likewise.
	* gcc.target/i386/no-callee-saved-11.c: Likewise.
	* gcc.target/i386/no-callee-saved-12.c: Likewise.
	* gcc.target/i386/no-callee-saved-13.c: Likewise.
	* gcc.target/i386/no-callee-saved-14.c: Likewise.
	* gcc.target/i386/no-callee-saved-15.c: Likewise.
	* gcc.target/i386/no-callee-saved-16.c: Likewise.
	* gcc.target/i386/no-callee-saved-17.c: Likewise.
	* gcc.target/i386/no-callee-saved-18.c: Likewise.
---
 gcc/config/i386/i386-expand.cc                | 72 ++++++++++++++++---
 gcc/config/i386/i386-options.cc               | 49 +++++++++----
 gcc/config/i386/i386.cc                       | 70 ++++++++++++++----
 gcc/config/i386/i386.h                        | 20 +++++-
 gcc/doc/extend.texi                           |  8 +++
 .../gcc.dg/torture/no-callee-saved-run-1a.c   | 23 ++++++
 .../gcc.dg/torture/no-callee-saved-run-1b.c   | 59 +++++++++++++++
 .../gcc.target/i386/no-callee-saved-1.c       | 30 ++++++++
 .../gcc.target/i386/no-callee-saved-10.c      | 46 ++++++++++++
 .../gcc.target/i386/no-callee-saved-11.c      | 11 +++
 .../gcc.target/i386/no-callee-saved-12.c      | 10 +++
 .../gcc.target/i386/no-callee-saved-13.c      | 16 +++++
 .../gcc.target/i386/no-callee-saved-14.c      | 16 +++++
 .../gcc.target/i386/no-callee-saved-15.c      | 17 +++++
 .../gcc.target/i386/no-callee-saved-16.c      | 16 +++++
 .../gcc.target/i386/no-callee-saved-17.c      | 16 +++++
 .../gcc.target/i386/no-callee-saved-18.c      | 51 +++++++++++++
 .../gcc.target/i386/no-callee-saved-2.c       | 30 ++++++++
 .../gcc.target/i386/no-callee-saved-3.c       |  8 +++
 .../gcc.target/i386/no-callee-saved-4.c       |  8 +++
 .../gcc.target/i386/no-callee-saved-5.c       | 11 +++
 .../gcc.target/i386/no-callee-saved-6.c       | 12 ++++
 .../gcc.target/i386/no-callee-saved-7.c       | 49 +++++++++++++
 .../gcc.target/i386/no-callee-saved-8.c       | 50 +++++++++++++
 .../gcc.target/i386/no-callee-saved-9.c       | 49 +++++++++++++
 25 files changed, 709 insertions(+), 38 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1b.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-1.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-10.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-11.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-12.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-13.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-14.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-15.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-16.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-17.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-18.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-2.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-3.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-4.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-5.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-6.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-7.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-8.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-9.c

Comments

Hongtao Liu Jan. 22, 2024, 4:03 a.m. UTC | #1
On Sat, Jan 20, 2024 at 10:30 PM H.J. Lu <hjl.tools@gmail.com> wrote:
>
> When an interrupt handler is implemented by an assembly stub which does:
>
> 1. Save all registers.
> 2. Call a C function.
> 3. Restore all registers.
> 4. Return from interrupt.
>
> it is completely unnecessary to save and restore any registers in the C
> function called by the assembly stub, even if they would normally be
> callee-saved.
>
> Add no_callee_saved_registers function attribute, which is complementary
> to no_caller_saved_registers function attribute, to mark a function which
> doesn't have any callee-saved registers.  Such a function won't save and
> restore any registers.  Classify function call-saved register handling
> type with:
>
> 1. Default call-saved registers.
> 2. No caller-saved registers with no_caller_saved_registers attribute.
> 3. No callee-saved registers with no_callee_saved_registers attribute.
>
> Disallow sibcall if callee is a no_callee_saved_registers function
> and caller isn't a no_callee_saved_registers function.  Otherwise,
> callee-saved registers won't be preserved.
>
> After a no_callee_saved_registers function is called, all registers may
> be clobbered.  If the calling function isn't a no_callee_saved_registers
> function, we need to preserve all registers which aren't used by function
> calls.
>
> gcc/
>
>         PR target/103503
>         PR target/113312
>         * config/i386/i386-expand.cc (ix86_expand_call): Set
>         call_no_callee_saved_registers to true when calling function
>         with no_callee_saved_registers attribute.  Replace
>         no_caller_saved_registers check with call_saved_registers check.
>         * config/i386/i386-options.cc (ix86_set_func_type): Set
>         call_saved_registers to TYPE_NO_CALLEE_SAVED_REGISTERS for
>         noreturn function.  Disallow no_callee_saved_registers with
>         interrupt or no_caller_saved_registers attributes together.
>         (ix86_set_current_function): Replace no_caller_saved_registers
>         check with call_saved_registers check.
>         (ix86_handle_no_caller_saved_registers_attribute): Renamed to ...
>         (ix86_handle_call_saved_registers_attribute): This.
>         (ix86_gnu_attributes): Add
>         ix86_handle_call_saved_registers_attribute.
>         * config/i386/i386.cc (ix86_conditional_register_usage): Replace
>         no_caller_saved_registers check with call_saved_registers check.
>         (ix86_function_ok_for_sibcall): Don't allow callee with
>         no_callee_saved_registers attribute when the calling function
>         has callee-saved registers.
>         (ix86_comp_type_attributes): Also check
>         no_callee_saved_registers.
>         (ix86_epilogue_uses): Replace no_caller_saved_registers check
>         with call_saved_registers check.
>         (ix86_hard_regno_scratch_ok): Likewise.
>         (ix86_save_reg): Replace no_caller_saved_registers check with
>         call_saved_registers check.  Don't save any registers for
>         TYPE_NO_CALLEE_SAVED_REGISTERS.  Save all registers with
>         TYPE_DEFAULT_CALL_SAVED_REGISTERS if function with
>         no_callee_saved_registers attribute is called.
>         (find_drap_reg): Replace no_caller_saved_registers check with
>         call_saved_registers check.
>         * config/i386/i386.h (call_saved_registers_type): New enum.
>         (machine_function): Replace no_caller_saved_registers with
>         call_saved_registers.  Add call_no_callee_saved_registers.
>         * doc/extend.texi: Document no_callee_saved_registers attribute.
>
> gcc/testsuite/
>
>         PR target/103503
>         PR target/113312
>         * gcc.dg/torture/no-callee-saved-run-1a.c: New file.
>         * gcc.dg/torture/no-callee-saved-run-1b.c: Likewise.
>         * gcc.target/i386/no-callee-saved-1.c: Likewise.
>         * gcc.target/i386/no-callee-saved-2.c: Likewise.
>         * gcc.target/i386/no-callee-saved-3.c: Likewise.
>         * gcc.target/i386/no-callee-saved-4.c: Likewise.
>         * gcc.target/i386/no-callee-saved-5.c: Likewise.
>         * gcc.target/i386/no-callee-saved-6.c: Likewise.
>         * gcc.target/i386/no-callee-saved-7.c: Likewise.
>         * gcc.target/i386/no-callee-saved-8.c: Likewise.
>         * gcc.target/i386/no-callee-saved-9.c: Likewise.
>         * gcc.target/i386/no-callee-saved-10.c: Likewise.
>         * gcc.target/i386/no-callee-saved-11.c: Likewise.
>         * gcc.target/i386/no-callee-saved-12.c: Likewise.
>         * gcc.target/i386/no-callee-saved-13.c: Likewise.
>         * gcc.target/i386/no-callee-saved-14.c: Likewise.
>         * gcc.target/i386/no-callee-saved-15.c: Likewise.
>         * gcc.target/i386/no-callee-saved-16.c: Likewise.
>         * gcc.target/i386/no-callee-saved-17.c: Likewise.
>         * gcc.target/i386/no-callee-saved-18.c: Likewise.
> ---
>  gcc/config/i386/i386-expand.cc                | 72 ++++++++++++++++---
>  gcc/config/i386/i386-options.cc               | 49 +++++++++----
>  gcc/config/i386/i386.cc                       | 70 ++++++++++++++----
>  gcc/config/i386/i386.h                        | 20 +++++-
>  gcc/doc/extend.texi                           |  8 +++
>  .../gcc.dg/torture/no-callee-saved-run-1a.c   | 23 ++++++
>  .../gcc.dg/torture/no-callee-saved-run-1b.c   | 59 +++++++++++++++
>  .../gcc.target/i386/no-callee-saved-1.c       | 30 ++++++++
>  .../gcc.target/i386/no-callee-saved-10.c      | 46 ++++++++++++
>  .../gcc.target/i386/no-callee-saved-11.c      | 11 +++
>  .../gcc.target/i386/no-callee-saved-12.c      | 10 +++
>  .../gcc.target/i386/no-callee-saved-13.c      | 16 +++++
>  .../gcc.target/i386/no-callee-saved-14.c      | 16 +++++
>  .../gcc.target/i386/no-callee-saved-15.c      | 17 +++++
>  .../gcc.target/i386/no-callee-saved-16.c      | 16 +++++
>  .../gcc.target/i386/no-callee-saved-17.c      | 16 +++++
>  .../gcc.target/i386/no-callee-saved-18.c      | 51 +++++++++++++
>  .../gcc.target/i386/no-callee-saved-2.c       | 30 ++++++++
>  .../gcc.target/i386/no-callee-saved-3.c       |  8 +++
>  .../gcc.target/i386/no-callee-saved-4.c       |  8 +++
>  .../gcc.target/i386/no-callee-saved-5.c       | 11 +++
>  .../gcc.target/i386/no-callee-saved-6.c       | 12 ++++
>  .../gcc.target/i386/no-callee-saved-7.c       | 49 +++++++++++++
>  .../gcc.target/i386/no-callee-saved-8.c       | 50 +++++++++++++
>  .../gcc.target/i386/no-callee-saved-9.c       | 49 +++++++++++++
>  25 files changed, 709 insertions(+), 38 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1a.c
>  create mode 100644 gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1b.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-1.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-10.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-11.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-12.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-13.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-14.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-15.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-16.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-17.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-18.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-2.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-3.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-4.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-5.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-6.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-7.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-8.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-9.c
>
> diff --git a/gcc/config/i386/i386-expand.cc b/gcc/config/i386/i386-expand.cc
> index 52754e114f4..c0c7c697440 100644
> --- a/gcc/config/i386/i386-expand.cc
> +++ b/gcc/config/i386/i386-expand.cc
> @@ -9739,17 +9739,41 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
>    rtx use = NULL, call;
>    unsigned int vec_len = 0.
>    tree fndecl;
> +  bool call_no_callee_saved_registers = false;
>
>    if (GET_CODE (XEXP (fnaddr, 0)) == SYMBOL_REF)
>      {
>        fndecl = SYMBOL_REF_DECL (XEXP (fnaddr, 0));
> -      if (fndecl
> -         && (lookup_attribute ("interrupt",
> -                               TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))))
> -       error ("interrupt service routine cannot be called directly");
> +      if (fndecl)
> +       {
> +         if (lookup_attribute ("interrupt",
> +                               TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))
> +           error ("interrupt service routine cannot be called directly");
> +         else if (lookup_attribute ("no_callee_saved_registers",
> +                                    TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))
> +           {
> +             cfun->machine->call_no_callee_saved_registers = true;
> +             call_no_callee_saved_registers = true;
> +           }
> +       }
>      }
>    else
> -    fndecl = NULL_TREE;
> +    {
> +      if (MEM_P (fnaddr))
> +       {
> +         tree mem_expr = MEM_EXPR (fnaddr);
> +         if (mem_expr != nullptr
> +             && TREE_CODE (mem_expr) == MEM_REF
> +             && lookup_attribute ("no_callee_saved_registers",
> +                                  TYPE_ATTRIBUTES (TREE_TYPE (mem_expr))))
> +           {
> +             cfun->machine->call_no_callee_saved_registers = true;
> +             call_no_callee_saved_registers = true;
> +           }
> +       }
> +
> +      fndecl = NULL_TREE;
> +    }
>
>    if (pop == const0_rtx)
>      pop = NULL;
> @@ -9884,13 +9908,18 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
>        vec[vec_len++] = pop;
>      }
>
> -  if (cfun->machine->no_caller_saved_registers
> +  static const char ix86_call_used_regs[] = CALL_USED_REGISTERS;
> +
> +  char clobbered_registers[FIRST_PSEUDO_REGISTER];
> +  memset (clobbered_registers, 0, sizeof (clobbered_registers));
> +
> +  if ((cfun->machine->call_saved_registers
> +       == TYPE_NO_CALLER_SAVED_REGISTERS)
>        && (!fndecl
>           || (!TREE_THIS_VOLATILE (fndecl)
>               && !lookup_attribute ("no_caller_saved_registers",
>                                     TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))))
>      {
> -      static const char ix86_call_used_regs[] = CALL_USED_REGISTERS;
>        bool is_64bit_ms_abi = (TARGET_64BIT
>                               && ix86_function_abi (fndecl) == MS_ABI);
>        char c_mask = CALL_USED_REGISTERS_MASK (is_64bit_ms_abi);
> @@ -9903,8 +9932,11 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
>                 || (ix86_call_used_regs[i] & c_mask))
>             && !STACK_REGNO_P (i)
>             && !MMX_REGNO_P (i))
> -         clobber_reg (&use,
> -                      gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i));
> +         {
> +           clobber_reg (&use,
> +                        gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i));
> +           clobbered_registers[i] = 1;
> +         }
>      }
>    else if (TARGET_64BIT_MS_ABI
>            && (!callarg2 || INTVAL (callarg2) != -2))
> @@ -9917,6 +9949,7 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
>           machine_mode mode = SSE_REGNO_P (regno) ? TImode : DImode;
>
>           clobber_reg (&use, gen_rtx_REG (mode, regno));
> +         clobbered_registers[i] = 1;
>         }
>
>        /* Set here, but it may get cleared later.  */
> @@ -9953,6 +9986,27 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
>          resolver could be used which clobbers R11 and R10.  */
>        clobber_reg (&use, gen_rtx_REG (DImode, R11_REG));
>        clobber_reg (&use, gen_rtx_REG (DImode, R10_REG));
> +      clobbered_registers[R11_REG] = 1;
> +      clobbered_registers[R10_REG] = 1;
> +    }
> +
> +  if (call_no_callee_saved_registers)
> +    {
> +      /* After calling a no_callee_saved_registers function, all
> +        registers may be clobbered.  Clobber all registers that are
> +        not clobbered yet and not used by the callee.  */
> +      bool is_64bit_ms_abi = (TARGET_64BIT
> +                             && ix86_function_abi (fndecl) == MS_ABI);
> +      char c_mask = CALL_USED_REGISTERS_MASK (is_64bit_ms_abi);
> +      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
> +       if (!fixed_regs[i]
> +           && !clobbered_registers[i]
It seems to me clobbered_registers is only used here which seems
redundant, remove  !clobbered_registers[i] should also be fine?

> +           && !(ix86_call_used_regs[i] == 1
> +                || (ix86_call_used_regs[i] & c_mask))
> +           && !STACK_REGNO_P (i)
> +           && !MMX_REGNO_P (i))
> +         clobber_reg (&use,
> +                      gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i));
>      }
>
>    if (vec_len > 1)
> diff --git a/gcc/config/i386/i386-options.cc b/gcc/config/i386/i386-options.cc
> index b6f634e9a32..0cdea30599e 100644
> --- a/gcc/config/i386/i386-options.cc
> +++ b/gcc/config/i386/i386-options.cc
> @@ -3371,6 +3371,10 @@ ix86_simd_clone_adjust (struct cgraph_node *node)
>  static void
>  ix86_set_func_type (tree fndecl)
>  {
> +  bool has_no_callee_saved_registers
> +    = lookup_attribute ("no_callee_saved_registers",
> +                       TYPE_ATTRIBUTES (TREE_TYPE (fndecl)));
> +
>    if (cfun->machine->func_type == TYPE_UNKNOWN)
>      {
>        if (lookup_attribute ("interrupt",
> @@ -3380,12 +3384,18 @@ ix86_set_func_type (tree fndecl)
>             error_at (DECL_SOURCE_LOCATION (fndecl),
>                       "interrupt and naked attributes are not compatible");
>
> +         if (has_no_callee_saved_registers)
> +           error_at (DECL_SOURCE_LOCATION (fndecl),
> +                     "%qs and %qs attributes are not compatible",
> +                     "interrupt", "no_callee_saved_registers");
> +
>           int nargs = 0;
>           for (tree arg = DECL_ARGUMENTS (fndecl);
>                arg;
>                arg = TREE_CHAIN (arg))
>             nargs++;
> -         cfun->machine->no_caller_saved_registers = true;
> +         cfun->machine->call_saved_registers
> +           = TYPE_NO_CALLER_SAVED_REGISTERS;
>           cfun->machine->func_type
>             = nargs == 2 ? TYPE_EXCEPTION : TYPE_INTERRUPT;
>
> @@ -3401,7 +3411,19 @@ ix86_set_func_type (tree fndecl)
>           cfun->machine->func_type = TYPE_NORMAL;
>           if (lookup_attribute ("no_caller_saved_registers",
>                                 TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))
> -           cfun->machine->no_caller_saved_registers = true;
> +           cfun->machine->call_saved_registers
> +             = TYPE_NO_CALLER_SAVED_REGISTERS;
> +         if (has_no_callee_saved_registers)
> +           {
> +             if (cfun->machine->call_saved_registers
> +                 == TYPE_NO_CALLER_SAVED_REGISTERS)
> +               error_at (DECL_SOURCE_LOCATION (fndecl),
> +                         "%qs and %qs attributes are not compatible",
> +                         "no_caller_saved_registers",
> +                         "no_callee_saved_registers");
> +             cfun->machine->call_saved_registers
> +               = TYPE_NO_CALLEE_SAVED_REGISTERS;
> +           }
>         }
>      }
>  }
> @@ -3571,7 +3593,7 @@ ix86_set_current_function (tree fndecl)
>      }
>    ix86_previous_fndecl = fndecl;
>
> -  static bool prev_no_caller_saved_registers;
> +  static call_saved_registers_type prev_call_saved_registers;
>
>    /* 64-bit MS and SYSV ABI have different set of call used registers.
>       Avoid expensive re-initialization of init_regs each time we switch
> @@ -3582,12 +3604,13 @@ ix86_set_current_function (tree fndecl)
>      reinit_regs ();
>    /* Need to re-initialize init_regs if caller-saved registers are
>       changed.  */
> -  else if (prev_no_caller_saved_registers
> -          != cfun->machine->no_caller_saved_registers)
> +  else if (prev_call_saved_registers
> +          != cfun->machine->call_saved_registers)
>      reinit_regs ();
>
>    if (cfun->machine->func_type != TYPE_NORMAL
> -      || cfun->machine->no_caller_saved_registers)
> +      || (cfun->machine->call_saved_registers
> +         == TYPE_NO_CALLER_SAVED_REGISTERS))
>      {
>        /* Don't allow SSE, MMX nor x87 instructions since they
>          may change processor state.  */
> @@ -3614,12 +3637,12 @@ ix86_set_current_function (tree fndecl)
>                    "the %<no_caller_saved_registers%> attribute", isa);
>           /* Don't issue the same error twice.  */
>           cfun->machine->func_type = TYPE_NORMAL;
> -         cfun->machine->no_caller_saved_registers = false;
> +         cfun->machine->call_saved_registers
> +           = TYPE_DEFAULT_CALL_SAVED_REGISTERS;
>         }
>      }
>
> -  prev_no_caller_saved_registers
> -    = cfun->machine->no_caller_saved_registers;
> +  prev_call_saved_registers = cfun->machine->call_saved_registers;
>  }
>
>  /* Implement the TARGET_OFFLOAD_OPTIONS hook.  */
> @@ -4018,8 +4041,8 @@ ix86_handle_fndecl_attribute (tree *node, tree name, tree args, int,
>  }
>
>  static tree
> -ix86_handle_no_caller_saved_registers_attribute (tree *, tree, tree,
> -                                                int, bool *)
> +ix86_handle_call_saved_registers_attribute (tree *, tree, tree,
> +                                           int, bool *)
>  {
>    return NULL_TREE;
>  }
> @@ -4181,7 +4204,9 @@ static const attribute_spec ix86_gnu_attributes[] =
>    { "interrupt", 0, 0, false, true, true, false,
>      ix86_handle_interrupt_attribute, NULL },
>    { "no_caller_saved_registers", 0, 0, false, true, true, false,
> -    ix86_handle_no_caller_saved_registers_attribute, NULL },
> +    ix86_handle_call_saved_registers_attribute, NULL },
> +  { "no_callee_saved_registers", 0, 0, false, true, true, true,
> +    ix86_handle_call_saved_registers_attribute, NULL },
>    { "naked", 0, 0, true, false, false, false,
>      ix86_handle_fndecl_attribute, NULL },
>    { "indirect_branch", 1, 1, true, false, false, false,
> diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
> index c5eaeedc7e0..f10e745fb40 100644
> --- a/gcc/config/i386/i386.cc
> +++ b/gcc/config/i386/i386.cc
> @@ -475,7 +475,9 @@ ix86_conditional_register_usage (void)
>       except fixed_regs and registers used for function return value
>       since aggregate_value_p checks call_used_regs[regno] on return
>       value.  */
> -  if (cfun && cfun->machine->no_caller_saved_registers)
> +  if (cfun
> +      && (cfun->machine->call_saved_registers
> +         == TYPE_NO_CALLER_SAVED_REGISTERS))
>      for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
>        if (!fixed_regs[i] && !ix86_function_value_regno_p (i))
>         call_used_regs[i] = 0;
> @@ -944,7 +946,8 @@ ix86_function_ok_for_sibcall (tree decl, tree exp)
>
>    /* Sibling call isn't OK if there are no caller-saved registers
>       since all registers must be preserved before return.  */
> -  if (cfun->machine->no_caller_saved_registers)
> +  if (cfun->machine->call_saved_registers
> +      == TYPE_NO_CALLER_SAVED_REGISTERS)
>      return false;
>
>    /* If we are generating position-independent code, we cannot sibcall
> @@ -977,6 +980,14 @@ ix86_function_ok_for_sibcall (tree decl, tree exp)
>        decl_or_type = type;
>      }
>
> +  /* Sibling call isn't OK if callee has no callee-saved registers
> +     and the calling function has callee-saved registers.  */
> +  if ((cfun->machine->call_saved_registers
> +       != TYPE_NO_CALLEE_SAVED_REGISTERS)
> +      && lookup_attribute ("no_callee_saved_registers",
> +                          TYPE_ATTRIBUTES (type)))
> +    return false;
> +
>    /* If outgoing reg parm stack space changes, we cannot do sibcall.  */
>    if ((OUTGOING_REG_PARM_STACK_SPACE (type)
>         != OUTGOING_REG_PARM_STACK_SPACE (TREE_TYPE (current_function_decl)))
> @@ -1139,6 +1150,12 @@ ix86_comp_type_attributes (const_tree type1, const_tree type2)
>        != ix86_function_regparm (type2, NULL))
>      return 0;
>
> +  if (lookup_attribute ("no_callee_saved_registers",
> +                       TYPE_ATTRIBUTES (type1))
> +      != lookup_attribute ("no_callee_saved_registers",
> +                          TYPE_ATTRIBUTES (type2)))
> +    return 0;
> +
>    return 1;
>  }
>
> @@ -6569,7 +6586,8 @@ ix86_epilogue_uses (int regno)
>       and restoring registers.  Don't explicitly save SP register since
>       it is always preserved.  */
>    return (epilogue_completed
> -         && cfun->machine->no_caller_saved_registers
> +         && (cfun->machine->call_saved_registers
> +             == TYPE_NO_CALLER_SAVED_REGISTERS)
>           && !fixed_regs[regno]
>           && !STACK_REGNO_P (regno)
>           && !MMX_REGNO_P (regno));
> @@ -6585,7 +6603,8 @@ ix86_hard_regno_scratch_ok (unsigned int regno)
>       as a scratch register after epilogue and use REGNO as scratch
>       register only if it has been used before to avoid saving and
>       restoring it.  */
> -  return (!cfun->machine->no_caller_saved_registers
> +  return ((cfun->machine->call_saved_registers
> +          != TYPE_NO_CALLER_SAVED_REGISTERS)
>           || (!epilogue_completed
>               && df_regs_ever_live_p (regno)));
>  }
> @@ -6595,14 +6614,32 @@ ix86_hard_regno_scratch_ok (unsigned int regno)
>  bool
>  ix86_save_reg (unsigned int regno, bool maybe_eh_return, bool ignore_outlined)
>  {
> -  /* If there are no caller-saved registers, we preserve all registers,
> -     except for MMX and x87 registers which aren't supported when saving
> -     and restoring registers.  Don't explicitly save SP register since
> -     it is always preserved.  */
> -  if (cfun->machine->no_caller_saved_registers)
> -    {
> -      /* Don't preserve registers used for function return value.  */
> -      rtx reg = crtl->return_rtx;
> +  rtx reg;
> +
> +  switch (cfun->machine->call_saved_registers)
> +    {
> +    case TYPE_DEFAULT_CALL_SAVED_REGISTERS:
> +      /* If any no_callee_saved_registers functions are called and this
> +        is not a no_callee_saved_registers function, we preserve all
> +        registers which aren't used by function calls, except for MMX
> +        and x87 registers which aren't supported when saving and
> +        restoring registers.  Don't explicitly save SP register since
> +        it is always preserved.  */
> +      if (cfun->machine->call_no_callee_saved_registers)
> +       return (!fixed_regs[regno]
> +               && !call_used_regs[regno]
> +               && !STACK_REGNO_P (regno)
> +               && !MMX_REGNO_P (regno));
> +      break;
> +
> +    case TYPE_NO_CALLER_SAVED_REGISTERS:
> +      /* If there are no caller-saved registers, we preserve all
> +        registers, except for MMX and x87 registers which aren't
> +        supported when saving and restoring registers.  Don't
> +        explicitly save SP register since it is always preserved.
> +
> +        Don't preserve registers used for function return value.  */
> +      reg = crtl->return_rtx;
>        if (reg)
>         {
>           unsigned int i = REGNO (reg);
> @@ -6618,6 +6655,9 @@ ix86_save_reg (unsigned int regno, bool maybe_eh_return, bool ignore_outlined)
>               && !MMX_REGNO_P (regno)
>               && (regno != HARD_FRAME_POINTER_REGNUM
>                   || !frame_pointer_needed));
> +
> +    case TYPE_NO_CALLEE_SAVED_REGISTERS:
> +      return false;
>      }
>
>    if (regno == REAL_PIC_OFFSET_TABLE_REGNUM
> @@ -7717,7 +7757,8 @@ find_drap_reg (void)
>          registers in epilogue, DRAP must not use caller-saved
>          register in such case.  */
>        if (DECL_STATIC_CHAIN (decl)
> -         || cfun->machine->no_caller_saved_registers
> +         || (cfun->machine->call_saved_registers
> +             == TYPE_NO_CALLER_SAVED_REGISTERS)
>           || crtl->tail_call_emit)
>         return R13_REG;
>
> @@ -7730,7 +7771,8 @@ find_drap_reg (void)
>          registers in epilogue, DRAP must not use caller-saved
>          register in such case.  */
>        if (DECL_STATIC_CHAIN (decl)
> -         || cfun->machine->no_caller_saved_registers
> +         || (cfun->machine->call_saved_registers
> +             == TYPE_NO_CALLER_SAVED_REGISTERS)
>           || crtl->tail_call_emit
>           || crtl->calls_eh_return)
>         return DI_REG;
> diff --git a/gcc/config/i386/i386.h b/gcc/config/i386/i386.h
> index b9c574e62e1..bf1ca6014f5 100644
> --- a/gcc/config/i386/i386.h
> +++ b/gcc/config/i386/i386.h
> @@ -2724,6 +2724,17 @@ enum function_type
>    TYPE_EXCEPTION
>  };
>
> +enum call_saved_registers_type
> +{
> +  TYPE_DEFAULT_CALL_SAVED_REGISTERS = 0,
> +  /* The current function is a function specified with the "interrupt"
> +     or "no_caller_saved_registers" attribute.  */
> +  TYPE_NO_CALLER_SAVED_REGISTERS,
> +  /* The current function is a function specified with the "noreturn"
> +     or "no_callee_saved_registers" attribute.  */
> +  TYPE_NO_CALLEE_SAVED_REGISTERS
> +};
> +
>  enum queued_insn_type
>  {
>    TYPE_NONE = 0,
> @@ -2793,9 +2804,12 @@ struct GTY(()) machine_function {
>    /* How to generate function return.  */
>    ENUM_BITFIELD(indirect_branch) function_return_type : 3;
>
> -  /* If true, the current function is a function specified with
> -     the "interrupt" or "no_caller_saved_registers" attribute.  */
> -  BOOL_BITFIELD no_caller_saved_registers : 1;
> +  /* Call saved registers type.  */
> +  ENUM_BITFIELD(call_saved_registers_type) call_saved_registers : 2;
> +
> +  /* If true, the current function calls no_callee_saved_registers
> +     functions.  */
> +  BOOL_BITFIELD call_no_callee_saved_registers : 1;
>
>    /* If true, there is register available for argument passing.  This
>       is used only in ix86_function_ok_for_sibcall by 32-bit to determine
> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
> index 0bc586d120e..4cafa6d416b 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -6767,6 +6767,14 @@ On x86-32 targets, the @code{stdcall} attribute causes the compiler to
>  assume that the called function pops off the stack space used to
>  pass arguments, unless it takes a variable number of arguments.
>
> +@cindex @code{no_callee_saved_registers} function attribute, x86
> +@item no_callee_saved_registers
> +Use this attribute to indicate that the specified function has no
> +callee-saved registers. That is, all registers can be used as scratch
> +registers. For example, this attribute can be used for a function
> +called from the interrupt handler assembly stub which will preserve
> +all registers and return from interrupt.
> +
>  @cindex @code{no_caller_saved_registers} function attribute, x86
>  @item no_caller_saved_registers
>  Use this attribute to indicate that the specified function has no
> diff --git a/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1a.c b/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1a.c
> new file mode 100644
> index 00000000000..8c48ec0c79a
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1a.c
> @@ -0,0 +1,23 @@
> +/* { dg-do run { target i?86-*-* x86_64-*-* } } */
> +/* { dg-additional-sources no-callee-saved-run-1b.c } */
> +
> +extern void bar0 (int, int, int, int, int, int, int, int, int)
> +   __attribute__ ((no_callee_saved_registers));
> +
> +void
> +foo (void)
> +{
> +  bar0 (0, 1, 2, 3, 4, 5, 6, 7, 8);
> +}
> +
> +int
> +bar (int x)
> +{
> +  return x;
> +}
> +
> +void
> +bad (void)
> +{
> +  __builtin_abort ();
> +}
> diff --git a/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1b.c b/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1b.c
> new file mode 100644
> index 00000000000..b3ce7e72e85
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1b.c
> @@ -0,0 +1,59 @@
> +/* { dg-do compile { target i?86-*-* x86_64-*-* } } */
> +
> +extern void foo (void);
> +extern void bad (void);
> +extern int bar (int);
> +
> +void
> +__attribute__ ((no_callee_saved_registers))
> +bar0 (int i0, int i1, int i2, int i3, int i4, int i5, int i6,
> +      int i7, int i8)
> +{
> +  if (i0 != 0)
> +     bad ();
> +
> +  if (i1 != 1)
> +     bad ();
> +
> +  if (i2 != 2)
> +     bad ();
> +
> +  if (i3 != 3)
> +     bad ();
> +
> +  if (i4 != 4)
> +     bad ();
> +
> +  if (i5 != 5)
> +     bad ();
> +
> +  if (i6 != 6)
> +     bad ();
> +
> +  if (i7 != 7)
> +     bad ();
> +
> +  if (i8 != 8)
> +     bad ();
> +
> +  int a,b,c,d,e,f,i;
> +  a = bar (5);
> +  b = bar (a);
> +  c = bar (b);
> +  d = bar (c);
> +  e = bar (d);
> +  f = bar (e);
> +  for (i = 1; i < 10; i++)
> +  {
> +    a += bar (a + i) + bar (b + i) +
> +        bar (c + i) + bar (d + i) +
> +        bar (e + i) + bar (f + i);
> +  }
> +}
> +
> +int
> +main ()
> +{
> +  foo ();
> +  return 0;
> +}
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-1.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-1.c
> new file mode 100644
> index 00000000000..8fe36eb5198
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-1.c
> @@ -0,0 +1,30 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
> +
> +extern int bar (int)
> +#ifndef __x86_64__
> +__attribute__ ((regparm(3)))
> +#endif
> +;
> +
> +__attribute__ ((no_callee_saved_registers))
> +void
> +foo (void *frame)
> +{
> +  int a,b,c,d,e,f,i;
> +  a = bar (5);
> +  b = bar (a);
> +  c = bar (b);
> +  d = bar (c);
> +  e = bar (d);
> +  f = bar (e);
> +  for (i = 1; i < 10; i++)
> +  {
> +    a += bar (a + i) + bar (b + i) +
> +        bar (c + i) + bar (d + i) +
> +        bar (e + i) + bar (f + i);
> +  }
> +}
> +
> +/* { dg-final { scan-assembler-not "push" } } */
> +/* { dg-final { scan-assembler-not "pop" } } */
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-10.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-10.c
> new file mode 100644
> index 00000000000..87766c6cd88
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-10.c
> @@ -0,0 +1,46 @@
> +/* { dg-do compile { target *-*-linux* } } */
> +/* { dg-options "-O2 -mgeneral-regs-only -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
> +
> +extern void bar (void) __attribute__ ((no_callee_saved_registers));
> +
> +__attribute__ ((no_caller_saved_registers))
> +void
> +foo (void)
> +{
> +  bar ();
> +}
> +
> +/* foo must save and restore all caller saved registers since bar won't
> +   preserve any.  */
> +/* { dg-final { scan-assembler-not "jmp\[\\t \]+_?bar" } } */
> +/* { dg-final { scan-assembler "call\[\\t \]+_?bar" } } */
> +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)ax" 1 } } */
> +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
> +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)cx" 1 } } */
> +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)dx" 1 } } */
> +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
> +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)si" 1 } } */
> +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)di" 1 } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r8" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r9" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r10" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r11" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)ax" 1 } } */
> +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
> +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)cx" 1 } } */
> +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)dx" 1 } } */
> +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
> +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)si" 1 } } */
> +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)di" 1 } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r8" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r9" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r10" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r11" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-11.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-11.c
> new file mode 100644
> index 00000000000..902a764489e
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-11.c
> @@ -0,0 +1,11 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2" } */
> +
> +extern void foo (void); /* { dg-note "previous declaration" } */
> +
> +__attribute__ ((no_callee_saved_registers))
> +void
> +foo (void) /* { dg-error "conflicting types" } */
> +{
> +}
> +
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-12.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-12.c
> new file mode 100644
> index 00000000000..5524a4af29c
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-12.c
> @@ -0,0 +1,10 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2" } */
> +
> +extern void foo (void) __attribute__ ((no_callee_saved_registers)); /* { dg-note "previous declaration" } */
> +
> +void
> +foo (void) /* { dg-error "conflicting types" } */
> +{
> +}
> +
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-13.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-13.c
> new file mode 100644
> index 00000000000..6757e72d848
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-13.c
> @@ -0,0 +1,16 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
> +
> +extern void foo (void);
> +
> +__attribute__ ((no_callee_saved_registers))
> +void
> +bar (void)
> +{
> +  foo ();
> +}
> +
> +/* { dg-final { scan-assembler-not "push" } } */
> +/* { dg-final { scan-assembler-not "pop" } } */
> +/* { dg-final { scan-assembler-not "call\[\\t \]+_?foo" } } */
> +/* { dg-final { scan-assembler "jmp\[\\t \]+_?foo" } } */
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-14.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-14.c
> new file mode 100644
> index 00000000000..2239e286e6a
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-14.c
> @@ -0,0 +1,16 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
> +
> +extern void bar (void) __attribute__ ((no_callee_saved_registers));
> +
> +__attribute__ ((no_callee_saved_registers))
> +void
> +foo (void)
> +{
> +  bar ();
> +}
> +
> +/* { dg-final { scan-assembler-not "push" } } */
> +/* { dg-final { scan-assembler-not "pop" } } */
> +/* { dg-final { scan-assembler "jmp\[\\t \]+_?bar" } } */
> +/* { dg-final { scan-assembler-not "call\[\\t \]+_?bar" } } */
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-15.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-15.c
> new file mode 100644
> index 00000000000..10135fec9c1
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-15.c
> @@ -0,0 +1,17 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
> +
> +typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers));
> +extern fn_t bar;
> +
> +__attribute__ ((no_callee_saved_registers))
> +void
> +foo (void)
> +{
> +  bar ();
> +}
> +
> +/* { dg-final { scan-assembler-not "push" } } */
> +/* { dg-final { scan-assembler-not "pop" } } */
> +/* { dg-final { scan-assembler "jmp" } } */
> +/* { dg-final { scan-assembler-not "call\[\\t \]+" } } */
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-16.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-16.c
> new file mode 100644
> index 00000000000..112d1764f3e
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-16.c
> @@ -0,0 +1,16 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
> +
> +typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers));
> +
> +__attribute__ ((no_callee_saved_registers))
> +void
> +foo (fn_t bar)
> +{
> +  bar ();
> +}
> +
> +/* { dg-final { scan-assembler-not "push" } } */
> +/* { dg-final { scan-assembler-not "pop" } } */
> +/* { dg-final { scan-assembler "jmp" } } */
> +/* { dg-final { scan-assembler-not "call\[\\t \]+" } } */
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-17.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-17.c
> new file mode 100644
> index 00000000000..1fd5daadf08
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-17.c
> @@ -0,0 +1,16 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
> +
> +extern void foo (void) __attribute__ ((no_caller_saved_registers));
> +
> +__attribute__ ((no_callee_saved_registers))
> +void
> +bar (void)
> +{
> +  foo ();
> +}
> +
> +/* { dg-final { scan-assembler-not "push" } } */
> +/* { dg-final { scan-assembler-not "pop" } } */
> +/* { dg-final { scan-assembler-not "call\[\\t \]+_?foo" } } */
> +/* { dg-final { scan-assembler "jmp\[\\t \]+_?foo" } } */
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-18.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-18.c
> new file mode 100644
> index 00000000000..e7101009be4
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-18.c
> @@ -0,0 +1,51 @@
> +/* { dg-do compile { target *-*-linux* } } */
> +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
> +
> +#include <stdint.h>
> +
> +typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers));
> +
> +void
> +foo (uintptr_t p)
> +{
> +  ((fn_t) p) ();
> +}
> +
> +/* foo must save and restore all caller saved registers since bar won't
> +   preserve any.  */
> +/* { dg-final { scan-assembler-not "jmp" } } */
> +/* { dg-final { scan-assembler "call\[\\t \]+" } } */
> +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)ax" } } */
> +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
> +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)cx" } } */
> +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)dx" } } */
> +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
> +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%esi" 1 { target ia32 } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rsi" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rdi" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r8" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r9" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r10" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r11" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)ax" } } */
> +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
> +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)cx" } } */
> +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)dx" } } */
> +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
> +/* { dg-final { scan-assembler-times "popl\[\\t \]*%esi" 1 { target ia32 } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rsi" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rdi" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r8" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r9" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r10" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r11" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-2.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-2.c
> new file mode 100644
> index 00000000000..ce4ab3b1799
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-2.c
> @@ -0,0 +1,30 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
> +
> +extern int bar (int) __attribute__ ((no_caller_saved_registers))
> +#ifndef __x86_64__
> +__attribute__ ((regparm(3)))
> +#endif
> +;
> +
> +__attribute__ ((no_callee_saved_registers))
> +void
> +foo (void *frame)
> +{
> +  int a,b,c,d,e,f,i;
> +  a = bar (5);
> +  b = bar (a);
> +  c = bar (b);
> +  d = bar (c);
> +  e = bar (d);
> +  f = bar (e);
> +  for (i = 1; i < 10; i++)
> +  {
> +    a += bar (a + i) + bar (b + i) +
> +        bar (c + i) + bar (d + i) +
> +        bar (e + i) + bar (f + i);
> +  }
> +}
> +
> +/* { dg-final { scan-assembler-not "push" } } */
> +/* { dg-final { scan-assembler-not "pop" } } */
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-3.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-3.c
> new file mode 100644
> index 00000000000..453272e11c0
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-3.c
> @@ -0,0 +1,8 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2" } */
> +
> +__attribute__ ((no_callee_saved_registers, no_caller_saved_registers))
> +void
> +foo (void) /* { dg-error "attributes are not compatible" } */
> +{
> +}
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-4.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-4.c
> new file mode 100644
> index 00000000000..ec566aaf09f
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-4.c
> @@ -0,0 +1,8 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -mgeneral-regs-only" } */
> +
> +__attribute__ ((no_callee_saved_registers, interrupt))
> +void
> +foo (void *frame) /* { dg-error "attributes are not compatible" } */
> +{
> +}
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-5.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-5.c
> new file mode 100644
> index 00000000000..b28b211986a
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-5.c
> @@ -0,0 +1,11 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2" } */
> +
> +typedef void (*fn_t) (void *) __attribute__ ((no_callee_saved_registers));
> +
> +void
> +foo (void *frame)
> +{
> +}
> +
> +fn_t func = foo; /* { dg-error "incompatible pointer type" } */
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-6.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-6.c
> new file mode 100644
> index 00000000000..a7b3bdabf43
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-6.c
> @@ -0,0 +1,12 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2" } */
> +
> +typedef void (*fn_t) (void *) __attribute__ ((no_callee_saved_registers));
> +
> +__attribute__ ((no_callee_saved_registers))
> +void
> +foo (void *frame)
> +{
> +}
> +
> +fn_t func = foo;
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-7.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-7.c
> new file mode 100644
> index 00000000000..a1837fdfd4b
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-7.c
> @@ -0,0 +1,49 @@
> +/* { dg-do compile { target *-*-linux* } } */
> +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
> +
> +extern void bar (void) __attribute__ ((no_callee_saved_registers));
> +
> +void
> +foo (void)
> +{
> +  bar ();
> +}
> +
> +/* foo must save and restore all caller saved registers since bar won't
> +   preserve any.  */
> +/* { dg-final { scan-assembler-not "jmp\[\\t \]+_?bar" } } */
> +/* { dg-final { scan-assembler "call\[\\t \]+_?bar" } } */
> +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)ax" } } */
> +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
> +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)cx" } } */
> +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)dx" } } */
> +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
> +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%esi" 1 { target ia32 } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rsi" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rdi" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r8" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r9" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r10" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r11" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)ax" } } */
> +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
> +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)cx" } } */
> +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)dx" } } */
> +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
> +/* { dg-final { scan-assembler-times "popl\[\\t \]*%esi" 1 { target ia32 } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rsi" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rdi" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r8" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r9" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r10" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r11" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-8.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-8.c
> new file mode 100644
> index 00000000000..90b98a21aef
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-8.c
> @@ -0,0 +1,50 @@
> +/* { dg-do compile { target *-*-linux* } } */
> +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
> +
> +typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers));
> +extern fn_t bar;
> +
> +void
> +foo (void)
> +{
> +  bar ();
> +}
> +
> +/* foo must save and restore all caller saved registers since bar won't
> +   preserve any.  */
> +/* { dg-final { scan-assembler-not "jmp" } } */
> +/* { dg-final { scan-assembler "call\[\\t \]+" } } */
> +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)ax" } } */
> +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
> +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)cx" } } */
> +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)dx" } } */
> +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
> +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%esi" 1 { target ia32 } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rsi" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rdi" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r8" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r9" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r10" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r11" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)ax" } } */
> +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
> +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)cx" } } */
> +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)dx" } } */
> +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
> +/* { dg-final { scan-assembler-times "popl\[\\t \]*%esi" 1 { target ia32 } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rsi" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rdi" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r8" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r9" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r10" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r11" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
> diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-9.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-9.c
> new file mode 100644
> index 00000000000..e261100ac1a
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-9.c
> @@ -0,0 +1,49 @@
> +/* { dg-do compile { target *-*-linux* } } */
> +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
> +
> +typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers));
> +
> +void
> +foo (fn_t bar)
> +{
> +  bar ();
> +}
> +
> +/* foo must save and restore all caller saved registers since bar won't
> +   preserve any.  */
> +/* { dg-final { scan-assembler-not "jmp" } } */
> +/* { dg-final { scan-assembler "call\[\\t \]+" } } */
> +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)ax" } } */
> +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
> +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)cx" } } */
> +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)dx" } } */
> +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
> +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%esi" 1 { target ia32 } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rsi" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rdi" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r8" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r9" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r10" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r11" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)ax" } } */
> +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
> +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)cx" } } */
> +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)dx" } } */
> +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
> +/* { dg-final { scan-assembler-times "popl\[\\t \]*%esi" 1 { target ia32 } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rsi" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rdi" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r8" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r9" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r10" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r11" { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
> +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
> --
> 2.43.0
>


--
BR,
Hongtao
H.J. Lu Jan. 22, 2024, 3:47 p.m. UTC | #2
On Sun, Jan 21, 2024 at 8:03 PM Hongtao Liu <crazylht@gmail.com> wrote:
>
> On Sat, Jan 20, 2024 at 10:30 PM H.J. Lu <hjl.tools@gmail.com> wrote:
> >
> > When an interrupt handler is implemented by an assembly stub which does:
> >
> > 1. Save all registers.
> > 2. Call a C function.
> > 3. Restore all registers.
> > 4. Return from interrupt.
> >
> > it is completely unnecessary to save and restore any registers in the C
> > function called by the assembly stub, even if they would normally be
> > callee-saved.
> >
> > Add no_callee_saved_registers function attribute, which is complementary
> > to no_caller_saved_registers function attribute, to mark a function which
> > doesn't have any callee-saved registers.  Such a function won't save and
> > restore any registers.  Classify function call-saved register handling
> > type with:
> >
> > 1. Default call-saved registers.
> > 2. No caller-saved registers with no_caller_saved_registers attribute.
> > 3. No callee-saved registers with no_callee_saved_registers attribute.
> >
> > Disallow sibcall if callee is a no_callee_saved_registers function
> > and caller isn't a no_callee_saved_registers function.  Otherwise,
> > callee-saved registers won't be preserved.
> >
> > After a no_callee_saved_registers function is called, all registers may
> > be clobbered.  If the calling function isn't a no_callee_saved_registers
> > function, we need to preserve all registers which aren't used by function
> > calls.
> >
> > gcc/
> >
> >         PR target/103503
> >         PR target/113312
> >         * config/i386/i386-expand.cc (ix86_expand_call): Set
> >         call_no_callee_saved_registers to true when calling function
> >         with no_callee_saved_registers attribute.  Replace
> >         no_caller_saved_registers check with call_saved_registers check.
> >         * config/i386/i386-options.cc (ix86_set_func_type): Set
> >         call_saved_registers to TYPE_NO_CALLEE_SAVED_REGISTERS for
> >         noreturn function.  Disallow no_callee_saved_registers with
> >         interrupt or no_caller_saved_registers attributes together.
> >         (ix86_set_current_function): Replace no_caller_saved_registers
> >         check with call_saved_registers check.
> >         (ix86_handle_no_caller_saved_registers_attribute): Renamed to ...
> >         (ix86_handle_call_saved_registers_attribute): This.
> >         (ix86_gnu_attributes): Add
> >         ix86_handle_call_saved_registers_attribute.
> >         * config/i386/i386.cc (ix86_conditional_register_usage): Replace
> >         no_caller_saved_registers check with call_saved_registers check.
> >         (ix86_function_ok_for_sibcall): Don't allow callee with
> >         no_callee_saved_registers attribute when the calling function
> >         has callee-saved registers.
> >         (ix86_comp_type_attributes): Also check
> >         no_callee_saved_registers.
> >         (ix86_epilogue_uses): Replace no_caller_saved_registers check
> >         with call_saved_registers check.
> >         (ix86_hard_regno_scratch_ok): Likewise.
> >         (ix86_save_reg): Replace no_caller_saved_registers check with
> >         call_saved_registers check.  Don't save any registers for
> >         TYPE_NO_CALLEE_SAVED_REGISTERS.  Save all registers with
> >         TYPE_DEFAULT_CALL_SAVED_REGISTERS if function with
> >         no_callee_saved_registers attribute is called.
> >         (find_drap_reg): Replace no_caller_saved_registers check with
> >         call_saved_registers check.
> >         * config/i386/i386.h (call_saved_registers_type): New enum.
> >         (machine_function): Replace no_caller_saved_registers with
> >         call_saved_registers.  Add call_no_callee_saved_registers.
> >         * doc/extend.texi: Document no_callee_saved_registers attribute.
> >
> > gcc/testsuite/
> >
> >         PR target/103503
> >         PR target/113312
> >         * gcc.dg/torture/no-callee-saved-run-1a.c: New file.
> >         * gcc.dg/torture/no-callee-saved-run-1b.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-1.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-2.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-3.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-4.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-5.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-6.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-7.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-8.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-9.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-10.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-11.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-12.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-13.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-14.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-15.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-16.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-17.c: Likewise.
> >         * gcc.target/i386/no-callee-saved-18.c: Likewise.
> > ---
> >  gcc/config/i386/i386-expand.cc                | 72 ++++++++++++++++---
> >  gcc/config/i386/i386-options.cc               | 49 +++++++++----
> >  gcc/config/i386/i386.cc                       | 70 ++++++++++++++----
> >  gcc/config/i386/i386.h                        | 20 +++++-
> >  gcc/doc/extend.texi                           |  8 +++
> >  .../gcc.dg/torture/no-callee-saved-run-1a.c   | 23 ++++++
> >  .../gcc.dg/torture/no-callee-saved-run-1b.c   | 59 +++++++++++++++
> >  .../gcc.target/i386/no-callee-saved-1.c       | 30 ++++++++
> >  .../gcc.target/i386/no-callee-saved-10.c      | 46 ++++++++++++
> >  .../gcc.target/i386/no-callee-saved-11.c      | 11 +++
> >  .../gcc.target/i386/no-callee-saved-12.c      | 10 +++
> >  .../gcc.target/i386/no-callee-saved-13.c      | 16 +++++
> >  .../gcc.target/i386/no-callee-saved-14.c      | 16 +++++
> >  .../gcc.target/i386/no-callee-saved-15.c      | 17 +++++
> >  .../gcc.target/i386/no-callee-saved-16.c      | 16 +++++
> >  .../gcc.target/i386/no-callee-saved-17.c      | 16 +++++
> >  .../gcc.target/i386/no-callee-saved-18.c      | 51 +++++++++++++
> >  .../gcc.target/i386/no-callee-saved-2.c       | 30 ++++++++
> >  .../gcc.target/i386/no-callee-saved-3.c       |  8 +++
> >  .../gcc.target/i386/no-callee-saved-4.c       |  8 +++
> >  .../gcc.target/i386/no-callee-saved-5.c       | 11 +++
> >  .../gcc.target/i386/no-callee-saved-6.c       | 12 ++++
> >  .../gcc.target/i386/no-callee-saved-7.c       | 49 +++++++++++++
> >  .../gcc.target/i386/no-callee-saved-8.c       | 50 +++++++++++++
> >  .../gcc.target/i386/no-callee-saved-9.c       | 49 +++++++++++++
> >  25 files changed, 709 insertions(+), 38 deletions(-)
> >  create mode 100644 gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1a.c
> >  create mode 100644 gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1b.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-1.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-10.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-11.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-12.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-13.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-14.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-15.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-16.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-17.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-18.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-2.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-3.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-4.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-5.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-6.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-7.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-8.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-9.c
> >
> > diff --git a/gcc/config/i386/i386-expand.cc b/gcc/config/i386/i386-expand.cc
> > index 52754e114f4..c0c7c697440 100644
> > --- a/gcc/config/i386/i386-expand.cc
> > +++ b/gcc/config/i386/i386-expand.cc
> > @@ -9739,17 +9739,41 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
> >    rtx use = NULL, call;
> >    unsigned int vec_len = 0.
> >    tree fndecl;
> > +  bool call_no_callee_saved_registers = false;
> >
> >    if (GET_CODE (XEXP (fnaddr, 0)) == SYMBOL_REF)
> >      {
> >        fndecl = SYMBOL_REF_DECL (XEXP (fnaddr, 0));
> > -      if (fndecl
> > -         && (lookup_attribute ("interrupt",
> > -                               TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))))
> > -       error ("interrupt service routine cannot be called directly");
> > +      if (fndecl)
> > +       {
> > +         if (lookup_attribute ("interrupt",
> > +                               TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))
> > +           error ("interrupt service routine cannot be called directly");
> > +         else if (lookup_attribute ("no_callee_saved_registers",
> > +                                    TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))
> > +           {
> > +             cfun->machine->call_no_callee_saved_registers = true;
> > +             call_no_callee_saved_registers = true;
> > +           }
> > +       }
> >      }
> >    else
> > -    fndecl = NULL_TREE;
> > +    {
> > +      if (MEM_P (fnaddr))
> > +       {
> > +         tree mem_expr = MEM_EXPR (fnaddr);
> > +         if (mem_expr != nullptr
> > +             && TREE_CODE (mem_expr) == MEM_REF
> > +             && lookup_attribute ("no_callee_saved_registers",
> > +                                  TYPE_ATTRIBUTES (TREE_TYPE (mem_expr))))
> > +           {
> > +             cfun->machine->call_no_callee_saved_registers = true;
> > +             call_no_callee_saved_registers = true;
> > +           }
> > +       }
> > +
> > +      fndecl = NULL_TREE;
> > +    }
> >
> >    if (pop == const0_rtx)
> >      pop = NULL;
> > @@ -9884,13 +9908,18 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
> >        vec[vec_len++] = pop;
> >      }
> >
> > -  if (cfun->machine->no_caller_saved_registers
> > +  static const char ix86_call_used_regs[] = CALL_USED_REGISTERS;
> > +
> > +  char clobbered_registers[FIRST_PSEUDO_REGISTER];
> > +  memset (clobbered_registers, 0, sizeof (clobbered_registers));
> > +
> > +  if ((cfun->machine->call_saved_registers
> > +       == TYPE_NO_CALLER_SAVED_REGISTERS)
> >        && (!fndecl
> >           || (!TREE_THIS_VOLATILE (fndecl)
> >               && !lookup_attribute ("no_caller_saved_registers",
> >                                     TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))))
> >      {
> > -      static const char ix86_call_used_regs[] = CALL_USED_REGISTERS;
> >        bool is_64bit_ms_abi = (TARGET_64BIT
> >                               && ix86_function_abi (fndecl) == MS_ABI);
> >        char c_mask = CALL_USED_REGISTERS_MASK (is_64bit_ms_abi);
> > @@ -9903,8 +9932,11 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
> >                 || (ix86_call_used_regs[i] & c_mask))
> >             && !STACK_REGNO_P (i)
> >             && !MMX_REGNO_P (i))
> > -         clobber_reg (&use,
> > -                      gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i));
> > +         {
> > +           clobber_reg (&use,
> > +                        gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i));
> > +           clobbered_registers[i] = 1;
> > +         }
> >      }
> >    else if (TARGET_64BIT_MS_ABI
> >            && (!callarg2 || INTVAL (callarg2) != -2))
> > @@ -9917,6 +9949,7 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
> >           machine_mode mode = SSE_REGNO_P (regno) ? TImode : DImode;
> >
> >           clobber_reg (&use, gen_rtx_REG (mode, regno));
> > +         clobbered_registers[i] = 1;
> >         }
> >
> >        /* Set here, but it may get cleared later.  */
> > @@ -9953,6 +9986,27 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
> >          resolver could be used which clobbers R11 and R10.  */
> >        clobber_reg (&use, gen_rtx_REG (DImode, R11_REG));
> >        clobber_reg (&use, gen_rtx_REG (DImode, R10_REG));
> > +      clobbered_registers[R11_REG] = 1;
> > +      clobbered_registers[R10_REG] = 1;
> > +    }
> > +
> > +  if (call_no_callee_saved_registers)
> > +    {
> > +      /* After calling a no_callee_saved_registers function, all
> > +        registers may be clobbered.  Clobber all registers that are
> > +        not clobbered yet and not used by the callee.  */
> > +      bool is_64bit_ms_abi = (TARGET_64BIT
> > +                             && ix86_function_abi (fndecl) == MS_ABI);
> > +      char c_mask = CALL_USED_REGISTERS_MASK (is_64bit_ms_abi);
> > +      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
> > +       if (!fixed_regs[i]
> > +           && !clobbered_registers[i]
> It seems to me clobbered_registers is only used here which seems
> redundant, remove  !clobbered_registers[i] should also be fine?
>

You are right.  Here is the v2 patch set:

https://patchwork.sourceware.org/project/gcc/list/?series=30050

Changes in v2:

1. Rebase against commit f9df00340e3
2. Don't add redundant clobbered_registers check in ix86_expand_call.

Thanks.
diff mbox series

Patch

diff --git a/gcc/config/i386/i386-expand.cc b/gcc/config/i386/i386-expand.cc
index 52754e114f4..c0c7c697440 100644
--- a/gcc/config/i386/i386-expand.cc
+++ b/gcc/config/i386/i386-expand.cc
@@ -9739,17 +9739,41 @@  ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
   rtx use = NULL, call;
   unsigned int vec_len = 0;
   tree fndecl;
+  bool call_no_callee_saved_registers = false;
 
   if (GET_CODE (XEXP (fnaddr, 0)) == SYMBOL_REF)
     {
       fndecl = SYMBOL_REF_DECL (XEXP (fnaddr, 0));
-      if (fndecl
-	  && (lookup_attribute ("interrupt",
-				TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))))
-	error ("interrupt service routine cannot be called directly");
+      if (fndecl)
+	{
+	  if (lookup_attribute ("interrupt",
+				TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))
+	    error ("interrupt service routine cannot be called directly");
+	  else if (lookup_attribute ("no_callee_saved_registers",
+				     TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))
+	    {
+	      cfun->machine->call_no_callee_saved_registers = true;
+	      call_no_callee_saved_registers = true;
+	    }
+	}
     }
   else
-    fndecl = NULL_TREE;
+    {
+      if (MEM_P (fnaddr))
+	{
+	  tree mem_expr = MEM_EXPR (fnaddr);
+	  if (mem_expr != nullptr
+	      && TREE_CODE (mem_expr) == MEM_REF
+	      && lookup_attribute ("no_callee_saved_registers",
+				   TYPE_ATTRIBUTES (TREE_TYPE (mem_expr))))
+	    {
+	      cfun->machine->call_no_callee_saved_registers = true;
+	      call_no_callee_saved_registers = true;
+	    }
+	}
+
+      fndecl = NULL_TREE;
+    }
 
   if (pop == const0_rtx)
     pop = NULL;
@@ -9884,13 +9908,18 @@  ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
       vec[vec_len++] = pop;
     }
 
-  if (cfun->machine->no_caller_saved_registers
+  static const char ix86_call_used_regs[] = CALL_USED_REGISTERS;
+
+  char clobbered_registers[FIRST_PSEUDO_REGISTER];
+  memset (clobbered_registers, 0, sizeof (clobbered_registers));
+
+  if ((cfun->machine->call_saved_registers
+       == TYPE_NO_CALLER_SAVED_REGISTERS)
       && (!fndecl
 	  || (!TREE_THIS_VOLATILE (fndecl)
 	      && !lookup_attribute ("no_caller_saved_registers",
 				    TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))))
     {
-      static const char ix86_call_used_regs[] = CALL_USED_REGISTERS;
       bool is_64bit_ms_abi = (TARGET_64BIT
 			      && ix86_function_abi (fndecl) == MS_ABI);
       char c_mask = CALL_USED_REGISTERS_MASK (is_64bit_ms_abi);
@@ -9903,8 +9932,11 @@  ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
 		|| (ix86_call_used_regs[i] & c_mask))
 	    && !STACK_REGNO_P (i)
 	    && !MMX_REGNO_P (i))
-	  clobber_reg (&use,
-		       gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i));
+	  {
+	    clobber_reg (&use,
+			 gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i));
+	    clobbered_registers[i] = 1;
+	  }
     }
   else if (TARGET_64BIT_MS_ABI
 	   && (!callarg2 || INTVAL (callarg2) != -2))
@@ -9917,6 +9949,7 @@  ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
 	  machine_mode mode = SSE_REGNO_P (regno) ? TImode : DImode;
 
 	  clobber_reg (&use, gen_rtx_REG (mode, regno));
+	  clobbered_registers[i] = 1;
 	}
 
       /* Set here, but it may get cleared later.  */
@@ -9953,6 +9986,27 @@  ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
 	 resolver could be used which clobbers R11 and R10.  */
       clobber_reg (&use, gen_rtx_REG (DImode, R11_REG));
       clobber_reg (&use, gen_rtx_REG (DImode, R10_REG));
+      clobbered_registers[R11_REG] = 1;
+      clobbered_registers[R10_REG] = 1;
+    }
+
+  if (call_no_callee_saved_registers)
+    {
+      /* After calling a no_callee_saved_registers function, all
+	 registers may be clobbered.  Clobber all registers that are
+	 not clobbered yet and not used by the callee.  */
+      bool is_64bit_ms_abi = (TARGET_64BIT
+			      && ix86_function_abi (fndecl) == MS_ABI);
+      char c_mask = CALL_USED_REGISTERS_MASK (is_64bit_ms_abi);
+      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+	if (!fixed_regs[i]
+	    && !clobbered_registers[i]
+	    && !(ix86_call_used_regs[i] == 1
+		 || (ix86_call_used_regs[i] & c_mask))
+	    && !STACK_REGNO_P (i)
+	    && !MMX_REGNO_P (i))
+	  clobber_reg (&use,
+		       gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i));
     }
 
   if (vec_len > 1)
diff --git a/gcc/config/i386/i386-options.cc b/gcc/config/i386/i386-options.cc
index b6f634e9a32..0cdea30599e 100644
--- a/gcc/config/i386/i386-options.cc
+++ b/gcc/config/i386/i386-options.cc
@@ -3371,6 +3371,10 @@  ix86_simd_clone_adjust (struct cgraph_node *node)
 static void
 ix86_set_func_type (tree fndecl)
 {
+  bool has_no_callee_saved_registers
+    = lookup_attribute ("no_callee_saved_registers",
+			TYPE_ATTRIBUTES (TREE_TYPE (fndecl)));
+
   if (cfun->machine->func_type == TYPE_UNKNOWN)
     {
       if (lookup_attribute ("interrupt",
@@ -3380,12 +3384,18 @@  ix86_set_func_type (tree fndecl)
 	    error_at (DECL_SOURCE_LOCATION (fndecl),
 		      "interrupt and naked attributes are not compatible");
 
+	  if (has_no_callee_saved_registers)
+	    error_at (DECL_SOURCE_LOCATION (fndecl),
+		      "%qs and %qs attributes are not compatible",
+		      "interrupt", "no_callee_saved_registers");
+
 	  int nargs = 0;
 	  for (tree arg = DECL_ARGUMENTS (fndecl);
 	       arg;
 	       arg = TREE_CHAIN (arg))
 	    nargs++;
-	  cfun->machine->no_caller_saved_registers = true;
+	  cfun->machine->call_saved_registers
+	    = TYPE_NO_CALLER_SAVED_REGISTERS;
 	  cfun->machine->func_type
 	    = nargs == 2 ? TYPE_EXCEPTION : TYPE_INTERRUPT;
 
@@ -3401,7 +3411,19 @@  ix86_set_func_type (tree fndecl)
 	  cfun->machine->func_type = TYPE_NORMAL;
 	  if (lookup_attribute ("no_caller_saved_registers",
 				TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))
-	    cfun->machine->no_caller_saved_registers = true;
+	    cfun->machine->call_saved_registers
+	      = TYPE_NO_CALLER_SAVED_REGISTERS;
+	  if (has_no_callee_saved_registers)
+	    {
+	      if (cfun->machine->call_saved_registers
+		  == TYPE_NO_CALLER_SAVED_REGISTERS)
+		error_at (DECL_SOURCE_LOCATION (fndecl),
+			  "%qs and %qs attributes are not compatible",
+			  "no_caller_saved_registers",
+			  "no_callee_saved_registers");
+	      cfun->machine->call_saved_registers
+		= TYPE_NO_CALLEE_SAVED_REGISTERS;
+	    }
 	}
     }
 }
@@ -3571,7 +3593,7 @@  ix86_set_current_function (tree fndecl)
     }
   ix86_previous_fndecl = fndecl;
 
-  static bool prev_no_caller_saved_registers;
+  static call_saved_registers_type prev_call_saved_registers;
 
   /* 64-bit MS and SYSV ABI have different set of call used registers.
      Avoid expensive re-initialization of init_regs each time we switch
@@ -3582,12 +3604,13 @@  ix86_set_current_function (tree fndecl)
     reinit_regs ();
   /* Need to re-initialize init_regs if caller-saved registers are
      changed.  */
-  else if (prev_no_caller_saved_registers
-	   != cfun->machine->no_caller_saved_registers)
+  else if (prev_call_saved_registers
+	   != cfun->machine->call_saved_registers)
     reinit_regs ();
 
   if (cfun->machine->func_type != TYPE_NORMAL
-      || cfun->machine->no_caller_saved_registers)
+      || (cfun->machine->call_saved_registers
+	  == TYPE_NO_CALLER_SAVED_REGISTERS))
     {
       /* Don't allow SSE, MMX nor x87 instructions since they
 	 may change processor state.  */
@@ -3614,12 +3637,12 @@  ix86_set_current_function (tree fndecl)
 		   "the %<no_caller_saved_registers%> attribute", isa);
 	  /* Don't issue the same error twice.  */
 	  cfun->machine->func_type = TYPE_NORMAL;
-	  cfun->machine->no_caller_saved_registers = false;
+	  cfun->machine->call_saved_registers
+	    = TYPE_DEFAULT_CALL_SAVED_REGISTERS;
 	}
     }
 
-  prev_no_caller_saved_registers
-    = cfun->machine->no_caller_saved_registers;
+  prev_call_saved_registers = cfun->machine->call_saved_registers;
 }
 
 /* Implement the TARGET_OFFLOAD_OPTIONS hook.  */
@@ -4018,8 +4041,8 @@  ix86_handle_fndecl_attribute (tree *node, tree name, tree args, int,
 }
 
 static tree
-ix86_handle_no_caller_saved_registers_attribute (tree *, tree, tree,
-						 int, bool *)
+ix86_handle_call_saved_registers_attribute (tree *, tree, tree,
+					    int, bool *)
 {
   return NULL_TREE;
 }
@@ -4181,7 +4204,9 @@  static const attribute_spec ix86_gnu_attributes[] =
   { "interrupt", 0, 0, false, true, true, false,
     ix86_handle_interrupt_attribute, NULL },
   { "no_caller_saved_registers", 0, 0, false, true, true, false,
-    ix86_handle_no_caller_saved_registers_attribute, NULL },
+    ix86_handle_call_saved_registers_attribute, NULL },
+  { "no_callee_saved_registers", 0, 0, false, true, true, true,
+    ix86_handle_call_saved_registers_attribute, NULL },
   { "naked", 0, 0, true, false, false, false,
     ix86_handle_fndecl_attribute, NULL },
   { "indirect_branch", 1, 1, true, false, false, false,
diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index c5eaeedc7e0..f10e745fb40 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -475,7 +475,9 @@  ix86_conditional_register_usage (void)
      except fixed_regs and registers used for function return value
      since aggregate_value_p checks call_used_regs[regno] on return
      value.  */
-  if (cfun && cfun->machine->no_caller_saved_registers)
+  if (cfun
+      && (cfun->machine->call_saved_registers
+	  == TYPE_NO_CALLER_SAVED_REGISTERS))
     for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
       if (!fixed_regs[i] && !ix86_function_value_regno_p (i))
 	call_used_regs[i] = 0;
@@ -944,7 +946,8 @@  ix86_function_ok_for_sibcall (tree decl, tree exp)
 
   /* Sibling call isn't OK if there are no caller-saved registers
      since all registers must be preserved before return.  */
-  if (cfun->machine->no_caller_saved_registers)
+  if (cfun->machine->call_saved_registers
+      == TYPE_NO_CALLER_SAVED_REGISTERS)
     return false;
 
   /* If we are generating position-independent code, we cannot sibcall
@@ -977,6 +980,14 @@  ix86_function_ok_for_sibcall (tree decl, tree exp)
       decl_or_type = type;
     }
 
+  /* Sibling call isn't OK if callee has no callee-saved registers
+     and the calling function has callee-saved registers.  */
+  if ((cfun->machine->call_saved_registers
+       != TYPE_NO_CALLEE_SAVED_REGISTERS)
+      && lookup_attribute ("no_callee_saved_registers",
+			   TYPE_ATTRIBUTES (type)))
+    return false;
+
   /* If outgoing reg parm stack space changes, we cannot do sibcall.  */
   if ((OUTGOING_REG_PARM_STACK_SPACE (type)
        != OUTGOING_REG_PARM_STACK_SPACE (TREE_TYPE (current_function_decl)))
@@ -1139,6 +1150,12 @@  ix86_comp_type_attributes (const_tree type1, const_tree type2)
       != ix86_function_regparm (type2, NULL))
     return 0;
 
+  if (lookup_attribute ("no_callee_saved_registers",
+			TYPE_ATTRIBUTES (type1))
+      != lookup_attribute ("no_callee_saved_registers",
+			   TYPE_ATTRIBUTES (type2)))
+    return 0;
+
   return 1;
 }
 
@@ -6569,7 +6586,8 @@  ix86_epilogue_uses (int regno)
      and restoring registers.  Don't explicitly save SP register since
      it is always preserved.  */
   return (epilogue_completed
-	  && cfun->machine->no_caller_saved_registers
+	  && (cfun->machine->call_saved_registers
+	      == TYPE_NO_CALLER_SAVED_REGISTERS)
 	  && !fixed_regs[regno]
 	  && !STACK_REGNO_P (regno)
 	  && !MMX_REGNO_P (regno));
@@ -6585,7 +6603,8 @@  ix86_hard_regno_scratch_ok (unsigned int regno)
      as a scratch register after epilogue and use REGNO as scratch
      register only if it has been used before to avoid saving and
      restoring it.  */
-  return (!cfun->machine->no_caller_saved_registers
+  return ((cfun->machine->call_saved_registers
+	   != TYPE_NO_CALLER_SAVED_REGISTERS)
 	  || (!epilogue_completed
 	      && df_regs_ever_live_p (regno)));
 }
@@ -6595,14 +6614,32 @@  ix86_hard_regno_scratch_ok (unsigned int regno)
 bool
 ix86_save_reg (unsigned int regno, bool maybe_eh_return, bool ignore_outlined)
 {
-  /* If there are no caller-saved registers, we preserve all registers,
-     except for MMX and x87 registers which aren't supported when saving
-     and restoring registers.  Don't explicitly save SP register since
-     it is always preserved.  */
-  if (cfun->machine->no_caller_saved_registers)
-    {
-      /* Don't preserve registers used for function return value.  */
-      rtx reg = crtl->return_rtx;
+  rtx reg;
+
+  switch (cfun->machine->call_saved_registers)
+    {
+    case TYPE_DEFAULT_CALL_SAVED_REGISTERS:
+      /* If any no_callee_saved_registers functions are called and this
+	 is not a no_callee_saved_registers function, we preserve all
+	 registers which aren't used by function calls, except for MMX
+	 and x87 registers which aren't supported when saving and
+	 restoring registers.  Don't explicitly save SP register since
+	 it is always preserved.  */
+      if (cfun->machine->call_no_callee_saved_registers)
+	return (!fixed_regs[regno]
+		&& !call_used_regs[regno]
+		&& !STACK_REGNO_P (regno)
+		&& !MMX_REGNO_P (regno));
+      break;
+
+    case TYPE_NO_CALLER_SAVED_REGISTERS:
+      /* If there are no caller-saved registers, we preserve all
+	 registers, except for MMX and x87 registers which aren't
+	 supported when saving and restoring registers.  Don't
+	 explicitly save SP register since it is always preserved.
+
+	 Don't preserve registers used for function return value.  */
+      reg = crtl->return_rtx;
       if (reg)
 	{
 	  unsigned int i = REGNO (reg);
@@ -6618,6 +6655,9 @@  ix86_save_reg (unsigned int regno, bool maybe_eh_return, bool ignore_outlined)
 	      && !MMX_REGNO_P (regno)
 	      && (regno != HARD_FRAME_POINTER_REGNUM
 		  || !frame_pointer_needed));
+
+    case TYPE_NO_CALLEE_SAVED_REGISTERS:
+      return false;
     }
 
   if (regno == REAL_PIC_OFFSET_TABLE_REGNUM
@@ -7717,7 +7757,8 @@  find_drap_reg (void)
 	 registers in epilogue, DRAP must not use caller-saved
 	 register in such case.  */
       if (DECL_STATIC_CHAIN (decl)
-	  || cfun->machine->no_caller_saved_registers
+	  || (cfun->machine->call_saved_registers
+	      == TYPE_NO_CALLER_SAVED_REGISTERS)
 	  || crtl->tail_call_emit)
 	return R13_REG;
 
@@ -7730,7 +7771,8 @@  find_drap_reg (void)
 	 registers in epilogue, DRAP must not use caller-saved
 	 register in such case.  */
       if (DECL_STATIC_CHAIN (decl)
-	  || cfun->machine->no_caller_saved_registers
+	  || (cfun->machine->call_saved_registers
+	      == TYPE_NO_CALLER_SAVED_REGISTERS)
 	  || crtl->tail_call_emit
 	  || crtl->calls_eh_return)
 	return DI_REG;
diff --git a/gcc/config/i386/i386.h b/gcc/config/i386/i386.h
index b9c574e62e1..bf1ca6014f5 100644
--- a/gcc/config/i386/i386.h
+++ b/gcc/config/i386/i386.h
@@ -2724,6 +2724,17 @@  enum function_type
   TYPE_EXCEPTION
 };
 
+enum call_saved_registers_type
+{
+  TYPE_DEFAULT_CALL_SAVED_REGISTERS = 0,
+  /* The current function is a function specified with the "interrupt"
+     or "no_caller_saved_registers" attribute.  */
+  TYPE_NO_CALLER_SAVED_REGISTERS,
+  /* The current function is a function specified with the "noreturn"
+     or "no_callee_saved_registers" attribute.  */
+  TYPE_NO_CALLEE_SAVED_REGISTERS
+};
+
 enum queued_insn_type
 {
   TYPE_NONE = 0,
@@ -2793,9 +2804,12 @@  struct GTY(()) machine_function {
   /* How to generate function return.  */
   ENUM_BITFIELD(indirect_branch) function_return_type : 3;
 
-  /* If true, the current function is a function specified with
-     the "interrupt" or "no_caller_saved_registers" attribute.  */
-  BOOL_BITFIELD no_caller_saved_registers : 1;
+  /* Call saved registers type.  */
+  ENUM_BITFIELD(call_saved_registers_type) call_saved_registers : 2;
+
+  /* If true, the current function calls no_callee_saved_registers
+     functions.  */
+  BOOL_BITFIELD call_no_callee_saved_registers : 1;
 
   /* If true, there is register available for argument passing.  This
      is used only in ix86_function_ok_for_sibcall by 32-bit to determine
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 0bc586d120e..4cafa6d416b 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -6767,6 +6767,14 @@  On x86-32 targets, the @code{stdcall} attribute causes the compiler to
 assume that the called function pops off the stack space used to
 pass arguments, unless it takes a variable number of arguments.
 
+@cindex @code{no_callee_saved_registers} function attribute, x86
+@item no_callee_saved_registers
+Use this attribute to indicate that the specified function has no
+callee-saved registers. That is, all registers can be used as scratch
+registers. For example, this attribute can be used for a function
+called from the interrupt handler assembly stub which will preserve
+all registers and return from interrupt.
+
 @cindex @code{no_caller_saved_registers} function attribute, x86
 @item no_caller_saved_registers
 Use this attribute to indicate that the specified function has no
diff --git a/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1a.c b/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1a.c
new file mode 100644
index 00000000000..8c48ec0c79a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1a.c
@@ -0,0 +1,23 @@ 
+/* { dg-do run { target i?86-*-* x86_64-*-* } } */
+/* { dg-additional-sources no-callee-saved-run-1b.c } */
+
+extern void bar0 (int, int, int, int, int, int, int, int, int)
+   __attribute__ ((no_callee_saved_registers));
+
+void
+foo (void)
+{
+  bar0 (0, 1, 2, 3, 4, 5, 6, 7, 8);
+}
+
+int
+bar (int x)
+{
+  return x;
+}
+
+void
+bad (void)
+{
+  __builtin_abort ();
+}
diff --git a/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1b.c b/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1b.c
new file mode 100644
index 00000000000..b3ce7e72e85
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1b.c
@@ -0,0 +1,59 @@ 
+/* { dg-do compile { target i?86-*-* x86_64-*-* } } */
+
+extern void foo (void);
+extern void bad (void);
+extern int bar (int);
+
+void
+__attribute__ ((no_callee_saved_registers))
+bar0 (int i0, int i1, int i2, int i3, int i4, int i5, int i6,
+      int i7, int i8)
+{
+  if (i0 != 0)
+     bad ();
+
+  if (i1 != 1)
+     bad ();
+
+  if (i2 != 2)
+     bad ();
+
+  if (i3 != 3)
+     bad ();
+
+  if (i4 != 4)
+     bad ();
+
+  if (i5 != 5)
+     bad ();
+
+  if (i6 != 6)
+     bad ();
+
+  if (i7 != 7)
+     bad ();
+
+  if (i8 != 8)
+     bad ();
+
+  int a,b,c,d,e,f,i;
+  a = bar (5);
+  b = bar (a);
+  c = bar (b);
+  d = bar (c);
+  e = bar (d);
+  f = bar (e);
+  for (i = 1; i < 10; i++)
+  {
+    a += bar (a + i) + bar (b + i) +
+	 bar (c + i) + bar (d + i) +
+	 bar (e + i) + bar (f + i);
+  }
+}
+
+int
+main ()
+{
+  foo ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-1.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-1.c
new file mode 100644
index 00000000000..8fe36eb5198
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-1.c
@@ -0,0 +1,30 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
+
+extern int bar (int)
+#ifndef __x86_64__
+__attribute__ ((regparm(3)))
+#endif
+;
+
+__attribute__ ((no_callee_saved_registers))
+void
+foo (void *frame)
+{
+  int a,b,c,d,e,f,i;
+  a = bar (5);
+  b = bar (a);
+  c = bar (b);
+  d = bar (c);
+  e = bar (d);
+  f = bar (e);
+  for (i = 1; i < 10; i++)
+  {
+    a += bar (a + i) + bar (b + i) +
+	 bar (c + i) + bar (d + i) +
+	 bar (e + i) + bar (f + i);
+  }
+}
+
+/* { dg-final { scan-assembler-not "push" } } */
+/* { dg-final { scan-assembler-not "pop" } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-10.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-10.c
new file mode 100644
index 00000000000..87766c6cd88
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-10.c
@@ -0,0 +1,46 @@ 
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -mgeneral-regs-only -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
+
+extern void bar (void) __attribute__ ((no_callee_saved_registers));
+
+__attribute__ ((no_caller_saved_registers))
+void
+foo (void)
+{
+  bar ();
+}
+
+/* foo must save and restore all caller saved registers since bar won't
+   preserve any.  */
+/* { dg-final { scan-assembler-not "jmp\[\\t \]+_?bar" } } */
+/* { dg-final { scan-assembler "call\[\\t \]+_?bar" } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)ax" 1 } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)cx" 1 } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)dx" 1 } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)si" 1 } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)di" 1 } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r8" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r9" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r10" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r11" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)ax" 1 } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)cx" 1 } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)dx" 1 } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)si" 1 } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)di" 1 } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r8" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r9" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r10" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r11" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-11.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-11.c
new file mode 100644
index 00000000000..902a764489e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-11.c
@@ -0,0 +1,11 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+extern void foo (void); /* { dg-note "previous declaration" } */
+
+__attribute__ ((no_callee_saved_registers))
+void
+foo (void) /* { dg-error "conflicting types" } */
+{
+}
+
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-12.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-12.c
new file mode 100644
index 00000000000..5524a4af29c
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-12.c
@@ -0,0 +1,10 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+extern void foo (void) __attribute__ ((no_callee_saved_registers)); /* { dg-note "previous declaration" } */
+
+void
+foo (void) /* { dg-error "conflicting types" } */
+{
+}
+
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-13.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-13.c
new file mode 100644
index 00000000000..6757e72d848
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-13.c
@@ -0,0 +1,16 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
+
+extern void foo (void);
+
+__attribute__ ((no_callee_saved_registers))
+void
+bar (void)
+{
+  foo ();
+}
+
+/* { dg-final { scan-assembler-not "push" } } */
+/* { dg-final { scan-assembler-not "pop" } } */
+/* { dg-final { scan-assembler-not "call\[\\t \]+_?foo" } } */
+/* { dg-final { scan-assembler "jmp\[\\t \]+_?foo" } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-14.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-14.c
new file mode 100644
index 00000000000..2239e286e6a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-14.c
@@ -0,0 +1,16 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
+
+extern void bar (void) __attribute__ ((no_callee_saved_registers));
+
+__attribute__ ((no_callee_saved_registers))
+void
+foo (void)
+{
+  bar ();
+}
+
+/* { dg-final { scan-assembler-not "push" } } */
+/* { dg-final { scan-assembler-not "pop" } } */
+/* { dg-final { scan-assembler "jmp\[\\t \]+_?bar" } } */
+/* { dg-final { scan-assembler-not "call\[\\t \]+_?bar" } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-15.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-15.c
new file mode 100644
index 00000000000..10135fec9c1
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-15.c
@@ -0,0 +1,17 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
+
+typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers));
+extern fn_t bar;
+
+__attribute__ ((no_callee_saved_registers))
+void
+foo (void)
+{
+  bar ();
+}
+
+/* { dg-final { scan-assembler-not "push" } } */
+/* { dg-final { scan-assembler-not "pop" } } */
+/* { dg-final { scan-assembler "jmp" } } */
+/* { dg-final { scan-assembler-not "call\[\\t \]+" } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-16.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-16.c
new file mode 100644
index 00000000000..112d1764f3e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-16.c
@@ -0,0 +1,16 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
+
+typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers));
+
+__attribute__ ((no_callee_saved_registers))
+void
+foo (fn_t bar)
+{
+  bar ();
+}
+
+/* { dg-final { scan-assembler-not "push" } } */
+/* { dg-final { scan-assembler-not "pop" } } */
+/* { dg-final { scan-assembler "jmp" } } */
+/* { dg-final { scan-assembler-not "call\[\\t \]+" } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-17.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-17.c
new file mode 100644
index 00000000000..1fd5daadf08
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-17.c
@@ -0,0 +1,16 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
+
+extern void foo (void) __attribute__ ((no_caller_saved_registers));
+
+__attribute__ ((no_callee_saved_registers))
+void
+bar (void)
+{
+  foo ();
+}
+
+/* { dg-final { scan-assembler-not "push" } } */
+/* { dg-final { scan-assembler-not "pop" } } */
+/* { dg-final { scan-assembler-not "call\[\\t \]+_?foo" } } */
+/* { dg-final { scan-assembler "jmp\[\\t \]+_?foo" } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-18.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-18.c
new file mode 100644
index 00000000000..e7101009be4
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-18.c
@@ -0,0 +1,51 @@ 
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
+
+#include <stdint.h>
+
+typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers));
+
+void
+foo (uintptr_t p)
+{
+  ((fn_t) p) ();
+}
+
+/* foo must save and restore all caller saved registers since bar won't
+   preserve any.  */
+/* { dg-final { scan-assembler-not "jmp" } } */
+/* { dg-final { scan-assembler "call\[\\t \]+" } } */
+/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)ax" } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
+/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)cx" } } */
+/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)dx" } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%esi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rsi" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rdi" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r8" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r9" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r10" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r11" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)ax" } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
+/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)cx" } } */
+/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)dx" } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
+/* { dg-final { scan-assembler-times "popl\[\\t \]*%esi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%rsi" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%rdi" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%r8" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%r9" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%r10" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%r11" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-2.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-2.c
new file mode 100644
index 00000000000..ce4ab3b1799
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-2.c
@@ -0,0 +1,30 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
+
+extern int bar (int) __attribute__ ((no_caller_saved_registers))
+#ifndef __x86_64__
+__attribute__ ((regparm(3)))
+#endif
+;
+
+__attribute__ ((no_callee_saved_registers))
+void
+foo (void *frame)
+{
+  int a,b,c,d,e,f,i;
+  a = bar (5);
+  b = bar (a);
+  c = bar (b);
+  d = bar (c);
+  e = bar (d);
+  f = bar (e);
+  for (i = 1; i < 10; i++)
+  {
+    a += bar (a + i) + bar (b + i) +
+	 bar (c + i) + bar (d + i) +
+	 bar (e + i) + bar (f + i);
+  }
+}
+
+/* { dg-final { scan-assembler-not "push" } } */
+/* { dg-final { scan-assembler-not "pop" } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-3.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-3.c
new file mode 100644
index 00000000000..453272e11c0
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-3.c
@@ -0,0 +1,8 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+__attribute__ ((no_callee_saved_registers, no_caller_saved_registers))
+void
+foo (void) /* { dg-error "attributes are not compatible" } */
+{
+}
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-4.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-4.c
new file mode 100644
index 00000000000..ec566aaf09f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-4.c
@@ -0,0 +1,8 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mgeneral-regs-only" } */
+
+__attribute__ ((no_callee_saved_registers, interrupt))
+void
+foo (void *frame) /* { dg-error "attributes are not compatible" } */
+{
+}
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-5.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-5.c
new file mode 100644
index 00000000000..b28b211986a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-5.c
@@ -0,0 +1,11 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+typedef void (*fn_t) (void *) __attribute__ ((no_callee_saved_registers));
+
+void
+foo (void *frame)
+{
+}
+
+fn_t func = foo; /* { dg-error "incompatible pointer type" } */
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-6.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-6.c
new file mode 100644
index 00000000000..a7b3bdabf43
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-6.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+typedef void (*fn_t) (void *) __attribute__ ((no_callee_saved_registers));
+
+__attribute__ ((no_callee_saved_registers))
+void
+foo (void *frame)
+{
+}
+
+fn_t func = foo;
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-7.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-7.c
new file mode 100644
index 00000000000..a1837fdfd4b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-7.c
@@ -0,0 +1,49 @@ 
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
+
+extern void bar (void) __attribute__ ((no_callee_saved_registers));
+
+void
+foo (void)
+{
+  bar ();
+}
+
+/* foo must save and restore all caller saved registers since bar won't
+   preserve any.  */
+/* { dg-final { scan-assembler-not "jmp\[\\t \]+_?bar" } } */
+/* { dg-final { scan-assembler "call\[\\t \]+_?bar" } } */
+/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)ax" } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
+/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)cx" } } */
+/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)dx" } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%esi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rsi" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rdi" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r8" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r9" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r10" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r11" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)ax" } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
+/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)cx" } } */
+/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)dx" } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
+/* { dg-final { scan-assembler-times "popl\[\\t \]*%esi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%rsi" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%rdi" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%r8" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%r9" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%r10" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%r11" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-8.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-8.c
new file mode 100644
index 00000000000..90b98a21aef
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-8.c
@@ -0,0 +1,50 @@ 
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
+
+typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers));
+extern fn_t bar;
+
+void
+foo (void)
+{
+  bar ();
+}
+
+/* foo must save and restore all caller saved registers since bar won't
+   preserve any.  */
+/* { dg-final { scan-assembler-not "jmp" } } */
+/* { dg-final { scan-assembler "call\[\\t \]+" } } */
+/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)ax" } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
+/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)cx" } } */
+/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)dx" } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%esi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rsi" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rdi" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r8" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r9" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r10" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r11" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)ax" } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
+/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)cx" } } */
+/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)dx" } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
+/* { dg-final { scan-assembler-times "popl\[\\t \]*%esi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%rsi" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%rdi" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%r8" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%r9" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%r10" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%r11" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-9.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-9.c
new file mode 100644
index 00000000000..e261100ac1a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-9.c
@@ -0,0 +1,49 @@ 
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */
+
+typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers));
+
+void
+foo (fn_t bar)
+{
+  bar ();
+}
+
+/* foo must save and restore all caller saved registers since bar won't
+   preserve any.  */
+/* { dg-final { scan-assembler-not "jmp" } } */
+/* { dg-final { scan-assembler "call\[\\t \]+" } } */
+/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)ax" } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
+/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)cx" } } */
+/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)dx" } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%esi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rsi" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rdi" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r8" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r9" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r10" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r11" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)ax" } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */
+/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)cx" } } */
+/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)dx" } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
+/* { dg-final { scan-assembler-times "popl\[\\t \]*%esi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%rsi" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%rdi" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%r8" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%r9" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%r10" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "popq\[\\t \]*%r11" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */