From: Djordje Todorovic <[email protected]>

The RISC-V psABI requires named 2 * XLEN scalar arguments and
return values to use significance-based register-pair ordering:
the lower-order XLEN bits are passed in the lower-numbered register,
and the higher-order XLEN bits are passed in the higher-numbered
register.  This ordering is independent of endianness and applies
to both RV32 and RV64.

On big-endian targets, significance ordering differs from memory-layout
ordering.  If exactly one argument register is available, the low-order
XLEN bits are passed in the register and the high-order XLEN bits are
passed on the stack.

Keep anonymous variadic arguments and aggregates on the existing
memory-layout ordering path, so va_arg spill layout remains
correct on big-endian targets.  Floating-point reals also remain on the
memory-layout ordering path according to the psABI.

The relevant big-endian calling-convention rules were introduced by the
RISC-V ELF psABI change recently [1].

[1] https://github.com/riscv-non-isa/riscv-elf-psabi-doc/pull/470

Co-authored-by: Eldar Osmanovic <[email protected]>
Signed-off-by: Djordje Todorovic <[email protected]>
Signed-off-by: Eldar Osmanovic <[email protected]>

gcc/ChangeLog:

        * config/riscv/riscv.cc (riscv_2xlen_significance_scalar_p):
        New function.
        (riscv_pass_2xlen_scalar_gpr_pair): Likewise.
        (riscv_pass_2xlen_scalar_gpr_stack): Likewise.
        (riscv_get_arg_info): Use significance-based ordering for named
        2 * XLEN scalar arguments and return values.
        (riscv_function_arg_1): New common implementation. Handle
        2 * XLEN scalar split between a GPR and the stack.
        (riscv_function_arg): Use riscv_function_arg_1.
        (riscv_function_incoming_arg): New function.
        (TARGET_FUNCTION_INCOMING_ARG): Define.

gcc/testsuite/ChangeLog:

        * gcc.target/riscv/abi/rv32be-abi-2xlen-aggregate-return.c: New test.
        * gcc.target/riscv/abi/rv32be-abi-2xlen-aggregate-split.c: New test.
        * gcc.target/riscv/abi/rv32be-abi-2xlen-aggregate.c: New test.
        * gcc.target/riscv/abi/rv32be-abi-2xlen-float-incoming.c: New test.
        * gcc.target/riscv/abi/rv32be-abi-2xlen-ilp32e-scalar.c: New test.
        * gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-arg.c: New test.
        * gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-incoming.c: New test.
        * gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-return.c: New test.
        * gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-sibcall.c: New test.
        * gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-split.c: New test.
        * gcc.target/riscv/abi/rv64be-abi-2xlen-scalar-incoming.c: New test.
        * gcc.target/riscv/abi/rv64be-abi-2xlen-vararg.c: New test.
---
 gcc/config/riscv/riscv.cc                     | 122 +++++++++++++++++-
 .../abi/rv32be-abi-2xlen-aggregate-return.c   |  52 ++++++++
 .../abi/rv32be-abi-2xlen-aggregate-split.c    |  40 ++++++
 .../riscv/abi/rv32be-abi-2xlen-aggregate.c    |  34 +++++
 .../abi/rv32be-abi-2xlen-float-incoming.c     |  70 ++++++++++
 .../abi/rv32be-abi-2xlen-ilp32e-scalar.c      |  66 ++++++++++
 .../riscv/abi/rv32be-abi-2xlen-scalar-arg.c   |  54 ++++++++
 .../abi/rv32be-abi-2xlen-scalar-incoming.c    |  67 ++++++++++
 .../abi/rv32be-abi-2xlen-scalar-return.c      |  24 ++++
 .../abi/rv32be-abi-2xlen-scalar-sibcall.c     |  25 ++++
 .../riscv/abi/rv32be-abi-2xlen-scalar-split.c |  41 ++++++
 .../abi/rv64be-abi-2xlen-scalar-incoming.c    |  92 +++++++++++++
 .../riscv/abi/rv64be-abi-2xlen-vararg.c       |  76 +++++++++++
 13 files changed, 760 insertions(+), 3 deletions(-)
 create mode 100644 
gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-aggregate-return.c
 create mode 100644 
gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-aggregate-split.c
 create mode 100644 
gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-aggregate.c
 create mode 100644 
gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-float-incoming.c
 create mode 100644 
gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-ilp32e-scalar.c
 create mode 100644 
gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-arg.c
 create mode 100644 
gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-incoming.c
 create mode 100644 
gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-return.c
 create mode 100644 
gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-sibcall.c
 create mode 100644 
gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-split.c
 create mode 100644 
gcc/testsuite/gcc.target/riscv/abi/rv64be-abi-2xlen-scalar-incoming.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/abi/rv64be-abi-2xlen-vararg.c

diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index 47b20b8cdb4..53be36cf37b 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -7178,6 +7178,81 @@ riscv_pass_fpr_pair (machine_mode mode, unsigned regno1,
                                   GEN_INT (offset2))));
 }
 
+/* Return true if MODE and TYPE describe a value that is exactly 2 * XLEN
+   bits wide and uses significance-based scalar ordering.  Floating-point
+   reals, aggregates and complex values follow memory-layout ordering.  */
+
+static bool
+riscv_2xlen_significance_scalar_p (machine_mode mode, const_tree type)
+{
+  if (!known_eq (GET_MODE_SIZE (mode), 2 * UNITS_PER_WORD)
+      || COMPLEX_MODE_P (mode)
+      || VECTOR_MODE_P (mode)
+      || mode == BLKmode)
+    return false;
+
+  if (!type)
+    return SCALAR_INT_MODE_P (mode);
+
+  if (AGGREGATE_TYPE_P (type)
+      || TREE_CODE (type) == COMPLEX_TYPE
+      || VECTOR_TYPE_P (type))
+    return false;
+
+  return (INTEGRAL_TYPE_P (type)
+         || FIXED_POINT_TYPE_P (type)
+         || POINTER_TYPE_P (type));
+}
+
+/* Pass or return a 2 * XLEN scalar in REGNO and REGNO + 1 using
+   significance-based ordering.  On big-endian targets this differs from
+   memory-layout ordering: the lower-numbered register holds the low-order
+   XLEN bits.  */
+
+static rtx
+riscv_pass_2xlen_scalar_gpr_pair (machine_mode mode, unsigned regno)
+{
+  HOST_WIDE_INT low_offset = BYTES_BIG_ENDIAN ? UNITS_PER_WORD : 0;
+  HOST_WIDE_INT high_offset = BYTES_BIG_ENDIAN ? 0 : UNITS_PER_WORD;
+
+  return gen_rtx_PARALLEL
+    (mode,
+     gen_rtvec (2,
+               gen_rtx_EXPR_LIST (VOIDmode,
+                                  gen_rtx_REG (word_mode, regno),
+                                  GEN_INT (low_offset)),
+               gen_rtx_EXPR_LIST (VOIDmode,
+                                  gen_rtx_REG (word_mode, regno + 1),
+                                  GEN_INT (high_offset))));
+}
+
+/* Pass a big-endian 2 * XLEN scalar between the last available GPR and
+   the first stack slot. TARGET_ARG_PARTIAL_BYTES normally assumes that
+   the register contains the first word in memory, which would be the
+   high-order word on a big-endian target. Describe both locations
+   explicitly so that the GPR instead contains the low-order word required
+   by the psABI. STACK_BASE is virtual_outgoing_args_rtx for normal calls
+   and virtual_incoming_args_rtx for incoming arguments and sibling calls.  */
+
+static rtx
+riscv_pass_2xlen_scalar_gpr_stack (machine_mode mode, unsigned regno,
+                                  rtx stack_base)
+{
+  gcc_assert (BYTES_BIG_ENDIAN);
+  gcc_assert (regno == GP_ARG_LAST);
+
+  rtx stack = gen_rtx_MEM (word_mode, stack_base);
+  set_mem_align (stack, BITS_PER_WORD);
+
+  return gen_rtx_PARALLEL
+    (mode,
+     gen_rtvec (2,
+               gen_rtx_EXPR_LIST (VOIDmode,
+                                  gen_rtx_REG (word_mode, regno),
+                                  GEN_INT (UNITS_PER_WORD)),
+               gen_rtx_EXPR_LIST (VOIDmode, stack, const0_rtx)));
+}
+
 /* Return true if VLS mode MODE fits in general purpose registers per the
    psABI.  The psABI allows aggregates up to 2 * XLEN bits to be passed in
    GPRs.  */
@@ -7693,6 +7768,14 @@ riscv_get_arg_info (struct riscv_arg_info *info, const 
CUMULATIVE_ARGS *cum,
        = MIN (num_words, MAX_ARGS_IN_REGISTERS - info->gpr_offset);
       info->stack_p = (num_words - info->num_gprs) != 0;
 
+      if (BYTES_BIG_ENDIAN
+         && named
+         && info->num_gprs == 2
+         && riscv_2xlen_significance_scalar_p (mode, type))
+       return riscv_pass_2xlen_scalar_gpr_pair (mode,
+                                                gpr_base + info->gpr_offset);
+
+
       if (info->num_gprs || return_p)
        return gen_rtx_REG (mode, gpr_base + info->gpr_offset);
     }
@@ -7700,10 +7783,13 @@ riscv_get_arg_info (struct riscv_arg_info *info, const 
CUMULATIVE_ARGS *cum,
   return NULL_RTX;
 }
 
-/* Implement TARGET_FUNCTION_ARG.  */
+/* Common implementation of TARGET_FUNCTION_ARG and
+   TARGET_FUNCTION_INCOMING_ARG. INCOMING_P selects the virtual stack base
+   needed by the explicit stack piece of a split 2 * XLEN scalar.  */
 
 static rtx
-riscv_function_arg (cumulative_args_t cum_v, const function_arg_info &arg)
+riscv_function_arg_1 (cumulative_args_t cum_v, const function_arg_info &arg,
+                     bool incoming_p)
 {
   CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
   struct riscv_arg_info info;
@@ -7711,7 +7797,35 @@ riscv_function_arg (cumulative_args_t cum_v, const 
function_arg_info &arg)
   if (arg.end_marker_p ())
     return nullptr;
 
-  return riscv_get_arg_info (&info, cum, arg.mode, arg.type, arg.named, false);
+  rtx loc
+    = riscv_get_arg_info (&info, cum, arg.mode, arg.type, arg.named, false);
+
+  if (BYTES_BIG_ENDIAN
+      && arg.named
+      && info.stack_p
+      && info.num_gprs == 1
+      && riscv_2xlen_significance_scalar_p (arg.mode, arg.type))
+    return riscv_pass_2xlen_scalar_gpr_stack
+      (arg.mode, GP_ARG_FIRST + info.gpr_offset,
+       incoming_p ? virtual_incoming_args_rtx : virtual_outgoing_args_rtx);
+
+  return loc;
+}
+
+/* Implement TARGET_FUNCTION_ARG  */
+
+static rtx
+riscv_function_arg (cumulative_args_t cum_v, const function_arg_info &arg)
+{
+  return riscv_function_arg_1 (cum_v, arg, false);
+}
+
+/* Implement TARGET_FUNCTION_INCOMING_ARG  */
+
+static rtx
+riscv_function_incoming_arg (cumulative_args_t cum_v, const function_arg_info 
&arg)
+{
+  return riscv_function_arg_1 (cum_v, arg, true);
 }
 
 /* Implement TARGET_FUNCTION_ARG_ADVANCE.  */
@@ -17079,6 +17193,8 @@ riscv_memtag_tag_bitsize ()
 #define TARGET_ARG_PARTIAL_BYTES riscv_arg_partial_bytes
 #undef TARGET_FUNCTION_ARG
 #define TARGET_FUNCTION_ARG riscv_function_arg
+#undef TARGET_FUNCTION_INCOMING_ARG
+#define TARGET_FUNCTION_INCOMING_ARG riscv_function_incoming_arg
 #undef TARGET_FUNCTION_ARG_ADVANCE
 #define TARGET_FUNCTION_ARG_ADVANCE riscv_function_arg_advance
 #undef TARGET_FUNCTION_ARG_BOUNDARY
diff --git 
a/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-aggregate-return.c 
b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-aggregate-return.c
new file mode 100644
index 00000000000..fd7939d5b2d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-aggregate-return.c
@@ -0,0 +1,52 @@
+/* { dg-do compile} */
+/* { dg-options "-O2 -march=rv32gc -mabi=ilp32 -mbig-endian" } */
+/* { dg-skip-if "" { *-*-* } { "-flto" } } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+typedef unsigned int u32;
+
+struct pair
+{
+  u32 high;
+  u32 low;
+};
+
+/* Aggregate returns use memory-layout ordering. On RV32BE the first field is
+   returned in a0 and the second field in a1.  */
+
+/*
+** make_pair:
+**     ...
+**     li      a0,1
+**     li      a1,2
+**     ...
+**     jr      ra
+*/
+struct pair __attribute__ ((noipa))
+make_pair (void)
+{
+  struct pair x = { 1, 2 };
+  return x;
+}
+
+extern struct pair get_pair (void);
+volatile u32 pair_sink[2];
+
+/* The caller must interpret a0 as high and a1 as low.  */
+
+/*
+** consume_pair_return:
+**     ...
+**     call    get_pair(?:@plt)?
+**     ...
+**     sw      a0,0\([a-z][0-9]+\)
+**     sw      a1,4\([a-z][0-9]+\)
+**     ...
+*/
+void __attribute__ ((noipa))
+consume_pair_return (void)
+{
+  struct pair x = get_pair ();
+  pair_sink[0] = x.high;
+  pair_sink[1] = x.low;
+}
diff --git 
a/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-aggregate-split.c 
b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-aggregate-split.c
new file mode 100644
index 00000000000..900cfa65895
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-aggregate-split.c
@@ -0,0 +1,40 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -march=rv32gc -mabi=ilp32 -mbig-endian" } */
+/* { dg-skip-if "" { *-*-* } { "-flto" } } */
+
+typedef unsigned int u32;
+struct pair
+{
+  u32 high;
+  u32 low;
+};
+
+extern void named_aggregate_split (u32, u32, u32, u32, u32, u32, u32, struct 
pair);
+
+void call_named_aggregate_split (u32 high, u32 low)
+{
+  struct pair x = { high, low };
+  named_aggregate_split (0, 1, 2, 3, 4, 5, 6, x);
+}
+
+volatile u32 sink_aggregate_split[2];
+
+void
+consume_named_aggregate_split (u32 pad0, u32 pad1, u32 pad2, u32 pad3,
+                               u32 pad4, u32 pad5, u32 pad6, struct pair x)
+{
+  sink_aggregate_split[0] = x.high;
+  sink_aggregate_split[1] = x.low;
+}
+
+/* Aggregates retain memory-layout ordering. With only a7 free, the first
+   word in memory (high) goes in a7 and the second word (low) goes at 0(sp)  */
+
+/* { dg-final { scan-assembler {\mmv[ \t]+a7,a0} } } */
+/* { dg-final { scan-assembler {\msw[ \t]+a1,0\(sp\)} } } */
+/* { dg-final { scan-assembler-not {\mmv[ \t]+a7,a1} } } */
+/* { dg-final { scan-assembler-not {\msw[ \t]+a0,0\(sp\)} } } */
+
+/* The callee must likewise interpret a7 as the first aggregate word.  */
+/* { dg-final { scan-assembler {\msw[ \t]+a7,(sink_aggregate_split,|0\()} } } 
*/
+/* { dg-final { scan-assembler-not {\msw[ 
\t]+a7,(sink_aggregate_split\+4|4\()} } } */
diff --git a/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-aggregate.c 
b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-aggregate.c
new file mode 100644
index 00000000000..cc619459bb2
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-aggregate.c
@@ -0,0 +1,34 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -march=rv32gc -mabi=ilp32 -mbig-endian" } */
+/* { dg-skip-if "" { *-*-* } { "-flto" } } */
+
+typedef unsigned int u32;
+struct pair
+{
+  u32 high;
+  u32 low;
+};
+
+extern void consume_pair (struct pair);
+
+void
+call_pair (u32 high, u32 low)
+{
+  struct pair x = { high, low };
+  consume_pair (x);
+}
+
+volatile unsigned int sink_pair[2];
+
+void
+store_pair (struct pair x)
+{
+  sink_pair[0] = x.high;
+  sink_pair[1] = x.low;
+}
+
+/* Aggregates keep memory-layout ordering: the lower-numbered register holds
+   the lower-addressed XLEN chunk.  */
+/* { dg-final { scan-assembler-not {\mmv[ \t]+a0,a1} } } */
+/* { dg-final { scan-assembler {\msw[ \t]+a0,(sink_pair,|0\()} } } */
+/* { dg-final { scan-assembler {\msw[ \t]+a1,(sink_pair\+4|4\()} } } */
diff --git 
a/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-float-incoming.c 
b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-float-incoming.c
new file mode 100644
index 00000000000..606e401262e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-float-incoming.c
@@ -0,0 +1,70 @@
+/* { dg-do compile} */
+/* { dg-options "-O2 -march=rv32gc -mabi=ilp32 -mbig-endian" } */
+/* { dg-skip-if "" { *-*-* } { "-O0" "-O1" "-O3" "-Og" "-Os" "-Oz" "-flto" } } 
*/
+/* { dg-final { check-function-bodies "**" "" } } */
+
+typedef unsigned int u32;
+typedef unsigned long long u64;
+
+union double_bits
+{
+  double value;
+  u64 bits;
+};
+
+/* Under the integer calling convention, floating-point reals are passed in
+   the same way as aggregates of the same size.  On RV32BE, the
+   lower addressed high word is therefore in the lower-numbered GPR.  */
+
+/*
+** double_pair_low:
+**     mv      a0,a1
+**     ret
+*/
+u32 __attribute__ ((noipa))
+double_pair_low (double x)
+{
+  union double_bits b = { .value = x };
+  return (u32) b.bits;
+}
+
+/*
+** double_pair_high:
+**     ret
+*/
+u32 __attribute__ ((noipa))
+double_pair_high (double x)
+{
+  union double_bits b = { .value = x };
+  return (u32) (b.bits >> 32);
+}
+
+/*
+** double_split_low:
+**     ...
+**     lw      a0,[0-9]+\(sp\)
+**     ...
+**     jr      ra
+*/
+u32 __attribute__ ((noipa))
+double_split_low (u32 pad0, u32 pad1, u32 pad2, u32 pad3,
+                  u32 pad4, u32 pad5, u32 pad6, double x)
+{
+  union double_bits b = { .value = x };
+  return (u32) (b.bits);
+}
+
+/*
+** double_split_high:
+**     ...
+**     mv      a0,a7
+**     ...
+**     jr      ra
+*/
+u32 __attribute__ ((noipa))
+double_split_high (u32 pad0, u32 pad1, u32 pad2, u32 pad3,
+                   u32 pad4, u32 pad5, u32 pad6, double x)
+{
+  union double_bits b = { .value = x };
+  return (u32) (b.bits >> 32);
+}
diff --git 
a/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-ilp32e-scalar.c 
b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-ilp32e-scalar.c
new file mode 100644
index 00000000000..cf6a54bfb53
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-ilp32e-scalar.c
@@ -0,0 +1,66 @@
+/* { dg-do compile} */
+/* { dg-options "-O2 -march=rv32e -mabi=ilp32e -mbig-endian" } */
+/* { dg-skip-if "" { *-*-* } { "-flto" } } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+typedef unsigned int u32;
+typedef unsigned long long u64;
+
+/* ILP32E has six argument GPRs. With five words already in a0-a4, a named
+   2 * XLEN scalar is split between a5 and the stack. Significance ordering
+   puts the low word in a5 and the high word at 0(sp)  */
+
+/*
+** ilp32e_split_low:
+**     ...
+**     mv      a0,a5
+**     ...
+**     jr      ra
+*/
+u32 __attribute__ ((noipa))
+ilp32e_split_low (u32 pad0, u32 pad1, u32 pad2, u32 pad3, u32 pad4, u64 x)
+{
+  return (u32) x;
+}
+
+/*
+** ilp32e_split_high:
+**     ...
+**     lw      a0,0\(sp\)
+**     ...
+**     jr      ra
+*/
+u32 __attribute__ ((noipa))
+ilp32e_split_high (u32 pad0, u32 pad1, u32 pad2, u32 pad3, u32 pad4, u64 x)
+{
+  return (u32) (x >> 32);
+}
+
+/* Once a0-a5 are occupied, the scalar is entirely on the stack in normal
+   big-endian memory order.  */
+
+/*
+** ilp32e_stack_low:
+**     ...
+**     lw      a0,4\(sp\)
+**     ret
+*/
+u32 __attribute__ ((noipa))
+ilp32e_stack_low (u32 pad0, u32 pad1, u32 pad2,
+                 u32 pad3, u32 pad4, u32 pad5, u64 x)
+{
+  return (u32) x;
+}
+
+
+/*
+** ilp32e_stack_high:
+**     lw      a0,0\(sp\)
+**     ret
+*/
+u32 __attribute__ ((noipa))
+ilp32e_stack_high (u32 pad0, u32 pad1, u32 pad2,
+                  u32 pad3, u32 pad4, u32 pad5, u64 x)
+{
+  return (u32) (x >> 32);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-arg.c 
b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-arg.c
new file mode 100644
index 00000000000..a47fab33636
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-arg.c
@@ -0,0 +1,54 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -march=rv32gc -mabi=ilp32 -mbig-endian" } */
+/* { dg-skip-if "" { *-*-* } { "-flto" } } */
+
+typedef unsigned int u32;
+typedef unsigned long long u64;
+
+extern void named_scalar (u64);
+extern void named_even_pair (u32, u32, u64);
+
+void
+call_named (u32 hi, u32 lo)
+{
+  u64 x = ((u64) hi << 32) | lo;
+  named_scalar (x);
+}
+
+void
+call_named_even_pair (u32 hi, u32 lo)
+{
+  u64 x = ((u64) hi << 32) | lo;
+  named_even_pair (0, 1, x);
+}
+
+volatile u32 sink_words[2];
+volatile u32 sink_even_pair[2];
+
+void
+consume_named (u64 x)
+{
+  sink_words[0] = (u32) x;
+  sink_words[1] = (u32) (x >> 32);
+}
+
+void
+consume_named_even_pair (u32 pad0, u32 pad1,
+                        u64 x)
+{
+  sink_even_pair[0] = (u32) x;
+  sink_even_pair[1] = (u32) (x >> 32);
+}
+
+/* Named 2 * XLEN scalars use significance ordering: a0 is the low word,
+   a1 is the high word, even for big-endian.  */
+/* { dg-final { scan-assembler {\mmv[ \t]+a0,a1} } } */
+/* { dg-final { scan-assembler {\msw[ \t]+a0,(sink_words,|0\()} } } */
+/* { dg-final { scan-assembler {\msw[ \t]+a1,(sink_words\+4|4\()} } } */
+/* { dg-final { scan-assembler-not {\msw[ \t]+a1,(sink_words,|0\()} } } */
+
+/* The same rule applies directly to even/odd register pairs used by P/Zilsd:
+   a2 is the low word and a3 is the high word.  */
+/* { dg-final { scan-assembler {\msw[ \t]+a2,(sink_even_pair,|0\()} } } */
+/* { dg-final { scan-assembler {\msw[ \t]+a3,(sink_even_pair\+4|4\()} } } */
+/* { dg-final { scan-assembler-not {\msw[ \t]+a3,(sink_even_pair,|0\()} } } */
diff --git 
a/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-incoming.c 
b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-incoming.c
new file mode 100644
index 00000000000..7c9bb8cabea
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-incoming.c
@@ -0,0 +1,67 @@
+/* { dg-do compile} */
+/* { dg-options "-O2 -march=rv32gc -mabi=ilp32 -mbig-endian" } */
+/* { dg-skip-if "" { *-*-* } { "-flto" } } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+typedef unsigned int u32;
+typedef unsigned long long u64;
+
+/* A named 2*XLEN scalar split between a7 and the stack uses significanse
+   ordering. The low word is in a7 and the high word is at 0(sp)  */
+
+/*
+** split_low:
+**     ...
+**     mv      a0,a7
+**     ...
+**     jr      ra
+*/
+u32 __attribute__ ((noipa))
+split_low (u32 pad0, u32 pad1, u32 pad2, u32 pad3,
+           u32 pad4, u32 pad5, u32 pad6, u64 x)
+{
+  return (u32) x;
+}
+
+/*
+** split_high:
+**     ...
+**     lw      a0,[0-9]+\(sp\)
+**     ...
+**     jr      ra
+*/
+u32 __attribute__ ((noipa))
+split_high (u32 pad0, u32 pad1, u32 pad2, u32 pad3,
+            u32 pad4, u32 pad5, u32 pad6, u64 x)
+{
+  return (u32) (x >> 32);
+}
+
+/* If no argument GPR is available, the scalar is passed entirely on the
+   stack in big-endian memory order: high at lower (sp) and low at high (sp)  
*/
+
+/*
+** stack_low:
+**     ...
+**     lw      a0,4\(sp\)
+**     ret
+*/
+u32 __attribute__ ((noipa))
+stack_low (u32 pad0, u32 pad1, u32 pad2, u32 pad3,
+           u32 pad4, u32 pad5, u32 pad6, u32 pad7, u64 x)
+{
+  return (u32) x;
+}
+
+/*
+** stack_high:
+**     ...
+**     lw      a0,0\(sp\)
+**     ret
+*/
+u32 __attribute__ ((noipa))
+stack_high (u32 pad0, u32 pad1, u32 pad2, u32 pad3,
+            u32 pad4, u32 pad5, u32 pad6, u32 pad7, u64 x)
+{
+  return (u32) (x >> 32);
+}
diff --git 
a/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-return.c 
b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-return.c
new file mode 100644
index 00000000000..b1853c50caa
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-return.c
@@ -0,0 +1,24 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -march=rv32gc -mabi=ilp32 -mbig-endian" } */
+/* { dg-skip-if "" { *-*-* } { "-flto" } } */
+
+unsigned long long
+ret_one (void)
+{
+  return 1;
+}
+
+extern unsigned long long ret_scalar (void);
+
+unsigned int
+use_ret (void)
+{
+  return (unsigned int) ret_scalar ();
+}
+
+/* Return values use significance ordering: a0 is the low word and a1 is the
+   high word.  The caller therefore needs no move from a1 to use the low word. 
 */
+/* { dg-final { scan-assembler {\mli[ \t]+a0,1} } } */
+/* { dg-final { scan-assembler {\mli[ \t]+a1,0} } } */
+/* { dg-final { scan-assembler-not {\mli[ \t]+a1,1} } } */
+/* { dg-final { scan-assembler-not {\mmv[ \t]+a0,a1} } } */
diff --git 
a/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-sibcall.c 
b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-sibcall.c
new file mode 100644
index 00000000000..390c7cfb30e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-sibcall.c
@@ -0,0 +1,25 @@
+/* { dg-do compile} */
+/* { dg-options "-O2 -march=rv32gc -mabi=ilp32 -mbig-endian" } */
+/* { dg-skip-if "" { *-*-* } { "-flto" } } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+typedef unsigned int u32;
+typedef unsigned long long u64;
+
+extern int tail_target (u32, u32, u32, u32, u32, u32, u32, u64);
+
+/* The split stack piece is already et the incoming argument base. Verify
+   that forwarding the unchanged argument can remain a sibling call.  */
+
+/*
+** tail_forward:
+**     ...
+**     tail    tail_target(?:@plt)?
+**     ...
+*/
+int
+tail_forward (u32 pad0, u32 pad1, u32 pad2, u32 pad3,
+              u32 pad4, u32 pad5, u32 pad6, u64 x)
+{
+  return tail_target (pad0, pad1, pad2, pad3, pad4, pad5, pad6, x);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-split.c 
b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-split.c
new file mode 100644
index 00000000000..5a58b274db6
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/abi/rv32be-abi-2xlen-scalar-split.c
@@ -0,0 +1,41 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -march=rv32gc -mabi=ilp32 -mbig-endian" } */
+/* { dg-skip-if "" { *-*-* } { "-flto" } } */
+
+typedef unsigned int u32;
+typedef unsigned long long u64;
+
+extern void named_odd_pair (u32, u64);
+extern void named_split (u32, u32, u32, u32, u32, u32, u32, u64);
+extern void named_stack (u32, u32, u32, u32, u32, u32, u32, u32, u64);
+
+
+void
+call_named_odd_pair (u32 high, u32 low)
+{
+  named_odd_pair (0, ((u64) high << 32) | low);
+}
+
+void
+call_named_split (u32 high, u32 low)
+{
+  named_split (0, 1, 2, 3, 4, 5, 6, ((u64) high << 32) | low);
+}
+
+void
+call_named_stack (u32 high, u32 low)
+{
+  named_stack (0, 1, 2, 3, 4, 5, 6, 7, ((u64) high << 32) | low);
+}
+
+/* With only a7 free, the low word goes in a7 and the high word goes at
+   0(sp)  */
+/* { dg-final { scan-assembler {\mmv[ \t]+a2,a0} } } */
+/* { dg-final { scan-assembler-not {\mmv[ \t]+a2,a1} } } */
+
+/* { dg-final { scan-assembler {\mmv[ \t]+a7,a1} } } */
+/* { dg-final { scan-assembler-not {\mmv[ \t]+a7,a0} } } */
+
+/* { dg-final { scan-assembler {\msw[ \t]+a1,4\(sp\)} } } */
+/* { dg-final { scan-assembler {\msw[ \t]+a0,0\(sp\)} } } */
+/* { dg-final { scan-assembler-not {\msw[ \t]+a1,0\(sp\)} } } */
diff --git 
a/gcc/testsuite/gcc.target/riscv/abi/rv64be-abi-2xlen-scalar-incoming.c 
b/gcc/testsuite/gcc.target/riscv/abi/rv64be-abi-2xlen-scalar-incoming.c
new file mode 100644
index 00000000000..6e6e219e2d0
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/abi/rv64be-abi-2xlen-scalar-incoming.c
@@ -0,0 +1,92 @@
+/* { dg-do compile} */
+/* { dg-options "-O2 -march=rv64gc -mabi=lp64 -mbig-endian" } */
+/* { dg-skip-if "" { *-*-* } { "-flto" } } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+typedef unsigned long u64;
+typedef unsigned __int128 u128;
+
+/* Verify that the 2*XLEN rules are XLEN-generic rather than specific to
+   RV32. A named unsigned __int128 uses significance ordering in GPRs.  */
+
+/*
+** pair_low:
+**     ret
+*/
+u64 __attribute__ ((noipa))
+pair_low (u128 x)
+{
+  return (u64) x;
+}
+
+/*
+** pair_high:
+**     mv      a0,a1
+**     ret
+*/
+u64 __attribute__ ((noipa))
+pair_high (u128 x)
+{
+  return (u64) (x >> 64);
+}
+
+/* With only a7 available, the low 64 bits are in a7 and the high 64 bits
+   are at 0(sp)  */
+
+/*
+** split_low:
+**     ...
+**     mv      a0,a7
+**     ...
+**     jr      ra
+*/
+u64 __attribute__ ((noipa))
+split_low (u64 pad0, u64 pad1, u64 pad2, u64 pad3,
+           u64 pad4, u64 pad5, u64 pad6, u128 x)
+{
+  return (u64) x;
+}
+
+/*
+** split_high:
+**     ...
+**     ld      a0,[0-9]+\(sp\)
+**     ...
+**     jr      ra
+*/
+u64 __attribute__ ((noipa))
+split_high (u64 pad0, u64 pad1, u64 pad2, u64 pad3,
+            u64 pad4, u64 pad5, u64 pad6, u128 x)
+{
+  return (u64) (x >> 64);
+}
+
+/* With no argument GPR available, normal big-endian stack layout applies.  */
+
+/*
+** stack_low:
+**     ...
+**     ld      a0,8\(sp\)
+**     ...
+**     ret
+*/
+u64 __attribute__ ((noipa))
+stack_low (u64 pad0, u64 pad1, u64 pad2, u64 pad3,
+           u64 pad4, u64 pad5, u64 pad6, u64 pad7, u128 x)
+{
+  return (u64) x;
+}
+
+/*
+** stack_high:
+**     ...
+**     ld      a0,0\(sp\)
+**     ...
+**     ret
+*/
+u64 __attribute__ ((noipa))
+stack_high (u64 pad0, u64 pad1, u64 pad2, u64 pad3,
+           u64 pad4, u64 pad5, u64 pad6, u64 pad7, u128 x)
+{
+  return (u64) (x >> 64);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/abi/rv64be-abi-2xlen-vararg.c 
b/gcc/testsuite/gcc.target/riscv/abi/rv64be-abi-2xlen-vararg.c
new file mode 100644
index 00000000000..2e0ccc78481
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/abi/rv64be-abi-2xlen-vararg.c
@@ -0,0 +1,76 @@
+/* { dg-do compile} */
+/* { dg-options "-O2 -march=rv64gc -mabi=lp64 -mbig-endian" } */
+/* { dg-skip-if "" { *-*-* } { "-flto" } } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+typedef unsigned long u64;
+typedef unsigned __int128 u128;
+typedef __builtin_va_list va_list;
+
+extern void take_var_u128 (u64, ...);
+
+/* A variadic u128 requires an aligned register pair. With a0 occupied by
+   the marker, a1 is skipped. Memory-layout oredering on RV64BE puts the high
+   64 bits in a2 and the low 64 bits in a3.  */
+
+/*
+** pass_var_u128:
+**     ...
+**     mv      a2,a0
+**     mv      a3,a1
+**     li      a0,0
+**     tail    take_var_u128(?:@plt)?
+*/
+void __attribute__ ((noipa))
+pass_var_u128 (u64 high, u64 low)
+{
+  u128 x = ((u128) high << 64) | low;
+  take_var_u128 (0, x);
+}
+
+/* va_arg observes the same memory ordering.  */
+
+/*
+** va_arg_u128_high:
+**     ...
+**     ld      a0,0\([a-z][0-9]+\)
+**     ret
+*/
+u64 __attribute__ ((noipa))
+va_arg_u128_high (va_list ap)
+{
+  u128 x = __builtin_va_arg (ap, u128);
+  return (u64) (x >> 64);
+}
+
+/*
+** va_arg_u128_low:
+**     ...
+**     ld      a0,8\([a-z][0-9]+\)
+**     ret
+*/
+u64 __attribute__ ((noipa))
+va_arg_u128_low (va_list ap)
+{
+  u128 x = __builtin_va_arg (ap, u128);
+  return (u64) x;
+}
+
+/* Exercise va_start and the conversion from memory-layout ordering in the
+   register-save area to significance ordering for a named u128 return.  */
+
+u128 __attribute__ ((noipa))
+va_u128_from_registers (u64 marker, ...)
+{
+  va_list ap;
+  u128 x;
+
+  __builtin_va_start (ap, marker);
+  x = __builtin_va_arg (ap, u128);
+  __builtin_va_end (ap);
+  return x;
+}
+
+/* a1 is skipped for alignment; the value itself arrives in a2/a3.  */
+/* { dg-final { scan-assembler {\msd[ \t]+a2,[0-9]+\(sp\)} } } */
+/* { dg-final { scan-assembler {\msd[ \t]+a3,[0-9]+\(sp\)} } } */
-- 
2.43.0

Reply via email to