On Thu, Nov 12, 2026 at 1:21 PM liuhongt <[email protected]> wrote:
>
> Unnamed 256-bit and 512-bit vector arguments must be passed on the
> stack, since the register save area set up by a variadic callee only
> holds 16 bytes per SSE register.  function_arg_advance_64,
> function_arg_64 and ix86_gimplify_va_arg implement this rule with an
> explicit check for the 256-bit and 512-bit vector modes.
>
> The explicit mode check is not enough.  Whether an argument goes in a
> single YMM/ZMM register is decided by classify_argument (SSE followed
> by all SSEUP), and that classification also applies to BLKmode
> aggregates without a vector mode, e.g. a union whose widest member is
> a 256-bit or 512-bit vector.  Such unnamed arguments slip past the
> mode check: the caller passes them in %ymm/%zmm, and va_arg reads them
> from consecutive 16-byte slots of the register save area, which yields
> wrong upper lanes, or segfaults on an aligned 32/64-byte load from a
> 16-byte aligned slot.
>
> Replace the explicit mode check with classify_argument: it only returns
> more than 2 classes for SSE followed by all SSEUP, i.e. exactly when
> the argument would be passed in a single 256-bit or 512-bit vector
> register.  Assert that invariant in the classify_argument wrapper with
> checking enabled.
>
> Bootstrapped and regtested on x86_64-pc-linux-gnu{-m32,}.
> Ok for trunk?
>
> gcc/ChangeLog:
>
>         PR target/128640
>         * config/i386/i386.cc (classify_argument): In the wrapper,
>         assert that more than 2 classes means SSE followed by all SSEUP.
>         (function_arg_advance_64): Use classify_argument instead of an
>         explicit vector mode check to pass unnamed 256-bit and 512-bit
>         vector arguments on stack.
>         (function_arg_64): Likewise.
>         (ix86_gimplify_va_arg): Likewise.
>
> gcc/testsuite/ChangeLog:
>
>         PR target/128640
>         * gcc.target/i386/pr128640-1.c: New test.
>         * gcc.target/i386/pr128640-2.c: New test.
>         * gcc.target/i386/pr128640-3.c: New test.

OK for mainline and backports.

No -Wpsabi note is needed here: the old behaviour was already
inconsistent. The caller passed the union in %ymm/%zmm while va_arg
read the register save area, so the value was never recoverable, and a
compliant callee (e.g. clang’s) already read the overflow area. Only
unnamed arguments change; named parameters still go in %ymm/%zmm.

Thanks,
Uros.

> ---
>  gcc/config/i386/i386.cc                    | 90 ++++++++--------------
>  gcc/testsuite/gcc.target/i386/pr128640-1.c | 54 +++++++++++++
>  gcc/testsuite/gcc.target/i386/pr128640-2.c | 52 +++++++++++++
>  gcc/testsuite/gcc.target/i386/pr128640-3.c | 27 +++++++
>  4 files changed, 166 insertions(+), 57 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.target/i386/pr128640-1.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/pr128640-2.c
>  create mode 100644 gcc/testsuite/gcc.target/i386/pr128640-3.c
>
> diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
> index 8943da8b068..283602d2bdc 100644
> --- a/gcc/config/i386/i386.cc
> +++ b/gcc/config/i386/i386.cc
> @@ -2747,6 +2747,18 @@ classify_argument (machine_mode mode, const_tree type,
>    static bool warned = false;
>    int n = classify_argument (mode, type, classes, bit_offset,
>                              zero_width_bitfields);
> +  /* More than 2 classes is only possible for a single 256-bit or
> +     512-bit vector register: SSE followed by all SSEUP.  This is
> +     relied upon by function_arg_advance_64, function_arg_64 and
> +     ix86_gimplify_va_arg, which pass unnamed 256-bit and 512-bit
> +     vector parameters on stack when classify_argument returns more
> +     than 2 classes.  */
> +  if (flag_checking && n > 2)
> +    {
> +      gcc_assert ((n == 4 || n == 8) && classes[0] == X86_64_SSE_CLASS);
> +      for (int i = 1; i < n; i++)
> +       gcc_assert (classes[i] == X86_64_SSEUP_CLASS);
> +    }
>    if (!zero_width_bitfields || warned || !warn_psabi)
>      return n;
>    enum x86_64_reg_class alt_classes[MAX_CLASSES];
> @@ -3239,10 +3251,11 @@ function_arg_advance_64 (CUMULATIVE_ARGS *cum, 
> machine_mode mode,
>                          const_tree type, HOST_WIDE_INT words, bool named)
>  {
>    int int_nregs, sse_nregs;
> +  enum x86_64_reg_class regclass[MAX_CLASSES];
>
> -  /* Unnamed 512 and 256bit vector mode parameters are passed on stack.  */
> -  if (!named && (VALID_AVX512F_REG_MODE (mode)
> -                || VALID_AVX256_REG_MODE (mode)))
> +  /* Unnamed 512 and 256bit vector parameters are passed on stack.  See
> +     function_arg_64.  */
> +  if (!named && classify_argument (mode, type, regclass, 0) > 2)
>      return 0;
>
>    if (!examine_argument (mode, type, false, &int_nregs, &sse_nregs)
> @@ -3492,32 +3505,14 @@ function_arg_64 (const CUMULATIVE_ARGS *cum, 
> machine_mode mode,
>                        : cum->sse_regno)
>                     : -1);
>
> -  switch (mode)
> -    {
> -    default:
> -      break;
> -
> -    case E_V16HFmode:
> -    case E_V16BFmode:
> -    case E_V8SFmode:
> -    case E_V8SImode:
> -    case E_V32QImode:
> -    case E_V16HImode:
> -    case E_V4DFmode:
> -    case E_V4DImode:
> -    case E_V32HFmode:
> -    case E_V32BFmode:
> -    case E_V16SFmode:
> -    case E_V16SImode:
> -    case E_V64QImode:
> -    case E_V32HImode:
> -    case E_V8DFmode:
> -    case E_V8DImode:
> -      /* Unnamed 256 and 512bit vector mode parameters are passed on stack.  
> */
> -      if (!named)
> -       return NULL;
> -      break;
> -    }
> +  /* Unnamed 256 and 512bit vector parameters are passed on stack.
> +     classify_argument returns more than 2 classes only for SSE followed
> +     by all SSEUP, i.e. a single 256-bit or 512-bit vector register.
> +     Besides the vector modes, this covers BLKmode aggregates such as a
> +     union with such a vector member.  */
> +  enum x86_64_reg_class regclass[MAX_CLASSES];
> +  if (!named && classify_argument (mode, type, regclass, 0) > 2)
> +    return NULL;
>
>    const int *parm_regs;
>    if (cum->preserve_none_abi)
> @@ -5035,34 +5030,15 @@ ix86_gimplify_va_arg (tree valist, tree type, 
> gimple_seq *pre_p,
>    rsize = CEIL (size, UNITS_PER_WORD);
>
>    nat_mode = type_natural_mode (type, NULL, false);
> -  switch (nat_mode)
> -    {
> -    case E_V16HFmode:
> -    case E_V16BFmode:
> -    case E_V8SFmode:
> -    case E_V8SImode:
> -    case E_V32QImode:
> -    case E_V16HImode:
> -    case E_V4DFmode:
> -    case E_V4DImode:
> -    case E_V32HFmode:
> -    case E_V32BFmode:
> -    case E_V16SFmode:
> -    case E_V16SImode:
> -    case E_V64QImode:
> -    case E_V32HImode:
> -    case E_V8DFmode:
> -    case E_V8DImode:
> -      /* Unnamed 256 and 512bit vector mode parameters are passed on stack.  
> */
> -      container = NULL;
> -      break;
> -
> -    default:
> -      container = construct_container (nat_mode, TYPE_MODE (type),
> -                                      type, false, X86_64_REGPARM_MAX,
> -                                      X86_64_SSE_REGPARM_MAX, intreg, 0);
> -      break;
> -    }
> +  /* Unnamed 256 and 512bit vector parameters are passed on stack.  See
> +     function_arg_64.  */
> +  enum x86_64_reg_class regclass[MAX_CLASSES];
> +  if (classify_argument (nat_mode, type, regclass, 0) > 2)
> +    container = NULL;
> +  else
> +    container = construct_container (nat_mode, TYPE_MODE (type),
> +                                    type, false, X86_64_REGPARM_MAX,
> +                                    X86_64_SSE_REGPARM_MAX, intreg, 0);
>
>    /* Pull the value out of the saved registers.  */
>
> diff --git a/gcc/testsuite/gcc.target/i386/pr128640-1.c 
> b/gcc/testsuite/gcc.target/i386/pr128640-1.c
> new file mode 100644
> index 00000000000..50019ca2d9b
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/pr128640-1.c
> @@ -0,0 +1,54 @@
> +/* PR target/128640 */
> +/* { dg-do run { target { ! ia32 } } } */
> +/* { dg-require-effective-target avx } */
> +/* { dg-options "-O2 -mavx" } */
> +
> +#include <stdarg.h>
> +#include "avx-check.h"
> +
> +typedef float v8sf __attribute__((vector_size (32)));
> +typedef long long v4di __attribute__((vector_size (32)));
> +
> +union u1 { v8sf f; };
> +union u2 { v8sf f; float x; };
> +struct s3 { union u1 u; };
> +union u4 { v4di f; };
> +
> +#ifndef CHECK
> +#define CHECK(X, N) \
> +  for (int i = 0; i < N; i++) \
> +    if ((X)[i] != i + 1) \
> +      abort ()
> +#endif
> +
> +__attribute__((noipa)) void
> +foo (int n, ...)
> +{
> +  va_list ap;
> +  va_start (ap, n);
> +  if (va_arg (ap, double) != 0.0)
> +    abort ();
> +  union u1 a = va_arg (ap, union u1);
> +  CHECK (a.f, 8);
> +  if (va_arg (ap, double) != 5.0)
> +    abort ();
> +  union u2 b = va_arg (ap, union u2);
> +  CHECK (b.f, 8);
> +  struct s3 c = va_arg (ap, struct s3);
> +  CHECK (c.u.f, 8);
> +  union u4 d = va_arg (ap, union u4);
> +  CHECK (d.f, 4);
> +  if (va_arg (ap, double) != 6.0)
> +    abort ();
> +  va_end (ap);
> +}
> +
> +static void
> +avx_test (void)
> +{
> +  union u1 a = { { 1, 2, 3, 4, 5, 6, 7, 8 } };
> +  union u2 b = { { 1, 2, 3, 4, 5, 6, 7, 8 } };
> +  struct s3 c = { { { 1, 2, 3, 4, 5, 6, 7, 8 } } };
> +  union u4 d = { { 1, 2, 3, 4 } };
> +  foo (1, 0.0, a, 5.0, b, c, d, 6.0);
> +}
> diff --git a/gcc/testsuite/gcc.target/i386/pr128640-2.c 
> b/gcc/testsuite/gcc.target/i386/pr128640-2.c
> new file mode 100644
> index 00000000000..99480fbab47
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/pr128640-2.c
> @@ -0,0 +1,52 @@
> +/* PR target/128640 */
> +/* { dg-do run { target { ! ia32 } } } */
> +/* { dg-require-effective-target avx512f } */
> +/* { dg-options "-O2 -mavx512f" } */
> +
> +#include <stdarg.h>
> +#include "avx512f-check.h"
> +
> +typedef float v16sf __attribute__((vector_size (64)));
> +typedef long long v8di __attribute__((vector_size (64)));
> +
> +union u1 { v16sf f; };
> +union u2 { v16sf f; float x; };
> +struct s3 { union u1 u; };
> +union u4 { v8di f; };
> +
> +#define CHECK(X, N) \
> +  for (int i = 0; i < N; i++) \
> +    if ((X)[i] != i + 1) \
> +      abort ()
> +
> +__attribute__((noipa)) void
> +foo (int n, ...)
> +{
> +  va_list ap;
> +  va_start (ap, n);
> +  if (va_arg (ap, double) != 0.0)
> +    abort ();
> +  union u1 a = va_arg (ap, union u1);
> +  CHECK (a.f, 16);
> +  if (va_arg (ap, double) != 5.0)
> +    abort ();
> +  union u2 b = va_arg (ap, union u2);
> +  CHECK (b.f, 16);
> +  struct s3 c = va_arg (ap, struct s3);
> +  CHECK (c.u.f, 16);
> +  union u4 d = va_arg (ap, union u4);
> +  CHECK (d.f, 8);
> +  if (va_arg (ap, double) != 6.0)
> +    abort ();
> +  va_end (ap);
> +}
> +
> +static void
> +avx512f_test (void)
> +{
> +  union u1 a = { { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 } };
> +  union u2 b = { { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 } };
> +  struct s3 c = { { { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 
> } } };
> +  union u4 d = { { 1, 2, 3, 4, 5, 6, 7, 8 } };
> +  foo (1, 0.0, a, 5.0, b, c, d, 6.0);
> +}
> diff --git a/gcc/testsuite/gcc.target/i386/pr128640-3.c 
> b/gcc/testsuite/gcc.target/i386/pr128640-3.c
> new file mode 100644
> index 00000000000..390e0d5b83a
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/pr128640-3.c
> @@ -0,0 +1,27 @@
> +/* PR target/128640 */
> +/* { dg-do compile { target { ! ia32 } } } */
> +/* { dg-options "-O2 -mavx512f" } */
> +
> +typedef float v8sf __attribute__((vector_size (32)));
> +typedef float v16sf __attribute__((vector_size (64)));
> +union u1 { v8sf f; };
> +union u2 { v16sf f; };
> +
> +void bar (int, ...);
> +
> +/* Unnamed 256-bit and 512-bit unions are passed on the stack, so no
> +   SSE register is used for the variadic arguments.  */
> +void
> +foo1 (union u1 *p)
> +{
> +  bar (1, *p);
> +}
> +
> +void
> +foo2 (union u2 *p)
> +{
> +  bar (1, *p);
> +}
> +
> +/* { dg-final { scan-assembler-times "xorl\[\\t \]+%eax, %eax" 2 } } */
> +/* { dg-final { scan-assembler-not "movl\[\\t \]+\\\$1, %eax" } } */
> --
> 2.34.1
>

Reply via email to