Cover the places where an object needs more alignment than the frame can
give it: the home slot of a by-value parameter, the hidden copy the caller
makes when the MS ABI passes an argument by reference, and over-aligned
locals.

The x86 tests scan for the move GCC selects instead of running.  A slot that
is short of the alignment the object needs still lands on a 32-byte boundary
about half the time, depending on the frame size of the function under test,
so a crash-based test would start passing or failing with unrelated codegen
changes.  x86 chooses between the aligned and the unaligned move from the
alignment GCC recorded for the slot rather than from the address it really
gets, so the selected move says directly whether the slot got the alignment
the object needed.

The two dg-do run C++ tests cover an over-aligned temporary reused by both
arms of a branch.

gcc/testsuite/ChangeLog:

        PR target/54412
        * g++.target/i386/pr54412-highpart-extract-rt.C: New test.
        * g++.target/i386/pr54412-prvalue-avx-get-low-rt.C: New test.
        * gcc.target/i386/pr54412-aligned-struct-byval-temp.c: New test.
        * gcc.target/i386/pr54412-avx512-aligned64.c: New test.
        * gcc.target/i386/pr54412-blkmode-temp-biggest-align.c: New test.
        * gcc.target/i386/pr54412-by-value-cases.c: New test.
        * gcc.target/i386/pr54412-callee-byref-param.c: New test.
        * gcc.target/i386/pr54412-caller-byref-temp.c: New test.
        * gcc.target/i386/pr54412-m256-byval-param.c: New test.
        * gcc.target/i386/pr54412-v4d-o0-aligned-locals.c: New test.

Signed-off-by: Oleg Tolmatcev <[email protected]>
---
 .../i386/pr54412-highpart-extract-rt.C        | 112 +++++++++++++++
 .../i386/pr54412-prvalue-avx-get-low-rt.C     | 127 ++++++++++++++++++
 .../i386/pr54412-aligned-struct-byval-temp.c  |  25 ++++
 .../i386/pr54412-avx512-aligned64.c           |  19 +++
 .../i386/pr54412-blkmode-temp-biggest-align.c |  27 ++++
 .../gcc.target/i386/pr54412-by-value-cases.c  |  39 ++++++
 .../i386/pr54412-callee-byref-param.c         |  19 +++
 .../i386/pr54412-caller-byref-temp.c          |  29 ++++
 .../i386/pr54412-m256-byval-param.c           |  23 ++++
 .../i386/pr54412-v4d-o0-aligned-locals.c      |  21 +++
 10 files changed, 441 insertions(+)
 create mode 100644 gcc/testsuite/g++.target/i386/pr54412-highpart-extract-rt.C
 create mode 100644 
gcc/testsuite/g++.target/i386/pr54412-prvalue-avx-get-low-rt.C
 create mode 100644 
gcc/testsuite/gcc.target/i386/pr54412-aligned-struct-byval-temp.c
 create mode 100644 gcc/testsuite/gcc.target/i386/pr54412-avx512-aligned64.c
 create mode 100644 
gcc/testsuite/gcc.target/i386/pr54412-blkmode-temp-biggest-align.c
 create mode 100644 gcc/testsuite/gcc.target/i386/pr54412-by-value-cases.c
 create mode 100644 gcc/testsuite/gcc.target/i386/pr54412-callee-byref-param.c
 create mode 100644 gcc/testsuite/gcc.target/i386/pr54412-caller-byref-temp.c
 create mode 100644 gcc/testsuite/gcc.target/i386/pr54412-m256-byval-param.c
 create mode 100644 
gcc/testsuite/gcc.target/i386/pr54412-v4d-o0-aligned-locals.c

diff --git a/gcc/testsuite/g++.target/i386/pr54412-highpart-extract-rt.C 
b/gcc/testsuite/g++.target/i386/pr54412-highpart-extract-rt.C
new file mode 100644
index 0000000000..b61dde2dab
--- /dev/null
+++ b/gcc/testsuite/g++.target/i386/pr54412-highpart-extract-rt.C
@@ -0,0 +1,112 @@
+/* PR target/54412 */
+/* Reusing an over-aligned temporary for both halves of an emulated
+   512-bit extract must not leave one branch using an uninitialized
+   aligned-slot address register.
+
+   This is also the escaped-pointer case: the noinline member functions get
+   the over-aligned object through THIS and access it assuming the 32-byte
+   alignment its type declares, so the object has to really have it.  */
+/* { dg-do run { target { x86_64-*-mingw* && avx2 } } } */
+/* { dg-options "-O0 -mavx2 -std=gnu++17 -fno-omit-frame-pointer" } */
+/* { dg-require-effective-target avx2_runtime } */
+
+#include "avx2-check.h"
+
+typedef long long v4di __attribute__ ((vector_size (32), aligned (32)));
+
+static inline v4di
+make_v4di (long long a0, long long a1, long long a2, long long a3)
+{
+  return (v4di) { a0, a1, a2, a3 };
+}
+
+struct Vec256b
+{
+  v4di ymm;
+
+  Vec256b () = default;
+  explicit Vec256b (v4di x) : ymm (x) {}
+
+  __attribute__ ((noinline, noclone))
+  operator v4di () const
+  {
+    return ymm;
+  }
+};
+
+struct Vec4q : public Vec256b
+{
+  Vec4q () = default;
+
+  __attribute__ ((noinline, noclone))
+  explicit Vec4q (v4di x)
+  {
+    ymm = x;
+  }
+
+  __attribute__ ((noinline, noclone))
+  long long
+  extract (int index) const
+  {
+    long long elems[4] __attribute__ ((aligned (32)));
+    __builtin_memcpy (elems, &ymm, sizeof (elems));
+    return elems[index & 3];
+  }
+};
+
+struct Vec8q
+{
+  Vec256b z0;
+  Vec256b z1;
+
+  Vec8q () = default;
+  Vec8q (Vec256b a, Vec256b b) : z0 (a), z1 (b) {}
+
+  __attribute__ ((noinline, noclone))
+  long long
+  extract (int index) const
+  {
+    if ((unsigned) index < 4)
+      return Vec4q (z0).extract (index);
+
+    return Vec4q (z1).extract (index - 4);
+  }
+};
+
+__attribute__ ((noinline, noclone))
+static Vec8q
+passthru (Vec8q x)
+{
+  return x;
+}
+
+__attribute__ ((noinline, noclone))
+static long long
+call_extract (Vec8q x, int index)
+{
+  Vec8q y = passthru (x);
+
+  if ((unsigned) index >= 4)
+    {
+      register void *p __asm__ ("rbx") = (void *) 2;
+      asm volatile ("" : : "r" (p) : "memory");
+    }
+
+  return y.extract (index);
+}
+
+static void
+avx2_test ()
+{
+  Vec8q x (Vec256b (make_v4di (101, 102, 103, 104)),
+          Vec256b (make_v4di (201, 202, 203, 204)));
+
+  if (call_extract (x, 1) != 102)
+    __builtin_abort ();
+
+  if (call_extract (x, 5) != 202)
+    __builtin_abort ();
+
+  if (call_extract (x, 7) != 204)
+    __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.target/i386/pr54412-prvalue-avx-get-low-rt.C 
b/gcc/testsuite/g++.target/i386/pr54412-prvalue-avx-get-low-rt.C
new file mode 100644
index 0000000000..aa06965219
--- /dev/null
+++ b/gcc/testsuite/g++.target/i386/pr54412-prvalue-avx-get-low-rt.C
@@ -0,0 +1,127 @@
+/* PR target/54412 */
+/* An over-aligned prvalue passed down a chain of noinline calls.  This is the
+   escaped-pointer case: the object reaches VEC8IB's constructor as a
+   VEC256B const & and the callee accesses it assuming the 32-byte alignment
+   its type declares, so the object has to really have it.  */
+/* { dg-do run { target { x86_64-*-mingw* && avx } } } */
+/* { dg-options "-O0 -mavx -std=gnu++17 -fno-omit-frame-pointer" } */
+
+#include "avx-check.h"
+
+typedef float v8sf __attribute__ ((vector_size (32), aligned (32)));
+
+static inline v8sf
+make_v8sf (float a0, float a1, float a2, float a3,
+          float a4, float a5, float a6, float a7)
+{
+  return (v8sf) { a0, a1, a2, a3, a4, a5, a6, a7 };
+}
+
+struct Vec8f
+{
+  v8sf ymm;
+
+  Vec8f () = default;
+  explicit Vec8f (v8sf x) : ymm (x) {}
+
+  __attribute__ ((noinline, noclone))
+  float
+  extract (int index) const
+  {
+    float elems[8];
+    __builtin_memcpy (elems, &ymm, sizeof (elems));
+    return elems[index & 7];
+  }
+};
+
+struct Vec16f
+{
+  Vec8f z0;
+  Vec8f z1;
+
+  Vec16f () = default;
+  Vec16f (Vec8f a, Vec8f b) : z0 (a), z1 (b) {}
+
+  __attribute__ ((noinline, noclone)) Vec8f get_low () const { return z0; }
+  __attribute__ ((noinline, noclone)) Vec8f get_high () const { return z1; }
+};
+
+struct Vec256b
+{
+  int v[8];
+
+  Vec256b () = default;
+  explicit Vec256b (int x0, int x1, int x2, int x3,
+                   int x4, int x5, int x6, int x7)
+    : v { x0, x1, x2, x3, x4, x5, x6, x7 }
+  {}
+
+  __attribute__ ((noinline, noclone))
+  int
+  get_low () const
+  {
+    return v[0];
+  }
+};
+
+struct Vec8ib
+{
+  int first;
+
+  __attribute__ ((noinline, noclone))
+  explicit Vec8ib (Vec256b const &x) : first (x.get_low ()) {}
+};
+
+__attribute__ ((noinline, noclone))
+static Vec16f
+passthru (Vec16f x)
+{
+  return x;
+}
+
+__attribute__ ((noinline, noclone))
+static Vec8f
+low_after_passthru (Vec16f x)
+{
+  return passthru (x).get_low ();
+}
+
+__attribute__ ((noinline, noclone))
+static Vec16f
+split_rebuild_after_passthru (Vec16f x)
+{
+  Vec16f y = passthru (x);
+  return Vec16f (y.get_low (), y.get_high ());
+}
+
+__attribute__ ((noinline, noclone))
+static Vec8ib
+wrap_low_ref (Vec256b x)
+{
+  return Vec8ib (x);
+}
+
+static void
+avx_test ()
+{
+  Vec16f x (Vec8f (make_v8sf (1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f)),
+          Vec8f (make_v8sf (9.f, 10.f, 11.f, 12.f, 13.f, 14.f, 15.f, 16.f)));
+  Vec8f y = low_after_passthru (x);
+
+  if (y.extract (0) != 1.f || y.extract (7) != 8.f)
+    __builtin_abort ();
+
+  Vec16f z = split_rebuild_after_passthru (x);
+  Vec8f zl = z.get_low ();
+  Vec8f zh = z.get_high ();
+
+  if (zl.extract (0) != 1.f || zl.extract (7) != 8.f)
+    __builtin_abort ();
+
+  if (zh.extract (0) != 9.f || zh.extract (7) != 16.f)
+    __builtin_abort ();
+
+  Vec8ib w = wrap_low_ref (Vec256b (11, 12, 13, 14, 15, 16, 17, 18));
+  if (w.first != 11)
+    __builtin_abort ();
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr54412-aligned-struct-byval-temp.c 
b/gcc/testsuite/gcc.target/i386/pr54412-aligned-struct-byval-temp.c
new file mode 100644
index 0000000000..ce64fdd213
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr54412-aligned-struct-byval-temp.c
@@ -0,0 +1,25 @@
+/* PR target/54412 */
+/* On Win64 a struct that is not 1, 2, 4 or 8 bytes is passed by reference,
+   so CALLER makes a hidden copy of *P and passes its address.  That copy
+   needs 32-byte alignment, but the frame only realigns to 16, so its slot
+   must be over-allocated and its address rounded up, which is the "and $-32"
+   below.  Before the fix the compiler took the copy's alignment from a value
+   that was first capped at 16, the most any type needs naturally without
+   AVX, so it never saw the 32 and left the copy on a plain 16-byte slot.
+
+   Only alignment that comes from the attribute is affected: a 32-byte vector
+   needs AVX, which raises that cap to 32 anyway.  The copy is the only
+   over-aligned object in CALLER, so the masking can only come from it.  */
+/* { dg-do compile { target x86_64-*-mingw* } } */
+/* { dg-options "-O2" } */
+/* { dg-final { scan-assembler {and[lq]?\t\$-32,} } } */
+
+struct S { char c[32]; } __attribute__((aligned (32)));
+
+void sink (struct S);
+
+void
+caller (struct S *p)
+{
+  sink (*p);
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr54412-avx512-aligned64.c 
b/gcc/testsuite/gcc.target/i386/pr54412-avx512-aligned64.c
new file mode 100644
index 0000000000..f861cec464
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr54412-avx512-aligned64.c
@@ -0,0 +1,19 @@
+/* PR target/54412 */
+/* Same root cause as pr54412-m256-byval-param.c, for a 64-byte aligned
+   AVX-512 by-value parameter.  A crash-based (dg-do run) test is not
+   reliable here either: whether the under-aligned home slot happens to
+   coincidentally land on a 64-byte boundary depends on this function's
+   exact frame size.  Check the selected instruction instead: vmovapd
+   assumes alignment, vmovupd does not, and MEM_ALIGN (not the real
+   runtime address) is what decides between them.  */
+/* { dg-do compile { target x86_64-*-mingw* } } */
+/* { dg-options "-O0 -mavx512f" } */
+/* { dg-final { scan-assembler-not {vmovapd\t%zmm0, -[0-9]+\(%rbp\)} } } */
+
+typedef double v8d __attribute__ ((vector_size (64), aligned (64)));
+
+__attribute__ ((noinline)) v8d
+roundtrip_v8d (v8d x)
+{
+  return x;
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr54412-blkmode-temp-biggest-align.c 
b/gcc/testsuite/gcc.target/i386/pr54412-blkmode-temp-biggest-align.c
new file mode 100644
index 0000000000..12ac89bd53
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr54412-blkmode-temp-biggest-align.c
@@ -0,0 +1,27 @@
+/* PR target/54412 */
+/* Companion to pr54412-aligned-struct-byval-temp.c, covering the case where
+   the over-alignment comes from BIGGEST_ALIGNMENT instead of an attribute.
+
+   Win64 passes this struct by reference, so CALLER materialises a hidden
+   copy through assign_temp.  The struct is BLKmode and asks for no
+   alignment of its own, so REQUIRED_ALIGN stays at BITS_PER_UNIT and the
+   copy is allocated on the ordinary path rather than through
+   assign_stack_local_aligned.  On that path get_stack_local_alignment
+   returns BIGGEST_ALIGNMENT, which -mavx raises to 256 bits, while
+   assign_stack_local_1 caps the slot at MAX_SUPPORTED_STACK_ALIGNMENT,
+   128 bits under SEH.  Taking the returned MEM's alignment from the request
+   rather than from the allocated slot therefore claimed 32-byte alignment
+   on a 16-byte slot, and the copy was stored with an aligned 32-byte move.  */
+/* { dg-do compile { target x86_64-*-mingw* } } */
+/* { dg-options "-O2 -mavx" } */
+/* { dg-final { scan-assembler-not {vmov(aps|dqa)\t%ymm[0-9]+, 
[0-9-]*\(%r[sb]p\)} } } */
+
+struct S { char c[32]; };
+
+void sink (struct S);
+
+void
+caller (struct S *p)
+{
+  sink (*p);
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr54412-by-value-cases.c 
b/gcc/testsuite/gcc.target/i386/pr54412-by-value-cases.c
new file mode 100644
index 0000000000..7dccc9d4b5
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr54412-by-value-cases.c
@@ -0,0 +1,39 @@
+/* PR target/54412 */
+/* Same root cause as pr54412-m256-byval-param.c, across a plain vector,
+   a __m256, and a struct wrapping a __m256, all passed by value.  A
+   crash-based (dg-do run) test is not reliable here: whether the
+   under-aligned home slot happens to coincidentally land on a 32-byte
+   boundary depends on each function's exact frame size.  Check the
+   selected instruction instead: vmovap{s,d} assumes alignment,
+   vmovup{s,d} does not, and MEM_ALIGN (not the real runtime address)
+   is what decides between them.  */
+/* { dg-do compile { target x86_64-*-mingw* } } */
+/* { dg-options "-O0 -mavx" } */
+/* { dg-final { scan-assembler-not {vmovap[sd]\t%ymm0, -[0-9]+\(%rbp\)} } } */
+
+#include <immintrin.h>
+
+typedef double v4d __attribute__ ((vector_size (32), aligned (32)));
+
+struct avx_wrapper
+{
+  __m256 value;
+};
+
+__attribute__ ((noinline)) v4d
+roundtrip_v4d (v4d x)
+{
+  return x;
+}
+
+__attribute__ ((noinline)) __m256
+pass_m256 (__m256 x)
+{
+  return x;
+}
+
+__attribute__ ((noinline)) struct avx_wrapper
+pass_wrapper (struct avx_wrapper x)
+{
+  return x;
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr54412-callee-byref-param.c 
b/gcc/testsuite/gcc.target/i386/pr54412-callee-byref-param.c
new file mode 100644
index 0000000000..41c22cd896
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr54412-callee-byref-param.c
@@ -0,0 +1,19 @@
+/* PR target/54412 */
+/* Same root cause as pr54412-m256-byval-param.c, for a plain
+   vector_size(32) by-value parameter.  A crash-based (dg-do run) test
+   is not reliable here: whether the under-aligned home slot happens to
+   coincidentally land on a 32-byte boundary depends on this function's
+   exact frame size.  Check the selected instruction instead: vmovaps
+   assumes alignment, vmovups does not, and MEM_ALIGN (not the real
+   runtime address) is what decides between them.  */
+/* { dg-do compile { target x86_64-*-mingw* } } */
+/* { dg-options "-O0 -mavx" } */
+/* { dg-final { scan-assembler-not {vmovaps\t%ymm0, -[0-9]+\(%rbp\)} } } */
+
+typedef float v8f __attribute__ ((vector_size (32), aligned (32)));
+
+__attribute__ ((noinline)) v8f
+param_matches (v8f x)
+{
+  return x;
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr54412-caller-byref-temp.c 
b/gcc/testsuite/gcc.target/i386/pr54412-caller-byref-temp.c
new file mode 100644
index 0000000000..7c0b335fd9
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr54412-caller-byref-temp.c
@@ -0,0 +1,29 @@
+/* PR target/54412 */
+/* Caller-side counterpart to the callee home-slot tests: passing a 32-byte
+   vector rvalue by value on Win64 uses an invisible by-reference temporary in
+   the caller.  Before the slot-realignment fix, assign_temp built that
+   temporary with ordinary fixed frame-slot allocation, so MEM_ALIGN still
+   claimed 32 bytes while the address was only 16-byte aligned, and GCC chose
+   an aligned store into a plain rbp-relative stack slot.  The fixed code
+   preserves the requested alignment by overallocating and aligning the
+   effective address inside the raw slot instead, so the caller-side store no
+   longer goes straight to a fixed rbp-relative slot.  */
+/* { dg-do compile { target x86_64-*-mingw* } } */
+/* { dg-options "-O0 -mavx -fno-omit-frame-pointer" } */
+/* { dg-final { scan-assembler-not {vmovaps\t%ymm[0-9]+, -[0-9]+\(%rbp\)} } } 
*/
+
+typedef float v8f __attribute__ ((vector_size (32), aligned (32)));
+
+__attribute__ ((noinline, noclone))
+static float
+first_lane (v8f x)
+{
+  return x[0];
+}
+
+__attribute__ ((noinline, noclone))
+float
+caller (v8f a, v8f b)
+{
+  return first_lane (a + b);
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr54412-m256-byval-param.c 
b/gcc/testsuite/gcc.target/i386/pr54412-m256-byval-param.c
new file mode 100644
index 0000000000..d87988aff4
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr54412-m256-byval-param.c
@@ -0,0 +1,23 @@
+/* PR target/54412 */
+/* Based on MSYS2/MINGW-packages#1209: passing an __m256 by value at -O0
+   crashed on Win64 SEH targets because the incoming-argument copy did not
+   get the 32-byte alignment the AVX load/store instructions assumed.
+
+   A crash-based (dg-do run) test for this is not reliable: whether the
+   under-aligned home slot happens to still land on a 32-byte boundary
+   depends on this function's exact frame size, which varies with
+   unrelated codegen changes.  Check the actual instruction GCC selects
+   for the home-slot store instead: vmovaps assumes 32-byte alignment,
+   vmovups does not, and MEM_ALIGN (not the real runtime address) is
+   what decides between them.  */
+/* { dg-do compile { target x86_64-*-mingw* } } */
+/* { dg-options "-O0 -mavx" } */
+/* { dg-final { scan-assembler-not {vmovaps\t%ymm0, -[0-9]+\(%rbp\)} } } */
+
+#include <immintrin.h>
+
+void
+foo (__m256 x)
+{
+  (void) x;
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr54412-v4d-o0-aligned-locals.c 
b/gcc/testsuite/gcc.target/i386/pr54412-v4d-o0-aligned-locals.c
new file mode 100644
index 0000000000..5f68aefafb
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr54412-v4d-o0-aligned-locals.c
@@ -0,0 +1,21 @@
+/* PR target/54412 */
+/* Same root cause as pr54412-m256-byval-param.c, for a vector_size(32)
+   double by-value parameter (no explicit "aligned" attribute -- the
+   vector type's natural alignment already requires 32 bytes).  A
+   crash-based (dg-do run) test is not reliable here: whether the
+   under-aligned home slot happens to coincidentally land on a 32-byte
+   boundary depends on this function's exact frame size.  Check the
+   selected instruction instead: vmovapd assumes alignment, vmovupd
+   does not, and MEM_ALIGN (not the real runtime address) is what
+   decides between them.  */
+/* { dg-do compile { target x86_64-*-mingw* } } */
+/* { dg-options "-O0 -mavx" } */
+/* { dg-final { scan-assembler-not {vmovapd\t%ymm0, -[0-9]+\(%rbp\)} } } */
+
+typedef double v4d __attribute__ ((vector_size (32)));
+
+__attribute__ ((noinline)) v4d
+f (v4d x)
+{
+  return x;
+}
-- 
2.55.0.windows.5

Reply via email to