https://gcc.gnu.org/g:d3779873a231a723aee979f9a5f24edcd6911091

commit r17-4466-gd3779873a231a723aee979f9a5f24edcd6911091
Author: panciyan <[email protected]>
Date:   Sat Sep 19 11:02:36 2026 -0600

    [PATCH] Fix alignment of partially-passed stack arguments
    
    For a partially-passed argument (part in registers, part on the
    stack), assign_parm_find_stack_rtl reconstructs the full value by
    spilling the register part just below the aligned stack slot.  The
    resulting address is therefore only aligned to the largest power of
    two dividing the size of the register part, not to the full argument
    boundary.  The function currently sets align to data->locate.boundary
    whenever data->entry_parm is non-null, which includes the partial-arg
    case, so the reconstructed MEM is wrongly marked with the full
    boundary alignment (e.g. A64 on RV32 for a DImode argument split
    between a register and the stack).  Clamp the alignment to the partial
    size.
    
    This is the root cause of a RISC-V zilsd issue where a single
    misaligned zilsd "ld"/"sd" was emitted for a split stack argument
    because riscv_zilsd_valid_mem_p trusted the over-stated MEM_ALIGN.
    With the slot now marked with the correct alignment the access is
    split into two 32-bit loads.
    
    Signed-off-by: Ciyan Pan <[email protected]>
    gcc/ChangeLog:
    
            * function.cc (assign_parm_find_stack_rtl): When data->partial is
            nonzero, limit the stack slot alignment to the largest power of
            two dividing the partial size.
    
    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.target/riscv/zilsd-align-split-arg-3.c: New test.
            * gcc.target/riscv/zilsd-align-split-arg-4.c: New test.

Diff:
---
 gcc/function.cc                                    | 10 ++++++
 .../gcc.target/riscv/zilsd-align-split-arg-1.c     | 37 ++++++++++++++++++++++
 .../gcc.target/riscv/zilsd-align-split-arg-2.c     | 22 +++++++++++++
 .../gcc.target/riscv/zilsd-align-split-arg-3.c     | 25 +++++++++++++++
 .../gcc.target/riscv/zilsd-align-split-arg-4.c     | 25 +++++++++++++++
 5 files changed, 119 insertions(+)

diff --git a/gcc/function.cc b/gcc/function.cc
index fd9da3003cdb..a563bccaff36 100644
--- a/gcc/function.cc
+++ b/gcc/function.cc
@@ -2728,6 +2728,16 @@ assign_parm_find_stack_rtl (tree parm, struct 
assign_parm_data_one *data)
       if (offset_align != 0)
        align = MIN (align, offset_align);
     }
+
+  /* If part of the argument is passed in registers and the rest on the
+     stack, the value is reconstructed by spilling the register part just
+     below the aligned stack slot.  The full value therefore starts at an
+     address that is only aligned to the largest power of two dividing the
+     size of the register part, not to FUNCTION_ARG_BOUNDARY.  */
+  if (data->partial)
+    align = MIN (align,
+                least_bit_hwi (data->partial) * BITS_PER_UNIT);
+
   set_mem_align (stack_parm, align);
 
   if (data->entry_parm)
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..78331658a086
--- /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
+   spills the low word of n to a 4-byte-aligned stack slot, the backend
+   must not reload that value with a single misaligned zilsd "ld" under
+   -mzilsd-strict-align; 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..ba04a90de209
--- /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.  The middle-end reconstructs the value starting at a
+   4-byte-aligned stack slot (offset 12), so under -mzilsd-strict-align
+   the backend must not access it with a single misaligned zilsd "ld"; it
+   must split the access into two 32-bit loads instead.  */
+
+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 access 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\)} } } */
diff --git a/gcc/testsuite/gcc.target/riscv/zilsd-align-split-arg-3.c 
b/gcc/testsuite/gcc.target/riscv/zilsd-align-split-arg-3.c
new file mode 100644
index 000000000000..6852643e9630
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/zilsd-align-split-arg-3.c
@@ -0,0 +1,25 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -march=rv32i_zilsd -mabi=ilp32" } */
+/* { dg-additional-options "-mzilsd-strict-align" } */
+
+/* Same split-argument reconstruction as zilsd-align-split-arg-2.c, but with
+   a large stack frame so the stack offset of the spilled high word of n no
+   longer fits in a single I-type immediate and address legitimization
+   materializes it into a register before the access.  The middle-end still
+   marks the reconstructed slot as only 4-byte aligned, so under
+   -mzilsd-strict-align the backend must split the access 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)
+{
+  volatile char buf[4096];
+  buf[0] = 1;
+  return n;
+}
+
+/* The only doubleword access in this function is the misaligned access of
+   n, so no zilsd ld/sd may appear at all; the access must use two lw.  */
+/* { dg-final { scan-assembler-not {ld\t} } } */
+/* { dg-final { scan-assembler-not {sd\t} } } */
diff --git a/gcc/testsuite/gcc.target/riscv/zilsd-align-split-arg-4.c 
b/gcc/testsuite/gcc.target/riscv/zilsd-align-split-arg-4.c
new file mode 100644
index 000000000000..5061bb9efedf
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/zilsd-align-split-arg-4.c
@@ -0,0 +1,25 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -march=rv32i_zilsd -mabi=ilp32" } */
+/* { dg-additional-options "-mzilsd-strict-align" } */
+
+/* Same split-argument reconstruction as zilsd-align-split-arg-2.c, but with
+   a frame whose stack offset of the spilled high word of n no longer fits in
+   a single I-type immediate, yet is still small enough to be formed from two
+   12-bit adds (unlike zilsd-align-split-arg-3.c, which needs a LUI high
+   part).  The middle-end still marks the reconstructed slot as only
+   4-byte aligned, so under -mzilsd-strict-align the backend must split the
+   access 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)
+{
+  volatile char buf[2040];
+  buf[0] = 1;
+  return n;
+}
+
+/* The only doubleword access in this function is the misaligned access of
+   n, so no zilsd ld/sd may appear at all; the access must use two lw.  */
+/* { dg-final { scan-assembler-not {ld\t} } } */
+/* { dg-final { scan-assembler-not {sd\t} } } */

Reply via email to