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