Richard Sandiford <[email protected]> writes:
> "H.J. Lu" <[email protected]> writes:
>> 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.  I made a start on what I wanted to try today, but realised
> that I needed a target-independent patch first.  I'll send that in a sec.

Here's what I came up with.  It's split into two parts:

- 3 prepatches to lay some groundwork.  If they're ok, I think they'd
  make sense independently of the main patch.

- some suggested changes to the main patch.  If versions of these
  patches are ok, they'd make more sense folded into your patch,
  rather than treated separately.

So the full sequence is:

- the first 3 of the attached patches (the ones with changelogs)
- the CALL_INSN_ABI_ID patch that I posted yesterday
- your LRA patch
- your main patch (has a minor conflict with the first 3 patches)
- the rest of the attached patches

Yes, it's a bit of a stack :)

I get three extra failures with this:

FAIL: gcc.target/i386/no-callee-saved-19b.c check-function-bodies start
FAIL: gcc.target/i386/no-callee-saved-19e.c check-function-bodies start
FAIL: gcc.target/i386/stack-check-17.c scan-assembler-times pop[ql] 1

The first two fail for both 32-bit and 64-bit.  The last one is
specific to 64-bit.

In no-callee-saved-19b.c and no-callee-saved-19e.c, the test is expecting
a call from no_caller_saved_registers to no_callee_saved_registers to
save the frame pointer.  But both your patch and LLVM seem to agree
that preserve_none does not change the frame pointer.  For LLVM, see:
https://godbolt.org/z/KrGePj3or where start does not save or restore
rbp and clobber does preserve rbp.  So maybe the test could be adjusted?

stack-check-17.c passes in its original form, in that we go back
to not having a pop for 64-bit.

I'm obviously far from an x86 expert, so there are probably lots of
details that I've got wrong or missed, but hopefully it's at least
somewhat useful.

Thanks,
Richard

>From cf09616f1652501d04f6cbbfe6b6884620b20809 Mon Sep 17 00:00:00 2001
From: Richard Sandiford <[email protected]>
Date: Thu, 14 May 2026 22:51:49 +0100
Subject: [PATCH 1/3] i386: Split out call_saved_registers_type detection

There are currently two places that want to query a function's
call_saved_registers_type: ix86_set_func_type (when setting up
cfun->machine) and ix86_type_no_callee_saved_registers_p
(a derived query).  The upcoming function_abi patch will add
another one, so this patch splits the code out into a subroutine.

ix86_set_func_type did some of the detection first, into
no_callee_saved_registers, but then ignored the result if
cfun->machine->func_type had already been set.  The patch therefore
moves all of the detection after the cfun->machine->func_type test,
rather than moving all of it before.

gcc/
	* config/i386/i386-protos.h (ix86_fntype_call_saved_registers):
	Declare.
	* config/i386/i386-options.cc (ix86_fntype_call_saved_registers): New
	function, split out from...
	(ix86_set_func_type): ...here.  Avoid calling it if the result
	would be ignored.
	* config/i386/i386.cc (ix86_type_no_callee_saved_registers_p): Use
	ix86_fntype_call_saved_registers instead of querying attributes
	directly.
---
 gcc/config/i386/i386-options.cc | 135 ++++++++++++++++----------------
 gcc/config/i386/i386-protos.h   |   2 +
 gcc/config/i386/i386.cc         |   8 +-
 3 files changed, 74 insertions(+), 71 deletions(-)

diff --git a/gcc/config/i386/i386-options.cc b/gcc/config/i386/i386-options.cc
index 7ffe9cd2a38..846af5bfae2 100644
--- a/gcc/config/i386/i386-options.cc
+++ b/gcc/config/i386/i386-options.cc
@@ -3264,13 +3264,53 @@ ix86_simd_clone_adjust (struct cgraph_node *node)
   ix86_set_current_function (node->decl);
 }
 
+/* Return the call_saved_registers_type for function type FNTYPE.
+   If LOC is nonnull, report incompatible attributes against that
+   location, otherwise remain silent.  */
 
+call_saved_registers_type
+ix86_fntype_call_saved_registers (const_tree fntype, location_t *loc)
+{
+  auto call_saved_registers = TYPE_DEFAULT_CALL_SAVED_REGISTERS;
+  const char *interrupt_conflict = nullptr;
+  if (lookup_attribute ("preserve_none", TYPE_ATTRIBUTES (fntype)))
+    {
+      call_saved_registers = TYPE_PRESERVE_NONE;
+      interrupt_conflict = "preserve_none";
+    }
+  else if (lookup_attribute ("no_callee_saved_registers",
+			     TYPE_ATTRIBUTES (fntype)))
+    {
+      call_saved_registers = TYPE_NO_CALLEE_SAVED_REGISTERS;
+      interrupt_conflict = "no_callee_saved_registers";
+    }
+  else if (lookup_attribute ("no_caller_saved_registers",
+			     TYPE_ATTRIBUTES (fntype)))
+    call_saved_registers = TYPE_NO_CALLER_SAVED_REGISTERS;
+
+  if (lookup_attribute ("interrupt", TYPE_ATTRIBUTES (fntype)))
+    {
+      if (loc && interrupt_conflict)
+	error_at (*loc, "%qs and %qs attributes are not compatible",
+		  "interrupt", interrupt_conflict);
+      return TYPE_NO_CALLER_SAVED_REGISTERS;
+    }
+
+  return call_saved_registers;
+}
 
 /* Set the func_type field from the function FNDECL.  */
 
 static void
 ix86_set_func_type (tree fndecl)
 {
+  if (cfun->machine->func_type != TYPE_UNKNOWN)
+    return;
+
+  cfun->machine->call_saved_registers
+    = ix86_fntype_call_saved_registers (TREE_TYPE (fndecl),
+					&DECL_SOURCE_LOCATION (fndecl));
+
   /* No need to save and restore callee-saved registers for a noreturn
      function with nothrow or compiled with -fno-exceptions unless when
      compiling with -O0 or -Og, except that it interferes with debugging
@@ -3286,74 +3326,37 @@ ix86_set_func_type (tree fndecl)
      function is marked with TREE_THIS_VOLATILE in the IR output, which
      leads to the incompatible attribute error in LTO1.  Ignore the
      interrupt function in this case.  */
-  enum call_saved_registers_type no_callee_saved_registers
-    = TYPE_DEFAULT_CALL_SAVED_REGISTERS;
-  if (lookup_attribute ("preserve_none",
-			     TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))
-    no_callee_saved_registers = TYPE_PRESERVE_NONE;
-  else if ((lookup_attribute ("no_callee_saved_registers",
-			      TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))
-	   || (ix86_noreturn_no_callee_saved_registers
-	       && TREE_THIS_VOLATILE (fndecl)
-	       && optimize
-	       && !optimize_debug
-	       && (TREE_NOTHROW (fndecl) || !flag_exceptions)
-	       && !lookup_attribute ("interrupt",
-				     TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))
-	       && !lookup_attribute ("no_caller_saved_registers",
-				 TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))))
-    no_callee_saved_registers = TYPE_NO_CALLEE_SAVED_REGISTERS;
-
-  if (cfun->machine->func_type == TYPE_UNKNOWN)
+  if (cfun->machine->call_saved_registers == TYPE_DEFAULT_CALL_SAVED_REGISTERS
+      && ix86_noreturn_no_callee_saved_registers
+      && TREE_THIS_VOLATILE (fndecl)
+      && optimize
+      && !optimize_debug
+      && (TREE_NOTHROW (fndecl) || !flag_exceptions))
+    cfun->machine->call_saved_registers = TYPE_NO_CALLEE_SAVED_REGISTERS;
+
+  if (lookup_attribute ("interrupt",
+			TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))
     {
-      if (lookup_attribute ("interrupt",
-			    TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))
-	{
-	  if (ix86_function_naked (fndecl))
-	    error_at (DECL_SOURCE_LOCATION (fndecl),
-		      "interrupt and naked attributes are not compatible");
-
-	  if (no_callee_saved_registers)
-	    {
-	      const char *attr;
-	      if (no_callee_saved_registers == TYPE_PRESERVE_NONE)
-		attr = "preserve_none";
-	      else
-		attr = "no_callee_saved_registers";
-	      error_at (DECL_SOURCE_LOCATION (fndecl),
-			"%qs and %qs attributes are not compatible",
-			"interrupt", attr);
-	    }
-
-	  int nargs = 0;
-	  for (tree arg = DECL_ARGUMENTS (fndecl);
-	       arg;
-	       arg = TREE_CHAIN (arg))
-	    nargs++;
-	  cfun->machine->call_saved_registers
-	    = TYPE_NO_CALLER_SAVED_REGISTERS;
-	  cfun->machine->func_type
-	    = nargs == 2 ? TYPE_EXCEPTION : TYPE_INTERRUPT;
-
-	  ix86_optimize_mode_switching[X86_DIRFLAG] = 1;
-
-	  /* Only dwarf2out.cc can handle -WORD(AP) as a pointer argument.  */
-	  if (write_symbols != NO_DEBUG && write_symbols != DWARF2_DEBUG)
-	    sorry ("only DWARF debug format is supported for interrupt "
-		   "service routine");
-	}
-      else
-	{
-	  cfun->machine->func_type = TYPE_NORMAL;
-	  if (no_callee_saved_registers)
-	    cfun->machine->call_saved_registers
-	      = no_callee_saved_registers;
-	  else if (lookup_attribute ("no_caller_saved_registers",
-				     TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))
-	    cfun->machine->call_saved_registers
-	      = TYPE_NO_CALLER_SAVED_REGISTERS;
-	}
+      if (ix86_function_naked (fndecl))
+	error_at (DECL_SOURCE_LOCATION (fndecl),
+		  "interrupt and naked attributes are not compatible");
+
+      int nargs = 0;
+      for (tree arg = DECL_ARGUMENTS (fndecl);
+	   arg;
+	   arg = TREE_CHAIN (arg))
+	nargs++;
+      cfun->machine->func_type = nargs == 2 ? TYPE_EXCEPTION : TYPE_INTERRUPT;
+
+      ix86_optimize_mode_switching[X86_DIRFLAG] = 1;
+
+      /* Only dwarf2out.cc can handle -WORD(AP) as a pointer argument.  */
+      if (write_symbols != NO_DEBUG && write_symbols != DWARF2_DEBUG)
+	sorry ("only DWARF debug format is supported for interrupt "
+	       "service routine");
     }
+  else
+    cfun->machine->func_type = TYPE_NORMAL;
 }
 
 /* Set the indirect_branch_type field from the function FNDECL.  */
diff --git a/gcc/config/i386/i386-protos.h b/gcc/config/i386/i386-protos.h
index 4ba4fb08556..c10553ab9c6 100644
--- a/gcc/config/i386/i386-protos.h
+++ b/gcc/config/i386/i386-protos.h
@@ -47,6 +47,8 @@ extern enum calling_abi ix86_function_type_abi (const_tree);
 extern bool ix86_use_pseudo_pic_reg (void);
 
 extern void ix86_reset_previous_fndecl (void);
+extern call_saved_registers_type
+  ix86_fntype_call_saved_registers (const_tree, location_t * = nullptr);
 
 extern bool ix86_using_red_zone (void);
 
diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index 2744c749578..f80e24a43ba 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -933,12 +933,10 @@ x86_64_elf_unique_section (tree decl, int reloc)
    attribute.  */
 
 bool
-ix86_type_no_callee_saved_registers_p (const_tree type)
+ix86_type_no_callee_saved_registers_p (const_tree fntype)
 {
-  return (lookup_attribute ("no_callee_saved_registers",
-			    TYPE_ATTRIBUTES (type)) != NULL
-	  || lookup_attribute ("preserve_none",
-			       TYPE_ATTRIBUTES (type)) != NULL);
+  auto type = ix86_fntype_call_saved_registers (fntype);
+  return type == TYPE_PRESERVE_NONE || type == TYPE_NO_CALLEE_SAVED_REGISTERS;
 }
 
 #ifdef COMMON_ASM_OP
-- 
2.54.0

>From 2195d40008abbe1beb6420f4398362fa7e60856e Mon Sep 17 00:00:00 2001
From: Richard Sandiford <[email protected]>
Date: Thu, 14 May 2026 22:43:40 +0100
Subject: [PATCH 2/3] i386: Avoid reading call_used_regs

call_used_regs is a legacy interface that targets use to tell
init_reg_sets_1 about the default (system) ABI.  In x86 terms,
that means the ABI associated with ix86_abi.

Targets can continue to read call_used_regs directly if they
don't have multiple ABIs.  But now that x86 does, it's better
to be explicit about which ABI is being queried.  This is
currently a nop but becomes important with the upcoming
function_abi patch.

As the comment in ix86_emit_tls_call says:

      /* TLS_GD and TLS_LD_BASE instructions are normal functions which
	 clobber caller-saved registers. [...]

So that function really is querying the system ABI.

Similarly, x86_order_regs_for_local_alloc is called when initialising
a target, rather than once per function, so it too should query the
system ABI.

ix86_save_reg and x86_64_select_profile_regnum ask about the
current function's ABI, which is crtl->abi.

gcc/
	* config/i386/i386-features.cc: Include function-abi.h.
	(ix86_emit_tls_call): Use default_function_abi instead of
	call_used_regs.
	* config/i386/i386.cc (x86_order_regs_for_local_alloc): Likewise.
	(ix86_save_reg, x86_64_select_profile_regnum): Use crtl->abi
	instead of call_used_regs.
---
 gcc/config/i386/i386-features.cc |  3 ++-
 gcc/config/i386/i386.cc          | 13 +++++++------
 2 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/gcc/config/i386/i386-features.cc b/gcc/config/i386/i386-features.cc
index b8d4f672da0..b5bdf96fb91 100644
--- a/gcc/config/i386/i386-features.cc
+++ b/gcc/config/i386/i386-features.cc
@@ -90,6 +90,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "tree-vector-builder.h"
 #include "debug.h"
 #include "dwarf2out.h"
+#include "function-abi.h"
 #include "i386-builtins.h"
 #include "i386-features.h"
 #include "i386-expand.h"
@@ -4219,7 +4220,7 @@ ix86_emit_tls_call (rtx tls_set, x86_cse_kind kind, basic_block bb,
 	 instructions.  */
       if (kind != X86_CSE_TLSDESC)
 	for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
-	  if (call_used_regs[i]
+	  if (default_function_abi.clobbers_full_reg_p (i)
 	      && !fixed_regs[i]
 	      && bitmap_bit_p (in, i))
 	    bitmap_set_bit (live_caller_saved_regs, i);
diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index f80e24a43ba..75b12d7924c 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -6857,7 +6857,8 @@ ix86_save_reg (unsigned int regno, bool maybe_eh_return, bool ignore_outlined)
     }
 
   return (df_regs_ever_live_p (regno)
-	  && !call_used_or_fixed_reg_p (regno)
+	  && !fixed_regs[regno]
+	  && !crtl->abi->clobbers_full_reg_p (regno)
 	  && (regno != HARD_FRAME_POINTER_REGNUM || !frame_pointer_needed));
 }
 
@@ -23659,13 +23660,13 @@ x86_order_regs_for_local_alloc (void)
 
    /* First allocate the local general purpose registers.  */
    for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
-     if (GENERAL_REGNO_P (i) && call_used_or_fixed_reg_p (i))
-	reg_alloc_order [pos++] = i;
+     if (GENERAL_REGNO_P (i) && default_function_abi.clobbers_full_reg_p (i))
+       reg_alloc_order [pos++] = i;
 
    /* Global general purpose registers.  */
    for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
-     if (GENERAL_REGNO_P (i) && !call_used_or_fixed_reg_p (i))
-	reg_alloc_order [pos++] = i;
+     if (GENERAL_REGNO_P (i) && !default_function_abi.clobbers_full_reg_p (i))
+       reg_alloc_order [pos++] = i;
 
    /* x87 registers come first in case we are doing FP math
       using them.  */
@@ -24127,7 +24128,7 @@ x86_64_select_profile_regnum (bool r11_ok ATTRIBUTE_UNUSED)
 #endif
 	&& TEST_HARD_REG_BIT (accessible_reg_set, i)
 	&& (ix86_save_reg (i, true, true)
-	    || (call_used_regs[i]
+	    || (crtl->abi->clobbers_full_reg_p (i)
 		&& !fixed_regs[i]
 		&& !REGNO_REG_SET_P (reg_live, i))))
       return i;
-- 
2.54.0

>From 96e49185553930b246fbe3bc1096234f9d7e02b4 Mon Sep 17 00:00:00 2001
From: Richard Sandiford <[email protected]>
Date: Thu, 14 May 2026 23:06:39 +0100
Subject: [PATCH 3/3] i386: Avoid always-true condition

ix86_function_arg is always passed a cumulative argument structure,
so the ?: test in the patch was redundant.

gcc/
	* config/i386/i386.cc (ix86_function_arg): Remove always-true
	condition.
---
 gcc/config/i386/i386.cc | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index 75b12d7924c..00e06823c6a 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -3600,9 +3600,7 @@ ix86_function_arg (cumulative_args_t cum_v, const function_arg_info &arg)
 
   if (TARGET_64BIT)
     {
-      enum calling_abi call_abi = cum ? cum->call_abi : ix86_abi;
-
-      if (call_abi == MS_ABI)
+      if (cum->call_abi == MS_ABI)
 	reg = function_arg_ms_64 (cum, mode, arg.mode, arg.named,
 				  arg.type, bytes);
       else
-- 
2.54.0

>From dcef144701e038af14a3b4aa60bc87942acfa140 Mon Sep 17 00:00:00 2001
From: Richard Sandiford <[email protected]>
Date: Thu, 14 May 2026 23:02:40 +0100
Subject: [PATCH 1/8] Use ix86_fntype_call_saved_registers

Use ix86_fntype_call_saved_registers (added in an earlier patch)
rather than looking up attributes directly.  This ensures that
crtl->abi is set correctly for interrupt functions.
---
 gcc/config/i386/i386.cc | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index 4c68372f2a1..0097203727e 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -22073,11 +22073,12 @@ ix86_alternate_abi (void)
 static int
 ix86_function_abi_id (const_tree fntype)
 {
-  if (ix86_type_no_callee_saved_registers_p (fntype))
+  auto call_saved_registers = ix86_fntype_call_saved_registers (fntype);
+  if (call_saved_registers == TYPE_PRESERVE_NONE
+      || call_saved_registers == TYPE_NO_CALLEE_SAVED_REGISTERS)
     return ABI_NO_CALLEE_SAVED;
 
-  if (lookup_attribute ("no_caller_saved_registers",
-			TYPE_ATTRIBUTES (fntype)))
+  if (call_saved_registers == TYPE_NO_CALLER_SAVED_REGISTERS)
     {
       tree type = TREE_TYPE (fntype);
       if (VOID_TYPE_P (type))
-- 
2.54.0

>From cc94b33e2f8df9fac59fcf9fd2378dfecea7ba4c Mon Sep 17 00:00:00 2001
From: Richard Sandiford <[email protected]>
Date: Thu, 14 May 2026 23:03:15 +0100
Subject: [PATCH 2/8] Enforce known return registers

Add an assert that a GPR result does not extend beyond AX and DX.
Falling through to the default/original ABI would probably just
be a missed optimisation when looking at callees, but it could
lead to wrong code when setting crtl->abi.
---
 gcc/config/i386/i386.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index 0097203727e..b415e50a34c 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -22103,7 +22103,7 @@ ix86_function_abi_id (const_tree fntype)
 		  return ABI_NO_CALLER_SAVED_RETURN_AX;
 		else if (nregs == 2)
 		  return ABI_NO_CALLER_SAVED_RETURN_AX_DX;
-		break;
+		gcc_unreachable ();
 	      case XMM0_REG:
 		return ABI_NO_CALLER_SAVED_RETURN_XMM0;
 	      default:
-- 
2.54.0

>From a7ee8c56d76376a3cb28c93a0575057e43904e09 Mon Sep 17 00:00:00 2001
From: Richard Sandiford <[email protected]>
Date: Thu, 14 May 2026 23:04:26 +0100
Subject: [PATCH 3/8] Tweak ix86_hard_regno_call_part_clobbered

As explained in the comment, no registers are only partly clobbered
by no_caller_saved_registers.  They are either fully clobbered
(return registers) or not clobbered at all.
---
 gcc/config/i386/i386.cc | 13 ++++---------
 1 file changed, 4 insertions(+), 9 deletions(-)

diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index b415e50a34c..fcc5259cb44 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -22244,16 +22244,11 @@ ix86_hard_regno_call_part_clobbered (unsigned int abi_id, unsigned int regno,
       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;
+      /* These ABIs return some values in SSE registers and preserve
+	 the rest.  The return value registers (XMM0 and possibly XMM1)
+	 are fully rather than partially call-clobbered.  */
+      return false;
 
     default:
       gcc_unreachable ();
-- 
2.54.0

>From 76cda1e5008e716bd8aa71b5371134e038bcebb8 Mon Sep 17 00:00:00 2001
From: Richard Sandiford <[email protected]>
Date: Thu, 14 May 2026 22:53:44 +0100
Subject: [PATCH 4/8] Avoid explicit ABI-switch clobbers

After: https://gcc.gnu.org/pipermail/gcc-patches/2026-May/716712.html
there is no need for ix86_expand_call to add clobbers for when one
ABI calls another ABI.
---
 gcc/config/i386/i386-expand.cc | 104 +++++----------------------------
 1 file changed, 16 insertions(+), 88 deletions(-)

diff --git a/gcc/config/i386/i386-expand.cc b/gcc/config/i386/i386-expand.cc
index 443eacbfe62..f99a2ae739a 100644
--- a/gcc/config/i386/i386-expand.cc
+++ b/gcc/config/i386/i386-expand.cc
@@ -11062,17 +11062,6 @@ construct_plt_address (rtx symbol)
   return tmp;
 }
 
-/* Additional registers that are clobbered by SYSV calls.  */
-
-static int const x86_64_ms_sysv_extra_clobbered_registers
-		 [NUM_X86_64_MS_CLOBBERED_REGS] =
-{
-  SI_REG, DI_REG,
-  XMM6_REG, XMM7_REG,
-  XMM8_REG, XMM9_REG, XMM10_REG, XMM11_REG,
-  XMM12_REG, XMM13_REG, XMM14_REG, XMM15_REG
-};
-
 rtx_insn *
 ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
 		  rtx callarg2,
@@ -11082,8 +11071,6 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
   rtx use = NULL, call;
   unsigned int vec_len = 0;
   tree fndecl;
-  function_abi caller_abi = fndecl_abi (current_function_decl);
-  function_abi callee_abi = caller_abi;
 
   if (SYMBOL_REF_P (XEXP (fnaddr, 0)))
     {
@@ -11093,24 +11080,13 @@ 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 (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;
 	}
     }
   else
-    {
-      if (MEM_P (fnaddr))
-	{
-	  tree mem_expr = MEM_EXPR (fnaddr);
-	  if (mem_expr != nullptr && TREE_CODE (mem_expr) == MEM_REF)
-	    callee_abi = fntype_abi (TREE_TYPE (mem_expr));
-	}
-
-      fndecl = NULL_TREE;
-    }
+    fndecl = NULL_TREE;
 
   if (pop == const0_rtx)
     pop = NULL;
@@ -11247,62 +11223,26 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
       vec[vec_len++] = pop;
     }
 
-  static const char ix86_call_used_regs[] = CALL_USED_REGISTERS;
-
-  if ((cfun->machine->call_saved_registers
-       == TYPE_NO_CALLER_SAVED_REGISTERS)
-      && (!fndecl
-	  || (!TREE_THIS_VOLATILE (fndecl)
-	      && !lookup_attribute ("no_caller_saved_registers",
-				    TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))))
+  /* Set here, but it may get cleared later.  */
+  if (TARGET_64BIT_MS_ABI
+      && (!callarg2 || INTVAL (callarg2) != -2)
+      && TARGET_CALL_MS2SYSV_XLOGUES)
     {
-      bool is_64bit_ms_abi = (TARGET_64BIT
-			      && ix86_function_abi (fndecl) == MS_ABI);
-      char c_mask = CALL_USED_REGISTERS_MASK (is_64bit_ms_abi);
-
-      /* If there are no caller-saved registers, add all registers
-	 that are clobbered by the call which returns.  */
-      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))
-	    && !STACK_REGNO_P (i)
-	    && !MMX_REGNO_P (i))
-	  clobber_reg (&use,
-		       gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i));
-    }
-  else if (TARGET_64BIT_MS_ABI
-	   && (!callarg2 || INTVAL (callarg2) != -2))
-    {
-      unsigned i;
+      if (!TARGET_SSE)
+	;
 
-      for (i = 0; i < NUM_X86_64_MS_CLOBBERED_REGS; i++)
-	{
-	  int regno = x86_64_ms_sysv_extra_clobbered_registers[i];
-	  machine_mode mode = SSE_REGNO_P (regno) ? TImode : DImode;
+      /* Don't break hot-patched functions.  */
+      else if (ix86_function_ms_hook_prologue (current_function_decl))
+	;
 
-	  clobber_reg (&use, gen_rtx_REG (mode, regno));
-	}
+      /* TODO: Cases not yet examined.  */
+      else if (flag_split_stack)
+	warn_once_call_ms2sysv_xlogues ("-fsplit-stack");
 
-      /* Set here, but it may get cleared later.  */
-      if (TARGET_CALL_MS2SYSV_XLOGUES)
+      else
 	{
-	  if (!TARGET_SSE)
-	    ;
-
-	  /* Don't break hot-patched functions.  */
-	  else if (ix86_function_ms_hook_prologue (current_function_decl))
-	    ;
-
-	  /* TODO: Cases not yet examined.  */
-	  else if (flag_split_stack)
-	    warn_once_call_ms2sysv_xlogues ("-fsplit-stack");
-
-	  else
-	    {
-	      gcc_assert (!reload_completed);
-	      cfun->machine->call_ms2sysv = true;
-	    }
+	  gcc_assert (!reload_completed);
+	  cfun->machine->call_ms2sysv = true;
 	}
     }
 
@@ -11320,18 +11260,6 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
       clobber_reg (&use, gen_rtx_REG (DImode, R10_REG));
     }
 
-  if (callee_abi.id () == ABI_NO_CALLEE_SAVED
-      && caller_abi.id () != ABI_NO_CALLEE_SAVED)
-    {
-      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
-	if (GENERAL_REGNO_P (i)
-	    && TEST_HARD_REG_BIT (accessible_reg_set, i)
-	    && i != HARD_FRAME_POINTER_REGNUM
-	    && callee_abi.clobbers_at_least_part_of_reg_p (i))
-	  clobber_reg (&use,
-		       gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i));
-    }
-
   if (vec_len > 1)
     call = gen_rtx_PARALLEL (VOIDmode, gen_rtvec_v (vec_len, vec));
   rtx_insn *call_insn = emit_call_insn (call);
-- 
2.54.0

>From 80a69561d19a62c40d1c97f5187107da8965d8c8 Mon Sep 17 00:00:00 2001
From: Richard Sandiford <[email protected]>
Date: Thu, 14 May 2026 22:59:49 +0100
Subject: [PATCH 5/8] Consolidate handling of fixed registers

The handling of fixed registers is somewhat subtle, so this patch
splits it out into a helper that each of the ix86_*_abi functions
can use.

The new code is based on the initialisation of regs_invalidated_by_call.
---
 gcc/config/i386/i386.cc | 67 ++++++++++++++++++++++-------------------
 1 file changed, 36 insertions(+), 31 deletions(-)

diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index fcc5259cb44..b90ab26700f 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -21875,26 +21875,39 @@ ix86_hard_regno_mode_ok (unsigned int regno, machine_mode mode)
   return false;
 }
 
+/* Initialize function_abis[ABI_ID] with the set of register clobbers
+   in FULL_REG_CLOBBERS, adjusting for rules that apply to all ABIs.  */
+
+static void
+ix86_initialize_abi (unsigned int abi_id, HARD_REG_SET full_reg_clobbers)
+{
+  /* The general rule is that fixed registers should be marked as
+     call-clobbered.  This includes global registers, inaccessible
+     registers, the flags register, and the FPSR.
+
+     Handle the exceptions below.  */
+  full_reg_clobbers |= fixed_reg_set;
+
+  /* Every ABI (even preserve_none) preserves EBP/RBP.  */
+  CLEAR_HARD_REG_BIT (full_reg_clobbers, HARD_FRAME_POINTER_REGNUM);
+
+  /* Treat GCC's internal frame-related registers as call-preserved.  */
+  CLEAR_HARD_REG_BIT (full_reg_clobbers, FRAME_POINTER_REGNUM);
+  CLEAR_HARD_REG_BIT (full_reg_clobbers, ARG_POINTER_REGNUM);
+
+  function_abis[abi_id].initialize (abi_id, full_reg_clobbers);
+}
+
 /* 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.  */
+   None of the enabled registers are preserved, except for the common
+   rules applied by ix86_initialize_abi.  */
 
 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);
-    }
+    ix86_initialize_abi (ABI_NO_CALLEE_SAVED, accessible_reg_set);
   return no_callee_saved_abi;
 }
 
@@ -21914,8 +21927,7 @@ ix86_no_caller_saved_abi_void (void)
 	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);
+      ix86_initialize_abi (ABI_NO_CALLER_SAVED_RETURN_VOID, full_reg_clobbers);
     }
   return no_caller_saved_abi;
 }
@@ -21938,8 +21950,7 @@ ix86_no_caller_saved_abi_ax (void)
 	    || (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);
+      ix86_initialize_abi (ABI_NO_CALLER_SAVED_RETURN_AX, full_reg_clobbers);
     }
   return no_caller_saved_abi;
 }
@@ -21963,8 +21974,8 @@ ix86_no_caller_saved_abi_ax_dx (void)
 	    || (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);
+      ix86_initialize_abi (ABI_NO_CALLER_SAVED_RETURN_AX_DX,
+			   full_reg_clobbers);
     }
   return no_caller_saved_abi;
 }
@@ -21987,8 +21998,7 @@ ix86_no_caller_saved_abi_xmm0 (void)
 	    || (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);
+      ix86_initialize_abi (ABI_NO_CALLER_SAVED_RETURN_XMM0, full_reg_clobbers);
     }
   return no_caller_saved_abi;
 }
@@ -22012,8 +22022,8 @@ ix86_no_caller_saved_abi_xmm0_xmm1 (void)
 	    || (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);
+      ix86_initialize_abi (ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1,
+			   full_reg_clobbers);
     }
   return no_caller_saved_abi;
 }
@@ -22037,16 +22047,11 @@ ix86_standard_abi (int abi_type)
 					      ? 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))))
+	if (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);
+      ix86_initialize_abi (abi_type, full_reg_clobbers);
     }
   return standard_abi;
 }
-- 
2.54.0

>From cef0fbc94cd41332a763c9f31b4d2a707e9f3150 Mon Sep 17 00:00:00 2001
From: Richard Sandiford <[email protected]>
Date: Thu, 14 May 2026 23:01:32 +0100
Subject: [PATCH 6/8] Consolidate no_caller_saved abis

This is just a suggestion.  It consolidates all the no_caller_saved_*
initialisations into a single function, similarly to ix86_standard_abi.
The return value registers can be handled by straight-line code outside
of the loop.
---
 gcc/config/i386/i386.cc | 142 +++++++++-------------------------------
 1 file changed, 32 insertions(+), 110 deletions(-)

diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index b90ab26700f..d56c36f3e32 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -21911,121 +21911,50 @@ ix86_no_callee_saved_abi (void)
   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.  */
+/* Return the descriptor of the no_caller_saved_registers function type
+   with ABI identifier ABI_ID.  All registers are preserved, except for:
 
-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);
-      ix86_initialize_abi (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.  */
+   - the return registers, which are enumerated in ABI_ID.
 
-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);
-      ix86_initialize_abi (ABI_NO_CALLER_SAVED_RETURN_AX, full_reg_clobbers);
-    }
-  return no_caller_saved_abi;
-}
+   - MMX and x87 registers, which aren't supported when saving and restoring
+     registers.
 
-/* 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.  */
+   - the common rules applied by ix86_initialize_abi.  */
 
 static const predefined_function_abi &
-ix86_no_caller_saved_abi_ax_dx (void)
+ix86_no_caller_saved_abi (unsigned int abi_id)
 {
-  auto &no_caller_saved_abi
-    = function_abis[ABI_NO_CALLER_SAVED_RETURN_AX_DX];
-  if (!no_caller_saved_abi.initialized_p ())
+  auto &abi = function_abis[abi_id];
+  if (!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)))
+	if (STACK_REGNO_P (i) || MMX_REGNO_P (i))
 	  SET_HARD_REG_BIT (full_reg_clobbers, i);
-      ix86_initialize_abi (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.  */
+      switch (abi_id)
+	{
+	case ABI_NO_CALLER_SAVED_RETURN_VOID:
+	  break;
 
-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);
-      ix86_initialize_abi (ABI_NO_CALLER_SAVED_RETURN_XMM0, full_reg_clobbers);
-    }
-  return no_caller_saved_abi;
-}
+	case ABI_NO_CALLER_SAVED_RETURN_AX_DX:
+	  SET_HARD_REG_BIT (full_reg_clobbers, DX_REG);
+	  /* Fall through.  */
+	case ABI_NO_CALLER_SAVED_RETURN_AX:
+	  SET_HARD_REG_BIT (full_reg_clobbers, AX_REG);
+	  break;
 
-/* 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.  */
+	case ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1:
+	  SET_HARD_REG_BIT (full_reg_clobbers, XMM1_REG);
+	  /* Fall through.  */
+	case ABI_NO_CALLER_SAVED_RETURN_XMM0:
+	  SET_HARD_REG_BIT (full_reg_clobbers, XMM0_REG);
+	  break;
+	}
 
-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);
-      ix86_initialize_abi (ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1,
-			   full_reg_clobbers);
+      ix86_initialize_abi (abi_id, full_reg_clobbers);
     }
-  return no_caller_saved_abi;
+  return abi;
 }
 
 /* Return the descriptor of the standard function ABI type.  If
@@ -22148,7 +22077,8 @@ ix86_function_abi_id (const_tree fntype)
 static const predefined_function_abi &
 ix86_fntype_abi (const_tree fntype)
 {
-  switch (ix86_function_abi_id (fntype))
+  unsigned int abi_id = ix86_function_abi_id (fntype);
+  switch (abi_id)
     {
     case ABI_ORIGINAL:
       return ix86_original_abi ();
@@ -22160,19 +22090,11 @@ ix86_fntype_abi (const_tree fntype)
       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 ();
+      return ix86_no_caller_saved_abi (abi_id);
 
     default:
       gcc_unreachable ();
-- 
2.54.0

>From 3761579076b9d9ad3b8a2986f199239d83969a09 Mon Sep 17 00:00:00 2001
From: Richard Sandiford <[email protected]>
Date: Thu, 14 May 2026 23:06:12 +0100
Subject: [PATCH 7/8] Remove ABI_ORIGINAL

This is the end goal of the series.  After the previous changes, we can
now make ix86_conditional_register_usage describe the system ABI and use
cfun->abi when querying the current function's ABI.  This also avoids
the need for reinit_regs.

As noted in ix86_save_reg, the one wrinkle I've found is
-mnoreturn-no-callee-saved-registers, which applies on top of the
ix86_fntype_abi.
---
 gcc/config/i386/i386-options.cc |  17 -----
 gcc/config/i386/i386.cc         | 111 ++++++--------------------------
 gcc/config/i386/i386.md         |  32 ++++-----
 3 files changed, 35 insertions(+), 125 deletions(-)

diff --git a/gcc/config/i386/i386-options.cc b/gcc/config/i386/i386-options.cc
index 846af5bfae2..7e5932664f7 100644
--- a/gcc/config/i386/i386-options.cc
+++ b/gcc/config/i386/i386-options.cc
@@ -3524,21 +3524,6 @@ ix86_set_current_function (tree fndecl)
     }
   ix86_previous_fndecl = fndecl;
 
-  static call_saved_registers_type prev_call_saved_registers;
-
-  /* 64-bit MS and SYSV ABI have different set of call used registers.
-     Avoid expensive re-initialization of init_regs each time we switch
-     function context.  */
-  if (TARGET_64BIT
-      && (call_used_or_fixed_reg_p (SI_REG)
-	  == (cfun->machine->call_abi == MS_ABI)))
-    reinit_regs ();
-  /* Need to re-initialize init_regs if caller-saved registers are
-     changed.  */
-  else if (prev_call_saved_registers
-	   != cfun->machine->call_saved_registers)
-    reinit_regs ();
-
   if (cfun->machine->func_type != TYPE_NORMAL
       || (cfun->machine->call_saved_registers
 	  == TYPE_NO_CALLER_SAVED_REGISTERS))
@@ -3586,8 +3571,6 @@ ix86_set_current_function (tree fndecl)
 	    = TYPE_DEFAULT_CALL_SAVED_REGISTERS;
 	}
     }
-
-  prev_call_saved_registers = cfun->machine->call_saved_registers;
 }
 
 /* Implement the TARGET_OFFLOAD_OPTIONS hook.  */
diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index d56c36f3e32..00ae939d805 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -503,34 +503,6 @@ ix86_conditional_register_usage (void)
 {
   int i, c_mask;
 
-  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)
     {
@@ -542,8 +514,10 @@ ix86_conditional_register_usage (void)
 	CLEAR_HARD_REG_BIT (accessible_reg_set, i);
     }
 
-  /*  See the definition of CALL_USED_REGISTERS in i386.h.  */
-  c_mask = CALL_USED_REGISTERS_MASK (TARGET_64BIT_MS_ABI);
+  /* Set up the call-used registers based on the system ABI (ix86_abi).
+
+     See the definition of CALL_USED_REGISTERS in i386.h.  */
+  c_mask = CALL_USED_REGISTERS_MASK (TARGET_64BIT && ix86_abi == MS_ABI);
 
   CLEAR_HARD_REG_SET (reg_class_contents[(int)CLOBBERED_REGS]);
 
@@ -3894,10 +3868,10 @@ ix86_function_value_regno_p (const unsigned int regno)
       /* Complex values are returned in %st(0)/%st(1) pair.  */
     case ST0_REG:
     case ST1_REG:
-      /* TODO: The function should depend on current function ABI but
-       builtins.cc would need updating then. Therefore we use the
-       default ABI.  */
-      if (TARGET_64BIT && ix86_cfun_abi () == MS_ABI)
+      /* TODO: An ABI identifier should be passed as a parameter.
+	 For now, most callers, including those in builtins.cc,
+	 expect us to use the default ABI.  */
+      if (TARGET_64BIT && ix86_abi == MS_ABI)
 	return false;
       return TARGET_FLOAT_RETURNS_IN_80387;
 
@@ -6794,8 +6768,6 @@ ix86_hard_regno_scratch_ok (unsigned int regno)
 bool
 ix86_save_reg (unsigned int regno, bool maybe_eh_return, bool ignore_outlined)
 {
-  rtx reg;
-
   /* Save and restore DRAP register between prologue and epilogue so
      that stack pointer can be restored.  */
   if (crtl->drap_reg
@@ -6803,41 +6775,12 @@ ix86_save_reg (unsigned int regno, bool maybe_eh_return, bool ignore_outlined)
       && !cfun->machine->no_drap_save_restore)
     return true;
 
-  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, we preserve all
-	 registers, except for MMX and x87 registers which aren't
-	 supported when saving and restoring registers.  Don't
-	 explicitly save SP register since it is always preserved.
-
-	 Don't preserve registers used for function return value.  */
-      reg = crtl->return_rtx;
-      if (reg)
-	{
-	  unsigned int i = REGNO (reg);
-	  unsigned int nregs = REG_NREGS (reg);
-	  while (nregs-- > 0)
-	    if ((i + nregs) == regno)
-	      return false;
-	}
-
-      return (df_regs_ever_live_p (regno)
-	      && !fixed_regs[regno]
-	      && !STACK_REGNO_P (regno)
-	      && !MMX_REGNO_P (regno)
-	      && (regno != HARD_FRAME_POINTER_REGNUM
-		  || !frame_pointer_needed));
-
-    case TYPE_NO_CALLEE_SAVED_REGISTERS:
-    case TYPE_PRESERVE_NONE:
-      if (regno != HARD_FRAME_POINTER_REGNUM)
-	return false;
-      break;
-    }
+  /* ??? Treat no_callee_saved_registers as a special case in order
+     to cope with -mnoreturn-no-callee-saved-registers, which is not
+     reflected in crtl->abi.  */
+  if (cfun->machine->call_saved_registers == TYPE_NO_CALLEE_SAVED_REGISTERS
+      && regno != HARD_FRAME_POINTER_REGNUM)
+    return false;
 
   if (regno == REAL_PIC_OFFSET_TABLE_REGNUM
       && pic_offset_table_rtx)
@@ -21985,15 +21928,6 @@ ix86_standard_abi (int abi_type)
   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 &
@@ -22069,7 +22003,7 @@ ix86_function_abi_id (const_tree fntype)
   if (ix86_function_type_abi (fntype) != ix86_abi)
     return ABI_ALTERNATE;
 
-  return ABI_ORIGINAL;
+  return ABI_DEFAULT;
 }
 
 /* Implement TARGET_FNTYPE_ABI.  */
@@ -22080,8 +22014,8 @@ ix86_fntype_abi (const_tree fntype)
   unsigned int abi_id = ix86_function_abi_id (fntype);
   switch (abi_id)
     {
-    case ABI_ORIGINAL:
-      return ix86_original_abi ();
+    case ABI_DEFAULT:
+      return default_function_abi;
 
     case ABI_ALTERNATE:
       return ix86_alternate_abi ();
@@ -22141,15 +22075,6 @@ static bool
 ix86_hard_regno_call_part_clobbered (unsigned int abi_id, unsigned int regno,
 				     machine_mode mode)
 {
-  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:
@@ -22159,7 +22084,7 @@ ix86_hard_regno_call_part_clobbered (unsigned int abi_id, unsigned int regno,
 	      && ((TARGET_64BIT && REX_SSE_REGNO_P (regno))
 		  || LEGACY_SSE_REGNO_P (regno)));
 
-    case ABI_ORIGINAL:
+    case ABI_DEFAULT:
     case ABI_ALTERNATE:
     case ABI_NO_CALLEE_SAVED:
       break;
diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md
index d44bf4b8c86..19bd8b7cbf3 100644
--- a/gcc/config/i386/i386.md
+++ b/gcc/config/i386/i386.md
@@ -511,33 +511,35 @@
    (FIRST_PSEUDO_REG		92)
   ])
 
-;; 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.
+;; Insn callee abi index.
 (define_constants
-  [(ABI_DEFAULT                             0)
+  [;; The system ABI; that is, the ABI associated with ix86_abi, and the
+   ;; ABI that an undecorated "void foo()" would have.
+   (ABI_DEFAULT                             0)
+   ;; An ABI that describes the effect of vzeroupper.  vzeroupper is
+   ;; represented as a call because calls are the main mechanism for
+   ;; expression partial register clobbers.
    (ABI_VZEROUPPER                          1)
-   (ABI_ORIGINAL                            2)
-   (ABI_ALTERNATE                           3)
-   (ABI_NO_CALLEE_SAVED                     4)
+   ;; ABI_ALTERNATE is the Windows function ABI if ix86_abi == SYSV_ABI
+   ;; and is the SYSV function ABI if ix86_abi == MS_ABI.
+   (ABI_ALTERNATE                           2)
+   (ABI_NO_CALLEE_SAVED                     3)
    ;; Return void.
-   (ABI_NO_CALLER_SAVED_RETURN_VOID         5)
+   (ABI_NO_CALLER_SAVED_RETURN_VOID         4)
    ;; 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)
+   (ABI_NO_CALLER_SAVED_RETURN_AX           5)
    ;; Return int64 and _Complex int in 32-bit.
    ;; Return _Complex int64 in 64-bit.
-   (ABI_NO_CALLER_SAVED_RETURN_AX_DX        7)
+   (ABI_NO_CALLER_SAVED_RETURN_AX_DX        6)
    ;; 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)
+   (ABI_NO_CALLER_SAVED_RETURN_XMM0         7)
    ;; Return _Complex double in SYSV 64-bit.
-   (ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1    9)
-   (ABI_UNKNOWN                             10)])
+   (ABI_NO_CALLER_SAVED_RETURN_XMM0_XMM1    8)
+   (ABI_UNKNOWN                             9)])
 
 ;; Insns whose names begin with "x86_" are emitted by gen_FOO calls
 ;; from i386.cc.
-- 
2.54.0

>From a0bb5bb13c00af11dc74d1329865475c5545de13 Mon Sep 17 00:00:00 2001
From: Richard Sandiford <[email protected]>
Date: Fri, 15 May 2026 09:32:34 +0100
Subject: [PATCH 8/8] A couple of tests

Don't know whether you think these are useful, but they did show
up the ICE that your LRA patch fixed.
---
 gcc/testsuite/gcc.target/i386/abi-mix-1.c | 20 ++++++++++++++++++++
 gcc/testsuite/gcc.target/i386/abi-mix-2.c |  6 ++++++
 2 files changed, 26 insertions(+)
 create mode 100644 gcc/testsuite/gcc.target/i386/abi-mix-1.c
 create mode 100644 gcc/testsuite/gcc.target/i386/abi-mix-2.c

diff --git a/gcc/testsuite/gcc.target/i386/abi-mix-1.c b/gcc/testsuite/gcc.target/i386/abi-mix-1.c
new file mode 100644
index 00000000000..342da642bf2
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/abi-mix-1.c
@@ -0,0 +1,20 @@
+/* { dg-options "-O2 -masm=att -mno-mmx -mno-80387 -msse" } */
+
+extern void callee_clobbered () [[gnu::preserve_none]];
+extern void callee_preserved () [[gnu::no_caller_saved_registers]];
+extern void callee_sysv () [[gnu::sysv_abi]];
+extern void callee_ms () [[gnu::ms_abi]];
+
+#define TEST(PREFIX, ATTR) \
+  void PREFIX##_clobbered () [[ATTR]] { callee_clobbered (); } \
+  void PREFIX##_preserved () [[ATTR]] { callee_preserved (); } \
+  void PREFIX##_sysv () [[ATTR]] { callee_sysv (); } \
+  void PREFIX##_ms () [[ATTR]] { callee_ms (); }
+
+TEST (clobbered, gnu::preserve_none)
+TEST (preserved, gnu::no_caller_saved_registers)
+TEST (sysv, gnu::sysv_abi)
+TEST (ms, gnu::ms_abi)
+
+/* { dg-final { scan-assembler-not "%st" } } */
+/* { dg-final { scan-assembler "%xmm" } } */
diff --git a/gcc/testsuite/gcc.target/i386/abi-mix-2.c b/gcc/testsuite/gcc.target/i386/abi-mix-2.c
new file mode 100644
index 00000000000..14428a6d3a4
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/abi-mix-2.c
@@ -0,0 +1,6 @@
+/* { dg-options "-O2 -masm=att -mno-mmx -mno-80387 -mno-sse" } */
+
+#include "abi-mix-1.c"
+
+/* { dg-final { scan-assembler-not "%st" } } */
+/* { dg-final { scan-assembler-not "%xmm" } } */
-- 
2.54.0

Reply via email to