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
