]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
aarch64: Add support for preserve_none function attribute [PR target/118328]
authorAlfie Richards <alfie.richards@arm.com>
Wed, 15 Oct 2025 13:34:55 +0000 (13:34 +0000)
committerAlfie Richards <alfie.richards@arm.com>
Fri, 7 Nov 2025 10:20:55 +0000 (10:20 +0000)
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, it 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_mingw_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
gcc/config/aarch64/aarch64.h
gcc/doc/extend.texi
gcc/testsuite/gcc.target/aarch64/preserve_none_1.c [new file with mode: 0644]
gcc/testsuite/gcc.target/aarch64/preserve_none_2.c [new file with mode: 0644]
gcc/testsuite/gcc.target/aarch64/preserve_none_3.c [new file with mode: 0644]
gcc/testsuite/gcc.target/aarch64/preserve_none_4.c [new file with mode: 0644]
gcc/testsuite/gcc.target/aarch64/preserve_none_5.c [new file with mode: 0644]
gcc/testsuite/gcc.target/aarch64/preserve_none_6.c [new file with mode: 0644]
gcc/testsuite/gcc.target/aarch64/preserve_none_mingw_1.c [new file with mode: 0644]

index 74e2f20de4e15be37cc5b5218fd44be997e62796..b3e752bbd8cd3736a144a92681873ff75755d33f 100644 (file)
@@ -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,10 @@ aarch64_reg_save_mode (unsigned int regno)
   if (FP_REGNUM_P (regno))
     switch (crtl->abi->id ())
       {
+      case ARM_PCS_PRESERVE_NONE:
+       /* In preserve_none all fpr registers are caller saved, so the choice
+          here should not matter.  Nevertheless, fall back to the base AAPCS
+          for consistency.  */
       case ARM_PCS_AAPCS64:
        /* Only the low 64 bits are saved by the base PCS.  */
        return DFmode;
@@ -2649,7 +2688,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 +6867,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 +7332,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 +7473,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 +7490,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 +7603,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 +7639,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 +7661,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 +7674,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 +7688,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 +7766,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 +7859,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 +7879,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
@@ -21777,8 +21898,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);
@@ -22163,7 +22285,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,
@@ -22187,8 +22310,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)
        {
@@ -25494,7 +25631,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.  */
@@ -30225,6 +30362,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"))
index 2cd929d83f964ad3f0347939844fdbeea4cb0de1..0e596b597449ea6ed32448a7caae52f65a9ceb1d 100644 (file)
@@ -696,6 +696,31 @@ through +ssve-fp8dot2.  */
 #define NUM_FP_ARG_REGS                        8
 #define NUM_PR_ARG_REGS                        4
 
+/* The argument passing regs for preserve_none pcs.  */
+#if TARGET_AARCH64_MS_ABI
+#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\
+}
+#else
+#define NUM_PRESERVE_NONE_ARG_REGS 24
+#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,\
+  R15_REGNUM\
+}
+#endif
+
+
 /* A Homogeneous Floating-Point or Short-Vector Aggregate may have at most
    four members.  */
 #define HA_MAX_NUM_FLDS                4
@@ -1150,6 +1175,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
 };
 
index a5b97f37d9d34a91d9c0fcba299d056e7004ba5d..616e05ebbd2c030c7bb5c602e71d74ff39d80a8c 100644 (file)
@@ -3970,6 +3970,27 @@ 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, with the
+exception of the stack register, link register (r30), and frame pointer (r29),
+all registers are caller saved, and can be used as scratch registers by the
+callee.
+
+Additionally, registers r20--r28, r0--r7, r10--r14, r9 and r15 are used for
+argument passing, in that order.  For Microsoft Windows targets
+r15 is not used for argument passing.
+
+The return value registers remain r0 and r1 in both cases.
+
+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 (file)
index 0000000..1390173
--- /dev/null
@@ -0,0 +1,143 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fno-schedule-insns2" } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+/* { dg-skip-if "" { *-*-mingw* } } */
+
+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 (file)
index 0000000..1bb89e0
--- /dev/null
@@ -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 (file)
index 0000000..0258177
--- /dev/null
@@ -0,0 +1,114 @@
+/* { 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, 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 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, int a24)
+{
+  /* 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 + a24;
+}
+
+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, 23);
+
+  if (res != 23 * 24 / 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, 24);
+
+  if (res != 24 * 25 / 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 (file)
index 0000000..fc8347d
--- /dev/null
@@ -0,0 +1,99 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fno-schedule-insns2" } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+/* { dg-skip-if "" { *-*-mingw* } } */
+
+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, int a24);
+
+/* Check the pcs argument order is correct. Should be x20-28, x0-7, x10-14, x9,
+ * x15 and that the return arg is x0 */
+
+/*
+** no_arg_stack_use_caller:
+** ...
+**     mov     w15, 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     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, 23)
+        + 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, int a24);
+
+/*
+** arg_stack_use_caller:
+** ...
+**     mov     w0, 24
+**     mov     w15, 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, 24)
+        + 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 (file)
index 0000000..d11bf10
--- /dev/null
@@ -0,0 +1,47 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fno-schedule-insns2" } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+/* { dg-skip-if "" { *-*-mingw* } } */
+
+#include <stdarg.h>
+int foo [[gnu::preserve_none]] (...);
+
+/* Check the pcs argument order is correct. Should be x20-28, x0-7, x10-14, x9, x15 and that the return arg is x0 */
+
+/*
+** bar:
+** ...
+**     mov     w15, 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     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, 23)
+        + 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 (file)
index 0000000..687e30a
--- /dev/null
@@ -0,0 +1,76 @@
+/* { 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 (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;
+
+  res = preserve_none_va_func (24, 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 != 24 * 23 / 2)
+    return 1;
+
+  res = preserve_none_va_func (25, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+                              14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24);
+  if (res != 25 * 24 / 2)
+    return 1;
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/preserve_none_mingw_1.c b/gcc/testsuite/gcc.target/aarch64/preserve_none_mingw_1.c
new file mode 100644 (file)
index 0000000..11703c8
--- /dev/null
@@ -0,0 +1,93 @@
+/* { dg-do compile { target aarch64*-*-mingw* } } */
+/* { 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;
+}