On RV32 a 2*XLEN argument passed when only one register remains is split: the low word goes in the odd argument register (a7) and the high word on the incoming stack. When the middle-end later reconstructs the value as a single DImode, riscv_zilsd_valid_mem_p relies only on MEM_ALIGN, which is mis-marked A64 for the spilled slot, so a single misaligned zilsd "ld"/"sd" is emitted (e.g. ld s0,76(sp)) and faults under -mzilsd-strict-align.
Double-check the address when it is a constant offset from a frame-related pointer (sp/hfp/ap/fp, all 16-byte aligned): reject the zilsd access unless the offset is a multiple of the required alignment, forcing riscv_split_64bit_move_p to use two 32-bit moves. Signed-off-by: Ciyan Pan <[email protected]> gcc/ChangeLog: * config/riscv/riscv.cc (riscv_zilsd_valid_mem_p): Double-check address. gcc/testsuite/ChangeLog: * gcc.target/riscv/zilsd-align-split-arg-1.c: New test. * gcc.target/riscv/zilsd-align-split-arg-2.c: New test. --- gcc/config/riscv/riscv.cc | 29 +++++++++++++-- .../riscv/zilsd-align-split-arg-1.c | 37 +++++++++++++++++++ .../riscv/zilsd-align-split-arg-2.c | 22 +++++++++++ 3 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 gcc/testsuite/gcc.target/riscv/zilsd-align-split-arg-1.c create mode 100644 gcc/testsuite/gcc.target/riscv/zilsd-align-split-arg-2.c diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc index f8b54fc4ae68..9fa420a39b41 100644 --- a/gcc/config/riscv/riscv.cc +++ b/gcc/config/riscv/riscv.cc @@ -1270,10 +1270,31 @@ riscv_zilsd_required_align (void) bool riscv_zilsd_valid_mem_p (rtx mem, machine_mode mode) { - return (TARGET_ZILSD - && riscv_2x_xlen_mode_p (mode) - && MEM_P (mem) - && MEM_ALIGN (mem) >= riscv_zilsd_required_align () * BITS_PER_UNIT); + if (!TARGET_ZILSD || !riscv_2x_xlen_mode_p (mode) || !MEM_P (mem)) + return false; + + if (MEM_ALIGN (mem) < riscv_zilsd_required_align () * BITS_PER_UNIT) + return false; + + /* MEM_ALIGN can be inaccurate for reconstructed split arguments where the + low word is held in an odd register and the high word lives on the + incoming stack. Such accesses materialize at a 4-byte-aligned stack + offset, so double-check the address when it is a constant offset from a + frame-related pointer (all 16-byte aligned on RISC-V). */ + rtx addr = XEXP (mem, 0); + if (GET_CODE (addr) == PLUS + && REG_P (XEXP (addr, 0)) + && CONST_INT_P (XEXP (addr, 1))) + { + unsigned base = REGNO (XEXP (addr, 0)); + if (base == STACK_POINTER_REGNUM + || base == HARD_FRAME_POINTER_REGNUM + || base == ARG_POINTER_REGNUM + || base == FRAME_POINTER_REGNUM) + return (INTVAL (XEXP (addr, 1)) % riscv_zilsd_required_align ()) == 0; + } + + return true; } /* Return the effective Zilsd memory access alignment policy. */ diff --git a/gcc/testsuite/gcc.target/riscv/zilsd-align-split-arg-1.c b/gcc/testsuite/gcc.target/riscv/zilsd-align-split-arg-1.c new file mode 100644 index 000000000000..b3a5785862b3 --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/zilsd-align-split-arg-1.c @@ -0,0 +1,37 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -march=rv32gcv_zilsd -mabi=ilp32d -mrvv-vector-bits=scalable -fno-vect-cost-model" } */ +/* { dg-additional-options "-mzilsd-strict-align" } */ + +#include <stdint-gcc.h> + +/* On RV32 an int64_t argument passed after seven pointer arguments is + split: the low word lands in the last available argument register (a7) + and the high word on the incoming stack. When the vectoriser later + materialises n as a single DImode value, the low word is spilled to a + 4-byte-aligned stack slot. With -mzilsd-strict-align the backend must + not reload that value with a single misaligned zilsd "ld"; it must split + the reload into two 32-bit loads instead, otherwise the access faults on + targets that trap on misaligned 64-bit loads. */ + +void __attribute__ ((noinline, noclone)) +f6 (uint64_t *__restrict a, uint64_t *__restrict b, uint64_t *__restrict c, + uint64_t *__restrict d, uint64_t *__restrict e, uint64_t *__restrict f, + uint64_t *__restrict g, int64_t n) +{ + for (int64_t i = 0; i < n; ++i) + { + a[i] = g[i * 6]; + b[i] = g[i * 6 + 1]; + c[i] = g[i * 6 + 2]; + d[i] = g[i * 6 + 3]; + e[i] = g[i * 6 + 4]; + f[i] = g[i * 6 + 5]; + } +} + +/* The spilled low word of n sits at a 4-byte-aligned stack offset (76); + it must be reloaded with "lw", never with a single misaligned zilsd + "ld". The vector loop counter, in contrast, lives at an 8-byte-aligned + offset (8) and may legitimately use ld/sd. */ +/* { dg-final { scan-assembler-not {ld\t[a-z0-9]+,76\(sp\)} } } */ +/* { dg-final { scan-assembler-not {sd\t[a-z0-9]+,76\(sp\)} } } */ diff --git a/gcc/testsuite/gcc.target/riscv/zilsd-align-split-arg-2.c b/gcc/testsuite/gcc.target/riscv/zilsd-align-split-arg-2.c new file mode 100644 index 000000000000..248b16b0686e --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/zilsd-align-split-arg-2.c @@ -0,0 +1,22 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -march=rv32i_zilsd -mabi=ilp32" } */ +/* { dg-additional-options "-mzilsd-strict-align" } */ + +/* On RV32 an int64_t argument passed after seven pointer arguments is + split between registers and the stack: the low word lands in the last + available argument register (a7) and the high word on the incoming + stack. Reloading n as a single 64-bit value therefore materialises a + zilsd "ld" from a 4-byte-aligned stack slot (offset 12). Under + -mzilsd-strict-align the backend must not emit such a misaligned + doubleword load; it must split the reload into two 32-bit loads. */ + +long long +f7 (long long *a, long long *b, long long *c, long long *d, + long long *e, long long *f, long long *g, long long n) +{ + return n; +} + +/* The misaligned reload must use two 32-bit "lw", never a single zilsd + "ld" from the 4-byte-aligned stack offset 12. */ +/* { dg-final { scan-assembler-not {ld\t[a-z0-9]+,12\(sp\)} } } */ -- 2.43.0
