On 06/11/2025 09:51, Tamar Christina wrote:
-----Original Message-----
From: Alfie Richards <[email protected]>
Sent: 06 November 2025 09:47
To: Tamar Christina <[email protected]>; [email protected]
Cc: Richard Earnshaw <[email protected]>; [email protected];
Alice Carlotti <[email protected]>; Alex Coplan
<[email protected]>; Wilco Dijkstra <[email protected]>;
[email protected]; [email protected]; [email protected];
[email protected]
Subject: Re: [PATCH v2] aarch64: Add support for preserve_none function
attribute [PR target/118328]

The 11/06/2025 08:00, Tamar Christina wrote:
Hi Alfie,

-----Original Message-----
From: Alfie Richards <[email protected]>
Sent: 23 October 2025 14:31
To: [email protected]
Cc: Richard Earnshaw <[email protected]>; Tamar Christina
<[email protected]>; [email protected]; Alice Carlotti
<[email protected]>; Alex Coplan <[email protected]>; Wilco
Dijkstra <[email protected]>; [email protected];
[email protected]; [email protected];
[email protected]; Alfie Richards
<[email protected]>
Subject: [PATCH v2] aarch64: Add support for preserve_none function
attribute [PR target/118328]

Hi all,

Updated the documentation part of this patch after some feedback.

Functional code unchanged.

Reg tested on AArch64.

Ok for master?

Alfie

-- >8 --

When applied to a function preserve_none changes the procedure call
standard
such that all registers except stack pointer, frame register, and link register
are caller saved. Additionally, changes the argument passing registers.

        PR target/118328

gcc/ChangeLog:

        * config/aarch64/aarch64.cc (handle_aarch64_vector_pcs_attribute):
        Add handling for ARM_PCS_PRESERVE_NONE.
        (aarch64_pcs_exclusions): New definition.
        (aarch64_gnu_attributes): Add entry for preserve_none and add
        aarch64_pcs_exclusions to aarch64_vector_pcs entry.
        (aarch64_preserve_none_abi): New function.
        (aarch64_fntype_abi): Add handling for preserve_none.
        (aarch64_reg_save_mode): Add handling for
ARM_PCS_PRESERVE_NONE.
        (aarch64_hard_regno_call_part_clobbered): Add handling for
        ARM_PCS_PRESERVE_NONE.
        (num_pcs_arg_regs): New helper function.
        (get_pcs_arg_reg): New helper function.
        (aarch64_function_ok_for_sibcall): Add handling for
ARM_PCS_PRESERVE_NONE.
        (aarch64_layout_arg): Add preserve_none argument lauout..
        (function_arg_preserve_none_regno_p): New helper function.
        (aarch64_function_arg): Update to handle preserve_none.
        (function_arg_preserve_none_regno_p): Update logic for
preserve_none.
        (aarch64_expand_builtin_va_start): Add preserve_none layout.
        (aarch64_setup_incoming_varargs): Add preserve_none layout.
        (aarch64_is_variant_pcs): Update for case of
ARM_PCS_PRESERVE_NONE.
        (aarch64_comp_type_attributes): Add preserve_none.
        * config/aarch64/aarch64.h (NUM_PRESERVE_NONE_ARG_REGS):
New macro.
        (PRESERVE_NONE_REGISTERS): New macro.
        (enum arm_pcs): Add ARM_PCS_PRESERVE_NONE.
        * doc/extend.texi (preserve_none): Add docs for new attribute.

gcc/testsuite/ChangeLog:

        * gcc.target/aarch64/preserve_none_1.c: New test.
        * gcc.target/aarch64/preserve_none_2.c: New test.
        * gcc.target/aarch64/preserve_none_3.c: New test.
        * gcc.target/aarch64/preserve_none_4.c: New test.
        * gcc.target/aarch64/preserve_none_5.c: New test.
        * gcc.target/aarch64/preserve_none_6.c: New test.
---
  gcc/config/aarch64/aarch64.cc                 | 178 ++++++++++++++++--
  gcc/config/aarch64/aarch64.h                  |  13 ++
  gcc/doc/extend.texi                           |  18 ++
  .../gcc.target/aarch64/preserve_none_1.c      | 142 ++++++++++++++
  .../gcc.target/aarch64/preserve_none_2.c      |  49 +++++
  .../gcc.target/aarch64/preserve_none_3.c      | 109 +++++++++++
  .../gcc.target/aarch64/preserve_none_4.c      |  93 +++++++++
  .../gcc.target/aarch64/preserve_none_5.c      |  45 +++++
  .../gcc.target/aarch64/preserve_none_6.c      |  66 +++++++
  9 files changed, 693 insertions(+), 20 deletions(-)
  create mode 100644 gcc/testsuite/gcc.target/aarch64/preserve_none_1.c
  create mode 100644 gcc/testsuite/gcc.target/aarch64/preserve_none_2.c
  create mode 100644 gcc/testsuite/gcc.target/aarch64/preserve_none_3.c
  create mode 100644 gcc/testsuite/gcc.target/aarch64/preserve_none_4.c
  create mode 100644 gcc/testsuite/gcc.target/aarch64/preserve_none_5.c
  create mode 100644 gcc/testsuite/gcc.target/aarch64/preserve_none_6.c

diff --git a/gcc/config/aarch64/aarch64.cc
b/gcc/config/aarch64/aarch64.cc
index b86064148fe..5bab2807c2a 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -749,6 +749,8 @@ handle_aarch64_vector_pcs_attribute (tree
*node,
tree name, tree,
        *no_add_attrs = true;
        return NULL_TREE;

+      /* Rely on the exclusions list for preserve_none.  */
+    case ARM_PCS_PRESERVE_NONE:
      case ARM_PCS_TLSDESC:
      case ARM_PCS_UNKNOWN:
        break;
@@ -851,6 +853,16 @@ handle_arm_shared (tree *node, tree name, tree
args,
    return NULL_TREE;
  }

+/* Mutually-exclusive function type attributes for various PCS variants.  */
+static const struct attribute_spec::exclusions aarch64_pcs_exclusions[] =
+{
+  /* Attribute name     exclusion applies to:
+                       function, type, variable */
+  { "aarch64_vector_pcs", false, true, false },
+  { "preserve_none", false, true, false },
+  { NULL, false, false, false }
+};
+
  /* Mutually-exclusive function type attributes for controlling PSTATE.SM.
*/
  static const struct attribute_spec::exclusions attr_streaming_exclusions[] =
  {
@@ -867,7 +879,10 @@ static const attribute_spec
aarch64_gnu_attributes[] =
    /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
         affects_type_identity, handler, exclude } */
    { "aarch64_vector_pcs", 0, 0, false, true,  true,  true,
-                         handle_aarch64_vector_pcs_attribute, NULL },
+                         handle_aarch64_vector_pcs_attribute,
+                         aarch64_pcs_exclusions },
+  { "preserve_none",      0, 0, false, true,  true,  true,  NULL,
+                         aarch64_pcs_exclusions },
    { "indirect_return",    0, 0, false, true, true, true, NULL, NULL },
    { "arm_sve_vector_bits", 1, 1, false, true,  false, true,

aarch64_sve::handle_arm_sve_vector_bits_attribute,
@@ -1317,6 +1332,23 @@ aarch64_sve_abi (void)
    return sve_abi;
  }

+/* Return the descriptor of the preserve_none PCS.  */
+
+static const predefined_function_abi &
+aarch64_preserve_none_abi (void)
+{
+  auto &preserve_none_abi = function_abis[ARM_PCS_PRESERVE_NONE];
+  if (!preserve_none_abi.initialized_p ())
+    {
+      HARD_REG_SET preserved_regs = {};
+      if (!CALL_USED_X18)
+       SET_HARD_REG_BIT (preserved_regs, R18_REGNUM);
+      auto full_reg_clobbers = reg_class_contents[ALL_REGS] &
~preserved_regs;
+      preserve_none_abi.initialize (ARM_PCS_PRESERVE_NONE,
full_reg_clobbers);
+    }
+  return preserve_none_abi;
+}
+
  /* If X is an UNSPEC_SALT_ADDR expression, return the address that it
     wraps, otherwise return X itself.  */

@@ -2312,6 +2344,9 @@ aarch64_fntype_abi (const_tree fntype)
    if (lookup_attribute ("aarch64_vector_pcs", TYPE_ATTRIBUTES (fntype)))
      return aarch64_simd_abi ();

+  if (lookup_attribute ("preserve_none", TYPE_ATTRIBUTES (fntype)))
+    return aarch64_preserve_none_abi ();
+
    if (aarch64_returns_value_in_sve_regs_p (fntype)
        || aarch64_takes_arguments_in_sve_regs_p (fntype))
      return aarch64_sve_abi ();
@@ -2519,6 +2554,9 @@ aarch64_reg_save_mode (unsigned int regno)
    if (FP_REGNUM_P (regno))
      switch (crtl->abi->id ())
        {
+      case ARM_PCS_PRESERVE_NONE:
+       /* We should never save FPRs for preserve_none, but nevertheless
+          treat it like the base PCS for consistency.  */

This comment is a bit confusing to me as the documentation you added
states
that FPRs are saved as callee saves by design.  So in that sense adding
preserve
none here makes sense, but the comment is confusing.

Ah yeah thats from the original prototype by Richard S and I didn't check
properly.

Appologies here btw, I replied too quickly here without properly thinking.
This comment is actually correct.

preserve_none changes all the registers (including FP and SIMD) to be caller saved (exluding LR SP).

As this code is to get the mode for storing callee saved registers it's return value should not matter for a preserve_none function.


        case ARM_PCS_AAPCS64:
        /* Only the low 64 bits are saved by the base PCS.  */
        return DFmode;
@@ -2649,7 +2687,9 @@ aarch64_hard_regno_call_part_clobbered
(unsigned int abi_id,
                                        unsigned int regno,
                                        machine_mode mode)
  {
-  if (FP_REGNUM_P (regno) && abi_id != ARM_PCS_SVE)
+  if (FP_REGNUM_P (regno)
+      && abi_id != ARM_PCS_SVE
+      && abi_id != ARM_PCS_PRESERVE_NONE)
      {
        poly_int64 per_register_size = GET_MODE_SIZE (mode);
        unsigned int nregs = hard_regno_nregs (regno, mode);
@@ -6826,6 +6866,10 @@ aarch64_function_ok_for_sibcall (tree, tree
exp)
    auto from_abi = crtl->abi->id ();
    auto to_abi = expr_callee_abi (exp).id ();

+  /* preserve_none functions can tail-call anything that the base PCS can.
*/
+  if (from_abi != to_abi && from_abi == ARM_PCS_PRESERVE_NONE)
+    from_abi = ARM_PCS_AAPCS64;
+
    /* ARM_PCS_SVE preserves strictly more than ARM_PCS_SIMD, which in
       turn preserves strictly more than the base PCS.  The callee must
       preserve everything that the caller is required to preserve.  */
@@ -7287,6 +7331,49 @@ bitint_or_aggr_of_bitint_p (tree type)
    return false;
  }

+/* How many GPR are available for argument passing in the procedure call
+   standard.  */
+static int
+num_pcs_arg_regs (enum arm_pcs pcs)
+{
+  switch (pcs)
+    {
+    case ARM_PCS_PRESERVE_NONE:
+      return NUM_PRESERVE_NONE_ARG_REGS;
+    case ARM_PCS_AAPCS64:
+    case ARM_PCS_SIMD:
+    case ARM_PCS_SVE:
+    case ARM_PCS_TLSDESC:
+    case ARM_PCS_UNKNOWN:
+      return NUM_ARG_REGS;
+    }
+  gcc_unreachable ();
+}
+
+/* Get the NUM'th GPR argument passing register from the PCS
procedure
call
+ * standard.  */
+
+static int
+get_pcs_arg_reg (enum arm_pcs pcs, int num)
+{
+  static const int ARM_PCS_PRESERVE_NONE_REGISTERS[] =
PRESERVE_NONE_REGISTERS;
+
+  gcc_assert (num < num_pcs_arg_regs (pcs));
+
+  switch (pcs)
+    {
+    case ARM_PCS_PRESERVE_NONE:
+      return ARM_PCS_PRESERVE_NONE_REGISTERS[num];
+    case ARM_PCS_AAPCS64:
+    case ARM_PCS_SIMD:
+    case ARM_PCS_SVE:
+    case ARM_PCS_TLSDESC:
+    case ARM_PCS_UNKNOWN:
+      return R0_REGNUM + num;
+    }
+  gcc_unreachable ();
+}
+
  /* Layout a function argument according to the AAPCS64 rules.  The rule
     numbers refer to the rule numbers in the AAPCS64.  ORIG_MODE is the
     mode that was originally given to us by the target hook, whereas the
@@ -7385,7 +7472,9 @@ aarch64_layout_arg (cumulative_args_t
pcum_v,
const function_arg_info &arg)
         unprototyped function.  There is no ABI-defined location we
         can return in this case, so we have no real choice but to raise
         an error immediately, even though this is only a query function.  */
-      if (arg.named && pcum->pcs_variant != ARM_PCS_SVE)
+      if (arg.named
+         && pcum->pcs_variant != ARM_PCS_SVE
+         && pcum->pcs_variant != ARM_PCS_PRESERVE_NONE)
        {
          gcc_assert (!pcum->silent_p);
          error ("SVE type %qT cannot be passed to an unprototyped
function",
@@ -7400,7 +7489,6 @@ aarch64_layout_arg (cumulative_args_t
pcum_v,
const function_arg_info &arg)
        pcum->aapcs_nextnvrn = pcum->aapcs_nvrn + pst_info.num_zr ();
        pcum->aapcs_nextnprn = pcum->aapcs_nprn + pst_info.num_pr ();
        gcc_assert (arg.named
-                 && pcum->pcs_variant == ARM_PCS_SVE
                  && pcum->aapcs_nextnvrn <= NUM_FP_ARG_REGS
                  && pcum->aapcs_nextnprn <= NUM_PR_ARG_REGS);
        pcum->aapcs_reg = pst_info.get_rtx (mode, V0_REGNUM + pcum-
aapcs_nvrn,
@@ -7514,7 +7602,7 @@ aarch64_layout_arg (cumulative_args_t
pcum_v,
const function_arg_info &arg)
    /* C6 - C9.  though the sign and zero extension semantics are
       handled elsewhere.  This is the case where the argument fits
       entirely general registers.  */
-  if (allocate_ncrn && (ncrn + nregs <= NUM_ARG_REGS))
+  if (allocate_ncrn && (ncrn + nregs <= num_pcs_arg_regs (pcum-
pcs_variant)))
      {
        gcc_assert (nregs == 0 || nregs == 1 || nregs == 2);

@@ -7550,7 +7638,7 @@ aarch64_layout_arg (cumulative_args_t
pcum_v,
const function_arg_info &arg)
                inform (input_location, "parameter passing for argument of
type "
                        "%qT changed in GCC 9.1", type);
              ++ncrn;
-             gcc_assert (ncrn + nregs <= NUM_ARG_REGS);
+             gcc_assert (ncrn + nregs <= num_pcs_arg_regs (pcum-
pcs_variant));
            }
        }

@@ -7572,7 +7660,8 @@ aarch64_layout_arg (cumulative_args_t
pcum_v,
const function_arg_info &arg)
        if (nregs == 0
          || (nregs == 1 && !sve_p)
          || GET_MODE_CLASS (mode) == MODE_INT)
-       pcum->aapcs_reg = gen_rtx_REG (mode, R0_REGNUM + ncrn);
+       pcum->aapcs_reg
+         = gen_rtx_REG (mode, get_pcs_arg_reg (pcum->pcs_variant, ncrn));
        else
        {
          rtx par;
@@ -7584,7 +7673,8 @@ aarch64_layout_arg (cumulative_args_t
pcum_v,
const function_arg_info &arg)
              scalar_int_mode reg_mode = word_mode;
              if (nregs == 1)
                reg_mode = int_mode_for_mode (mode).require ();
-             rtx tmp = gen_rtx_REG (reg_mode, R0_REGNUM + ncrn + i);
+             int reg = get_pcs_arg_reg (pcum->pcs_variant, ncrn + i);
+             rtx tmp = gen_rtx_REG (reg_mode, reg);
              tmp = gen_rtx_EXPR_LIST (VOIDmode, tmp,
                                       GEN_INT (i * UNITS_PER_WORD));
              XVECEXP (par, 0, i) = tmp;
@@ -7597,7 +7687,7 @@ aarch64_layout_arg (cumulative_args_t
pcum_v,
const function_arg_info &arg)
      }

    /* C.11  */
-  pcum->aapcs_nextncrn = NUM_ARG_REGS;
+  pcum->aapcs_nextncrn = num_pcs_arg_regs (pcum->pcs_variant);

    /* The argument is passed on stack; record the needed number of words
for
       this argument and align the total size if necessary.  */
@@ -7675,7 +7765,8 @@ aarch64_function_arg (cumulative_args_t
pcum_v,
const function_arg_info &arg)
    CUMULATIVE_ARGS *pcum = get_cumulative_args (pcum_v);
    gcc_assert (pcum->pcs_variant == ARM_PCS_AAPCS64
              || pcum->pcs_variant == ARM_PCS_SIMD
-             || pcum->pcs_variant == ARM_PCS_SVE);
+             || pcum->pcs_variant == ARM_PCS_SVE
+             || pcum->pcs_variant == ARM_PCS_PRESERVE_NONE);

    if (arg.end_marker_p ())
      {
@@ -7767,7 +7858,8 @@ aarch64_function_arg_advance
(cumulative_args_t pcum_v,
    CUMULATIVE_ARGS *pcum = get_cumulative_args (pcum_v);
    if (pcum->pcs_variant == ARM_PCS_AAPCS64
        || pcum->pcs_variant == ARM_PCS_SIMD
-      || pcum->pcs_variant == ARM_PCS_SVE)
+      || pcum->pcs_variant == ARM_PCS_SVE
+      || pcum->pcs_variant == ARM_PCS_PRESERVE_NONE)
      {
        aarch64_layout_arg (pcum_v, arg);
        gcc_assert ((pcum->aapcs_reg != NULL_RTX)
@@ -7786,13 +7878,41 @@ aarch64_function_arg_advance
(cumulative_args_t pcum_v,
      }
  }

-bool
-aarch64_function_arg_regno_p (unsigned regno)
+/* Checks if a register is live at entry of a preserve_none pcs function.
+   That is, it used for passing registers.  See
ARM_PCS_PRESERVE_NONE_REGISTERS
+   for full list and order of argument passing registers.  */
+
+static bool
+function_arg_preserve_none_regno_p (unsigned regno)
  {
-  return ((GP_REGNUM_P (regno) && regno < R0_REGNUM +
NUM_ARG_REGS)
+  return ((GP_REGNUM_P (regno) && regno != R8_REGNUM && regno !=
R15_REGNUM
+          && regno != R16_REGNUM && regno != R17_REGNUM && regno !=
R18_REGNUM
+          && regno != R19_REGNUM && regno != R29_REGNUM && regno !=
R30_REGNUM)
          || (FP_REGNUM_P (regno) && regno < V0_REGNUM +
NUM_FP_ARG_REGS)
          || (PR_REGNUM_P (regno) && regno < P0_REGNUM +
NUM_PR_ARG_REGS));
  }
+/* Implements FUNCTION_ARG_REGNO_P.  */
+bool
+aarch64_function_arg_regno_p (unsigned regno)
+{
+  enum arm_pcs pcs
+    = cfun ? (arm_pcs) fndecl_abi (cfun->decl).id () : ARM_PCS_AAPCS64;
+
+  switch (pcs)
+    {
+    case ARM_PCS_AAPCS64:
+    case ARM_PCS_SIMD:
+    case ARM_PCS_SVE:
+    case ARM_PCS_TLSDESC:
+    case ARM_PCS_UNKNOWN:
+      return ((GP_REGNUM_P (regno) && regno < R0_REGNUM +
NUM_ARG_REGS)
+             || (FP_REGNUM_P (regno) && regno < V0_REGNUM +
NUM_FP_ARG_REGS)
+             || (PR_REGNUM_P (regno) && regno < P0_REGNUM +
NUM_PR_ARG_REGS));
+    case ARM_PCS_PRESERVE_NONE:
+      return function_arg_preserve_none_regno_p (regno);
+    }
+  gcc_unreachable ();
+}

  /* Implement FUNCTION_ARG_BOUNDARY.  Every parameter gets at least
     PARM_BOUNDARY bits of alignment, but will be given anything up
@@ -21804,8 +21924,9 @@ aarch64_expand_builtin_va_start (tree
valist, rtx
nextarg ATTRIBUTE_UNUSED)

    cum = &crtl->args.info;
    if (cfun->va_list_gpr_size)
-    gr_save_area_size = MIN ((NUM_ARG_REGS - cum->aapcs_ncrn) *
UNITS_PER_WORD,
-                            cfun->va_list_gpr_size);
+    gr_save_area_size = MIN ((num_pcs_arg_regs (cum->pcs_variant)
+                             - cum->aapcs_ncrn)
+                            * UNITS_PER_WORD, cfun->va_list_gpr_size);
    if (cfun->va_list_fpr_size)
      vr_save_area_size = MIN ((NUM_FP_ARG_REGS - cum->aapcs_nvrn)
                             * UNITS_PER_VREG, cfun->va_list_fpr_size);
@@ -22190,7 +22311,8 @@ aarch64_setup_incoming_varargs
(cumulative_args_t cum_v,
    /* Found out how many registers we need to save.
       Honor tree-stdvar analysis results.  */
    if (cfun->va_list_gpr_size)
-    gr_saved = MIN (NUM_ARG_REGS - local_cum.aapcs_ncrn,
+    gr_saved = MIN (num_pcs_arg_regs (local_cum.pcs_variant)
+                   - local_cum.aapcs_ncrn,
                    cfun->va_list_gpr_size / UNITS_PER_WORD);
    if (cfun->va_list_fpr_size)
      vr_saved = MIN (NUM_FP_ARG_REGS - local_cum.aapcs_nvrn,
@@ -22214,8 +22336,22 @@ aarch64_setup_incoming_varargs
(cumulative_args_t cum_v,
          mem = gen_frame_mem (BLKmode, ptr);
          set_mem_alias_set (mem, get_varargs_alias_set ());

-         move_block_from_reg (local_cum.aapcs_ncrn + R0_REGNUM,
-                              mem, gr_saved);
+         /* For preserve_none pcs we can't use move_block_from_reg as the
+            argument passing register order is not consecutive.  */
+         if (local_cum.pcs_variant == ARM_PCS_PRESERVE_NONE)
+           {
+             for (int i = 0; i < gr_saved; ++i)
+               {
+                 rtx tem = operand_subword (mem, i, 1, BLKmode);
+                 gcc_assert (tem);
+                 int reg = get_pcs_arg_reg (local_cum.pcs_variant,
+                                            local_cum.aapcs_ncrn + i);
+                 emit_move_insn (tem, gen_rtx_REG (word_mode, reg));
+               }
+           }
+         else
+           move_block_from_reg (R0_REGNUM + local_cum.aapcs_ncrn,
mem,
+                                gr_saved);
        }
        if (vr_saved > 0)
        {
@@ -25521,7 +25657,7 @@ aarch64_is_variant_pcs (tree fndecl)
  {
    /* Check for ABIs that preserve more registers than usual.  */
    arm_pcs pcs = (arm_pcs) fndecl_abi (fndecl).id ();
-  if (pcs == ARM_PCS_SIMD || pcs == ARM_PCS_SVE)
+  if (pcs == ARM_PCS_SIMD || pcs == ARM_PCS_SVE || pcs ==
ARM_PCS_PRESERVE_NONE)
      return true;

    /* Check for ABIs that allow PSTATE.SM to be 1 on entry.  */
@@ -30252,6 +30388,8 @@ aarch64_comp_type_attributes (const_tree
type1, const_tree type2)

    if (!check_attr ("gnu", "aarch64_vector_pcs"))
      return 0;
+  if (!check_attr ("gnu", "preserve_none"))
+    return 0;
    if (!check_attr ("gnu", "indirect_return"))
      return 0;
    if (!check_attr ("gnu", "Advanced SIMD type"))
diff --git a/gcc/config/aarch64/aarch64.h
b/gcc/config/aarch64/aarch64.h
index 2cd929d83f9..79528696da0 100644
--- a/gcc/config/aarch64/aarch64.h
+++ b/gcc/config/aarch64/aarch64.h
@@ -696,6 +696,17 @@ through +ssve-fp8dot2.  */
  #define NUM_FP_ARG_REGS                       8
  #define NUM_PR_ARG_REGS                       4

+/* The argument passing regs for preserve_none pcs.  */
+#define NUM_PRESERVE_NONE_ARG_REGS 23
+#define PRESERVE_NONE_REGISTERS \
+{ \
+  R20_REGNUM, R21_REGNUM, R22_REGNUM, R23_REGNUM,
R24_REGNUM, R25_REGNUM,\
+  R26_REGNUM, R27_REGNUM, R28_REGNUM,\
+  R0_REGNUM, R1_REGNUM, R2_REGNUM, R3_REGNUM, R4_REGNUM,
R5_REGNUM,\
+  R6_REGNUM, R7_REGNUM,\
+  R10_REGNUM, R11_REGNUM, R12_REGNUM, R13_REGNUM,
R14_REGNUM, R9_REGNUM\
+}
+

According to the LLVM documentation on this[1], R15 is also an argument
passing
register on non-Windows.  So I think it should be here conditionally?

[1] https://clang.llvm.org/docs/AttributeReference.html#preserve-none

Ah thank you for catching that. My mistake. Will fix.


  /* A Homogeneous Floating-Point or Short-Vector Aggregate may have at
most
     four members.  */
  #define HA_MAX_NUM_FLDS               4
@@ -1150,6 +1161,8 @@ enum arm_pcs
    ARM_PCS_SVE,                        /* For functions that pass or return
                                   values in SVE registers.  */
    ARM_PCS_TLSDESC,            /* For targets of tlsdesc calls.  */
+  ARM_PCS_PRESERVE_NONE,       /* PCS variant with no call-preserved
+                                  registers except X29.  */
    ARM_PCS_UNKNOWN
  };

diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index fb117f59665..6643c00e11e 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -3930,6 +3930,24 @@ threads, such as the POSIX
@code{swapcontext}
function.  This attribute
  adds a @code{BTI J} instruction when BTI is enabled e.g. via
  @option{-mbranch-protection}.

+@cindex @code{preserve_none} function attribute, AArch64
+@item preserve_none
+Use this attribute to change the procedure call standard of the specified
+function to the preserve-none variant.
+
+The preserve-none ABI variant modifies the AAPCS such that has no
+callee-saved registers (including SIMD and floating-point registers).
+That is, all registers, except for stack register,
+link register (r30), and frame pointer (r29), are shifted to be caller saved,
+and can be used as scratch registers by the callee.
+

The LLVM documentation says they are callee saved though, not caller.

The clang documentation says "So all general registers are caller saved
registers."
which matches here (though I need to update the language for fp/simd
registers).

But the AArch64 specific documentation below that states:

On AArch64, only LR and FP are preserved by the callee.

So there's an exclusion for AArch64 isn't there?

I dont believe so. Caller saved and callee saved are the opposites. So saying "general registers are caller saved" and "only LR and SP are callee saved" is basically equivalent.

Thanks,
Alfie>
Thanks,
Tamar

It also deifinitely should be making registers caller saved either way.

So one of these seem odd.

+Additionally, registers r20--r28, r0--r7, r10--r14, and finally r9 are used
for
+argument passing, in that order.  The return value registers remain r0 and
r1.
+

r15 is also an argument passing on non-Windows isn't it?

Will fix.


Thanks,
Tamar

+All other details are the same as for the AAPCS ABI.
+
+This ABI has not been stabilized, and may be subject to change in future
+versions.
  @end table

  The above target attributes can be specified as follows:
diff --git a/gcc/testsuite/gcc.target/aarch64/preserve_none_1.c
b/gcc/testsuite/gcc.target/aarch64/preserve_none_1.c
new file mode 100644
index 00000000000..a411af23256
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/preserve_none_1.c
@@ -0,0 +1,142 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fno-schedule-insns2" } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+
+void normal_callee();
+void preserve_none_callee() [[gnu::preserve_none]];
+
+#pragma GCC target "+sve"
+
+/*
+** preserve_none_caller1:
+** ?#APP
+**     nop
+** ?#NO_APP
+**     ret
+*/
+void preserve_none_caller1() [[gnu::preserve_none]]
+{
+  asm volatile ("nop" ::: "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7",
+               "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
+               "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23",
+               "x24", "x25", "x26", "x27", "x28",
+
+               "z0", "z1", "z2", "z3", "z4", "z5", "z6", "z7",
+               "z8", "z9", "z10", "z11", "z12", "z13", "z14", "z15",
+               "z16", "z17", "z18", "z19", "z20", "z21", "z22", "z23",
+               "z24", "z25", "z26", "z27", "z28", "z29", "z30", "z31",
+
+               "p0", "p1", "p2", "p3", "p4", "p5", "p6", "p7",
+               "p8", "p9", "p10", "p11", "p12", "p13", "p14", "p15");
+}
+
+/*
+** preserve_none_caller2:
+**     stp     x29, x30, \[sp, #?-16\]!
+**     mov     x29, sp
+**     bl      normal_callee
+**     mov     w0, w20
+**     ldp     x29, x30, \[sp\], #?16
+**     ret
+*/
+int preserve_none_caller2(int x) [[gnu::preserve_none]]
+{
+  normal_callee();
+  return x;
+}
+
+/*
+** preserve_none_caller3:
+**     stp     x29, x30, \[sp, #?-32\]!
+**     mov     x29, sp
+**     str     w20, \[sp, #?[0-9]+\]
+**     bl      preserve_none_callee
+**     ldr     w0, \[sp, #?[0-9]+\]
+**     ldp     x29, x30, \[sp\], #?32
+**     ret
+*/
+int preserve_none_caller3(int x) [[gnu::preserve_none]]
+{
+  preserve_none_callee();
+  return x;
+}
+
+/*
+** preserve_none_caller4:
+**     b       preserve_none_callee
+*/
+void preserve_none_caller4() [[gnu::preserve_none]]
+{
+  preserve_none_callee();
+}
+
+/*
+** preserve_none_caller5:
+**     b       preserve_none_callee
+*/
+void preserve_none_caller5(__SVBool_t x) [[gnu::preserve_none]]
+{
+  preserve_none_callee();
+}
+
+/*
+** normal_caller1:
+**     stp     x29, x30, \[sp, #?-160\]!
+**     mov     x29, sp
+**     stp     x19, x20, \[sp, #?16\]
+**     stp     x21, x22, \[sp, #?32\]
+**     stp     x23, x24, \[sp, #?48\]
+**     stp     x25, x26, \[sp, #?64\]
+**     stp     x27, x28, \[sp, #?80\]
+**     stp     d8, d9, \[sp, #?96\]
+**     stp     d10, d11, \[sp, #?112\]
+**     stp     d12, d13, \[sp, #?128\]
+**     stp     d14, d15, \[sp, #?144\]
+**     bl      preserve_none_callee
+**     ldp     d8, d9, \[sp, #?96\]
+**     ldp     d10, d11, \[sp, #?112\]
+**     ldp     d12, d13, \[sp, #?128\]
+**     ldp     d14, d15, \[sp, #?144\]
+**     ldp     x19, x20, \[sp, #?16\]
+**     ldp     x21, x22, \[sp, #?32\]
+**     ldp     x23, x24, \[sp, #?48\]
+**     ldp     x25, x26, \[sp, #?64\]
+**     ldp     x27, x28, \[sp, #?80\]
+**     ldp     x29, x30, \[sp\], #?160
+**     ret
+*/
+void normal_caller1()
+{
+  preserve_none_callee();
+}
+
+/*
+** normal_caller2:
+**     stp     x29, x30, \[sp, #?-160\]!
+**     mov     x29, sp
+**     stp     x19, x20, \[sp, #?16\]
+**     stp     x21, x22, \[sp, #?32\]
+**     stp     x23, x24, \[sp, #?48\]
+**     stp     x25, x26, \[sp, #?64\]
+**     stp     x27, x28, \[sp, #?80\]
+**     stp     d8, d9, \[sp, #?96\]
+**     stp     d10, d11, \[sp, #?112\]
+**     stp     d12, d13, \[sp, #?128\]
+**     stp     d14, d15, \[sp, #?144\]
+**     blr     x0
+**     ldp     d8, d9, \[sp, #?96\]
+**     ldp     d10, d11, \[sp, #?112\]
+**     ldp     d12, d13, \[sp, #?128\]
+**     ldp     d14, d15, \[sp, #?144\]
+**     ldp     x19, x20, \[sp, #?16\]
+**     ldp     x21, x22, \[sp, #?32\]
+**     ldp     x23, x24, \[sp, #?48\]
+**     ldp     x25, x26, \[sp, #?64\]
+**     ldp     x27, x28, \[sp, #?80\]
+**     ldp     x29, x30, \[sp\], #?160
+**     ret
+*/
+void normal_caller2(void (*callee)() [[gnu::preserve_none]])
+{
+  callee();
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/preserve_none_2.c
b/gcc/testsuite/gcc.target/aarch64/preserve_none_2.c
new file mode 100644
index 00000000000..1bb89e026e5
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/preserve_none_2.c
@@ -0,0 +1,49 @@
+/* { dg-options "" } */
+
+void multi1() [[gnu::aarch64_vector_pcs, gnu::preserve_none]]; /* { dg-
warning {ignoring attribute 'preserve_none' because it conflicts} } */
+void multi2() [[gnu::preserve_none, gnu::aarch64_vector_pcs]]; /* { dg-
warning {ignoring attribute 'aarch64_vector_pcs' because it conflicts} } */
+
+void normal_callee();
+void preserve_none_callee() [[gnu::preserve_none]];
+void vector_callee() [[gnu::aarch64_vector_pcs]];
+void sve_callee(__SVBool_t);
+void sve_preserve_none_callee(__SVBool_t) [[gnu::preserve_none]];
+
+void (*normal_ptr)();
+void (*preserve_none_ptr)() [[gnu::preserve_none]];
+void (*vector_ptr)() [[gnu::aarch64_vector_pcs]];
+void (*sve_ptr)(__SVBool_t);
+void (*sve_preserve_none_ptr)(__SVBool_t) [[gnu::preserve_none]];
+
+void f()
+{
+  normal_ptr = normal_callee;
+  normal_ptr = preserve_none_callee; /* { dg-error {incompatible pointer
type} } */
+  normal_ptr = vector_callee; /* { dg-error {incompatible pointer type} } */
+  normal_ptr = sve_callee; /* { dg-error {incompatible pointer type} } */
+  normal_ptr = sve_preserve_none_callee; /* { dg-error {incompatible
pointer
type} } */
+
+  preserve_none_ptr = normal_callee; /* { dg-error {incompatible pointer
type} } */
+  preserve_none_ptr = preserve_none_callee;
+  preserve_none_ptr = vector_callee; /* { dg-error {incompatible pointer
type}
} */
+  preserve_none_ptr = sve_callee; /* { dg-error {incompatible pointer type}
}
*/
+  preserve_none_ptr = sve_preserve_none_callee; /* { dg-error
{incompatible
pointer type} } */
+
+  vector_ptr = normal_callee; /* { dg-error {incompatible pointer type} } */
+  vector_ptr = preserve_none_callee; /* { dg-error {incompatible pointer
type}
} */
+  vector_ptr = vector_callee;
+  vector_ptr = sve_callee; /* { dg-error {incompatible pointer type} } */
+  vector_ptr = sve_preserve_none_callee; /* { dg-error {incompatible
pointer
type} } */
+
+  sve_ptr = normal_callee; /* { dg-error {incompatible pointer type} } */
+  sve_ptr = preserve_none_callee; /* { dg-error {incompatible pointer type}
}
*/
+  sve_ptr = vector_callee; /* { dg-error {incompatible pointer type} } */
+  sve_ptr = sve_callee;
+  sve_ptr = sve_preserve_none_callee; /* { dg-error {incompatible pointer
type} } */
+
+  sve_preserve_none_ptr = normal_callee; /* { dg-error {incompatible
pointer
type} } */
+  sve_preserve_none_ptr = preserve_none_callee; /* { dg-error
{incompatible
pointer type} } */
+  sve_preserve_none_ptr = vector_callee; /* { dg-error {incompatible
pointer
type} } */
+  sve_preserve_none_ptr = sve_callee; /* { dg-error {incompatible pointer
type} } */
+  sve_preserve_none_ptr = sve_preserve_none_callee;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/preserve_none_3.c
b/gcc/testsuite/gcc.target/aarch64/preserve_none_3.c
new file mode 100644
index 00000000000..7a47190687d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/preserve_none_3.c
@@ -0,0 +1,109 @@
+/* { dg-do run } */
+/* { dg-options "-O2 -std=gnu23" } */
+
+int no_arg_stack_use_callee [[gnu::preserve_none, gnu::noinline,
gnu::noipa]]
+                       (int a0, int a1, int a2, int a3, int a4, int a5, int a6,
+                        int a7, int a8, int a9, int a10, int a11, int a12,
+                        int a13, int a14, int a15, int a16, int a17, int a18,
+                        int a19, int a20, int a21, int a22) {
+  /* Clobber all the registers to check they are correctly marked live at the
+     start.  */
+  asm volatile ("mov x0, #0;"
+               "mov x1, #0;"
+               "mov x2, #0;"
+               "mov x3, #0;"
+               "mov x4, #0;"
+               "mov x5, #0;"
+               "mov x6, #0;"
+               "mov x7, #0;"
+               "mov x8, #0;"
+               "mov x9, #0;"
+               "mov x10, #0;"
+               "mov x11, #0;"
+               "mov x12, #0;"
+               "mov x13, #0;"
+               "mov x14, #0;"
+               "mov x15, #0;"
+               "mov x16, #0;"
+               "mov x17, #0;"
+               "mov x18, #0;"
+               "mov x19, #0;"
+               "mov x20, #0;"
+               "mov x21, #0;"
+               "mov x22, #0;"
+               "mov x23, #0;"
+               "mov x24, #0;"
+               "mov x25, #0;"
+               "mov x26, #0;"
+               "mov x27, #0;"
+               "mov x28, #0;"
+               ::: "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7",
+               "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
+               "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23",
+               "x24", "x25", "x26", "x27", "x28");
+
+  return a0 + a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 +
a13
+        + a14 + a15 + a16 + a17 + a18 + a19 + a20 + a21 + a22;
+}
+
+int arg_stack_use_callee [[gnu::preserve_none, gnu::noinline,
gnu::noipa]]
+                       (int a0, int a1, int a2, int a3, int a4, int a5, int a6,
+                        int a7, int a8, int a9, int a10, int a11, int a12,
+                        int a13, int a14, int a15, int a16, int a17, int a18,
+                        int a19, int a20, int a21, int a22, int a23) {
+  /* Clobber all the registers to check they are correctly marked live at the
+     start.  */
+  asm volatile ("mov x0, #0;"
+               "mov x1, #0;"
+               "mov x2, #0;"
+               "mov x3, #0;"
+               "mov x4, #0;"
+               "mov x5, #0;"
+               "mov x6, #0;"
+               "mov x7, #0;"
+               "mov x8, #0;"
+               "mov x9, #0;"
+               "mov x10, #0;"
+               "mov x11, #0;"
+               "mov x12, #0;"
+               "mov x13, #0;"
+               "mov x14, #0;"
+               "mov x15, #0;"
+               "mov x16, #0;"
+               "mov x17, #0;"
+               "mov x18, #0;"
+               "mov x19, #0;"
+               "mov x20, #0;"
+               "mov x21, #0;"
+               "mov x22, #0;"
+               "mov x23, #0;"
+               "mov x24, #0;"
+               "mov x25, #0;"
+               "mov x26, #0;"
+               "mov x27, #0;"
+               "mov x28, #0;"
+               ::: "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7",
+               "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
+               "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23",
+               "x24", "x25", "x26", "x27", "x28");
+
+  return a0 + a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 +
a13
+        + a14 + a15 + a16 + a17 + a18 + a19 + a20 + a21 + a22 + a23;
+}
+
+int main () {
+
+  int res = no_arg_stack_use_callee (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13,
14,
+                              15, 16, 17, 18, 19, 20, 21, 22);
+
+  if (res != 22 * 23 / 2)
+    return 1;
+
+  res = arg_stack_use_callee(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+                              15, 16, 17, 18, 19, 20, 21, 22, 23);
+
+  if (res != 23 * 24 / 2)
+    return 1;
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/preserve_none_4.c
b/gcc/testsuite/gcc.target/aarch64/preserve_none_4.c
new file mode 100644
index 00000000000..22338c96711
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/preserve_none_4.c
@@ -0,0 +1,93 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fno-schedule-insns2" } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+
+int no_arg_stack_use_callee [[gnu::preserve_none, gnu::noinline,
gnu::noipa]]
+                       (int a0, int a1, int a2, int a3, int a4, int a5, int a6,
+                        int a7, int a8, int a9, int a10, int a11, int a12,
+                        int a13, int a14, int a15, int a16, int a17, int a18,
+                        int a19, int a20, int a21, int a22);
+
+/* Check the pcs argument order is correct. Should be x20-28, x0-7, x10-
14,
x9, and that the return arg is x0 */
+
+/*
+** no_arg_stack_use_caller:
+** ...
+**     mov     w9, 22
+**     mov     w14, 21
+**     mov     w13, 20
+**     mov     w12, 19
+**     mov     w11, 18
+**     mov     w10, 17
+**     mov     w7, 16
+**     mov     w6, 15
+**     mov     w5, 14
+**     mov     w4, 13
+**     mov     w3, 12
+**     mov     w2, 11
+**     mov     w1, 10
+**     mov     w0, 9
+**     mov     w28, 8
+**     mov     w27, 7
+**     mov     w26, 6
+**     mov     w25, 5
+**     mov     w24, 4
+**     mov     w23, 3
+**     mov     w22, 2
+**     mov     w21, 1
+**     mov     w20, 0
+**     bl      no_arg_stack_use_callee
+**     add     w0, w0, 1
+** ...
+*/
+int no_arg_stack_use_caller [[gnu::preserve_none]] ()
+{
+  return no_arg_stack_use_callee (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+                                 14, 15, 16, 17, 18, 19, 20, 21, 22)
+        + 1;
+}
+
+int arg_stack_use_callee [[gnu::preserve_none, gnu::noinline,
gnu::noipa]]
+                       (int a0, int a1, int a2, int a3, int a4, int a5, int a6,
+                        int a7, int a8, int a9, int a10, int a11, int a12,
+                        int a13, int a14, int a15, int a16, int a17, int a18,
+                        int a19, int a20, int a21, int a22, int a23);
+
+/*
+** arg_stack_use_caller:
+** ...
+**     mov     w0, 23
+**     mov     w9, 22
+**     mov     w14, 21
+**     mov     w13, 20
+**     mov     w12, 19
+**     mov     w11, 18
+**     mov     w10, 17
+**     mov     w7, 16
+**     mov     w6, 15
+**     mov     w5, 14
+**     mov     w4, 13
+**     mov     w3, 12
+**     mov     w2, 11
+**     mov     w1, 10
+**     mov     w28, 8
+**     mov     w27, 7
+**     mov     w26, 6
+**     mov     w25, 5
+**     mov     w24, 4
+**     mov     w23, 3
+**     mov     w22, 2
+**     mov     w21, 1
+**     mov     w20, 0
+**     str     w0, \[sp\]
+**     mov     w0, 9
+**     bl      arg_stack_use_callee
+**     add     w0, w0, 1
+** ...
+*/
+int arg_stack_use_caller [[gnu::preserve_none]] ()
+{
+  return arg_stack_use_callee (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 
14,
+                              15, 16, 17, 18, 19, 20, 21, 22, 23)
+        + 1;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/preserve_none_5.c
b/gcc/testsuite/gcc.target/aarch64/preserve_none_5.c
new file mode 100644
index 00000000000..87b22646fb1
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/preserve_none_5.c
@@ -0,0 +1,45 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fno-schedule-insns2" } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+
+#include <stdarg.h>
+int foo [[gnu::preserve_none]] (...);
+
+/* Check the pcs argument order is correct. Should be x20-28, x0-7, x10-
14,
x9, and that the return arg is x0 */
+
+/*
+** bar:
+** ...
+**     mov     w9, 22
+**     mov     w14, 21
+**     mov     w13, 20
+**     mov     w12, 19
+**     mov     w11, 18
+**     mov     w10, 17
+**     mov     w7, 16
+**     mov     w6, 15
+**     mov     w5, 14
+**     mov     w4, 13
+**     mov     w3, 12
+**     mov     w2, 11
+**     mov     w1, 10
+**     mov     w0, 9
+**     mov     w28, 8
+**     mov     w27, 7
+**     mov     w26, 6
+**     mov     w25, 5
+**     mov     w24, 4
+**     mov     w23, 3
+**     mov     w22, 2
+**     mov     w21, 1
+**     mov     w20, 0
+**     bl      foo
+**     add     w0, w0, 1
+** ...
+*/
+int bar [[gnu::preserve_none]] ()
+{
+  return foo (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+             19, 20, 21, 22)
+        + 1;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/preserve_none_6.c
b/gcc/testsuite/gcc.target/aarch64/preserve_none_6.c
new file mode 100644
index 00000000000..e576df40e77
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/preserve_none_6.c
@@ -0,0 +1,66 @@
+/* { dg-do run } */
+/* { dg-options "-O2 -std=gnu23" } */
+
+#include <stdarg.h>
+#include <stdio.h>
+
+int preserve_none_va_func [[gnu::preserve_none, gnu::noinline,
gnu::noclone]] (int count, ...) {
+  asm volatile ("mov x0, #0;"
+               "mov x1, #0;"
+               "mov x2, #0;"
+               "mov x3, #0;"
+               "mov x4, #0;"
+               "mov x5, #0;"
+               "mov x6, #0;"
+               "mov x7, #0;"
+               "mov x8, #0;"
+               "mov x9, #0;"
+               "mov x10, #0;"
+               "mov x11, #0;"
+               "mov x12, #0;"
+               "mov x13, #0;"
+               "mov x14, #0;"
+               "mov x15, #0;"
+               "mov x16, #0;"
+               "mov x17, #0;"
+               "mov x18, #0;"
+               "mov x19, #0;"
+               "mov x20, #0;"
+               "mov x21, #0;"
+               "mov x22, #0;"
+               "mov x23, #0;"
+               "mov x24, #0;"
+               "mov x25, #0;"
+               "mov x26, #0;"
+               "mov x27, #0;"
+               "mov x28, #0;"
+               ::: "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7",
+               "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
+               "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23",
+               "x24", "x25", "x26", "x27", "x28");
+
+  int sum = 0;
+
+  va_list args;
+
+  va_start (args, count);
+  for (int i = 0; i < count; i++)
+    sum += va_arg(args, int);
+  va_end (args);
+
+  return sum;
+}
+
+int main () {
+  int res = preserve_none_va_func (22, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12,
13,
+                                  14, 15, 16, 17, 18, 19, 20, 21);
+  if (res != 22 * 21 / 2)
+    return 1;
+
+  res = preserve_none_va_func (23, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13,
+                                  14, 15, 16, 17, 18, 19, 20, 21, 22);
+  if (res != 23 * 22 / 2)
+    return 1;
+
+  return 0;
+}
--
2.34.1


--
Alfie Richards

Reply via email to