On Wed, May 13, 2026 at 7:11 PM Richard Sandiford
<[email protected]> wrote:
>
> "H.J. Lu" <[email protected]> writes:
> > Implement TARGET_FNTYPE_ABI to avoid spills of callee-saved registers
> > when calling functions with no_caller_saved_registers attribute.
> >
> > 1. For functions with no_callee_saved_registers attribute, frame register
> > is preserved to mitigate PR target/114116.  MMX and x87 registers aren't
> > clobbered if MMX and x87 aren't enabled.
> > 2. For functions with no_caller_saved_registers attribute, MMX and x87
> > registers are clobbered since saving and restoring registers doesn't
> > include MMX nor x87 registers.
> > 3. Don't mark disabled registers as call used to avoid reg_to_stack
> > crashes when x87 registers are still accessed even with -mno-mmx
> > -mno-80387.
> > 4. Add ABI_ORIGINAL which is the function ABI without attributes on
> > the current function.
> > 5. Add ABI_ALTERNATE which is the alternate function ABI from ABI_DEFAULT.
> > If ix86_abi is SYSV_ABI, ABI_ALTERNATE is the function ABI for MS_ABI.
> > Otherwise, ABI_ALTERNATE is the function ABI for SYSV_ABI.
> >
> > Tested on Linux/x86-64 and with CPython 3.14.4.
>
> Like I mentioned in the PR trail for 125266, I don't think
> the ix86_original_abi stuff, and the use of cfun in
> ix86_conditional_register_usage, is correct.  Maybe the simplest
> thing would be for me to come up with a counterproposal, based on
> this patch.  It might be a few days before I can give it a go though.

Hi Richard,

I found a typo in the v2 patch.   Here is the v3 patch.  I also changed
ix86_original_abi and ix86_alternate_abi implementation:

/* Return the descriptor of the standard function ABI type.  If
   ABI_TYPE == ABI_ALTERNATE, return the function alternate ABI type.  */

static const predefined_function_abi &
ix86_standard_abi (int abi_type)
{
  static const char ix86_call_used_regs[] = CALL_USED_REGISTERS;
  auto &standard_abi = function_abis[abi_type];
  if (!standard_abi.initialized_p ())
    {
      HARD_REG_SET full_reg_clobbers = {};

      /* Add all registers that are clobbered by the call.  NB: If the
         current ABI is SYSV_ABI, the alternate ABI is MS_ABI.   */
      bool is_64bit_ms_abi = (TARGET_64BIT
                              && ix86_abi == (abi_type == ABI_ALTERNATE
                                              ? SYSV_ABI : MS_ABI));
      char c_mask = CALL_USED_REGISTERS_MASK (is_64bit_ms_abi);
      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
        if (global_regs[i]
            || (!fixed_regs[i]
                && (ix86_call_used_regs[i] == 1
                    || (ix86_call_used_regs[i] & c_mask))))
          SET_HARD_REG_BIT (full_reg_clobbers, i);

      SET_HARD_REG_BIT (full_reg_clobbers, FLAGS_REG);
      SET_HARD_REG_BIT (full_reg_clobbers, FPSR_REG);

      standard_abi.initialize (abi_type, full_reg_clobbers);
    }
  return standard_abi;
}

/* Return the descriptor of the function ABI type without attributes
   on the current function.  */

static const predefined_function_abi &
ix86_original_abi (void)
{
  return ix86_standard_abi (ABI_ORIGINAL);
}

/* Return the descriptor of the function alternate ABI type.  */

static const predefined_function_abi &
ix86_alternate_abi (void)
{
  return ix86_standard_abi (ABI_ALTERNATE);
}

Thanks.

> Thanks,
> Richard
>
> >
> > gcc/
> >
> > PR target/124798
> > * config/i386/i386-expand.cc: Include "function-abi.h".
> > (ix86_expand_call): Add call clobbers only when callee is
> > no-callee-saved and caller isn't.  Use callee abi to get the
> > list of call clobbers.
> > * config/i386/i386-options.cc (ix86_init_machine_status): Call
> > ix86_init_original_abi.
> > * config/i386/i386-protos.h
> > (ix86_type_no_callee_saved_registers_p): Removed.
> > (ix86_init_original_abi): New.
> > * config/i386/i386.cc (ix86_conditional_register_usage): Mark
> > all, but frame pointer, are caller-saved in no-callee-saved and
> > preserve_none functions.  Clear disabled registers in
> > call_used_regs.
> > (ix86_type_no_callee_saved_registers_p): Make it static.
> > (ix86_no_callee_saved_abi): New function.
> > (ix86_no_caller_saved_abi_void): Likewise.
> > (ix86_no_caller_saved_abi_ax): Likewise.
> > (ix86_no_caller_saved_abi_ax_dx): Likewise.
> > (ix86_no_caller_saved_abi_xmm0): Likewise.
> > (ix86_no_caller_saved_abi_xmm0_xmm1): Likewise.
> > (ix86_original_abi): Likewise.
> > (ix86_alternate_abi): Likewise.
> > (ix86_init_original_abi): Likewise.
> > (ix86_function_abi_id): Likewise.
> > (ix86_fntype_abi): Likewise.
> > (ix86_hard_regno_call_part_clobbered): Handle newly added ABIs.
> > (TARGET_FNTYPE_ABI): New.
> > * config/i386/i386.md: Add ABI_ORIGINAL, ABI_ALTERNATE,
> > ABI_NO_CALLEE_SAVED, ABI_NO_CALLER_SAVED_RETURN_VOID,
> > ABI_NO_CALLER_SAVED_RETURN_AX, ABI_NO_CALLER_SAVED_RETURN_AX_DX,
> > ABI_NO_CALLER_SAVED_RETURN_XMM0 and
> > ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1.
> >
> > gcc/testsuite/
> >
> > PR target/124798
> > * gcc.target/i386/no-callee-saved-18.c: Don't check frame
> > register.
> > * gcc.target/i386/no-callee-saved-19b.c: Update the expected
> > instruction order.
> > * gcc.target/i386/no-callee-saved-19d.c: Likewise.
> > * gcc.target/i386/no-callee-saved-19e.c: Likewise.
> > * gcc.target/i386/no-callee-saved-2.c: Check frame register isn't
> > saved nor restored in 64-bit mode.
> > * gcc.target/i386/no-callee-saved-8.c: Expect no saving nor
> > restoring caller-saved registers.
> > * gcc.target/i386/no-callee-saved-9.c: Likewise.
> > * gcc.target/i386/preserve-none-14.c: Don't check frame register.
> > * gcc.target/i386/preserve-none-23.c: Likewise.
> > * gcc.target/i386/preserve-none-7.c: Check frame register isn't
> > saved nor restored in 64-bit mode
> > * gcc.target/i386/no-caller-saved-1-ms.c: New test.
> > * gcc.target/i386/no-caller-saved-1-sysv.c: Likewise.
> > * gcc.target/i386/no-caller-saved-1.c: Likewise.
> > * gcc.target/i386/no-caller-saved-2.c: Likewise.
> > * gcc.target/i386/no-caller-saved-3.c: Likewise.
> > * gcc.target/i386/no-caller-saved-4.c: Likewise.
> > * gcc.target/i386/no-caller-saved-5.c: Likewise.
> > * gcc.target/i386/no-caller-saved-6.c: Likewise.
> > * gcc.target/i386/no-caller-saved-7.c: Likewise.
> > * gcc.target/i386/stack-check-17.c: Also expect 1 pop in 64-bit
> > mode.
> >
> >
> > --
> > H.J.
> >
> > From 8f6ebc95073e972b75796e9109ca1242f7bee94d Mon Sep 17 00:00:00 2001
> > From: "H.J. Lu" <[email protected]>
> > Date: Tue, 14 Apr 2026 18:37:20 +0800
> > Subject: [PATCH v2] x86: Implement TARGET_FNTYPE_ABI
> >
> > Implement TARGET_FNTYPE_ABI to avoid spills of callee-saved registers
> > when calling functions with no_caller_saved_registers attribute.
> >
> > 1. For functions with no_callee_saved_registers attribute, frame register
> > is preserved to mitigate PR target/114116.  MMX and x87 registers aren't
> > clobbered if MMX and x87 aren't enabled.
> > 2. For functions with no_caller_saved_registers attribute, MMX and x87
> > registers are clobbered since saving and restoring registers doesn't
> > include MMX nor x87 registers.
> > 3. Don't mark disabled registers as call used to avoid reg_to_stack
> > crashes when x87 registers are still accessed even with -mno-mmx
> > -mno-80387.
> > 4. Add ABI_ORIGINAL which is the function ABI without attributes on
> > the current function.
> > 5. Add ABI_ALTERNATE which is the alternate function ABI from ABI_DEFAULT.
> > If ix86_abi is SYSV_ABI, ABI_ALTERNATE is the function ABI for MS_ABI.
> > Otherwise, ABI_ALTERNATE is the function ABI for SYSV_ABI.
> >
> > Tested on Linux/x86-64 and with CPython 3.14.4.
> >
> > gcc/
> >
> >       PR target/124798
> >       * config/i386/i386-expand.cc: Include "function-abi.h".
> >       (ix86_expand_call): Add call clobbers only when callee is
> >       no-callee-saved and caller isn't.  Use callee abi to get the
> >       list of call clobbers.
> >       * config/i386/i386-options.cc (ix86_init_machine_status): Call
> >       ix86_init_original_abi.
> >       * config/i386/i386-protos.h
> >       (ix86_type_no_callee_saved_registers_p): Removed.
> >       (ix86_init_original_abi): New.
> >       * config/i386/i386.cc (ix86_conditional_register_usage): Mark
> >       all, but frame pointer, are caller-saved in no-callee-saved and
> >       preserve_none functions.  Clear disabled registers in
> >       call_used_regs.
> >       (ix86_type_no_callee_saved_registers_p): Make it static.
> >       (ix86_no_callee_saved_abi): New function.
> >       (ix86_no_caller_saved_abi_void): Likewise.
> >       (ix86_no_caller_saved_abi_ax): Likewise.
> >       (ix86_no_caller_saved_abi_ax_dx): Likewise.
> >       (ix86_no_caller_saved_abi_xmm0): Likewise.
> >       (ix86_no_caller_saved_abi_xmm0_xmm1): Likewise.
> >       (ix86_original_abi): Likewise.
> >       (ix86_alternate_abi): Likewise.
> >       (ix86_init_original_abi): Likewise.
> >       (ix86_function_abi_id): Likewise.
> >       (ix86_fntype_abi): Likewise.
> >       (ix86_hard_regno_call_part_clobbered): Handle newly added ABIs.
> >       (TARGET_FNTYPE_ABI): New.
> >       * config/i386/i386.md: Add ABI_ORIGINAL, ABI_ALTERNATE,
> >       ABI_NO_CALLEE_SAVED, ABI_NO_CALLER_SAVED_RETURN_VOID,
> >       ABI_NO_CALLER_SAVED_RETURN_AX, ABI_NO_CALLER_SAVED_RETURN_AX_DX,
> >       ABI_NO_CALLER_SAVED_RETURN_XMM0 and
> >       ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1.
> >
> > gcc/testsuite/
> >
> >       PR target/124798
> >       * gcc.target/i386/no-callee-saved-18.c: Don't check frame
> >       register.
> >       * gcc.target/i386/no-callee-saved-19b.c: Update the expected
> >       instruction order.
> >       * gcc.target/i386/no-callee-saved-19d.c: Likewise.
> >       * gcc.target/i386/no-callee-saved-19e.c: Likewise.
> >       * gcc.target/i386/no-callee-saved-2.c: Check frame register isn't
> >       saved nor restored in 64-bit mode.
> >       * gcc.target/i386/no-callee-saved-8.c: Expect no saving nor
> >       restoring caller-saved registers.
> >       * gcc.target/i386/no-callee-saved-9.c: Likewise.
> >       * gcc.target/i386/preserve-none-14.c: Don't check frame register.
> >       * gcc.target/i386/preserve-none-23.c: Likewise.
> >       * gcc.target/i386/preserve-none-7.c: Check frame register isn't
> >       saved nor restored in 64-bit mode
> >       * gcc.target/i386/no-caller-saved-1-ms.c: New test.
> >       * gcc.target/i386/no-caller-saved-1-sysv.c: Likewise.
> >       * gcc.target/i386/no-caller-saved-1.c: Likewise.
> >       * gcc.target/i386/no-caller-saved-2.c: Likewise.
> >       * gcc.target/i386/no-caller-saved-3.c: Likewise.
> >       * gcc.target/i386/no-caller-saved-4.c: Likewise.
> >       * gcc.target/i386/no-caller-saved-5.c: Likewise.
> >       * gcc.target/i386/no-caller-saved-6.c: Likewise.
> >       * gcc.target/i386/no-caller-saved-7.c: Likewise.
> >       * gcc.target/i386/stack-check-17.c: Also expect 1 pop in 64-bit
> >       mode.
> >
> > Signed-off-by: H.J. Lu <[email protected]>
> > ---
> >  gcc/config/i386/i386-expand.cc                |  31 +-
> >  gcc/config/i386/i386-options.cc               |   4 +
> >  gcc/config/i386/i386-protos.h                 |   2 +-
> >  gcc/config/i386/i386.cc                       | 393 +++++++++++++++++-
> >  gcc/config/i386/i386.md                       |  30 +-
> >  .../gcc.target/i386/no-callee-saved-18.c      |   2 -
> >  .../gcc.target/i386/no-callee-saved-19b.c     |   8 +-
> >  .../gcc.target/i386/no-callee-saved-19d.c     |   6 +-
> >  .../gcc.target/i386/no-callee-saved-19e.c     |   8 +-
> >  .../gcc.target/i386/no-callee-saved-2.c       |  10 +-
> >  .../gcc.target/i386/no-callee-saved-8.c       |   8 +-
> >  .../gcc.target/i386/no-callee-saved-9.c       |  10 +-
> >  .../gcc.target/i386/no-caller-saved-1-ms.c    |  50 +++
> >  .../gcc.target/i386/no-caller-saved-1-sysv.c  |  46 ++
> >  .../gcc.target/i386/no-caller-saved-1.c       |  50 +++
> >  .../gcc.target/i386/no-caller-saved-2.c       |  49 +++
> >  .../gcc.target/i386/no-caller-saved-3.c       |  49 +++
> >  .../gcc.target/i386/no-caller-saved-4.c       |  44 ++
> >  .../gcc.target/i386/no-caller-saved-5.c       |  34 ++
> >  .../gcc.target/i386/no-caller-saved-6.c       |  34 ++
> >  .../gcc.target/i386/no-caller-saved-7.c       |  49 +++
> >  .../gcc.target/i386/preserve-none-14.c        |   2 -
> >  .../gcc.target/i386/preserve-none-23.c        |   2 -
> >  .../gcc.target/i386/preserve-none-7.c         |  10 +-
> >  .../gcc.target/i386/stack-check-17.c          |   3 +-
> >  25 files changed, 860 insertions(+), 74 deletions(-)
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-1-ms.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-1-sysv.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-1.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-2.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-3.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-4.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-5.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-6.c
> >  create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-7.c
> >
> > diff --git a/gcc/config/i386/i386-expand.cc b/gcc/config/i386/i386-expand.cc
> > index df44a4eb99d..443eacbfe62 100644
> > --- a/gcc/config/i386/i386-expand.cc
> > +++ b/gcc/config/i386/i386-expand.cc
> > @@ -94,6 +94,7 @@ along with GCC; see the file COPYING3.  If not see
> >  #include "i386-builtins.h"
> >  #include "i386-expand.h"
> >  #include "asan.h"
> > +#include "function-abi.h"
> >
> >  /* Split one or more double-mode RTL references into pairs of half-mode
> >     references.  The RTL can be REG, offsettable MEM, integer constant, or
> > @@ -11081,7 +11082,8 @@ 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;
> > +  function_abi caller_abi = fndecl_abi (current_function_decl);
> > +  function_abi callee_abi = caller_abi;
> >
> >    if (SYMBOL_REF_P (XEXP (fnaddr, 0)))
> >      {
> > @@ -11091,8 +11093,8 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx 
> > callarg1,
> >         if (lookup_attribute ("interrupt",
> >                               TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))
> >           error ("interrupt service routine cannot be called directly");
> > -       else if (ix86_type_no_callee_saved_registers_p (TREE_TYPE (fndecl)))
> > -         call_no_callee_saved_registers = true;
> > +       else if (TREE_CODE (fndecl) == FUNCTION_DECL)
> > +         callee_abi = fndecl_abi (fndecl);
> >         if (fndecl == current_function_decl
> >             && decl_binds_to_current_def_p (fndecl))
> >           cfun->machine->recursive_function = true;
> > @@ -11103,10 +11105,8 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx 
> > callarg1,
> >        if (MEM_P (fnaddr))
> >       {
> >         tree mem_expr = MEM_EXPR (fnaddr);
> > -       if (mem_expr != nullptr
> > -           && TREE_CODE (mem_expr) == MEM_REF
> > -           && ix86_type_no_callee_saved_registers_p (TREE_TYPE (mem_expr)))
> > -         call_no_callee_saved_registers = true;
> > +       if (mem_expr != nullptr && TREE_CODE (mem_expr) == MEM_REF)
> > +         callee_abi = fntype_abi (TREE_TYPE (mem_expr));
> >       }
> >
> >        fndecl = NULL_TREE;
> > @@ -11320,21 +11320,14 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx 
> > callarg1,
> >        clobber_reg (&use, gen_rtx_REG (DImode, R10_REG));
> >      }
> >
> > -  if (call_no_callee_saved_registers)
> > +  if (callee_abi.id () == ABI_NO_CALLEE_SAVED
> > +      && caller_abi.id () != ABI_NO_CALLEE_SAVED)
> >      {
> > -      /* After calling a no_callee_saved_registers function, all
> > -      registers may be clobbered.  Clobber all registers that are
> > -      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]
> > +     if (GENERAL_REGNO_P (i)
> > +         && TEST_HARD_REG_BIT (accessible_reg_set, i)
> >           && i != HARD_FRAME_POINTER_REGNUM
> > -         && !(ix86_call_used_regs[i] == 1
> > -              || (ix86_call_used_regs[i] & c_mask))
> > -         && !STACK_REGNO_P (i)
> > -         && !MMX_REGNO_P (i))
> > +         && callee_abi.clobbers_at_least_part_of_reg_p (i))
> >         clobber_reg (&use,
> >                      gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i));
> >      }
> > diff --git a/gcc/config/i386/i386-options.cc 
> > b/gcc/config/i386/i386-options.cc
> > index 7ffe9cd2a38..890b36835dd 100644
> > --- a/gcc/config/i386/i386-options.cc
> > +++ b/gcc/config/i386/i386-options.cc
> > @@ -2004,6 +2004,10 @@ ix86_init_machine_status (void)
> >    f->stack_frame_required = true;
> >    f->silent_p = true;
> >
> > +  /* NB: Call ix86_init_original_abi to make a copy of the function ABI
> > +     without attributes on the current function.  */
> > +  ix86_init_original_abi ();
> > +
> >    return f;
> >  }
> >
> > diff --git a/gcc/config/i386/i386-protos.h b/gcc/config/i386/i386-protos.h
> > index 4ba4fb08556..1664a1a06a3 100644
> > --- a/gcc/config/i386/i386-protos.h
> > +++ b/gcc/config/i386/i386-protos.h
> > @@ -283,10 +283,10 @@ extern tree ix86_valid_target_attribute_tree (tree, 
> > tree,
> >                                             struct gcc_options *,
> >                                             struct gcc_options *, bool);
> >  extern unsigned int ix86_get_callcvt (const_tree);
> > -extern bool ix86_type_no_callee_saved_registers_p (const_tree);
> >
> >  #endif
> >
> > +extern void ix86_init_original_abi (void);
> >  extern rtx ix86_tls_module_base (void);
> >  extern bool ix86_gpr_tls_address_pattern_p (rtx);
> >  extern bool ix86_tls_address_pattern_p (rtx);
> > diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
> > index 2744c749578..ab30d03e998 100644
> > --- a/gcc/config/i386/i386.cc
> > +++ b/gcc/config/i386/i386.cc
> > @@ -503,16 +503,33 @@ ix86_conditional_register_usage (void)
> >  {
> >    int i, c_mask;
> >
> > -  /* If there are no caller-saved registers, preserve all registers.
> > -     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->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;
> > +  if (cfun)
> > +    switch (cfun->machine->call_saved_registers)
> > +      {
> > +      case TYPE_DEFAULT_CALL_SAVED_REGISTERS:
> > +     break;
> > +
> > +      case TYPE_NO_CALLER_SAVED_REGISTERS:
> > +     /* If there are no caller-saved registers, preserve all
> > +        registers.  except fixed_regs and registers used for
> > +        function return value since aggregate_value_p checks
> > +        call_used_regs[regno] on return value.  */
> > +     for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
> > +       if (!fixed_regs[i] && !ix86_function_value_regno_p (i))
> > +         call_used_regs[i] = 0;
> > +     break;
> > +
> > +      case TYPE_NO_CALLEE_SAVED_REGISTERS:
> > +      case TYPE_PRESERVE_NONE:
> > +     /* All, but frame pointer, are caller-saved.  */
> > +     for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
> > +       if (GENERAL_REGNO_P (i)
> > +           || SSE_REGNO_P (i)
> > +           || MASK_REGNO_P (i))
> > +         call_used_regs[i] = 1;
> > +     call_used_regs[BP_REG] = 0;
> > +     break;
> > +      }
> >
> >    /* For 32-bit targets, disable the REX registers.  */
> >    if (! TARGET_64BIT)
> > @@ -571,6 +588,13 @@ ix86_conditional_register_usage (void)
> >        for (i = FIRST_REX2_INT_REG; i <= LAST_REX2_INT_REG; i++)
> >       CLEAR_HARD_REG_BIT (accessible_reg_set, i);
> >      }
> > +
> > +  /* If a register is disabled, it can't be used for call.  This avoids
> > +     reg_to_stack crashes when x87 registers are still accessed even
> > +     with -mno-mmx -mno-80387.  */
> > +  for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
> > +    if (!TEST_HARD_REG_BIT (accessible_reg_set, i))
> > +      call_used_regs[i] = 0;
> >  }
> >
> >  /* Canonicalize a comparison from one we don't have to one we do have.  */
> > @@ -932,7 +956,7 @@ x86_64_elf_unique_section (tree decl, int reloc)
> >  /* Return true if TYPE has no_callee_saved_registers or preserve_none
> >     attribute.  */
> >
> > -bool
> > +static bool
> >  ix86_type_no_callee_saved_registers_p (const_tree type)
> >  {
> >    return (lookup_attribute ("no_callee_saved_registers",
> > @@ -21854,6 +21878,307 @@ ix86_hard_regno_mode_ok (unsigned int regno, 
> > machine_mode mode)
> >    return false;
> >  }
> >
> > +/* Return the descriptor of no_callee_saved_registers function type.
> > +   None of registers are preserved, except for frame register to
> > +   mitigate PR target/114116.  MMX and x87 registers are preserved
> > +   if MMX and x87 aren't enabled.  */
> > +
> > +static const predefined_function_abi &
> > +ix86_no_callee_saved_abi (void)
> > +{
> > +  auto &no_callee_saved_abi = function_abis[ABI_NO_CALLEE_SAVED];
> > +  if (!no_callee_saved_abi.initialized_p ())
> > +    {
> > +      HARD_REG_SET full_reg_clobbers = reg_class_contents[ALL_REGS];
> > +      CLEAR_HARD_REG_BIT (full_reg_clobbers, HARD_FRAME_POINTER_REGNUM);
> > +      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
> > +     if ((!TARGET_80387 && STACK_REGNO_P (i))
> > +         || (!TARGET_MMX && MMX_REGNO_P (i)))
> > +       CLEAR_HARD_REG_BIT (full_reg_clobbers, i);
> > +      no_callee_saved_abi.initialize (ABI_NO_CALLEE_SAVED,
> > +                                   full_reg_clobbers);
> > +    }
> > +  return no_callee_saved_abi;
> > +}
> > +
> > +/* Return the descriptor of no_caller_saved_registers function type.
> > +   All registers are preserved, except for MMX and x87 registers
> > +   which aren't supported when saving and restoring registers.  */
> > +
> > +static const predefined_function_abi &
> > +ix86_no_caller_saved_abi_void (void)
> > +{
> > +  auto &no_caller_saved_abi
> > +    = function_abis[ABI_NO_CALLER_SAVED_RETURN_VOID];
> > +  if (!no_caller_saved_abi.initialized_p ())
> > +    {
> > +      HARD_REG_SET full_reg_clobbers = {};
> > +      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
> > +     if ((TARGET_80387 && STACK_REGNO_P (i))
> > +         || (TARGET_MMX && MMX_REGNO_P (i)))
> > +       SET_HARD_REG_BIT (full_reg_clobbers, i);
> > +      no_caller_saved_abi.initialize
> > +     (ABI_NO_CALLER_SAVED_RETURN_VOID, full_reg_clobbers);
> > +    }
> > +  return no_caller_saved_abi;
> > +}
> > +
> > +/* Return the descriptor of no_caller_saved_registers function type.
> > +   All registers are preserved, except for AX used for return value,
> > +   MMX and x87 registers which aren't supported when saving and
> > +   restoring registers.  */
> > +
> > +static const predefined_function_abi &
> > +ix86_no_caller_saved_abi_ax (void)
> > +{
> > +  auto &no_caller_saved_abi
> > +    = function_abis[ABI_NO_CALLER_SAVED_RETURN_AX];
> > +  if (!no_caller_saved_abi.initialized_p ())
> > +    {
> > +      HARD_REG_SET full_reg_clobbers = {};
> > +      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
> > +     if (i == AX_REG
> > +         || (TARGET_80387 && STACK_REGNO_P (i))
> > +         || (TARGET_MMX && MMX_REGNO_P (i)))
> > +       SET_HARD_REG_BIT (full_reg_clobbers, i);
> > +      no_caller_saved_abi.initialize
> > +     (ABI_NO_CALLER_SAVED_RETURN_AX, full_reg_clobbers);
> > +    }
> > +  return no_caller_saved_abi;
> > +}
> > +
> > +/* Return the descriptor of no_caller_saved_registers function type.
> > +   All registers are preserved, except for AX/DX used for return value,
> > +   MMX and x87 registers which aren't supported when saving and
> > +   restoring registers.  */
> > +
> > +static const predefined_function_abi &
> > +ix86_no_caller_saved_abi_ax_dx (void)
> > +{
> > +  auto &no_caller_saved_abi
> > +    = function_abis[ABI_NO_CALLER_SAVED_RETURN_AX_DX];
> > +  if (!no_caller_saved_abi.initialized_p ())
> > +    {
> > +      HARD_REG_SET full_reg_clobbers = {};
> > +      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
> > +     if (i == AX_REG
> > +         || i == DX_REG
> > +         || (TARGET_80387 && STACK_REGNO_P (i))
> > +         || (TARGET_MMX && MMX_REGNO_P (i)))
> > +       SET_HARD_REG_BIT (full_reg_clobbers, i);
> > +      no_caller_saved_abi.initialize
> > +     (ABI_NO_CALLER_SAVED_RETURN_AX_DX, full_reg_clobbers);
> > +    }
> > +  return no_caller_saved_abi;
> > +}
> > +
> > +/* Return the descriptor of no_caller_saved_registers function type.
> > +   All registers are preserved, except for XMM0 used for return value,
> > +   MMX and x87 registers which aren't supported when saving and
> > +   restoring registers.  */
> > +
> > +static const predefined_function_abi &
> > +ix86_no_caller_saved_abi_xmm0 (void)
> > +{
> > +  auto &no_caller_saved_abi
> > +    = function_abis[ABI_NO_CALLER_SAVED_RETURN_XMM0];
> > +  if (!no_caller_saved_abi.initialized_p ())
> > +    {
> > +      HARD_REG_SET full_reg_clobbers = {};
> > +      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
> > +     if (i == XMM0_REG
> > +         || (TARGET_80387 && STACK_REGNO_P (i))
> > +         || (TARGET_MMX && MMX_REGNO_P (i)))
> > +       SET_HARD_REG_BIT (full_reg_clobbers, i);
> > +      no_caller_saved_abi.initialize
> > +     (ABI_NO_CALLER_SAVED_RETURN_XMM0, full_reg_clobbers);
> > +    }
> > +  return no_caller_saved_abi;
> > +}
> > +
> > +/* Return the descriptor of no_caller_saved_registers function type.
> > +   All registers are preserved, except for XMM0/XMM1 used for return
> > +   value, MMX and x87 registers which aren't supported when saving and
> > +   restoring registers.  */
> > +
> > +static const predefined_function_abi &
> > +ix86_no_caller_saved_abi_xmm0_xmm1 (void)
> > +{
> > +  auto &no_caller_saved_abi
> > +    = function_abis[ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1];
> > +  if (!no_caller_saved_abi.initialized_p ())
> > +    {
> > +      HARD_REG_SET full_reg_clobbers = {};
> > +      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
> > +     if (i == XMM0_REG
> > +         || i == XMM1_REG
> > +         || (TARGET_80387 && STACK_REGNO_P (i))
> > +         || (TARGET_MMX && MMX_REGNO_P (i)))
> > +       SET_HARD_REG_BIT (full_reg_clobbers, i);
> > +      no_caller_saved_abi.initialize
> > +     (ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1, full_reg_clobbers);
> > +    }
> > +  return no_caller_saved_abi;
> > +}
> > +
> > +/* Return the descriptor of the function ABI type without attributes
> > +   on the current function.  */
> > +
> > +static const predefined_function_abi &
> > +ix86_original_abi (void)
> > +{
> > +  auto &original_abi = function_abis[ABI_ORIGINAL];
> > +  if (!original_abi.initialized_p ())
> > +    {
> > +      HARD_REG_SET full_reg_clobbers
> > +     = default_function_abi.full_reg_clobbers ();
> > +      original_abi.initialize (ABI_ORIGINAL, full_reg_clobbers);
> > +    }
> > +  return original_abi;
> > +}
> > +
> > +/* Return the descriptor of the function alternate ABI type.  */
> > +
> > +static const predefined_function_abi &
> > +ix86_alternate_abi (void)
> > +{
> > +  static const char ix86_call_used_regs[] = CALL_USED_REGISTERS;
> > +  auto &alternate_abi = function_abis[ABI_ALTERNATE];
> > +  if (!alternate_abi.initialized_p ())
> > +    {
> > +      HARD_REG_SET full_reg_clobbers = {};
> > +
> > +      /* Add all registers that are clobbered by the call.  NB: If the
> > +      current ABI is SYSV_ABI, the alternate ABI is MS_ABI.   */
> > +      bool is_64bit_ms_abi = TARGET_64BIT && ix86_abi == SYSV_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]
> > +         && (ix86_call_used_regs[i] == 1
> > +             || (ix86_call_used_regs[i] & c_mask)))
> > +       SET_HARD_REG_BIT (full_reg_clobbers, i);
> > +      alternate_abi.initialize (ABI_ORIGINAL, full_reg_clobbers);
> > +    }
> > +  return alternate_abi;
> > +}
> > +
> > +/* Make ABI_ORIGINAL a copy of the function ABI without attributes on
> > +   the current function.  */
> > +
> > +void
> > +ix86_init_original_abi (void)
> > +{
> > +  gcc_assert (default_function_abi.initialized_p ());
> > +  ix86_original_abi ();
> > +}
> > +
> > +/* Return the function ABI ID based on FNTYPE.  */
> > +
> > +static int
> > +ix86_function_abi_id (const_tree fntype)
> > +{
> > +  if (ix86_type_no_callee_saved_registers_p (fntype))
> > +    return ABI_NO_CALLEE_SAVED;
> > +
> > +  if (lookup_attribute ("no_caller_saved_registers",
> > +                     TYPE_ATTRIBUTES (fntype)))
> > +    {
> > +      tree type = TREE_TYPE (fntype);
> > +      if (VOID_TYPE_P (type))
> > +     return ABI_NO_CALLER_SAVED_RETURN_VOID;
> > +      /* AX register contains the address of the return value location
> > +      passed in by the caller.  */
> > +      else if (ix86_return_in_memory (type, fntype))
> > +     return ABI_NO_CALLER_SAVED_RETURN_AX;
> > +      rtx ret = ix86_function_value (type, fntype, false);
> > +      unsigned int nregs;
> > +      if (REG_P (ret))
> > +     {
> > +       unsigned int regno = REGNO (ret);
> > +       if (STACK_REGNO_P (regno) || MMX_REGNO_P (regno))
> > +         return ABI_NO_CALLER_SAVED_RETURN_VOID;
> > +       else
> > +         switch (regno)
> > +           {
> > +           case AX_REG:
> > +             nregs = REG_NREGS (ret);
> > +             if (nregs == 1)
> > +               return ABI_NO_CALLER_SAVED_RETURN_AX;
> > +             else if (nregs == 2)
> > +               return ABI_NO_CALLER_SAVED_RETURN_AX_DX;
> > +             break;
> > +           case XMM0_REG:
> > +             return ABI_NO_CALLER_SAVED_RETURN_XMM0;
> > +           default:
> > +             gcc_unreachable ();
> > +           }
> > +     }
> > +      else if (GET_CODE (ret) == PARALLEL && XVECLEN (ret, 0) == 2)
> > +     {
> > +       rtx x0 = XVECEXP (ret, 0, 0);
> > +       rtx x1 = XVECEXP (ret, 0, 1);
> > +       if (GET_CODE (x0) == EXPR_LIST
> > +           && GET_CODE (x1) == EXPR_LIST)
> > +         {
> > +           x0 = XEXP (x0, 0);
> > +           x1 = XEXP (x1, 0);
> > +           if (REG_P (x0)
> > +               && REGNO (x0) == XMM0_REG
> > +               && REG_P (x1)
> > +               && REGNO (x1) == XMM1_REG)
> > +             return ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1;
> > +         }
> > +
> > +       gcc_unreachable ();
> > +     }
> > +    }
> > +
> > +  /* NB: This must be the last since other attributes change the
> > +     function ABI.  */
> > +  if (ix86_function_type_abi (fntype) != ix86_abi)
> > +    return ABI_ALTERNATE;
> > +
> > +  return ABI_ORIGINAL;
> > +}
> > +
> > +/* Implement TARGET_FNTYPE_ABI.  */
> > +
> > +static const predefined_function_abi &
> > +ix86_fntype_abi (const_tree fntype)
> > +{
> > +  switch (ix86_function_abi_id (fntype))
> > +    {
> > +    case ABI_ORIGINAL:
> > +      return ix86_original_abi ();
> > +
> > +    case ABI_ALTERNATE:
> > +      return ix86_alternate_abi ();
> > +
> > +    case ABI_NO_CALLEE_SAVED:
> > +      return ix86_no_callee_saved_abi ();
> > +
> > +    case ABI_NO_CALLER_SAVED_RETURN_VOID:
> > +      return ix86_no_caller_saved_abi_void ();
> > +
> > +    case ABI_NO_CALLER_SAVED_RETURN_AX:
> > +      return ix86_no_caller_saved_abi_ax ();
> > +
> > +    case ABI_NO_CALLER_SAVED_RETURN_AX_DX:
> > +      return ix86_no_caller_saved_abi_ax_dx ();
> > +
> > +    case ABI_NO_CALLER_SAVED_RETURN_XMM0:
> > +      return ix86_no_caller_saved_abi_xmm0 ();
> > +
> > +    case ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1:
> > +      return ix86_no_caller_saved_abi_xmm0_xmm1 ();
> > +
> > +    default:
> > +      gcc_unreachable ();
> > +    }
> > +
> > +  return default_function_abi;
> > +}
> > +
> >  /* Implement TARGET_INSN_CALLEE_ABI.  */
> >
> >  const predefined_function_abi &
> > @@ -21904,12 +22229,51 @@ static bool
> >  ix86_hard_regno_call_part_clobbered (unsigned int abi_id, unsigned int 
> > regno,
> >                                    machine_mode mode)
> >  {
> > -  /* Special ABI for vzeroupper which only clobber higher part of sse 
> > regs.  */
> > -  if (abi_id == ABI_VZEROUPPER)
> > +  if (abi_id == ABI_DEFAULT)
> > +    {
> > +      /* Get the ABI ID from the current function.  */
> > +      if (cfun)
> > +     abi_id = ix86_function_abi_id (TREE_TYPE (cfun->decl));
> > +      else
> > +     abi_id = ABI_ORIGINAL;
> > +    }
> > +
> > +  switch (abi_id)
> > +    {
> > +    case ABI_VZEROUPPER:
> > +      /* Special ABI for vzeroupper which only clobbers higher part of
> > +      SSE registers.  */
> >        return (GET_MODE_SIZE (mode) > 16
> >             && ((TARGET_64BIT && REX_SSE_REGNO_P (regno))
> >                 || LEGACY_SSE_REGNO_P (regno)));
> >
> > +    case ABI_ORIGINAL:
> > +    case ABI_ALTERNATE:
> > +    case ABI_NO_CALLEE_SAVED:
> > +      break;
> > +
> > +    case ABI_NO_CALLER_SAVED_RETURN_VOID:
> > +    case ABI_NO_CALLER_SAVED_RETURN_AX:
> > +    case ABI_NO_CALLER_SAVED_RETURN_AX_DX:
> > +      /* These ABIs don't clobber SSE registers.  */
> > +      return false;
> > +
> > +    case ABI_NO_CALLER_SAVED_RETURN_XMM0:
> > +      /* This ABI only clobbers XMM0.  */
> > +      if (regno != XMM0_REG)
> > +     return false;
> > +      break;
> > +
> > +    case ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1:
> > +      /* This ABI only clobbers XMM0 and XMM1.  */
> > +      if (regno != XMM0_REG && regno != XMM1_REG)
> > +     return false;
> > +      break;
> > +
> > +    default:
> > +      gcc_unreachable ();
> > +    }
> > +
> >    return SSE_REGNO_P (regno) && GET_MODE_SIZE (mode) > 16;
> >  }
> >
> > @@ -28786,6 +29150,9 @@ ix86_libgcc_floating_mode_supported_p
> >  #define TARGET_HARD_REGNO_CALL_PART_CLOBBERED \
> >    ix86_hard_regno_call_part_clobbered
> >
> > +#undef TARGET_FNTYPE_ABI
> > +#define TARGET_FNTYPE_ABI ix86_fntype_abi
> > +
> >  #undef TARGET_INSN_CALLEE_ABI
> >  #define TARGET_INSN_CALLEE_ABI ix86_insn_callee_abi
> >
> > diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md
> > index b4e397bc925..3ac7aca9461 100644
> > --- a/gcc/config/i386/i386.md
> > +++ b/gcc/config/i386/i386.md
> > @@ -511,11 +511,33 @@ (define_constants
> >     (FIRST_PSEUDO_REG         92)
> >    ])
> >
> > -;; Insn callee abi index.
> > +;; Insn callee abi index.  ABI_DEFAULT is the funtion ABI for the
> > +;; current function.  ABI_ORIGINAL is the function ABI without
> > +;; attributes on the current function.  ABI_ALTERNATE is the Windows
> > +;; function ABI if ix86_abi == SYSV_ABI and is the SYSV function ABI
> > +;; if ix86_abi == MS_ABI.
> >  (define_constants
> > -  [(ABI_DEFAULT              0)
> > -   (ABI_VZEROUPPER   1)
> > -   (ABI_UNKNOWN              2)])
> > +  [(ABI_DEFAULT                             0)
> > +   (ABI_VZEROUPPER                          1)
> > +   (ABI_ORIGINAL                            2)
> > +   (ABI_ALTERNATE                           3)
> > +   (ABI_NO_CALLEE_SAVED                     4)
> > +   ;; Return void.
> > +   (ABI_NO_CALLER_SAVED_RETURN_VOID         5)
> > +   ;; Return char, short, int in 32-bit/64-bit.
> > +   ;; Return int64 and _Complex int in 64-bit.
> > +   ;; Return _Complex float in MS 32-bit/64-bit.
> > +   (ABI_NO_CALLER_SAVED_RETURN_AX           6)
> > +   ;; Return int64 and _Complex int in 32-bit.
> > +   ;; Return _Complex int64 in 64-bit.
> > +   (ABI_NO_CALLER_SAVED_RETURN_AX_DX        7)
> > +   ;; Return float and double in 64-bit.
> > +   ;; Return _Complex float in SYSV 64-bit.
> > +   ;; Return int28, _Complex double in MS 64-bit.
> > +   (ABI_NO_CALLER_SAVED_RETURN_XMM0         8)
> > +   ;; Return _Complex double in SYSV 64-bit.
> > +   (ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1    9)
> > +   (ABI_UNKNOWN                             10)])
> >


-- 
H.J.
From 9c8c3b224440d3123865be50d76dc8567a2293c9 Mon Sep 17 00:00:00 2001
From: "H.J. Lu" <[email protected]>
Date: Tue, 14 Apr 2026 18:37:20 +0800
Subject: [PATCH v3] x86: Implement TARGET_FNTYPE_ABI

Implement TARGET_FNTYPE_ABI to avoid spills of callee-saved registers
when calling functions with no_caller_saved_registers attribute.

1. For functions with no_callee_saved_registers attribute, frame register
is preserved to mitigate PR target/114116.  MMX and x87 registers aren't
clobbered if MMX and x87 aren't enabled.
2. For functions with no_caller_saved_registers attribute, MMX and x87
registers are clobbered since saving and restoring registers doesn't
include MMX nor x87 registers.
3. Don't mark disabled registers as call used to avoid reg_to_stack
crashes when x87 registers are still accessed even with -mno-mmx
-mno-80387.
4. Add ABI_ORIGINAL which is the function ABI without attributes on
the current function.
5. Add ABI_ALTERNATE which is the alternate function ABI from ABI_DEFAULT.
If ix86_abi is SYSV_ABI, ABI_ALTERNATE is the function ABI for MS_ABI.
Otherwise, ABI_ALTERNATE is the function ABI for SYSV_ABI.

Tested on Linux/x86-64 and with CPython 3.14.4.

gcc/

	PR target/124798
	* config/i386/i386-expand.cc: Include "function-abi.h".
	(ix86_expand_call): Add call clobbers only when callee is
	no-callee-saved and caller isn't.  Use callee abi to get the
	list of call clobbers.
	* config/i386/i386-protos.h
	(ix86_type_no_callee_saved_registers_p): Removed.
	* config/i386/i386.cc (ix86_conditional_register_usage): Mark
	all, but frame pointer, are caller-saved in no-callee-saved and
	preserve_none functions.  Clear disabled registers in
	call_used_regs.
	(ix86_type_no_callee_saved_registers_p): Make it static.
	(ix86_no_callee_saved_abi): New function.
	(ix86_no_caller_saved_abi_void): Likewise.
	(ix86_no_caller_saved_abi_ax): Likewise.
	(ix86_no_caller_saved_abi_ax_dx): Likewise.
	(ix86_no_caller_saved_abi_xmm0): Likewise.
	(ix86_no_caller_saved_abi_xmm0_xmm1): Likewise.
	(ix86_standard_abi): Likewise.
	(ix86_original_abi): Likewise.
	(ix86_alternate_abi): Likewise.
	(ix86_function_abi_id): Likewise.
	(ix86_fntype_abi): Likewise.
	(ix86_hard_regno_call_part_clobbered): Handle newly added ABIs.
	(TARGET_FNTYPE_ABI): New.
	* config/i386/i386.md: Add ABI_ORIGINAL, ABI_ALTERNATE,
	ABI_NO_CALLEE_SAVED, ABI_NO_CALLER_SAVED_RETURN_VOID,
	ABI_NO_CALLER_SAVED_RETURN_AX, ABI_NO_CALLER_SAVED_RETURN_AX_DX,
	ABI_NO_CALLER_SAVED_RETURN_XMM0 and
	ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1.

gcc/testsuite/

	PR target/124798
	* gcc.target/i386/no-callee-saved-18.c: Don't check frame
	register.
	* gcc.target/i386/no-callee-saved-19b.c: Update the expected
	instruction order.
	* gcc.target/i386/no-callee-saved-19d.c: Likewise.
	* gcc.target/i386/no-callee-saved-19e.c: Likewise.
	* gcc.target/i386/no-callee-saved-2.c: Check frame register isn't
	saved nor restored in 64-bit mode.
	* gcc.target/i386/no-callee-saved-8.c: Expect no saving nor
	restoring caller-saved registers.
	* gcc.target/i386/no-callee-saved-9.c: Likewise.
	* gcc.target/i386/preserve-none-14.c: Don't check frame register.
	* gcc.target/i386/preserve-none-23.c: Likewise.
	* gcc.target/i386/preserve-none-7.c: Check frame register isn't
	saved nor restored in 64-bit mode
	* gcc.target/i386/no-caller-saved-1-ms.c: New test.
	* gcc.target/i386/no-caller-saved-1-sysv.c: Likewise.
	* gcc.target/i386/no-caller-saved-1.c: Likewise.
	* gcc.target/i386/no-caller-saved-2.c: Likewise.
	* gcc.target/i386/no-caller-saved-3.c: Likewise.
	* gcc.target/i386/no-caller-saved-4.c: Likewise.
	* gcc.target/i386/no-caller-saved-5.c: Likewise.
	* gcc.target/i386/no-caller-saved-6.c: Likewise.
	* gcc.target/i386/no-caller-saved-7.c: Likewise.
	* gcc.target/i386/stack-check-17.c: Also expect 1 pop in 64-bit
	mode.

Signed-off-by: H.J. Lu <[email protected]>
---
 gcc/config/i386/i386-expand.cc                |  31 +-
 gcc/config/i386/i386-protos.h                 |   1 -
 gcc/config/i386/i386.cc                       | 392 +++++++++++++++++-
 gcc/config/i386/i386.md                       |  30 +-
 .../gcc.target/i386/no-callee-saved-18.c      |   2 -
 .../gcc.target/i386/no-callee-saved-19b.c     |   8 +-
 .../gcc.target/i386/no-callee-saved-19d.c     |   6 +-
 .../gcc.target/i386/no-callee-saved-19e.c     |   8 +-
 .../gcc.target/i386/no-callee-saved-2.c       |  10 +-
 .../gcc.target/i386/no-callee-saved-8.c       |   8 +-
 .../gcc.target/i386/no-callee-saved-9.c       |  10 +-
 .../gcc.target/i386/no-caller-saved-1-ms.c    |  50 +++
 .../gcc.target/i386/no-caller-saved-1-sysv.c  |  46 ++
 .../gcc.target/i386/no-caller-saved-1.c       |  50 +++
 .../gcc.target/i386/no-caller-saved-2.c       |  49 +++
 .../gcc.target/i386/no-caller-saved-3.c       |  49 +++
 .../gcc.target/i386/no-caller-saved-4.c       |  44 ++
 .../gcc.target/i386/no-caller-saved-5.c       |  34 ++
 .../gcc.target/i386/no-caller-saved-6.c       |  34 ++
 .../gcc.target/i386/no-caller-saved-7.c       |  49 +++
 .../gcc.target/i386/preserve-none-14.c        |   2 -
 .../gcc.target/i386/preserve-none-23.c        |   2 -
 .../gcc.target/i386/preserve-none-7.c         |  10 +-
 .../gcc.target/i386/stack-check-17.c          |   3 +-
 24 files changed, 854 insertions(+), 74 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-1-ms.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-1-sysv.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-1.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-2.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-3.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-4.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-5.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-6.c
 create mode 100644 gcc/testsuite/gcc.target/i386/no-caller-saved-7.c

diff --git a/gcc/config/i386/i386-expand.cc b/gcc/config/i386/i386-expand.cc
index df44a4eb99d..443eacbfe62 100644
--- a/gcc/config/i386/i386-expand.cc
+++ b/gcc/config/i386/i386-expand.cc
@@ -94,6 +94,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "i386-builtins.h"
 #include "i386-expand.h"
 #include "asan.h"
+#include "function-abi.h"
 
 /* Split one or more double-mode RTL references into pairs of half-mode
    references.  The RTL can be REG, offsettable MEM, integer constant, or
@@ -11081,7 +11082,8 @@ 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;
+  function_abi caller_abi = fndecl_abi (current_function_decl);
+  function_abi callee_abi = caller_abi;
 
   if (SYMBOL_REF_P (XEXP (fnaddr, 0)))
     {
@@ -11091,8 +11093,8 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
 	  if (lookup_attribute ("interrupt",
 				TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))
 	    error ("interrupt service routine cannot be called directly");
-	  else if (ix86_type_no_callee_saved_registers_p (TREE_TYPE (fndecl)))
-	    call_no_callee_saved_registers = true;
+	  else if (TREE_CODE (fndecl) == FUNCTION_DECL)
+	    callee_abi = fndecl_abi (fndecl);
 	  if (fndecl == current_function_decl
 	      && decl_binds_to_current_def_p (fndecl))
 	    cfun->machine->recursive_function = true;
@@ -11103,10 +11105,8 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
       if (MEM_P (fnaddr))
 	{
 	  tree mem_expr = MEM_EXPR (fnaddr);
-	  if (mem_expr != nullptr
-	      && TREE_CODE (mem_expr) == MEM_REF
-	      && ix86_type_no_callee_saved_registers_p (TREE_TYPE (mem_expr)))
-	    call_no_callee_saved_registers = true;
+	  if (mem_expr != nullptr && TREE_CODE (mem_expr) == MEM_REF)
+	    callee_abi = fntype_abi (TREE_TYPE (mem_expr));
 	}
 
       fndecl = NULL_TREE;
@@ -11320,21 +11320,14 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
       clobber_reg (&use, gen_rtx_REG (DImode, R10_REG));
     }
 
-  if (call_no_callee_saved_registers)
+  if (callee_abi.id () == ABI_NO_CALLEE_SAVED
+      && caller_abi.id () != ABI_NO_CALLEE_SAVED)
     {
-      /* After calling a no_callee_saved_registers function, all
-	 registers may be clobbered.  Clobber all registers that are
-	 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]
+	if (GENERAL_REGNO_P (i)
+	    && TEST_HARD_REG_BIT (accessible_reg_set, i)
 	    && i != HARD_FRAME_POINTER_REGNUM
-	    && !(ix86_call_used_regs[i] == 1
-		 || (ix86_call_used_regs[i] & c_mask))
-	    && !STACK_REGNO_P (i)
-	    && !MMX_REGNO_P (i))
+	    && callee_abi.clobbers_at_least_part_of_reg_p (i))
 	  clobber_reg (&use,
 		       gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i));
     }
diff --git a/gcc/config/i386/i386-protos.h b/gcc/config/i386/i386-protos.h
index 4ba4fb08556..893abf9274a 100644
--- a/gcc/config/i386/i386-protos.h
+++ b/gcc/config/i386/i386-protos.h
@@ -283,7 +283,6 @@ extern tree ix86_valid_target_attribute_tree (tree, tree,
 					      struct gcc_options *,
 					      struct gcc_options *, bool);
 extern unsigned int ix86_get_callcvt (const_tree);
-extern bool ix86_type_no_callee_saved_registers_p (const_tree);
 
 #endif
 
diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index 2744c749578..0eea6fddc03 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -503,16 +503,33 @@ ix86_conditional_register_usage (void)
 {
   int i, c_mask;
 
-  /* If there are no caller-saved registers, preserve all registers.
-     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->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;
+  if (cfun)
+    switch (cfun->machine->call_saved_registers)
+      {
+      case TYPE_DEFAULT_CALL_SAVED_REGISTERS:
+	break;
+
+      case TYPE_NO_CALLER_SAVED_REGISTERS:
+	/* If there are no caller-saved registers, preserve all
+	   registers.  except fixed_regs and registers used for
+	   function return value since aggregate_value_p checks
+	   call_used_regs[regno] on return value.  */
+	for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+	  if (!fixed_regs[i] && !ix86_function_value_regno_p (i))
+	    call_used_regs[i] = 0;
+	break;
+
+      case TYPE_NO_CALLEE_SAVED_REGISTERS:
+      case TYPE_PRESERVE_NONE:
+	/* All, but frame pointer, are caller-saved.  */
+	for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+	  if (GENERAL_REGNO_P (i)
+	      || SSE_REGNO_P (i)
+	      || MASK_REGNO_P (i))
+	    call_used_regs[i] = 1;
+	call_used_regs[BP_REG] = 0;
+	break;
+      }
 
   /* For 32-bit targets, disable the REX registers.  */
   if (! TARGET_64BIT)
@@ -571,6 +588,13 @@ ix86_conditional_register_usage (void)
       for (i = FIRST_REX2_INT_REG; i <= LAST_REX2_INT_REG; i++)
 	CLEAR_HARD_REG_BIT (accessible_reg_set, i);
     }
+
+  /* If a register is disabled, it can't be used for call.  This avoids
+     reg_to_stack crashes when x87 registers are still accessed even
+     with -mno-mmx -mno-80387.  */
+  for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+    if (!TEST_HARD_REG_BIT (accessible_reg_set, i))
+      call_used_regs[i] = 0;
 }
 
 /* Canonicalize a comparison from one we don't have to one we do have.  */
@@ -932,7 +956,7 @@ x86_64_elf_unique_section (tree decl, int reloc)
 /* Return true if TYPE has no_callee_saved_registers or preserve_none
    attribute.  */
 
-bool
+static bool
 ix86_type_no_callee_saved_registers_p (const_tree type)
 {
   return (lookup_attribute ("no_callee_saved_registers",
@@ -21854,6 +21878,306 @@ ix86_hard_regno_mode_ok (unsigned int regno, machine_mode mode)
   return false;
 }
 
+/* Return the descriptor of no_callee_saved_registers function type.
+   None of registers are preserved, except for frame register to
+   mitigate PR target/114116.  MMX and x87 registers are preserved
+   if MMX and x87 aren't enabled.  */
+
+static const predefined_function_abi &
+ix86_no_callee_saved_abi (void)
+{
+  auto &no_callee_saved_abi = function_abis[ABI_NO_CALLEE_SAVED];
+  if (!no_callee_saved_abi.initialized_p ())
+    {
+      HARD_REG_SET full_reg_clobbers = reg_class_contents[ALL_REGS];
+      CLEAR_HARD_REG_BIT (full_reg_clobbers, HARD_FRAME_POINTER_REGNUM);
+      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+	if ((!TARGET_80387 && STACK_REGNO_P (i))
+	    || (!TARGET_MMX && MMX_REGNO_P (i)))
+	  CLEAR_HARD_REG_BIT (full_reg_clobbers, i);
+      no_callee_saved_abi.initialize (ABI_NO_CALLEE_SAVED,
+				      full_reg_clobbers);
+    }
+  return no_callee_saved_abi;
+}
+
+/* Return the descriptor of no_caller_saved_registers function type.
+   All registers are preserved, except for MMX and x87 registers
+   which aren't supported when saving and restoring registers.  */
+
+static const predefined_function_abi &
+ix86_no_caller_saved_abi_void (void)
+{
+  auto &no_caller_saved_abi
+    = function_abis[ABI_NO_CALLER_SAVED_RETURN_VOID];
+  if (!no_caller_saved_abi.initialized_p ())
+    {
+      HARD_REG_SET full_reg_clobbers = {};
+      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+	if ((TARGET_80387 && STACK_REGNO_P (i))
+	    || (TARGET_MMX && MMX_REGNO_P (i)))
+	  SET_HARD_REG_BIT (full_reg_clobbers, i);
+      no_caller_saved_abi.initialize
+	(ABI_NO_CALLER_SAVED_RETURN_VOID, full_reg_clobbers);
+    }
+  return no_caller_saved_abi;
+}
+
+/* Return the descriptor of no_caller_saved_registers function type.
+   All registers are preserved, except for AX used for return value,
+   MMX and x87 registers which aren't supported when saving and
+   restoring registers.  */
+
+static const predefined_function_abi &
+ix86_no_caller_saved_abi_ax (void)
+{
+  auto &no_caller_saved_abi
+    = function_abis[ABI_NO_CALLER_SAVED_RETURN_AX];
+  if (!no_caller_saved_abi.initialized_p ())
+    {
+      HARD_REG_SET full_reg_clobbers = {};
+      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+	if (i == AX_REG
+	    || (TARGET_80387 && STACK_REGNO_P (i))
+	    || (TARGET_MMX && MMX_REGNO_P (i)))
+	  SET_HARD_REG_BIT (full_reg_clobbers, i);
+      no_caller_saved_abi.initialize
+	(ABI_NO_CALLER_SAVED_RETURN_AX, full_reg_clobbers);
+    }
+  return no_caller_saved_abi;
+}
+
+/* Return the descriptor of no_caller_saved_registers function type.
+   All registers are preserved, except for AX/DX used for return value,
+   MMX and x87 registers which aren't supported when saving and
+   restoring registers.  */
+
+static const predefined_function_abi &
+ix86_no_caller_saved_abi_ax_dx (void)
+{
+  auto &no_caller_saved_abi
+    = function_abis[ABI_NO_CALLER_SAVED_RETURN_AX_DX];
+  if (!no_caller_saved_abi.initialized_p ())
+    {
+      HARD_REG_SET full_reg_clobbers = {};
+      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+	if (i == AX_REG
+	    || i == DX_REG
+	    || (TARGET_80387 && STACK_REGNO_P (i))
+	    || (TARGET_MMX && MMX_REGNO_P (i)))
+	  SET_HARD_REG_BIT (full_reg_clobbers, i);
+      no_caller_saved_abi.initialize
+	(ABI_NO_CALLER_SAVED_RETURN_AX_DX, full_reg_clobbers);
+    }
+  return no_caller_saved_abi;
+}
+
+/* Return the descriptor of no_caller_saved_registers function type.
+   All registers are preserved, except for XMM0 used for return value,
+   MMX and x87 registers which aren't supported when saving and
+   restoring registers.  */
+
+static const predefined_function_abi &
+ix86_no_caller_saved_abi_xmm0 (void)
+{
+  auto &no_caller_saved_abi
+    = function_abis[ABI_NO_CALLER_SAVED_RETURN_XMM0];
+  if (!no_caller_saved_abi.initialized_p ())
+    {
+      HARD_REG_SET full_reg_clobbers = {};
+      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+	if (i == XMM0_REG
+	    || (TARGET_80387 && STACK_REGNO_P (i))
+	    || (TARGET_MMX && MMX_REGNO_P (i)))
+	  SET_HARD_REG_BIT (full_reg_clobbers, i);
+      no_caller_saved_abi.initialize
+	(ABI_NO_CALLER_SAVED_RETURN_XMM0, full_reg_clobbers);
+    }
+  return no_caller_saved_abi;
+}
+
+/* Return the descriptor of no_caller_saved_registers function type.
+   All registers are preserved, except for XMM0/XMM1 used for return
+   value, MMX and x87 registers which aren't supported when saving and
+   restoring registers.  */
+
+static const predefined_function_abi &
+ix86_no_caller_saved_abi_xmm0_xmm1 (void)
+{
+  auto &no_caller_saved_abi
+    = function_abis[ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1];
+  if (!no_caller_saved_abi.initialized_p ())
+    {
+      HARD_REG_SET full_reg_clobbers = {};
+      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+	if (i == XMM0_REG
+	    || i == XMM1_REG
+	    || (TARGET_80387 && STACK_REGNO_P (i))
+	    || (TARGET_MMX && MMX_REGNO_P (i)))
+	  SET_HARD_REG_BIT (full_reg_clobbers, i);
+      no_caller_saved_abi.initialize
+	(ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1, full_reg_clobbers);
+    }
+  return no_caller_saved_abi;
+}
+
+/* Return the descriptor of the standard function ABI type.  If
+   ABI_TYPE == ABI_ALTERNATE, return the function alternate ABI type.  */
+
+static const predefined_function_abi &
+ix86_standard_abi (int abi_type)
+{
+  static const char ix86_call_used_regs[] = CALL_USED_REGISTERS;
+  auto &standard_abi = function_abis[abi_type];
+  if (!standard_abi.initialized_p ())
+    {
+      HARD_REG_SET full_reg_clobbers = {};
+
+      /* Add all registers that are clobbered by the call.  NB: If the
+	 current ABI is SYSV_ABI, the alternate ABI is MS_ABI.   */
+      bool is_64bit_ms_abi = (TARGET_64BIT
+			      && ix86_abi == (abi_type == ABI_ALTERNATE
+					      ? SYSV_ABI : MS_ABI));
+      char c_mask = CALL_USED_REGISTERS_MASK (is_64bit_ms_abi);
+      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+	if (global_regs[i]
+	    || (!fixed_regs[i]
+		&& (ix86_call_used_regs[i] == 1
+		    || (ix86_call_used_regs[i] & c_mask))))
+	  SET_HARD_REG_BIT (full_reg_clobbers, i);
+
+      SET_HARD_REG_BIT (full_reg_clobbers, FLAGS_REG);
+      SET_HARD_REG_BIT (full_reg_clobbers, FPSR_REG);
+
+      standard_abi.initialize (abi_type, full_reg_clobbers);
+    }
+  return standard_abi;
+}
+
+/* Return the descriptor of the function ABI type without attributes
+   on the current function.  */
+
+static const predefined_function_abi &
+ix86_original_abi (void)
+{
+  return ix86_standard_abi (ABI_ORIGINAL);
+}
+
+/* Return the descriptor of the function alternate ABI type.  */
+
+static const predefined_function_abi &
+ix86_alternate_abi (void)
+{
+  return ix86_standard_abi (ABI_ALTERNATE);
+}
+
+/* Return the function ABI ID based on FNTYPE.  */
+
+static int
+ix86_function_abi_id (const_tree fntype)
+{
+  if (ix86_type_no_callee_saved_registers_p (fntype))
+    return ABI_NO_CALLEE_SAVED;
+
+  if (lookup_attribute ("no_caller_saved_registers",
+			TYPE_ATTRIBUTES (fntype)))
+    {
+      tree type = TREE_TYPE (fntype);
+      if (VOID_TYPE_P (type))
+	return ABI_NO_CALLER_SAVED_RETURN_VOID;
+      /* AX register contains the address of the return value location
+	 passed in by the caller.  */
+      else if (ix86_return_in_memory (type, fntype))
+	return ABI_NO_CALLER_SAVED_RETURN_AX;
+      rtx ret = ix86_function_value (type, fntype, false);
+      unsigned int nregs;
+      if (REG_P (ret))
+	{
+	  unsigned int regno = REGNO (ret);
+	  if (STACK_REGNO_P (regno) || MMX_REGNO_P (regno))
+	    return ABI_NO_CALLER_SAVED_RETURN_VOID;
+	  else
+	    switch (regno)
+	      {
+	      case AX_REG:
+		nregs = REG_NREGS (ret);
+		if (nregs == 1)
+		  return ABI_NO_CALLER_SAVED_RETURN_AX;
+		else if (nregs == 2)
+		  return ABI_NO_CALLER_SAVED_RETURN_AX_DX;
+		break;
+	      case XMM0_REG:
+		return ABI_NO_CALLER_SAVED_RETURN_XMM0;
+	      default:
+		gcc_unreachable ();
+	      }
+	}
+      else if (GET_CODE (ret) == PARALLEL && XVECLEN (ret, 0) == 2)
+	{
+	  rtx x0 = XVECEXP (ret, 0, 0);
+	  rtx x1 = XVECEXP (ret, 0, 1);
+	  if (GET_CODE (x0) == EXPR_LIST
+	      && GET_CODE (x1) == EXPR_LIST)
+	    {
+	      x0 = XEXP (x0, 0);
+	      x1 = XEXP (x1, 0);
+	      if (REG_P (x0)
+		  && REGNO (x0) == XMM0_REG
+		  && REG_P (x1)
+		  && REGNO (x1) == XMM1_REG)
+		return ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1;
+	    }
+
+	  gcc_unreachable ();
+	}
+    }
+
+  /* NB: This must be the last since other attributes change the
+     function ABI.  */
+  if (ix86_function_type_abi (fntype) != ix86_abi)
+    return ABI_ALTERNATE;
+
+  return ABI_ORIGINAL;
+}
+
+/* Implement TARGET_FNTYPE_ABI.  */
+
+static const predefined_function_abi &
+ix86_fntype_abi (const_tree fntype)
+{
+  switch (ix86_function_abi_id (fntype))
+    {
+    case ABI_ORIGINAL:
+      return ix86_original_abi ();
+
+    case ABI_ALTERNATE:
+      return ix86_alternate_abi ();
+
+    case ABI_NO_CALLEE_SAVED:
+      return ix86_no_callee_saved_abi ();
+
+    case ABI_NO_CALLER_SAVED_RETURN_VOID:
+      return ix86_no_caller_saved_abi_void ();
+
+    case ABI_NO_CALLER_SAVED_RETURN_AX:
+      return ix86_no_caller_saved_abi_ax ();
+
+    case ABI_NO_CALLER_SAVED_RETURN_AX_DX:
+      return ix86_no_caller_saved_abi_ax_dx ();
+
+    case ABI_NO_CALLER_SAVED_RETURN_XMM0:
+      return ix86_no_caller_saved_abi_xmm0 ();
+
+    case ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1:
+      return ix86_no_caller_saved_abi_xmm0_xmm1 ();
+
+    default:
+      gcc_unreachable ();
+    }
+
+  return default_function_abi;
+}
+
 /* Implement TARGET_INSN_CALLEE_ABI.  */
 
 const predefined_function_abi &
@@ -21904,12 +22228,51 @@ static bool
 ix86_hard_regno_call_part_clobbered (unsigned int abi_id, unsigned int regno,
 				     machine_mode mode)
 {
-  /* Special ABI for vzeroupper which only clobber higher part of sse regs.  */
-  if (abi_id == ABI_VZEROUPPER)
+  if (abi_id == ABI_DEFAULT)
+    {
+      /* Get the ABI ID from the current function.  */
+      if (cfun)
+	abi_id = ix86_function_abi_id (TREE_TYPE (cfun->decl));
+      else
+	abi_id = ABI_ORIGINAL;
+    }
+
+  switch (abi_id)
+    {
+    case ABI_VZEROUPPER:
+      /* Special ABI for vzeroupper which only clobbers higher part of
+	 SSE registers.  */
       return (GET_MODE_SIZE (mode) > 16
 	      && ((TARGET_64BIT && REX_SSE_REGNO_P (regno))
 		  || LEGACY_SSE_REGNO_P (regno)));
 
+    case ABI_ORIGINAL:
+    case ABI_ALTERNATE:
+    case ABI_NO_CALLEE_SAVED:
+      break;
+
+    case ABI_NO_CALLER_SAVED_RETURN_VOID:
+    case ABI_NO_CALLER_SAVED_RETURN_AX:
+    case ABI_NO_CALLER_SAVED_RETURN_AX_DX:
+      /* These ABIs don't clobber SSE registers.  */
+      return false;
+
+    case ABI_NO_CALLER_SAVED_RETURN_XMM0:
+      /* This ABI only clobbers XMM0.  */
+      if (regno != XMM0_REG)
+	return false;
+      break;
+
+    case ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1:
+      /* This ABI only clobbers XMM0 and XMM1.  */
+      if (regno != XMM0_REG && regno != XMM1_REG)
+	return false;
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
   return SSE_REGNO_P (regno) && GET_MODE_SIZE (mode) > 16;
 }
 
@@ -28786,6 +29149,9 @@ ix86_libgcc_floating_mode_supported_p
 #define TARGET_HARD_REGNO_CALL_PART_CLOBBERED \
   ix86_hard_regno_call_part_clobbered
 
+#undef TARGET_FNTYPE_ABI
+#define TARGET_FNTYPE_ABI ix86_fntype_abi
+
 #undef TARGET_INSN_CALLEE_ABI
 #define TARGET_INSN_CALLEE_ABI ix86_insn_callee_abi
 
diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md
index a486ea3d79d..61f94f4210d 100644
--- a/gcc/config/i386/i386.md
+++ b/gcc/config/i386/i386.md
@@ -511,11 +511,33 @@ (define_constants
    (FIRST_PSEUDO_REG		92)
   ])
 
-;; Insn callee abi index.
+;; Insn callee abi index.  ABI_DEFAULT is the funtion ABI for the
+;; current function.  ABI_ORIGINAL is the function ABI without
+;; attributes on the current function.  ABI_ALTERNATE is the Windows
+;; function ABI if ix86_abi == SYSV_ABI and is the SYSV function ABI
+;; if ix86_abi == MS_ABI.
 (define_constants
-  [(ABI_DEFAULT		0)
-   (ABI_VZEROUPPER	1)
-   (ABI_UNKNOWN		2)])
+  [(ABI_DEFAULT                             0)
+   (ABI_VZEROUPPER                          1)
+   (ABI_ORIGINAL                            2)
+   (ABI_ALTERNATE                           3)
+   (ABI_NO_CALLEE_SAVED                     4)
+   ;; Return void.
+   (ABI_NO_CALLER_SAVED_RETURN_VOID         5)
+   ;; Return char, short, int in 32-bit/64-bit.
+   ;; Return int64 and _Complex int in 64-bit.
+   ;; Return _Complex float in MS 32-bit/64-bit.
+   (ABI_NO_CALLER_SAVED_RETURN_AX           6)
+   ;; Return int64 and _Complex int in 32-bit.
+   ;; Return _Complex int64 in 64-bit.
+   (ABI_NO_CALLER_SAVED_RETURN_AX_DX        7)
+   ;; Return float and double in 64-bit.
+   ;; Return _Complex float in SYSV 64-bit.
+   ;; Return int28, _Complex double in MS 64-bit.
+   (ABI_NO_CALLER_SAVED_RETURN_XMM0         8)
+   ;; Return _Complex double in SYSV 64-bit.
+   (ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1    9)
+   (ABI_UNKNOWN                             10)])
 
 ;; Insns whose names begin with "x86_" are emitted by gen_FOO calls
 ;; from i386.cc.
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-18.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-18.c
index 128b9c46e8e..5e228753d8a 100644
--- a/gcc/testsuite/gcc.target/i386/no-callee-saved-18.c
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-18.c
@@ -19,7 +19,6 @@ foo (uintptr_t p)
 /* { 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-not "push(?:l|q)\[\\t \]*%(?:e|r)bp" } } */
 /* { 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 } } } */
@@ -36,7 +35,6 @@ foo (uintptr_t p)
 /* { 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-not "pop(?:l|q)\[\\t \]*%(?:e|r)bp" } } */
 /* { 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 } } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-19b.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-19b.c
index dc38936a61a..d784065b456 100644
--- a/gcc/testsuite/gcc.target/i386/no-callee-saved-19b.c
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-19b.c
@@ -52,15 +52,15 @@
 **	.cfi_startproc
 **	subl	\$376, %esp
 **...
+**	movq	%rdi, 296\(%rsp\)
+**...
+**	movl	\$code\+4, %edi
+**	movq	%rbp, 304\(%rsp\)
 **	movq	%rax, 256\(%rsp\)
 **	movq	%rdx, 264\(%rsp\)
 **	movq	%rcx, 272\(%rsp\)
 **	movq	%rbx, 280\(%rsp\)
 **	movq	%rsi, 288\(%rsp\)
-**	movq	%rdi, 296\(%rsp\)
-**...
-**	movl	\$code\+4, %edi
-**	movq	%rbp, 304\(%rsp\)
 **	movq	%r8, 312\(%rsp\)
 **	movq	%r9, 320\(%rsp\)
 **	movq	%r10, 328\(%rsp\)
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-19d.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-19d.c
index 4657e170350..bb9dce13350 100644
--- a/gcc/testsuite/gcc.target/i386/no-callee-saved-19d.c
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-19d.c
@@ -50,14 +50,14 @@
 **	.cfi_startproc
 **	subq	\$504, %rsp
 **...
+**	movq	%rdi, 304\(%rsp\)
+**...
+**	movl	\$code\+8, %edi
 **	movq	%rax, 264\(%rsp\)
 **	movq	%rdx, 272\(%rsp\)
 **	movq	%rcx, 280\(%rsp\)
 **	movq	%rbx, 288\(%rsp\)
 **	movq	%rsi, 296\(%rsp\)
-**	movq	%rdi, 304\(%rsp\)
-**...
-**	movl	\$code\+8, %edi
 **	movq	%r8, 312\(%rsp\)
 **	movq	%r9, 320\(%rsp\)
 **	movq	%r10, 328\(%rsp\)
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-19e.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-19e.c
index 8e0bbe82eae..617bb755f85 100644
--- a/gcc/testsuite/gcc.target/i386/no-callee-saved-19e.c
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-19e.c
@@ -52,15 +52,15 @@
 **	.cfi_startproc
 **	subl	\$504, %esp
 **...
+**	movq	%rdi, 296\(%rsp\)
+**...
+**	movl	\$code\+4, %edi
+**	movq	%rbp, 304\(%rsp\)
 **	movq	%rax, 256\(%rsp\)
 **	movq	%rdx, 264\(%rsp\)
 **	movq	%rcx, 272\(%rsp\)
 **	movq	%rbx, 280\(%rsp\)
 **	movq	%rsi, 288\(%rsp\)
-**	movq	%rdi, 296\(%rsp\)
-**...
-**	movl	\$code\+4, %edi
-**	movq	%rbp, 304\(%rsp\)
 **	movq	%r8, 312\(%rsp\)
 **	movq	%r9, 320\(%rsp\)
 **	movq	%r10, 328\(%rsp\)
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-2.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-2.c
index e074ca51df4..86864ea9bff 100644
--- a/gcc/testsuite/gcc.target/i386/no-callee-saved-2.c
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-2.c
@@ -26,7 +26,9 @@ foo (void *frame)
   }
 }
 
-/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
-/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
-/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*" 1 } } */
-/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*" 1 } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*" { 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
index ed3d96bdca0..692166c98e9 100644
--- a/gcc/testsuite/gcc.target/i386/no-callee-saved-8.c
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-8.c
@@ -44,7 +44,7 @@ foo (void)
 /* { 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 } } } } */
+/* { 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 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-9.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-9.c
index 7730c5903d4..7acaff2ad35 100644
--- a/gcc/testsuite/gcc.target/i386/no-callee-saved-9.c
+++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-9.c
@@ -17,7 +17,6 @@ foo (fn_t bar)
 /* { 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-not "push(?:l|q)\[\\t \]*%(?:e|r)bp" } } */
 /* { 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 } } } */
@@ -34,7 +33,6 @@ foo (fn_t bar)
 /* { 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-not "pop(?:l|q)\[\\t \]*%(?:e|r)bp" } } */
 /* { 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 } } } */
@@ -43,7 +41,7 @@ foo (fn_t bar)
 /* { 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 } } } } */
+/* { 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 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-caller-saved-1-ms.c b/gcc/testsuite/gcc.target/i386/no-caller-saved-1-ms.c
new file mode 100644
index 00000000000..9a834d49870
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-caller-saved-1-ms.c
@@ -0,0 +1,50 @@
+/* PR target/124798  */
+/* { dg-do compile } */
+/* { dg-options "-mabi=sysv -O2 -mtune=corei7 -mtune-ctrl=^prologue_using_move,^epilogue_using_move -fomit-frame-pointer" } */
+
+extern void foo (void) __attribute__ ((no_caller_saved_registers, ms_abi));
+
+void
+qux (void)
+{
+  int a, b, c, d, e, f;
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		: "=r" (a), "=r" (b), "=r" (c), "=r" (d), "=r" (e), "=r" (f));
+#ifdef __x86_64__
+  int g, h, i, j, k, l, m, n, o, p;
+  asm volatile ("# %0 %1 %2 %3 %4 %5 %6 %7 %8 %9"
+		: "=r" (g), "=r" (h), "=r" (i), "=r" (j), "=r" (k), "=r" (l), "=r" (m), "=r" (n), "=r" (o), "=r" (p));
+#endif
+  foo ();
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		:: "r" (a), "r" (b), "r" (c), "r" (d), "r" (e), "r" (f));
+#ifdef __x86_64__
+  asm volatile ("# %0 %1 %2 %3 %4 %5 %6 %7 %8 %9"
+		: : "r" (g), "r" (h), "r" (i), "r" (j), "r" (k), "r" (l), "r" (m), "r" (n), "r" (o), "r" (p));
+#endif
+}
+
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%edx, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%ecx, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%esi, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%edi, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %edx" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %ecx" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %esi" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %edi" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r8d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r9d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r10d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r11d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r12d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r13d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r14d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r15d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r8d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r9d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r10d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r11d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r12d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r13d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r14d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r15d" { target { ! ia32 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-caller-saved-1-sysv.c b/gcc/testsuite/gcc.target/i386/no-caller-saved-1-sysv.c
new file mode 100644
index 00000000000..4bd3eecfcc6
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-caller-saved-1-sysv.c
@@ -0,0 +1,46 @@
+/* PR target/124798  */
+/* { dg-do compile } */
+/* { dg-options "-mabi=ms -O2 -mtune=corei7 -mtune-ctrl=^prologue_using_move,^epilogue_using_move -fomit-frame-pointer" } */
+
+extern void foo (void) __attribute__ ((no_caller_saved_registers, sysv_abi));
+
+void
+qux (void)
+{
+  int a, b, c, d, e, f;
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		: "=r" (a), "=r" (b), "=r" (c), "=r" (d), "=r" (e), "=r" (f));
+#ifdef __x86_64__
+  int g, h, i, j, k, l, m, n, o, p;
+  asm volatile ("# %0 %1 %2 %3 %4 %5 %6 %7 %8 %9"
+		: "=r" (g), "=r" (h), "=r" (i), "=r" (j), "=r" (k), "=r" (l), "=r" (m), "=r" (n), "=r" (o), "=r" (p));
+#endif
+  foo ();
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		:: "r" (a), "r" (b), "r" (c), "r" (d), "r" (e), "r" (f));
+#ifdef __x86_64__
+  asm volatile ("# %0 %1 %2 %3 %4 %5 %6 %7 %8 %9"
+		: : "r" (g), "r" (h), "r" (i), "r" (j), "r" (k), "r" (l), "r" (m), "r" (n), "r" (o), "r" (p));
+#endif
+}
+
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%edx, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%ecx, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %edx" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %ecx" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r8d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r9d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r10d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r11d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r12d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r13d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r14d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r15d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r8d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r9d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r10d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r11d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r12d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r13d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r14d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r15d" { target { ! ia32 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-caller-saved-1.c b/gcc/testsuite/gcc.target/i386/no-caller-saved-1.c
new file mode 100644
index 00000000000..fc8ab95c7e8
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-caller-saved-1.c
@@ -0,0 +1,50 @@
+/* PR target/124798  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -mtune=corei7 -mtune-ctrl=^prologue_using_move,^epilogue_using_move -fomit-frame-pointer" } */
+
+[[gnu::no_caller_saved_registers]] extern void foo (void);
+
+void
+qux (void)
+{
+  int a, b, c, d, e, f;
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		: "=r" (a), "=r" (b), "=r" (c), "=r" (d), "=r" (e), "=r" (f));
+#ifdef __x86_64__
+  int g, h, i, j, k, l, m, n, o, p;
+  asm volatile ("# %0 %1 %2 %3 %4 %5 %6 %7 %8 %9"
+		: "=r" (g), "=r" (h), "=r" (i), "=r" (j), "=r" (k), "=r" (l), "=r" (m), "=r" (n), "=r" (o), "=r" (p));
+#endif
+  foo ();
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		:: "r" (a), "r" (b), "r" (c), "r" (d), "r" (e), "r" (f));
+#ifdef __x86_64__
+  asm volatile ("# %0 %1 %2 %3 %4 %5 %6 %7 %8 %9"
+		: : "r" (g), "r" (h), "r" (i), "r" (j), "r" (k), "r" (l), "r" (m), "r" (n), "r" (o), "r" (p));
+#endif
+}
+
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%edx, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%ecx, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%esi, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%edi, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %edx" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %ecx" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %esi" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %edi" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r8d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r9d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r10d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r11d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r12d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r13d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r14d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r15d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r8d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r9d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r10d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r11d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r12d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r13d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r14d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r15d" { target { ! ia32 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-caller-saved-2.c b/gcc/testsuite/gcc.target/i386/no-caller-saved-2.c
new file mode 100644
index 00000000000..47b671dfa40
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-caller-saved-2.c
@@ -0,0 +1,49 @@
+/* PR target/124798  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -mtune=corei7 -mtune-ctrl=^prologue_using_move,^epilogue_using_move -fomit-frame-pointer" } */
+
+[[gnu::no_caller_saved_registers]] extern int foo (void);
+
+int
+qux (void)
+{
+  int a, b, c, d, e, f;
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		: "=r" (a), "=r" (b), "=r" (c), "=r" (d), "=r" (e), "=r" (f));
+#ifdef __x86_64__
+  int g, h, i, j, k, l, m, n, o, p;
+  asm volatile ("# %0 %1 %2 %3 %4 %5 %6 %7 %8 %9"
+		: "=r" (g), "=r" (h), "=r" (i), "=r" (j), "=r" (k), "=r" (l), "=r" (m), "=r" (n), "=r" (o), "=r" (p));
+#endif
+  int ret = foo ();
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		:: "r" (a), "r" (b), "r" (c), "r" (d), "r" (e), "r" (f));
+#ifdef __x86_64__
+  asm volatile ("# %0 %1 %2 %3 %4 %5 %6 %7 %8 %9"
+		: : "r" (g), "r" (h), "r" (i), "r" (j), "r" (k), "r" (l), "r" (m), "r" (n), "r" (o), "r" (p));
+#endif
+
+  return ret;
+}
+
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%ecx, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%esi, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%edi, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %ecx" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %esi" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %edi" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r8d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r9d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r10d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r11d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r12d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r13d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r14d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r15d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r8d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r9d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r10d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r11d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r12d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r13d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r15d" { target { ! ia32 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-caller-saved-3.c b/gcc/testsuite/gcc.target/i386/no-caller-saved-3.c
new file mode 100644
index 00000000000..990b870c323
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-caller-saved-3.c
@@ -0,0 +1,49 @@
+/* PR target/124798  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -mtune=corei7 -mtune-ctrl=^prologue_using_move,^epilogue_using_move -fomit-frame-pointer" } */
+
+[[gnu::no_caller_saved_registers]] extern long long foo (void);
+
+long long
+qux (void)
+{
+  int a, b, c, d, e, f;
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		: "=r" (a), "=r" (b), "=r" (c), "=r" (d), "=r" (e), "=r" (f));
+#ifdef __x86_64__
+  int g, h, i, j, k, l, m, n, o, p;
+  asm volatile ("# %0 %1 %2 %3 %4 %5 %6 %7 %8 %9"
+		: "=r" (g), "=r" (h), "=r" (i), "=r" (j), "=r" (k), "=r" (l), "=r" (m), "=r" (n), "=r" (o), "=r" (p));
+#endif
+  long long ret = foo ();
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		:: "r" (a), "r" (b), "r" (c), "r" (d), "r" (e), "r" (f));
+#ifdef __x86_64__
+  asm volatile ("# %0 %1 %2 %3 %4 %5 %6 %7 %8 %9"
+		: : "r" (g), "r" (h), "r" (i), "r" (j), "r" (k), "r" (l), "r" (m), "r" (n), "r" (o), "r" (p));
+#endif
+
+  return ret;
+}
+
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%ecx, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%esi, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%edi, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %ecx" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %esi" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %edi" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r8d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r9d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r10d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r11d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r12d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r13d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r14d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r15d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r8d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r9d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r10d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r11d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r12d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r13d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r15d" { target { ! ia32 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-caller-saved-4.c b/gcc/testsuite/gcc.target/i386/no-caller-saved-4.c
new file mode 100644
index 00000000000..9f6b494bdb7
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-caller-saved-4.c
@@ -0,0 +1,44 @@
+/* PR target/124798  */
+/* { dg-do compile { target int128 } } */
+/* { dg-options "-O2 -mtune=corei7 -mtune-ctrl=^prologue_using_move,^epilogue_using_move -fomit-frame-pointer" } */
+
+[[gnu::no_caller_saved_registers]] extern __int128 foo (void);
+
+__int128
+qux (void)
+{
+  int a, b, c, d, e, f;
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		: "=r" (a), "=r" (b), "=r" (c), "=r" (d), "=r" (e), "=r" (f));
+#ifdef __x86_64__
+  int g, h, i, j, k, l, m, n;
+  asm volatile ("# %0 %1 %2 %3 %4 %5 %6 %7"
+		: "=r" (g), "=r" (h), "=r" (i), "=r" (j), "=r" (k), "=r" (l), "=r" (m), "=r" (n));
+#endif
+  __int128 ret = foo ();
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		:: "r" (a), "r" (b), "r" (c), "r" (d), "r" (e), "r" (f));
+#ifdef __x86_64__
+  asm volatile ("# %0 %1 %2 %3 %4 %5 %6 %7"
+		: : "r" (g), "r" (h), "r" (i), "r" (j), "r" (k), "r" (l), "r" (m), "r" (n));
+#endif
+
+  return ret;
+}
+
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%edx, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%ecx, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%esi, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%edi, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %edx" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %ecx" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %esi" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %edi" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r8d, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r9d, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r10d, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r11d, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r8d" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r9d" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r10d" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r11d" } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-caller-saved-5.c b/gcc/testsuite/gcc.target/i386/no-caller-saved-5.c
new file mode 100644
index 00000000000..98f58fe92f5
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-caller-saved-5.c
@@ -0,0 +1,34 @@
+/* PR target/124798  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -mtune=corei7 -msse2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move -fomit-frame-pointer" } */
+
+[[gnu::no_caller_saved_registers]] extern float foo (void);
+
+float
+qux (void)
+{
+  float a, b, c, d, e, f;
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		: "=v" (a), "=v" (b), "=v" (c), "=v" (d), "=v" (e), "=v" (f));
+#ifdef __x86_64__
+  float g, h, i, j, k, l, m, n, o, p;
+  asm volatile ("# %0 %1"
+		: "=v" (g), "=v" (h));
+  asm volatile ("# %0 %1 %2 %3 %4 %5 %6 %7"
+		: "=v" (i), "=v" (j), "=v" (k), "=v" (l), "=v" (m), "=v" (n), "=v" (o), "=v" (p));
+#endif
+  float ret = foo ();
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		:: "v" (a), "v" (b), "v" (c), "v" (d), "v" (e), "v" (f));
+#ifdef __x86_64__
+  asm volatile ("# %0 %1"
+		:: "v" (g), "v" (h));
+  asm volatile ("# %0 %1 %2 %3 %4 %5 %6 %7"
+		: : "v" (i), "v" (j), "v" (k), "v" (l), "v" (m), "v" (n), "v" (o), "v" (p));
+#endif
+
+  return ret;
+}
+
+/* { dg-final { scan-assembler-not "movss\[ \\t\]+%xmm\[0-9\]+, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %xmm\[0-9\]+" } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-caller-saved-6.c b/gcc/testsuite/gcc.target/i386/no-caller-saved-6.c
new file mode 100644
index 00000000000..5eb5b102843
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-caller-saved-6.c
@@ -0,0 +1,34 @@
+/* PR target/124798  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -mtune=corei7 -msse2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move -fomit-frame-pointer" } */
+
+[[gnu::no_caller_saved_registers]] extern _Complex double foo (void);
+
+_Complex double
+qux (void)
+{
+  double a, b, c, d, e, f;
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		: "=v" (a), "=v" (b), "=v" (c), "=v" (d), "=v" (e), "=v" (f));
+#ifdef __x86_64__
+  double g, h, i, j, k, l, m, n, o, p;
+  asm volatile ("# %0 %1"
+		: "=v" (g), "=v" (h));
+  asm volatile ("# %0 %1 %2 %3 %4 %5 %6 %7"
+		: "=v" (i), "=v" (j), "=v" (k), "=v" (l), "=v" (m), "=v" (n), "=v" (o), "=v" (p));
+#endif
+  _Complex double ret = foo ();
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		:: "v" (a), "v" (b), "v" (c), "v" (d), "v" (e), "v" (f));
+#ifdef __x86_64__
+  asm volatile ("# %0 %1"
+		:: "v" (g), "v" (h));
+  asm volatile ("# %0 %1 %2 %3 %4 %5 %6 %7"
+		: : "v" (i), "v" (j), "v" (k), "v" (l), "v" (m), "v" (n), "v" (o), "v" (p));
+#endif
+
+  return ret;
+}
+
+/* { dg-final { scan-assembler-not "movss\[ \\t\]+%xmm\[0-9\]+, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %xmm\[0-9\]+" } } */
diff --git a/gcc/testsuite/gcc.target/i386/no-caller-saved-7.c b/gcc/testsuite/gcc.target/i386/no-caller-saved-7.c
new file mode 100644
index 00000000000..c8a99e950da
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/no-caller-saved-7.c
@@ -0,0 +1,49 @@
+/* PR target/124798  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -mtune=corei7 -mtune-ctrl=^prologue_using_move,^epilogue_using_move -fomit-frame-pointer" } */
+
+typedef struct
+{
+  double d[16];
+} record;
+
+[[gnu::no_caller_saved_registers]] extern record foo (void);
+
+record
+qux (void)
+{
+  int a, b, c, d, e, f;
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		: "=r" (a), "=r" (b), "=r" (c), "=r" (d), "=r" (e), "=r" (f));
+#ifdef __x86_64__
+  int g, h, i, j;
+  asm volatile ("# %0 %1 %2 %3"
+		: "=r" (g), "=r" (h), "=r" (i), "=r" (j));
+#endif
+  record ret = foo ();
+  asm volatile ("# %0 %1 %2 %3 %4 %5"
+		:: "r" (a), "r" (b), "r" (c), "r" (d), "r" (e), "r" (f));
+#ifdef __x86_64__
+  asm volatile ("# %0 %1 %2 %3"
+		:: "r" (g), "r" (h), "r" (i), "r" (j));
+#endif
+
+  return ret;
+}
+
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%edx, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%ecx, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%esi, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%edi, \[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %edx" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %ecx" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %esi" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %edi" } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r8d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r9d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r10d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+%r11d, \[0-9\]*\\(%\[re\]?sp\\)" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r8d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r9d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r10d" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "mov(l|q)\[ \\t\]+\[0-9\]*\\(%\[re\]?sp\\), %r11d" { target { ! ia32 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/preserve-none-14.c b/gcc/testsuite/gcc.target/i386/preserve-none-14.c
index ca23b586fa1..175eb25acd6 100644
--- a/gcc/testsuite/gcc.target/i386/preserve-none-14.c
+++ b/gcc/testsuite/gcc.target/i386/preserve-none-14.c
@@ -17,7 +17,6 @@ foo (fn_t bar)
 /* { 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-not "push(?:l|q)\[\\t \]*%(?:e|r)bp" } } */
 /* { 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 } } } */
@@ -34,7 +33,6 @@ foo (fn_t bar)
 /* { 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-not "pop(?:l|q)\[\\t \]*%(?:e|r)bp" } } */
 /* { 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 } } } */
diff --git a/gcc/testsuite/gcc.target/i386/preserve-none-23.c b/gcc/testsuite/gcc.target/i386/preserve-none-23.c
index 8e83879443f..629bd695374 100644
--- a/gcc/testsuite/gcc.target/i386/preserve-none-23.c
+++ b/gcc/testsuite/gcc.target/i386/preserve-none-23.c
@@ -19,7 +19,6 @@ foo (uintptr_t p)
 /* { 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-not "push(?:l|q)\[\\t \]*%(?:e|r)bp" } } */
 /* { 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 } } } */
@@ -36,7 +35,6 @@ foo (uintptr_t p)
 /* { 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-not "pop(?:l|q)\[\\t \]*%(?:e|r)bp" } } */
 /* { 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 } } } */
diff --git a/gcc/testsuite/gcc.target/i386/preserve-none-7.c b/gcc/testsuite/gcc.target/i386/preserve-none-7.c
index 2c80560887c..6f252ee50a4 100644
--- a/gcc/testsuite/gcc.target/i386/preserve-none-7.c
+++ b/gcc/testsuite/gcc.target/i386/preserve-none-7.c
@@ -26,7 +26,9 @@ foo (void *frame)
   }
 }
 
-/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
-/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */
-/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*" 1 } } */
-/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*" 1 } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*" { target { ! ia32 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/stack-check-17.c b/gcc/testsuite/gcc.target/i386/stack-check-17.c
index 924a459c4e2..ed2f341b106 100644
--- a/gcc/testsuite/gcc.target/i386/stack-check-17.c
+++ b/gcc/testsuite/gcc.target/i386/stack-check-17.c
@@ -32,5 +32,4 @@ f3 (void)
    register on ia32 for a noreturn function.  */
 /* { dg-final { scan-assembler-times "push\[ql\]" 1 { target { ! ia32 } } } }  */
 /* { dg-final { scan-assembler-times "push\[ql\]" 3 { target ia32 } } }  */
-/* { dg-final { scan-assembler-not "pop" { target { ! ia32 } } } } */
-/* { dg-final { scan-assembler-times "pop" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "pop\[ql\]" 1 } }  */
-- 
2.54.0

Reply via email to